@remnic/core 9.8.0 → 9.9.1
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/dist/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +2 -2
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +2 -2
- package/dist/access-cli.js +6 -6
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +4 -4
- package/dist/access-identity-continuity-surface.js +2 -2
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +2 -2
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +3 -3
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +2 -2
- package/dist/access-operations-batch.js +2 -2
- package/dist/access-operations.d.ts +4 -4
- package/dist/access-operations.js +3 -3
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +2 -2
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +2 -2
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +2 -2
- package/dist/access-schema.d.ts +76 -76
- package/dist/{access-service-DEL4qCrc.d.ts → access-service-DbZ1G3zV.d.ts} +4 -4
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +2 -2
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/{chunk-2EJOEICD.js → chunk-EHCBW6DN.js} +4 -3
- package/dist/{chunk-2EJOEICD.js.map → chunk-EHCBW6DN.js.map} +1 -1
- package/dist/{chunk-IDZFBSHN.js → chunk-OGCAYLNA.js} +2 -2
- package/dist/{chunk-SICXL4MK.js → chunk-PJSK6OJM.js} +44 -8
- package/dist/chunk-PJSK6OJM.js.map +1 -0
- package/dist/{chunk-76GLCM57.js → chunk-PMUD7ERF.js} +3 -3
- package/dist/{chunk-TDTXKOAJ.js → chunk-XQDRKZ7V.js} +7 -5
- package/dist/chunk-XQDRKZ7V.js.map +1 -0
- package/dist/{chunk-IYNGIMVG.js → chunk-ZVAYK7GU.js} +3 -3
- package/dist/{cli-C_pqqTCw.d.ts → cli-5tNLVTvJ.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +5 -5
- package/dist/index.d.ts +407 -407
- package/dist/index.js +6 -6
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/orchestrator.js +6 -6
- package/dist/schemas.d.ts +76 -76
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/transfer/types.d.ts +66 -66
- package/dist/write-quarantine.d.ts +18 -3
- package/dist/write-quarantine.js +1 -1
- package/package.json +2 -2
- package/src/access-observe-quarantine.test.ts +52 -0
- package/src/access-observe-write-surface.ts +12 -5
- package/src/access-service.ts +4 -4
- package/src/wearables/registry.ts +1 -0
- package/src/write-quarantine.test.ts +52 -0
- package/src/write-quarantine.ts +56 -7
- package/dist/chunk-SICXL4MK.js.map +0 -1
- package/dist/chunk-TDTXKOAJ.js.map +0 -1
- /package/dist/{chunk-IDZFBSHN.js.map → chunk-OGCAYLNA.js.map} +0 -0
- /package/dist/{chunk-76GLCM57.js.map → chunk-PMUD7ERF.js.map} +0 -0
- /package/dist/{chunk-IYNGIMVG.js.map → chunk-ZVAYK7GU.js.map} +0 -0
|
@@ -191,3 +191,55 @@ test("list() surfaces a symlink-escaping root instead of reporting empty", async
|
|
|
191
191
|
}
|
|
192
192
|
});
|
|
193
193
|
});
|
|
194
|
+
|
|
195
|
+
test("entries() pairs each record with its on-disk path and list() projects records", async () => {
|
|
196
|
+
await withTempDir(async (dir) => {
|
|
197
|
+
const store = new WriteQuarantineStore(dir);
|
|
198
|
+
const written = await store.quarantine({
|
|
199
|
+
operation: "memory_store",
|
|
200
|
+
principal: "alice",
|
|
201
|
+
attemptedNamespace: "victim-secret",
|
|
202
|
+
payload: { content: "hi" },
|
|
203
|
+
});
|
|
204
|
+
const entries = await store.entries();
|
|
205
|
+
assert.equal(entries.length, 1);
|
|
206
|
+
assert.equal(entries[0]?.path, written);
|
|
207
|
+
assert.ok(isInside(store.root, entries[0]?.path ?? ""));
|
|
208
|
+
assert.equal(entries[0]?.record.operation, "memory_store");
|
|
209
|
+
assert.deepEqual(entries[0]?.record.payload, { content: "hi" });
|
|
210
|
+
assert.deepEqual(
|
|
211
|
+
await store.list(),
|
|
212
|
+
entries.map((entry) => entry.record)
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("removeEntry deletes an entry by its path and is idempotent", async () => {
|
|
218
|
+
await withTempDir(async (dir) => {
|
|
219
|
+
const store = new WriteQuarantineStore(dir);
|
|
220
|
+
await store.quarantine({ operation: "observe", principal: "alice", attemptedNamespace: "ns", payload: 1 });
|
|
221
|
+
const [entry] = await store.entries();
|
|
222
|
+
assert.ok(entry);
|
|
223
|
+
assert.equal(await store.removeEntry(entry.path), true);
|
|
224
|
+
assert.equal(await store.count(), 0);
|
|
225
|
+
// An already-absent path removes nothing.
|
|
226
|
+
assert.equal(await store.removeEntry(entry.path), false);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("removeEntry refuses a path outside the quarantine root", async () => {
|
|
231
|
+
await withTempDir(async (dir) => {
|
|
232
|
+
const store = new WriteQuarantineStore(dir);
|
|
233
|
+
await store.quarantine({ operation: "observe", principal: "alice", attemptedNamespace: "ns", payload: 1 });
|
|
234
|
+
const outside = await mkdtemp(path.join(tmpdir(), "remnic-quarantine-outside-"));
|
|
235
|
+
const victim = path.join(outside, "keep.json");
|
|
236
|
+
await writeFile(victim, "{}", "utf8");
|
|
237
|
+
try {
|
|
238
|
+
assert.equal(await store.removeEntry(victim), false);
|
|
239
|
+
assert.equal((await readdir(outside)).length, 1, "file outside root must survive");
|
|
240
|
+
assert.equal(await store.count(), 1, "in-root entry untouched");
|
|
241
|
+
} finally {
|
|
242
|
+
await rm(outside, { recursive: true, force: true });
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
package/src/write-quarantine.ts
CHANGED
|
@@ -48,6 +48,12 @@ export interface QuarantinedRecord {
|
|
|
48
48
|
payload: unknown;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/** A quarantined record paired with the absolute on-disk path it was read from. */
|
|
52
|
+
export interface QuarantineEntry {
|
|
53
|
+
record: QuarantinedRecord;
|
|
54
|
+
path: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
export interface QuarantineCaps {
|
|
52
58
|
/** Keep at most this many records per principal (newest win). */
|
|
53
59
|
maxEntriesPerPrincipal: number;
|
|
@@ -145,12 +151,13 @@ export class WriteQuarantineStore {
|
|
|
145
151
|
|
|
146
152
|
/**
|
|
147
153
|
* All valid, non-expired quarantined records across every principal, oldest
|
|
148
|
-
* first
|
|
149
|
-
*
|
|
154
|
+
* first, each paired with its absolute on-disk path. Expired records are
|
|
155
|
+
* deleted on read (lazy GC), so an inactive principal bucket with no
|
|
156
|
+
* follow-up write is still bounded by age.
|
|
150
157
|
*/
|
|
151
|
-
async
|
|
158
|
+
async entries(): Promise<QuarantineEntry[]> {
|
|
152
159
|
const now = Date.now();
|
|
153
|
-
const
|
|
160
|
+
const entries: QuarantineEntry[] = [];
|
|
154
161
|
for (const dir of await this.principalDirs()) {
|
|
155
162
|
for (const file of await this.entryFiles(dir)) {
|
|
156
163
|
const info = await stat(file).catch(() => null);
|
|
@@ -165,11 +172,53 @@ export class WriteQuarantineStore {
|
|
|
165
172
|
} catch {
|
|
166
173
|
continue;
|
|
167
174
|
}
|
|
168
|
-
if (isQuarantinedRecord(parsed))
|
|
175
|
+
if (isQuarantinedRecord(parsed)) entries.push({ record: parsed, path: file });
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
entries.sort((a, b) => {
|
|
179
|
+
const at = a.record.timestamp;
|
|
180
|
+
const bt = b.record.timestamp;
|
|
181
|
+
return at < bt ? -1 : at > bt ? 1 : 0;
|
|
182
|
+
});
|
|
183
|
+
return entries;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** As {@link entries} but projected to records only (backward-compatible view). */
|
|
187
|
+
async list(): Promise<QuarantinedRecord[]> {
|
|
188
|
+
return (await this.entries()).map((entry) => entry.record);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Delete one quarantined entry by its absolute on-disk path (as returned by
|
|
193
|
+
* {@link entries}). The path MUST resolve inside the quarantine root, mirroring
|
|
194
|
+
* the store's containment posture; a path outside the root — or one already
|
|
195
|
+
* absent — removes nothing. Returns true only when a file was actually deleted.
|
|
196
|
+
*/
|
|
197
|
+
async removeEntry(entryPath: string): Promise<boolean> {
|
|
198
|
+
const rootAbs = path.resolve(this.root);
|
|
199
|
+
const rel = path.relative(rootAbs, path.resolve(entryPath));
|
|
200
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
// Symlink-race-safe resolution (issue #1888): resolveSafeStoragePath rejects
|
|
204
|
+
// any symlinked path component and verifies the REAL path stays inside the
|
|
205
|
+
// real quarantine root before we touch it, so a swapped component cannot
|
|
206
|
+
// redirect the deletion outside the store — lexical containment alone is not
|
|
207
|
+
// sufficient against a TOCTOU symlink swap.
|
|
208
|
+
let safe: string;
|
|
209
|
+
try {
|
|
210
|
+
safe = await resolveSafeStoragePath(rootAbs, rel);
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
await rm(safe, { force: false });
|
|
216
|
+
return true;
|
|
217
|
+
} catch (err) {
|
|
218
|
+
const errno = err as NodeJS.ErrnoException;
|
|
219
|
+
if (errno?.code === "ENOENT") return false;
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
173
222
|
}
|
|
174
223
|
|
|
175
224
|
/** Total valid, non-expired quarantined record count across every principal. */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/write-quarantine.ts"],"sourcesContent":["/**\n * Dead-letter quarantine for writes rejected by the namespace write-ACL\n * (issue #1888).\n *\n * When `observe` / `memory_store` / `suggestion_submit` fail namespace\n * authorization, the payload used to be dropped with no recoverable copy: a\n * client configured with a namespace that matches no policy (and is not the\n * default/shared namespace) had every write in the window silently destroyed.\n * The ACL rejection is CORRECT and stays fail-closed; this store only\n * preserves the payload so it can be replayed after the config is fixed,\n * following the AGENTS.md \"reject loudly but never destroy state\" posture\n * (patterns 14/42).\n *\n * Records live OUTSIDE any memory namespace at\n * `<memoryDir>/state/quarantine/<principalSafe>/<timestamp>-<id>.json`, keyed\n * by the encoded principal, and are bounded per principal by count and age.\n * Every path is resolved from `memoryDir` through `resolveSafeStoragePath`, so\n * a symlinked `state`/`quarantine` cannot redirect writes outside the memory\n * directory. The store never re-routes a payload into a writable namespace.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { encodeStoragePathSegment, resolveSafeStoragePath } from \"./storage-paths.js\";\n\nexport type QuarantineOperation = \"observe\" | \"memory_store\" | \"suggestion_submit\";\n\nexport interface QuarantineEntryInput {\n operation: QuarantineOperation;\n /** Authenticated principal the write was attributed to, if any. */\n principal: string | undefined;\n /** Namespace the caller attempted to write and was denied. */\n attemptedNamespace: string;\n /** The original request payload, preserved verbatim for replay. */\n payload: unknown;\n /** ISO timestamp; defaults to now. Injectable for deterministic tests. */\n timestamp?: string;\n}\n\nexport interface QuarantinedRecord {\n operation: QuarantineOperation;\n principal: string | null;\n attemptedNamespace: string;\n timestamp: string;\n payload: unknown;\n}\n\nexport interface QuarantineCaps {\n /** Keep at most this many records per principal (newest win). */\n maxEntriesPerPrincipal: number;\n /** Drop records older than this many milliseconds. */\n maxAgeMs: number;\n}\n\nexport const DEFAULT_QUARANTINE_CAPS: QuarantineCaps = {\n maxEntriesPerPrincipal: 500,\n maxAgeMs: 14 * 24 * 60 * 60 * 1000,\n};\n\nconst QUARANTINE_DIRNAME = \"quarantine\";\n\nfunction validateCaps(caps: QuarantineCaps): void {\n if (!Number.isInteger(caps.maxEntriesPerPrincipal) || caps.maxEntriesPerPrincipal < 1) {\n throw new Error(\n `WriteQuarantineStore: maxEntriesPerPrincipal must be a positive integer (got ${caps.maxEntriesPerPrincipal})`\n );\n }\n if (!Number.isFinite(caps.maxAgeMs) || caps.maxAgeMs <= 0) {\n throw new Error(\n `WriteQuarantineStore: maxAgeMs must be a positive finite number of milliseconds (got ${caps.maxAgeMs})`\n );\n }\n}\n\nfunction isQuarantinedRecord(value: unknown): value is QuarantinedRecord {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return (\n (record.operation === \"observe\" ||\n record.operation === \"memory_store\" ||\n record.operation === \"suggestion_submit\") &&\n (record.principal === null || typeof record.principal === \"string\") &&\n typeof record.attemptedNamespace === \"string\" &&\n typeof record.timestamp === \"string\" &&\n \"payload\" in record\n );\n}\n\nexport class WriteQuarantineStore {\n private readonly memoryDir: string;\n\n constructor(\n memoryDir: string,\n private readonly caps: QuarantineCaps = DEFAULT_QUARANTINE_CAPS\n ) {\n validateCaps(caps);\n this.memoryDir = path.resolve(memoryDir);\n }\n\n /** Absolute quarantine root (`<memoryDir>/state/quarantine`). */\n get root(): string {\n return path.join(this.memoryDir, \"state\", QUARANTINE_DIRNAME);\n }\n\n /**\n * Resolve a path under the quarantine root, checked for containment and\n * symlink traversal from `memoryDir` down — so a symlinked `state` or\n * `quarantine` component that escapes the memory directory is rejected.\n */\n private safeDir(...segments: string[]): Promise<string> {\n return resolveSafeStoragePath(this.memoryDir, \"state\", QUARANTINE_DIRNAME, ...segments);\n }\n\n /**\n * Park a rejected write. Returns the absolute path of the record written.\n * Prunes the principal's bucket to the configured caps afterward.\n */\n async quarantine(input: QuarantineEntryInput): Promise<string> {\n const timestamp = input.timestamp ?? new Date().toISOString();\n const record: QuarantinedRecord = {\n operation: input.operation,\n principal: input.principal ?? null,\n attemptedNamespace: input.attemptedNamespace,\n timestamp,\n payload: input.payload,\n };\n const principalSegment = encodeStoragePathSegment(input.principal ?? \"unknown\");\n const fileName = `${timestamp.replace(/[:.]/g, \"-\")}-${randomUUID().slice(0, 8)}.json`;\n\n const dir = await this.safeDir(principalSegment);\n await mkdir(dir, { recursive: true });\n // The temp write uses the exclusive `wx` flag so a pre-planted symlink at\n // the temp name can never be followed (O_EXCL); rename is atomic (rule 42).\n const finalPath = await this.safeDir(principalSegment, fileName);\n const tmpPath = await this.safeDir(principalSegment, `${fileName}.tmp`);\n await writeFile(tmpPath, JSON.stringify(record, null, 2), { encoding: \"utf8\", flag: \"wx\" });\n await rename(tmpPath, finalPath);\n\n await this.pruneDir(dir);\n return finalPath;\n }\n\n /**\n * All valid, non-expired quarantined records across every principal, oldest\n * first. Expired records are deleted on read (lazy GC), so an inactive\n * principal bucket with no follow-up write is still bounded by age.\n */\n async list(): Promise<QuarantinedRecord[]> {\n const now = Date.now();\n const records: QuarantinedRecord[] = [];\n for (const dir of await this.principalDirs()) {\n for (const file of await this.entryFiles(dir)) {\n const info = await stat(file).catch(() => null);\n if (!info) continue;\n if (now - info.mtimeMs > this.caps.maxAgeMs) {\n await rm(file, { force: true }).catch(() => {});\n continue;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(await readFile(file, \"utf8\"));\n } catch {\n continue;\n }\n if (isQuarantinedRecord(parsed)) records.push(parsed);\n }\n }\n records.sort((a, b) => (a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0));\n return records;\n }\n\n /** Total valid, non-expired quarantined record count across every principal. */\n async count(): Promise<number> {\n return (await this.list()).length;\n }\n\n /** lstat (no symlink follow): true only for a real directory. */\n private async isRealDir(dir: string): Promise<boolean> {\n const info = await lstat(dir).catch(() => null);\n return info?.isDirectory() === true;\n }\n\n private async principalDirs(): Promise<string[]> {\n // safeDir() rejects a symlinked/containment-escaping root — let that\n // propagate instead of masquerading as an empty store.\n const root = await this.safeDir();\n if (!(await this.isRealDir(root))) return [];\n let entries: Dirent[];\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch (err) {\n // Root vanished mid-scan → legitimately empty. Any other failure\n // (permissions, IO) is a genuine inspection failure and must surface.\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw err;\n }\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => path.join(root, entry.name))\n .sort();\n }\n\n private async entryFiles(dir: string): Promise<string[]> {\n if (!(await this.isRealDir(dir))) return [];\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") return [];\n throw err;\n }\n return entries\n .filter((entry) => entry.isFile() && entry.name.endsWith(\".json\"))\n .map((entry) => path.join(dir, entry.name))\n .sort();\n }\n\n private async pruneDir(dir: string): Promise<void> {\n const files = await this.entryFiles(dir);\n const now = Date.now();\n const survivors: Array<{ path: string; mtimeMs: number }> = [];\n for (const file of files) {\n const info = await stat(file).catch(() => null);\n if (!info) continue;\n if (now - info.mtimeMs > this.caps.maxAgeMs) {\n await rm(file, { force: true }).catch(() => {});\n continue;\n }\n survivors.push({ path: file, mtimeMs: info.mtimeMs });\n }\n const surplus = survivors.length - this.caps.maxEntriesPerPrincipal;\n if (surplus <= 0) return;\n survivors.sort((a, b) => a.mtimeMs - b.mtimeMs || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n for (const victim of survivors.slice(0, surplus)) {\n await rm(victim.path, { force: true }).catch(() => {});\n }\n }\n}\n"],"mappings":"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","names":[]}
|