{"record":{"id":"ed4da0cd0b340b18","repo":"can1357/oh-my-pi","slug":"security-scan-contains-duplicate-finding-ids","errorCode":null,"errorMessage":"Security scan contains duplicate finding ids","messagePattern":"Security scan contains duplicate finding ids","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/security/contracts/validation.ts","lineNumber":36,"sourceCode":"\tconst result = securityScanSchema(value);\n\tif (result instanceof type.errors) throw schemaError(\"Security scan\", result);\n\treturn result as SecurityScan;\n}\n\nexport function parseSecurityScanPlan(value: unknown): SecurityScanPlan {\n\tconst { securityScanPlanSchema } = getSecurityContractSchemas();\n\tconst result = securityScanPlanSchema(value);\n\tif (result instanceof type.errors) throw schemaError(\"Security scan plan\", result);\n\treturn result as SecurityScanPlan;\n}\n\nexport function parseSecurityScanBundle(value: unknown): SecurityScanBundle {\n\tconst { securityScanBundleSchema } = getSecurityContractSchemas();\n\tconst result = securityScanBundleSchema(value);\n\tif (result instanceof type.errors) throw schemaError(\"Security scan bundle\", result);\n\tconst bundle = result as SecurityScanBundle;\n\tconst findingIds = new Set(bundle.findings.map(finding => finding.id));\n\tif (findingIds.size !== bundle.findings.length) throw new Error(\"Security scan contains duplicate finding ids\");\n\tconst referencedFindingIds = new Set(bundle.scan.findingIds);\n\tif (referencedFindingIds.size !== bundle.scan.findingIds.length) {\n\t\tthrow new Error(\"Security scan manifest contains duplicate finding references\");\n\t}\n\tfor (const findingId of referencedFindingIds) {\n\t\tif (!findingIds.has(findingId)) throw new Error(`Security scan references missing finding: ${findingId}`);\n\t}\n\tfor (const findingId of findingIds) {\n\t\tif (!referencedFindingIds.has(findingId))\n\t\t\tthrow new Error(`Security scan omits finding from manifest: ${findingId}`);\n\t}\n\tfor (const finding of bundle.findings) {\n\t\tif (finding.scanId !== bundle.scan.id) {\n\t\t\tthrow new Error(`Finding ${finding.id} belongs to ${finding.scanId}, expected ${bundle.scan.id}`);\n\t\t}\n\t\tconst evidenceIds = new Set(finding.evidence.map(evidence => evidence.id));\n\t\tif (evidenceIds.size !== finding.evidence.length) {\n\t\t\tthrow new Error(`Finding ${finding.id} contains duplicate evidence ids`);","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/security/contracts/validation.ts#L18-L54","documentation":"parseSecurityScanBundle validates the structural contract of a scan bundle beyond the schema: finding ids must be unique. Duplicate ids make by-id lookups and diffing ambiguous, so parsing fails fast with this error. It runs for every bundle read from disk, imported from Codex/SARIF, or cached in the store.","triggerScenarios":"Loading a JSON bundle file whose findings array contains two entries with the same id; importing a SARIF or Codex bundle whose conversion produced colliding ids.","commonSituations":"Manually merging two scan exports by concatenating findings arrays; a generator bug reusing placeholder ids; SARIF results from multiple runs merged without re-keying ids.","solutions":["Regenerate the bundle from the scanner instead of hand-merging exports","If merging intentionally, re-key ids to be unique (e.g. prefix with source scan id) and keep scan.findingIds consistent","Validate before parse: JSON.parse the file and check new Set(ids).size === ids.length","Re-export a single authoritative scan rather than concatenating multiple runs"],"exampleFix":"// before\nconst merged = { ...a, findings: [...a.findings, ...b.findings] };\nparseSecurityScanBundle(merged); // throws: duplicate ids\n// after\nconst findings = [...a.findings, ...b.findings.map(f => ({ ...f, id: `${b.scan.id}:${f.id}` }))];\nparseSecurityScanBundle({ ...a, findings, scan: { ...a.scan, findingIds: findings.map(f => f.id) } });","handlingStrategy":"validation","validationCode":"const raw = JSON.parse(text);\nconst ids = raw.findings.map((f: { id: string }) => f.id);\nif (new Set(ids).size !== ids.length) throw new Error(\"Bundle has duplicate finding ids; regenerate it\");","typeGuard":null,"tryCatchPattern":"try {\n\tbundle = parseSecurityScanBundle(value);\n} catch (err) {\n\tif (err instanceof Error && err.message === \"Security scan contains duplicate finding ids\") {\n\t\t// dedupe/re-key ids or re-export the scan\n\t} else throw err;\n}","preventionTips":["Never concatenate findings arrays from multiple exports without re-keying ids","Derive ids from the scanner, not placeholders","Validate exported bundles before archiving them","Test your bundle generator for id collisions"],"tags":["validation","duplicate-data","security-scan"],"backgroundTag":"duplicate-finding-ids","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}