{"record":{"id":"0ea3301392e42014","repo":"can1357/oh-my-pi","slug":"security-scan-project-key-bundle-scan-projectkey","errorCode":null,"errorMessage":"Security scan project key ${bundle.scan.projectKey} does not match ${this.#projectKey}","messagePattern":"Security scan project key (.+?) does not match (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/store.ts","lineNumber":243,"sourceCode":"\t\t\tthrow new Error(`Invalid security store scan index at ${this.#indexPath()}`);\n\t\t}\n\t\tif (\n\t\t\tvalue.planIds !== undefined &&\n\t\t\t(!Array.isArray(value.planIds) || !value.planIds.every(id => typeof id === \"string\"))\n\t\t) {\n\t\t\tthrow new Error(`Invalid security store plan index at ${this.#indexPath()}`);\n\t\t}\n\t\treturn { ...value, planIds: value.planIds ?? [] } as SecurityStoreIndex;\n\t}\n\n\tasync #writeIndex(index: SecurityStoreIndex): Promise<void> {\n\t\tawait writeSecurityFileAtomic(this.#indexPath(), `${JSON.stringify(index, null, 2)}\\n`);\n\t}\n\n\tasync #putBundleUnlocked(input: SecurityScanBundle): Promise<void> {\n\t\tconst bundle = parseSecurityScanBundle(input);\n\t\tif (bundle.scan.projectKey !== this.#projectKey) {\n\t\t\tthrow new Error(`Security scan project key ${bundle.scan.projectKey} does not match ${this.#projectKey}`);\n\t\t}\n\t\tconst scanDirectory = this.#scanDirectory(bundle.scan.id);\n\t\tawait ensurePrivateDirectory(scanDirectory);\n\t\tawait writeSecurityFileAtomic(\n\t\t\tpath.join(scanDirectory, \"findings.json\"),\n\t\t\t`${JSON.stringify(bundle.findings, null, 2)}\\n`,\n\t\t);\n\t\tif (bundle.report !== undefined) {\n\t\t\tawait writeSecurityFileAtomic(path.join(scanDirectory, \"report.md\"), bundle.report);\n\t\t} else {\n\t\t\tawait fs.rm(path.join(scanDirectory, \"report.md\"), { force: true });\n\t\t}\n\t\tif (bundle.sarif !== undefined) {\n\t\t\tawait writeSecurityFileAtomic(\n\t\t\t\tpath.join(scanDirectory, \"results.sarif\"),\n\t\t\t\t`${JSON.stringify(bundle.sarif, null, 2)}\\n`,\n\t\t\t);\n\t\t} else {","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/store.ts#L225-L261","documentation":"#putBundleUnlocked() enforces that a scan bundle's projectKey matches the key derived from the repository root this SecurityStore was opened for. projectKey is computed by SecurityStore.open() via encodeSecurityProjectKey(realpath(repositoryRoot)). Writing a bundle stamped with another project's key would cross-contaminate stores, so it is rejected.","triggerScenarios":"Calling putBundle(), updateDisposition(), or updateValidation() with a bundle whose scan.projectKey differs from store.projectKey — e.g. the bundle was produced in a different checkout/clone or moved repository, or the store was opened with a different stateRoot against a moved repo whose canonical path changed.","commonSituations":"Copy-pasting scan bundles between machines or repos; a repository cloned to a new path so its canonical root (and thus projectKey) changed; opening the store via openForCwd() in a subdirectory of a different VCS root than where the scan ran; renaming/moving the repo between scan creation and store write.","solutions":["Open the SecurityStore for the repository the scan actually ran against (matching canonical realpath) so projectKeys align.","Re-emit the bundle with scan.projectKey set to store.projectKey only if the scan genuinely belongs to this repository and the old key is stale.","Use writeSecurityBundleToDirectory() / getBundle() on the original store instead of copying bundles across store instances.","Log both keys in the message and compare with the original scan store's projectKey to confirm which side is stale before editing anything."],"exampleFix":"// before\nconst storeA = await SecurityStore.open('/repos/app');\nawait storeA.putBundle(bundleFromOtherRepo); // scan.projectKey = key('/repos/app-copy')\n// after\nconst storeB = await SecurityStore.open('/repos/app-copy');\nawait storeB.putBundle(bundleFromOtherRepo); // open the store for the scan's own repo","handlingStrategy":"validation","validationCode":"const store = await SecurityStore.open(repoRoot);\nif (bundle.scan.projectKey !== store.projectKey) {\n  throw new Error(`bundle belongs to ${bundle.scan.projectKey}, not ${store.projectKey}`);\n}","typeGuard":"function matchesStore(bundle: SecurityScanBundle, store: SecurityStore): boolean {\n  return bundle.scan.projectKey === store.projectKey;\n}","tryCatchPattern":"try {\n  await store.putBundle(bundle);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('does not match')) {\n    // open the store for the bundle's own repository instead\n  } else throw err;\n}","preventionTips":["Always write bundles to the store opened from the same repositoryRoot that produced them.","Log store.projectKey and scan.projectKey together when moving scans between environments.","Re-derive project keys after repositories are moved or cloned; never assume keys are portable.","Use openForCwd() inside the scan's working tree so the canonical root matches."],"tags":["security-store","project-key-mismatch","identity-check"],"backgroundTag":"project-key-mismatch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}