openfused 0.3.9 → 0.3.11
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 +11 -1
- package/dist/cli.js +17 -6
- package/dist/mcp.js +1 -1
- package/dist/sync.js +29 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -241,10 +241,20 @@ The daemon has two modes:
|
|
|
241
241
|
# Full mode — serves everything to trusted LAN peers
|
|
242
242
|
openfused serve --store ./my-context --port 9781
|
|
243
243
|
|
|
244
|
-
# Public mode —
|
|
244
|
+
# Public mode — PROFILE.md + inbox + outbox pickup (for WAN/tunnels)
|
|
245
245
|
openfused serve --store ./my-context --port 9781 --public
|
|
246
246
|
```
|
|
247
247
|
|
|
248
|
+
Public mode endpoints:
|
|
249
|
+
|
|
250
|
+
| Endpoint | Method | Purpose |
|
|
251
|
+
|----------|--------|---------|
|
|
252
|
+
| `/` | GET | Service info |
|
|
253
|
+
| `/profile` | GET | Your PROFILE.md (public address card) |
|
|
254
|
+
| `/config` | GET | Your public keys (JSON) |
|
|
255
|
+
| `/inbox` | POST | Accept signed messages (rejects invalid signatures) |
|
|
256
|
+
| `/outbox/{name}` | GET | Pickup replies addressed to `{name}` (encrypted) |
|
|
257
|
+
|
|
248
258
|
## File Watching
|
|
249
259
|
|
|
250
260
|
`openfuse watch` combines three things:
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as registry from "./registry.js";
|
|
|
8
8
|
import { fingerprint } from "./crypto.js";
|
|
9
9
|
import { resolve, join } from "node:path";
|
|
10
10
|
import { readFile } from "node:fs/promises";
|
|
11
|
-
const VERSION = "0.3.
|
|
11
|
+
const VERSION = "0.3.11";
|
|
12
12
|
const program = new Command();
|
|
13
13
|
program
|
|
14
14
|
.name("openfuse")
|
|
@@ -544,8 +544,13 @@ program
|
|
|
544
544
|
const reg = registry.resolveRegistry(opts.registry);
|
|
545
545
|
try {
|
|
546
546
|
const manifest = await registry.discover(name, reg);
|
|
547
|
-
|
|
548
|
-
//
|
|
547
|
+
// Auto-import key (untrusted) + add as peer so future `openfuse sync` can
|
|
548
|
+
// deliver replies and pull context. Key is deliberately NOT trusted — the user
|
|
549
|
+
// must explicitly `openfuse key trust <name>` after out-of-band verification.
|
|
550
|
+
// NOTE: manifest data comes from the registry and is attacker-controlled.
|
|
551
|
+
// The endpoint URL is stored as-is; a malicious entry could point at an internal
|
|
552
|
+
// service. Sync will pull from it — consider validating URL scheme/host.
|
|
553
|
+
let config = await store.readConfig();
|
|
549
554
|
if (!config.keyring.some((e) => e.signingKey === manifest.publicKey)) {
|
|
550
555
|
config.keyring.push({
|
|
551
556
|
name: manifest.name,
|
|
@@ -556,10 +561,16 @@ program
|
|
|
556
561
|
trusted: false,
|
|
557
562
|
added: new Date().toISOString(),
|
|
558
563
|
});
|
|
559
|
-
await store.writeConfig(config);
|
|
560
|
-
console.log(`Imported key for ${manifest.name} from registry [untrusted]`);
|
|
561
|
-
console.log(` Run \`openfuse key trust ${manifest.name}\` to trust`);
|
|
562
564
|
}
|
|
565
|
+
if (manifest.endpoint && !config.peers.some((p) => p.name === manifest.name)) {
|
|
566
|
+
config.peers.push({
|
|
567
|
+
id: (await import("nanoid")).nanoid(12),
|
|
568
|
+
name: manifest.name,
|
|
569
|
+
url: manifest.endpoint,
|
|
570
|
+
access: "read",
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
await store.writeConfig(config);
|
|
563
574
|
const filename = await store.sendInbox(name, message);
|
|
564
575
|
// Try direct HTTP delivery if endpoint is http(s)
|
|
565
576
|
if (manifest.endpoint.startsWith("http")) {
|
package/dist/mcp.js
CHANGED
|
@@ -23,7 +23,7 @@ const storeDir = process.env.OPENFUSE_DIR || process.argv[3] || ".";
|
|
|
23
23
|
const store = new ContextStore(resolve(storeDir));
|
|
24
24
|
const server = new McpServer({
|
|
25
25
|
name: "openfuse",
|
|
26
|
-
version: "0.3.
|
|
26
|
+
version: "0.3.11",
|
|
27
27
|
});
|
|
28
28
|
// --- Context ---
|
|
29
29
|
server.tool("context_read", "Read the agent's CONTEXT.md (working memory)", async () => {
|
package/dist/sync.js
CHANGED
|
@@ -153,6 +153,35 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
|
|
|
153
153
|
errors.push(`${dir}/: ${e.message}`);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
// Pull peer's outbox for messages addressed to us (HTTP version).
|
|
157
|
+
// The peer's daemon filters to _to-{myName}.json, but the JSON body is untrusted —
|
|
158
|
+
// a malicious peer controls every field. We must sanitize before using any value
|
|
159
|
+
// in a filename to prevent path traversal (e.g. from="../../.keys/evil").
|
|
160
|
+
const config = await store.readConfig();
|
|
161
|
+
const myName = config.name;
|
|
162
|
+
const inboxDir = join(store.root, "inbox");
|
|
163
|
+
await mkdir(inboxDir, { recursive: true });
|
|
164
|
+
try {
|
|
165
|
+
const resp = await fetch(`${baseUrl}/outbox/${myName}`);
|
|
166
|
+
if (resp.ok) {
|
|
167
|
+
const messages = (await resp.json());
|
|
168
|
+
for (const msg of messages) {
|
|
169
|
+
const ts = (msg.timestamp || new Date().toISOString()).replace(/[:.]/g, "-");
|
|
170
|
+
const from = msg.from || "unknown";
|
|
171
|
+
// SECURITY: sanitize remote-controlled values before constructing local filenames.
|
|
172
|
+
// Without this, a malicious "from" like "../../.keys/x" could write outside inbox/.
|
|
173
|
+
const safeFrom = from.replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
174
|
+
const safeTs = ts.replace(/[^a-zA-Z0-9\-_]/g, "");
|
|
175
|
+
const fname = `${safeTs}_from-${safeFrom}_to-${myName}.json`;
|
|
176
|
+
const dest = join(inboxDir, fname);
|
|
177
|
+
if (!existsSync(dest)) {
|
|
178
|
+
await writeFile(dest, JSON.stringify(msg, null, 2));
|
|
179
|
+
pulled.push(`outbox→${fname}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
156
185
|
// Push outbox → peer inbox
|
|
157
186
|
const outboxDir = join(store.root, "outbox");
|
|
158
187
|
if (existsSync(outboxDir)) {
|