{"record":{"id":"e03e3127d8e64599","repo":"can1357/oh-my-pi","slug":"invalid-security-plan-id-planid","errorCode":null,"errorMessage":"Invalid security plan id: ${planId}","messagePattern":"Invalid security plan id: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/store.ts","lineNumber":195,"sourceCode":"\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({\n\t\t\t\tschemaVersion: STORE_SCHEMA_VERSION,\n\t\t\t\tprojectKey: this.#projectKey,\n\t\t\t\trepositoryRoot: this.#repositoryRoot,\n\t\t\t\tscanIds: [],\n\t\t\t\tplanIds: [],","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/store.ts#L177-L213","documentation":"SecurityStore.#planPath enforces that plan ids match /^secplan_[a-zA-Z0-9]+$/ before using them in the plans/<id>.json path. This rejects malformed ids and prevents traversal out of the store's plans directory.","triggerScenarios":"putPlan/getPlan called with an id missing the 'secplan_' prefix or containing illegal characters (slashes, dots, hyphens, whitespace), usually an id produced outside the store.","commonSituations":"Hand-rolled plan ids like 'plan-001', ids remembered from a previous schema, passing a scan id where a plan id is expected, or truncated ids from logs.","solutions":["Use the plan id generated by putPlan (secplan_ prefixed) rather than a custom one","Check you are not passing a scan id (secscan_...) to getPlan","Sanitize/validate the id with /^secplan_[a-zA-Z0-9]+$/ before calling the store","Recreate the plan if the original id is lost"],"exampleFix":"// before\nstore.getPlan(\"plan-001\");\n// after\nconst { planId } = await store.putPlan(plan); // \"secplan_x9y8...\"\nstore.getPlan(planId);","handlingStrategy":"validation","validationCode":"if (!/^secplan_[a-zA-Z0-9]+$/.test(planId)) throw new Error(\"bad plan id\");","typeGuard":"function isPlanId(v: unknown): v is string { return typeof v === \"string\" && /^secplan_[a-zA-Z0-9]+$/.test(v); }","tryCatchPattern":"try { store.getPlan(planId); } catch (e) { if (String(e.message).startsWith(\"Invalid security plan id\")) { /* re-fetch id */ } else throw e; }","preventionTips":["Don't confuse secplan_ and secscan_ ids","Validate format before store calls"],"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"}