openfused 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -154,11 +154,10 @@ The `age` format is interoperable — Rust CLI and TypeScript SDK use the same k
154
154
  Public registry at `registry.openfused.dev`. Any agent can register, discover others, and send messages.
155
155
 
156
156
  ```bash
157
- # Register your agent
158
- # Registers as yourname.openfused.net
157
+ # Register (auto-names as yourname.openfused.net)
159
158
  openfuse register --endpoint https://your-server.com:2053
160
159
 
161
- # Or use your own domain:
160
+ # Or register with a custom domain
162
161
  openfuse register --name yourname.company.com --endpoint https://yourname.company.com:2053
163
162
 
164
163
  # Discover an agent
@@ -334,6 +333,10 @@ Works over local filesystem, GCS buckets (gcsfuse), S3, or any FUSE-mountable st
334
333
  - **Any CLI agent** — if it can read files, it can use OpenFused
335
334
  - **Any cloud** — GCP, AWS, Azure, bare metal, your laptop
336
335
 
336
+ ## Community
337
+
338
+ [Discord](https://discord.gg/gbR9TURc) · [GitHub Discussions](https://github.com/openfused/openfused/discussions) · [Contributing](CONTRIBUTING.md)
339
+
337
340
  ## Philosophy
338
341
 
339
342
  > *Intelligence is what happens when information flows through a sufficiently complex and appropriately organized system. The medium is not the message. The medium is just the medium. The message is the pattern.*
package/dist/store.js CHANGED
@@ -192,10 +192,11 @@ export class ContextStore {
192
192
  else {
193
193
  signed = await signMessage(this.root, config.name, message);
194
194
  }
195
- // Envelope filename encodes routing metadata so sync can match outbox files to peers
196
- // without parsing JSON. Colons/dots replaced to stay filesystem-safe across OS.
195
+ // Envelope filename includes short fingerprint to disambiguate name collisions.
196
+ // Two agents named "carlos" with different keys get different filenames.
197
+ const shortFp = entry ? entry.fingerprint.replace(/:/g, "").slice(0, 8) : "unknown";
197
198
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
198
- const filename = `${timestamp}_from-${config.name}_to-${peerId}.json`;
199
+ const filename = `${timestamp}_from-${config.name}_to-${peerId}-${shortFp}.json`;
199
200
  await writeFile(join(this.root, "outbox", filename), serializeSignedMessage(signed));
200
201
  return filename;
201
202
  }
package/dist/sync.js CHANGED
@@ -32,6 +32,19 @@ export async function checkSsrf(url) {
32
32
  }
33
33
  }
34
34
  const execFile = promisify(execFileCb);
35
+ // Strip dangerous HTML from peer content before writing to disk.
36
+ // Peer-synced files get read by agents/LLMs — malicious HTML could execute
37
+ // if rendered in a browser or trick an LLM into acting on injected instructions.
38
+ function sanitizePeerContent(raw) {
39
+ return raw
40
+ .replace(/<script[\s\S]*?<\/script>/gi, "[REMOVED: script tag]")
41
+ .replace(/<iframe[\s\S]*?<\/iframe>/gi, "[REMOVED: iframe tag]")
42
+ .replace(/<object[\s\S]*?<\/object>/gi, "[REMOVED: object tag]")
43
+ .replace(/<embed[\s\S]*?>/gi, "[REMOVED: embed tag]")
44
+ .replace(/<link[\s\S]*?>/gi, "[REMOVED: link tag]")
45
+ .replace(/on\w+\s*=\s*["'][^"']*["']/gi, "[REMOVED: event handler]")
46
+ .replace(/javascript\s*:/gi, "[REMOVED: javascript URI]");
47
+ }
35
48
  // Archive instead of delete: preserves audit trail and lets agents review what was sent.
36
49
  // Without this, sync would re-deliver the same message every cycle.
37
50
  async function archiveSent(outboxDir, fname) {
@@ -140,17 +153,28 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
140
153
  const errors = [];
141
154
  // SSRF check: block requests to private/reserved IPs
142
155
  await checkSsrf(baseUrl);
156
+ // Try /read/{file} (full mode) then /profile (public mode) for PROFILE.md.
157
+ // CONTEXT.md 404s are silently skipped — peers in public mode don't serve it.
143
158
  for (const file of ["CONTEXT.md", "PROFILE.md"]) {
144
159
  try {
145
- const resp = await fetch(`${baseUrl}/read/${file}`);
160
+ const url = file === "PROFILE.md"
161
+ ? `${baseUrl}/profile` // public mode serves /profile not /read/PROFILE.md
162
+ : `${baseUrl}/read/${file}`;
163
+ let resp = await fetch(url);
164
+ // Fallback: try /read/ path if /profile didn't work (full mode daemon)
165
+ if (!resp.ok && file === "PROFILE.md") {
166
+ resp = await fetch(`${baseUrl}/read/${file}`);
167
+ }
146
168
  if (resp.ok) {
147
- await writeFile(join(peerDir, file), await resp.text());
169
+ const raw = sanitizePeerContent(await resp.text());
170
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
171
+ const wrapped = `<external_content_unverified from="${esc(peer.name)}" file="${esc(file)}">\n${raw}\n</external_content_unverified>`;
172
+ await writeFile(join(peerDir, file), wrapped);
148
173
  pulled.push(file);
149
174
  }
175
+ // Don't report 404s as errors — peer may be in public mode
150
176
  }
151
- catch (e) {
152
- errors.push(`${file}: ${e.message}`);
153
- }
177
+ catch { }
154
178
  }
155
179
  for (const dir of ["shared", "knowledge"]) {
156
180
  try {
@@ -170,7 +194,10 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
170
194
  continue;
171
195
  const r = await fetch(`${baseUrl}/read/${dir}/${safeName}`);
172
196
  if (r.ok) {
173
- await writeFile(join(localDir, safeName), Buffer.from(await r.arrayBuffer()));
197
+ const raw = sanitizePeerContent(Buffer.from(await r.arrayBuffer()).toString("utf-8"));
198
+ const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
199
+ const wrapped = `<external_content_unverified from="${esc(peer.name)}" file="${esc(safeName)}">\n${raw}\n</external_content_unverified>`;
200
+ await writeFile(join(localDir, safeName), wrapped);
174
201
  pulled.push(`${dir}/${safeName}`);
175
202
  }
176
203
  }
@@ -241,7 +268,7 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
241
268
  for (const fname of await readdir(outboxDir)) {
242
269
  if (!fname.endsWith(".json"))
243
270
  continue;
244
- if (!fname.includes(`_to-${peer.name}.json`) && !fname.includes(peer.id))
271
+ if (!fname.includes(`_to-${peer.name}-`) && !fname.includes(`_to-${peer.name}.json`) && !fname.includes(peer.id))
245
272
  continue;
246
273
  try {
247
274
  const body = await readFile(join(outboxDir, fname), "utf-8");
@@ -299,9 +326,10 @@ async function syncSsh(store, peer, host, remotePath, peerDir) {
299
326
  try {
300
327
  await execFile("rsync", [
301
328
  "-az", "--ignore-existing",
302
- "--include", `*_to-${myName}.json`,
329
+ "--include", `*_to-${myName}-*.json`, // new format with fingerprint
330
+ "--include", `*_to-${myName}.json`, // legacy format without fingerprint
303
331
  "--include", `*_to-all.json`,
304
- "--include", `*_${myName}.json`, // legacy format (pre-envelope)
332
+ "--include", `*_${myName}.json`, // pre-envelope legacy
305
333
  "--exclude", "*",
306
334
  `${host}:${remotePath}/outbox/`,
307
335
  `${inboxDir}/`,
@@ -319,7 +347,7 @@ async function syncSsh(store, peer, host, remotePath, peerDir) {
319
347
  for (const fname of await readdir(outboxDir)) {
320
348
  if (!fname.endsWith(".json"))
321
349
  continue;
322
- if (!fname.includes(`_to-${peer.name}.json`) && !fname.includes(peer.id))
350
+ if (!fname.includes(`_to-${peer.name}-`) && !fname.includes(`_to-${peer.name}.json`) && !fname.includes(peer.id))
323
351
  continue;
324
352
  try {
325
353
  await execFile("rsync", ["-az", join(outboxDir, fname), `${host}:${remotePath}/inbox/${fname}`]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openfused",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "The file protocol for AI agent context. Encrypted, signed, peer-to-peer.",
5
5
  "license": "MIT",
6
6
  "type": "module",