openfused 0.3.10 → 0.3.12
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 +7 -2
- package/dist/mcp.js +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +2 -2
- package/dist/store.js +2 -2
- package/dist/sync.js +9 -2
- 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.12";
|
|
12
12
|
const program = new Command();
|
|
13
13
|
program
|
|
14
14
|
.name("openfuse")
|
|
@@ -544,7 +544,12 @@ program
|
|
|
544
544
|
const reg = registry.resolveRegistry(opts.registry);
|
|
545
545
|
try {
|
|
546
546
|
const manifest = await registry.discover(name, reg);
|
|
547
|
-
// Auto-import key + add as peer so `openfuse sync`
|
|
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.
|
|
548
553
|
let config = await store.readConfig();
|
|
549
554
|
if (!config.keyring.some((e) => e.signingKey === manifest.publicKey)) {
|
|
550
555
|
config.keyring.push({
|
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.12",
|
|
27
27
|
});
|
|
28
28
|
// --- Context ---
|
|
29
29
|
server.tool("context_read", "Read the agent's CONTEXT.md (working memory)", async () => {
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContextStore } from "./store.js";
|
|
2
|
-
export declare const DEFAULT_REGISTRY = "https://
|
|
2
|
+
export declare const DEFAULT_REGISTRY = "https://registry.openfused.dev";
|
|
3
3
|
export interface Manifest {
|
|
4
4
|
name: string;
|
|
5
5
|
endpoint: string;
|
package/dist/registry.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// This is TOFU (Trust On First Use) done right: the registry distributes keys,
|
|
8
8
|
// but never asserts trust. Trust is a local decision.
|
|
9
9
|
import { signMessage, fingerprint } from "./crypto.js";
|
|
10
|
-
//
|
|
11
|
-
export const DEFAULT_REGISTRY = "https://
|
|
10
|
+
// Registry API lives on .dev (product layer). DNS TXT records on .net (protocol layer).
|
|
11
|
+
export const DEFAULT_REGISTRY = "https://registry.openfused.dev";
|
|
12
12
|
export function resolveRegistry(flag) {
|
|
13
13
|
return flag || process.env.OPENFUSE_REGISTRY || DEFAULT_REGISTRY;
|
|
14
14
|
}
|
package/dist/store.js
CHANGED
|
@@ -172,10 +172,10 @@ export class ContextStore {
|
|
|
172
172
|
const entry = config.keyring.find((e) => e.name === peerId || e.address.startsWith(`${peerId}@`));
|
|
173
173
|
let signed;
|
|
174
174
|
if (entry?.encryptionKey) {
|
|
175
|
-
signed = await signAndEncrypt(this.root, config.
|
|
175
|
+
signed = await signAndEncrypt(this.root, config.name, message, entry.encryptionKey);
|
|
176
176
|
}
|
|
177
177
|
else {
|
|
178
|
-
signed = await signMessage(this.root, config.
|
|
178
|
+
signed = await signMessage(this.root, config.name, message);
|
|
179
179
|
}
|
|
180
180
|
// Envelope filename encodes routing metadata so sync can match outbox files to peers
|
|
181
181
|
// without parsing JSON. Colons/dots replaced to stay filesystem-safe across OS.
|
package/dist/sync.js
CHANGED
|
@@ -153,7 +153,10 @@ 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)
|
|
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").
|
|
157
160
|
const config = await store.readConfig();
|
|
158
161
|
const myName = config.name;
|
|
159
162
|
const inboxDir = join(store.root, "inbox");
|
|
@@ -165,7 +168,11 @@ async function syncHttp(store, peer, baseUrl, peerDir) {
|
|
|
165
168
|
for (const msg of messages) {
|
|
166
169
|
const ts = (msg.timestamp || new Date().toISOString()).replace(/[:.]/g, "-");
|
|
167
170
|
const from = msg.from || "unknown";
|
|
168
|
-
|
|
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`;
|
|
169
176
|
const dest = join(inboxDir, fname);
|
|
170
177
|
if (!existsSync(dest)) {
|
|
171
178
|
await writeFile(dest, JSON.stringify(msg, null, 2));
|