{"record":{"id":"79955aea9085dfe9","repo":"can1357/oh-my-pi","slug":"invalid-security-scan-id-scanid","errorCode":null,"errorMessage":"Invalid security scan id: ${scanId}","messagePattern":"Invalid security scan id: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/store.ts","lineNumber":190,"sourceCode":"\t\toptions.signal?.throwIfAborted();\n\t\tconst repositoryRoot = vcs.repo(resolvedCwd)?.root() ?? resolvedCwd;\n\t\treturn SecurityStore.open(repositoryRoot, options);\n\t}\n\n\tget repositoryRoot(): string {\n\t\treturn this.#repositoryRoot;\n\t}\n\n\tget projectKey(): string {\n\t\treturn this.#projectKey;\n\t}\n\n\tget projectDirectory(): string {\n\t\treturn this.#projectDirectory;\n\t}\n\n\t#scanDirectory(scanId: string): string {\n\t\tif (!/^secscan_[a-zA-Z0-9]+$/.test(scanId)) throw new Error(`Invalid security scan id: ${scanId}`);\n\t\treturn path.join(this.#projectDirectory, \"scans\", scanId);\n\t}\n\n\t#planPath(planId: string): string {\n\t\tif (!/^secplan_[a-zA-Z0-9]+$/.test(planId)) throw new Error(`Invalid security plan id: ${planId}`);\n\t\treturn path.join(this.#projectDirectory, \"plans\", `${planId}.json`);\n\t}\n\n\t#indexPath(): string {\n\t\treturn path.join(this.#projectDirectory, \"index.json\");\n\t}\n\n\tasync #ensureIndex(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#readIndex();\n\t\t} catch (error) {\n\t\t\tif (!isEnoent(error)) throw error;\n\t\t\tawait this.#writeIndex({","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/store.ts#L172-L208","documentation":"SecurityStore.#scanDirectory guards against path traversal and malformed ids by requiring scan ids to match /^secscan_[a-zA-Z0-9]+$/. Any other string is rejected before it is used to build the scans/<id> filesystem path, so untrusted ids cannot escape the store directory.","triggerScenarios":"getScan/rawFindings/report/sarifText/scanDirectory called with an id that lacks the 'secscan_' prefix, contains '/', '..', spaces, hyphens, or is empty — typically an id from user/LLM input or an older store.","commonSituations":"Storing and passing back a bare fingerprint instead of the scan id, truncating an id in UI/log copy-paste, hand-writing 'sec-scan_123', or legacy ids from a previous schema version.","solutions":["Use the exact scan id returned by the store when the scan was created (e.g. secscan_abc123)","Strip any decorations (quotes, ellipses, path prefixes) from a copy-pasted id","Regenerate the scan if the id came from an old/incompatible store","Validate ids against /^secscan_[a-zA-Z0-9]+$/ at your boundary before calling the store"],"exampleFix":"// before\nstore.getScan(\"my-scan-1\");\n// after\nconst { scanId } = await store.putScan(plan, findings); // \"secscan_1a2b...\"\nstore.getScan(scanId);","handlingStrategy":"validation","validationCode":"if (!/^secscan_[a-zA-Z0-9]+$/.test(scanId)) throw new Error(\"bad scan id\");","typeGuard":"function isScanId(v: unknown): v is string { return typeof v === \"string\" && /^secscan_[a-zA-Z0-9]+$/.test(v); }","tryCatchPattern":"try { store.getScan(scanId); } catch (e) { if (String(e.message).startsWith(\"Invalid security scan id\")) { /* re-fetch id */ } else throw e; }","preventionTips":["Treat ids as opaque strings from API responses","Validate format at boundaries"],"tags":["validation","path-traversal","input"],"backgroundTag":"invalid-identifier-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}