mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-07 13:22:26 +00:00
fix(semantic): delete indexes written before secrets stopped being indexed
Fixing the writer does not rewrite files already on disk, so a profile that built an index before today still has one holding stored secrets in plaintext. Reporting a version string in a status field and expecting the user to notice is not a remedy for that. Both implementations now check the manifest's model_id on every read — status, search, and the API equivalents — and delete records + manifest when it predates the fix. Reading a stale index would also mean serving results derived from the secrets it should never have held, so search purges before it reads rather than after. Deleting is safe: the index is a derived cache, rebuilt from the vault in milliseconds, and the user is told to rebuild. A manifest that cannot be parsed is deliberately NOT treated as stale. "Cannot tell" must not mean "delete it", or every corrupt-manifest read would throw away a working index; that case is tested on both sides. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01Vtx94nXDmNgXEeThUZP4QN
This commit is contained in:
1 parent
fdbdb12596
commit
885cbe5665
8 files changed
+228
-23
No files matched your search
@@ -8,7 +8,7 @@
|
||||
* is the part nobody notices — which secret a given entry produces.
|
||||
*/
|
||||
|
||||
import { readFile, mkdir } from "node:fs/promises";
|
||||
import { readFile, mkdir, rm } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { basename, dirname, join, resolve as resolvePath } from "node:path";
|
||||
import {
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
searchSemanticRecords,
|
||||
semanticManifest,
|
||||
semanticStatus,
|
||||
isStaleSemanticIndex,
|
||||
type SemanticRecord,
|
||||
totpCodeAt,
|
||||
Bip85,
|
||||
@@ -822,6 +823,30 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
|
||||
const semanticRecordsPath = (): string => join(semanticDir(), "records.json");
|
||||
const semanticManifestPath = (): string => join(semanticDir(), "manifest.json");
|
||||
|
||||
async function readSemanticManifest(): Promise<Record<string, unknown>> {
|
||||
if (!existsSync(semanticManifestPath())) return {};
|
||||
try {
|
||||
return JSON.parse(await readFile(semanticManifestPath(), "utf8")) as Record<string, unknown>;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an index written before secrets stopped being indexed.
|
||||
*
|
||||
* Such a file holds stored secrets in the clear, and fixing the writer does
|
||||
* not rewrite what is already on disk. Deleting is safe — it is a derived
|
||||
* cache — and serving results from it would mean serving results derived
|
||||
* from secrets it should never have held.
|
||||
*/
|
||||
async function purgeStaleSemantic(): Promise<void> {
|
||||
if (!isStaleSemanticIndex(await readSemanticManifest())) return;
|
||||
for (const path of [semanticRecordsPath(), semanticManifestPath()]) {
|
||||
if (existsSync(path)) await rm(path, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function readSemanticRecords(): Promise<SemanticRecord[]> {
|
||||
if (!existsSync(semanticRecordsPath())) return [];
|
||||
try {
|
||||
@@ -862,14 +887,8 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
|
||||
}
|
||||
|
||||
server.route("GET", "/api/v1/semantic/status", async () => {
|
||||
let manifest: Record<string, unknown> = {};
|
||||
if (existsSync(semanticManifestPath())) {
|
||||
try {
|
||||
manifest = JSON.parse(await readFile(semanticManifestPath(), "utf8"));
|
||||
} catch {
|
||||
// Corrupt manifest reports as not-built; the fix is a rebuild.
|
||||
}
|
||||
}
|
||||
await purgeStaleSemantic();
|
||||
const manifest = await readSemanticManifest();
|
||||
return { json: semanticStatus(manifest, (await readSemanticRecords()).length) };
|
||||
});
|
||||
|
||||
@@ -883,6 +902,7 @@ export function registerRoutes(server: ApiServer, ctx: ApiContext): void {
|
||||
|
||||
server.route("POST", "/api/v1/semantic/search", async (req) => {
|
||||
const body = bodyObject(req);
|
||||
await purgeStaleSemantic();
|
||||
const records = await readSemanticRecords();
|
||||
if (records.length === 0) {
|
||||
throw new HttpError(409, "no semantic index; POST /api/v1/semantic/build first");
|
||||
|
||||
Reference in new issue
Block a user