openfused 0.3.16 → 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) {
@@ -153,7 +166,10 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
153
166
  resp = await fetch(`${baseUrl}/read/${file}`);
154
167
  }
155
168
  if (resp.ok) {
156
- 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);
157
173
  pulled.push(file);
158
174
  }
159
175
  // Don't report 404s as errors — peer may be in public mode
@@ -178,7 +194,10 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
178
194
  continue;
179
195
  const r = await fetch(`${baseUrl}/read/${dir}/${safeName}`);
180
196
  if (r.ok) {
181
- 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);
182
201
  pulled.push(`${dir}/${safeName}`);
183
202
  }
184
203
  }
@@ -249,7 +268,7 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
249
268
  for (const fname of await readdir(outboxDir)) {
250
269
  if (!fname.endsWith(".json"))
251
270
  continue;
252
- 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))
253
272
  continue;
254
273
  try {
255
274
  const body = await readFile(join(outboxDir, fname), "utf-8");
@@ -307,9 +326,10 @@ async function syncSsh(store, peer, host, remotePath, peerDir) {
307
326
  try {
308
327
  await execFile("rsync", [
309
328
  "-az", "--ignore-existing",
310
- "--include", `*_to-${myName}.json`,
329
+ "--include", `*_to-${myName}-*.json`, // new format with fingerprint
330
+ "--include", `*_to-${myName}.json`, // legacy format without fingerprint
311
331
  "--include", `*_to-all.json`,
312
- "--include", `*_${myName}.json`, // legacy format (pre-envelope)
332
+ "--include", `*_${myName}.json`, // pre-envelope legacy
313
333
  "--exclude", "*",
314
334
  `${host}:${remotePath}/outbox/`,
315
335
  `${inboxDir}/`,
@@ -327,7 +347,7 @@ async function syncSsh(store, peer, host, remotePath, peerDir) {
327
347
  for (const fname of await readdir(outboxDir)) {
328
348
  if (!fname.endsWith(".json"))
329
349
  continue;
330
- 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))
331
351
  continue;
332
352
  try {
333
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.16",
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",