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 +6 -3
- package/dist/store.js +4 -3
- package/dist/sync.js +38 -10
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
196
|
-
//
|
|
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
|
|
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
|
-
|
|
169
|
+
const raw = sanitizePeerContent(await resp.text());
|
|
170
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
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
|
-
|
|
197
|
+
const raw = sanitizePeerContent(Buffer.from(await r.arrayBuffer()).toString("utf-8"));
|
|
198
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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}
|
|
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`, //
|
|
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}`]);
|