@clear-capabilities/agentic-security-scanner 0.142.0 → 0.144.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +399 -0
  2. package/bin/agentic-security.js +530 -54
  3. package/dist/1.index.js +223 -0
  4. package/dist/113.index.js +108 -17
  5. package/dist/144.index.js +163 -0
  6. package/dist/178.index.js +1 -1
  7. package/dist/238.index.js +3 -2
  8. package/dist/265.index.js +191 -0
  9. package/dist/384.index.js +1 -1
  10. package/dist/435.index.js +165 -52
  11. package/dist/526.index.js +108 -17
  12. package/dist/552.index.js +97 -0
  13. package/dist/637.index.js +1 -1
  14. package/dist/730.index.js +311 -0
  15. package/dist/736.index.js +301 -0
  16. package/dist/824.index.js +7 -0
  17. package/dist/905.index.js +88 -22
  18. package/dist/920.index.js +491 -0
  19. package/dist/970.index.js +109 -0
  20. package/dist/agentic-security.mjs +13 -13
  21. package/dist/agentic-security.mjs.sha256 +1 -1
  22. package/dist/calibration-seed.json +2 -0
  23. package/package.json +19 -11
  24. package/src/dataflow/index.js +18 -0
  25. package/src/dataflow/privacy-catalog.js +290 -0
  26. package/src/dataflow/privacy-deep-walker.js +515 -0
  27. package/src/dataflow/privacy-governance.js +126 -0
  28. package/src/dataflow/privacy-inventory.js +154 -0
  29. package/src/dataflow/privacy-sink-policy.js +125 -0
  30. package/src/dataflow/privacy-taint.js +115 -54
  31. package/src/dataflow/privacy-taxonomy.js +233 -0
  32. package/src/discovery/disprove.js +7 -3
  33. package/src/discovery/hunter.js +9 -5
  34. package/src/discovery/index.js +2 -2
  35. package/src/discovery/llm-invoke.js +69 -13
  36. package/src/egress/audit.js +147 -0
  37. package/src/egress/policy.js +313 -0
  38. package/src/egress/redact.js +180 -0
  39. package/src/engine.js +575 -288
  40. package/src/fix/apply-fix-service.js +403 -0
  41. package/src/fix/approver-registry.js +157 -0
  42. package/src/llm-validator/index.js +86 -9
  43. package/src/llm-validator/model-status.js +66 -0
  44. package/src/mcp/tools.js +157 -50
  45. package/src/pipeline/analyzer-supervisor.js +93 -0
  46. package/src/pipeline/analyzer-worker.js +26 -0
  47. package/src/pipeline/annotator-runner.js +33 -0
  48. package/src/pipeline/assurance-mode.js +91 -0
  49. package/src/pipeline/cascade-worker-pool.js +172 -0
  50. package/src/pipeline/cascade-worker.js +43 -0
  51. package/src/pipeline/coverage-ledger.js +0 -0
  52. package/src/pipeline/detector-runner.js +51 -0
  53. package/src/pipeline/enrichment-completion.js +58 -0
  54. package/src/pipeline/evidence-provenance.js +91 -0
  55. package/src/pipeline/finding-schema.js +101 -0
  56. package/src/pipeline/legacy-compat.js +101 -0
  57. package/src/pipeline/producer-collector.js +48 -0
  58. package/src/pipeline/producer-registry.js +112 -0
  59. package/src/pipeline/scan-health.js +144 -0
  60. package/src/posture/CLAUDE.md +2 -0
  61. package/src/posture/accuracy-scorecard.js +96 -1
  62. package/src/posture/adversary-agent.js +15 -3
  63. package/src/posture/artifact-registry.js +217 -0
  64. package/src/posture/auditor-walkthrough.js +70 -8
  65. package/src/posture/calibration-feedback.js +201 -0
  66. package/src/posture/calibration-seed.json +2 -0
  67. package/src/posture/calibration.js +25 -0
  68. package/src/posture/compliance-evidence-signing.js +131 -0
  69. package/src/posture/compliance-policy.js +314 -17
  70. package/src/posture/custom-rules.js +36 -0
  71. package/src/posture/deterministic.js +8 -1
  72. package/src/posture/encryption-provider.js +205 -0
  73. package/src/posture/evidence-grade-wording.js +71 -0
  74. package/src/posture/fix-history.js +113 -19
  75. package/src/posture/fix-honesty-gate.js +47 -6
  76. package/src/posture/fix-verify.js +56 -7
  77. package/src/posture/fleet.js +0 -0
  78. package/src/posture/flow-narration.js +7 -2
  79. package/src/posture/legal-hold.js +140 -0
  80. package/src/posture/llm-redteam.js +10 -1
  81. package/src/posture/material-change.js +90 -0
  82. package/src/posture/policy-bundle.js +274 -0
  83. package/src/posture/privacy-framework.js +33 -6
  84. package/src/posture/production-feedback.js +179 -0
  85. package/src/posture/retention-policy.js +132 -0
  86. package/src/posture/risk-dollars.js +216 -26
  87. package/src/posture/scan-checkpoint.js +176 -31
  88. package/src/posture/state-dir.js +36 -1
  89. package/src/posture/state-lifecycle-report.js +77 -0
  90. package/src/posture/suppressions.js +59 -3
  91. package/src/privacy/ir-adapter.js +380 -0
  92. package/src/report/index.js +83 -18
  93. package/src/report/oscal.js +635 -0
  94. package/src/sast/cpp.js +3 -14
  95. package/src/sca/llm-function-extract.js +6 -0
@@ -1 +1 @@
1
- 9b5c22a2088e4b17eafbbc3e70ea6fcb0a53fd5ae3e7d8d4848eeed8da6e2679 agentic-security.mjs
1
+ cdbd5909c8fe1682a351069baff2466e3d1ee7c525f2122909a4954f0c488edf agentic-security.mjs
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "_doc": "Seed calibration corpus for P1.3 / FR-UX-1. Each entry is (family, tp, fp) from running the engine against labeled benchmarks. The runtime merges this with the customer's .agentic-security/validator-metrics.json. Customer counts override when their n is higher.",
3
3
  "_source": "OWASP Benchmark v1.2 (Java) + Juliet Java + Juliet C/C++ + synthetic-bench fixtures + curated NodeGoat — counts as-of v0.48.0. ~30 samples per family minimum for calibrated emit.",
4
+ "_generatedAt": "2026-05-18T19:15:00-07:00",
5
+ "_generatedAtNote": "FR-207: the actual git commit timestamp of this file (git log -1 --format=%aI), recorded here as a machine-readable field so posture/calibration.js's calibrationFreshness() can compute a real age instead of inferring one from the '_source' version string. Update this value only when the family counts below actually change.",
4
6
  "_caveat": "This is a SEED corpus, not a held-out test set. The PRD G1 target (Brier ≤ 0.10) requires a separate held-out labeled set; that work is queued for Phase 5 finalization. Calibrated values shipped now are honest empirical TP rates from this seed, with their Wilson 95% CI and N visible so consumers can judge.",
5
7
  "families": {
6
8
  "sql-injection": { "tp": 41, "fp": 3 },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.142.0",
4
- "description": "Scanner engine for the agentic-security Claude Code plugin SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
3
+ "version": "0.144.0",
4
+ "description": "Scanner engine for the agentic-security Claude Code plugin \u2014 SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
@@ -40,7 +40,7 @@
40
40
  "@babel/preset-react": "^8.0.1",
41
41
  "@babel/preset-typescript": "^8.0.1",
42
42
  "java-parser": "^3.0.1",
43
- "js-yaml": "^5.3.0",
43
+ "js-yaml": "^5.4.1",
44
44
  "safe-regex": "^2.1.1"
45
45
  },
46
46
  "optionalDependencies": {
@@ -48,7 +48,7 @@
48
48
  "web-tree-sitter": "0.20.8"
49
49
  },
50
50
  "devDependencies": {
51
- "@types/node": "^26.2.0",
51
+ "@types/node": "^26.3.0",
52
52
  "@vercel/ncc": "^0.45.0"
53
53
  },
54
54
  "overrides": {
@@ -59,19 +59,19 @@
59
59
  "build": "ncc build bin/agentic-security.js -o dist --minify -e web-tree-sitter -e tree-sitter-wasms && rm -rf dist/compliance-frameworks && mkdir -p dist/compliance-frameworks && cp src/posture/compliance-frameworks/*.json dist/compliance-frameworks/ && mv dist/index.js dist/agentic-security.mjs && rm -f dist/package.json && chmod +x dist/agentic-security.mjs && node -e \"const fs=require('fs');const p='dist/agentic-security.mjs';const c=fs.readFileSync(p,'utf8');if(!c.startsWith('#!'))fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c);\" && node -e \"const fs=require('fs');const c=require('crypto');const h=c.createHash('sha256').update(fs.readFileSync('dist/agentic-security.mjs')).digest('hex');fs.writeFileSync('dist/agentic-security.mjs.sha256',h+' agentic-security.mjs\\n');\"",
60
60
  "prepare": "node ../scripts/pre-push-gate.mjs --install-hook",
61
61
  "prepublishOnly": "npm run build && node ../scripts/sync-scanner-changelog.mjs && node ../scripts/release-check.mjs",
62
- "test": "npm run test:smoke && npm run test:glob && npm run test:sast && npm run test:posture && npm run test:dataflow && npm run test:mcp && npm run test:report && npm run test:bench-modules && npm run test:lifecycle && npm run test:eval && npm run test:discovery && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && npm run test:python",
62
+ "test": "node ../scripts/run-unit-tests.mjs && AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && npm run test:python",
63
63
  "test:python": "node ../scripts/run-python-tests.mjs",
64
64
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
65
65
  "test:glob": "node --test test/glob-compat.test.js",
66
- "test:sast": "node --test test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js",
67
- "test:posture": "node --test test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js",
68
- "test:dataflow": "node --test test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js",
66
+ "test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js",
67
+ "test:posture": "node --test test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/release-workflow.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js",
68
+ "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js",
69
69
  "test:mcp": "node --test test/mcp.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js",
70
- "test:report": "node --test test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js",
70
+ "test:report": "node --test test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/scan-health.test.js test/finding-schema.test.js test/evidence-provenance.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
71
71
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
72
72
  "test:lifecycle": "node --test test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs",
73
73
  "test:eval": "node --test test/independent-eval.test.js test/realworld-recall.test.js",
74
- "test:discovery": "node --test test/discovery-partition.test.js test/discovery-lenses.test.js test/discovery-hunter.test.js test/discovery-confirm.test.js test/discovery-disprove.test.js test/discovery-judge.test.js test/discovery-run.test.js test/discovery-wiring.test.js test/discovery-llm-invoke.test.js",
74
+ "test:discovery": "node --test test/discovery-partition.test.js test/discovery-lenses.test.js test/discovery-hunter.test.js test/discovery-confirm.test.js test/discovery-disprove.test.js test/discovery-judge.test.js test/discovery-run.test.js test/discovery-wiring.test.js test/discovery-llm-invoke.test.js test/local-only-mode.test.js",
75
75
  "eval:independent": "node ../bench/independent-eval/runner.mjs",
76
76
  "eval:independent:gate": "node ../bench/independent-eval/runner.mjs --gate default",
77
77
  "smoke": "node bin/agentic-security.js scan test/fixtures/vulnerable-js",
@@ -85,6 +85,8 @@
85
85
  "bench:cve-replay:ci": "node ../bench/cve-replay/runner.mjs --ci",
86
86
  "bench:cve-replay:check": "node ../bench/cve-replay/runner.mjs --check-baseline",
87
87
  "bench:cve-replay:update-baseline": "node ../bench/cve-replay/runner.mjs --update-baseline",
88
+ "bench:independent:gate:check": "node ../scripts/independent-population-gate.mjs",
89
+ "bench:independent:update-gate-baseline": "node ../scripts/independent-population-gate.mjs --update-baseline",
88
90
  "bench:router-replay": "node ../bench/router-replay/runner.mjs",
89
91
  "bench:router-replay:check": "node ../bench/router-replay/runner.mjs --check-baseline",
90
92
  "bench:router-replay:update-baseline": "node ../bench/router-replay/runner.mjs --update-baseline",
@@ -98,6 +100,9 @@
98
100
  "bench:ttff": "node ../bench/ttff/runner.mjs",
99
101
  "bench:ttff:check": "node ../bench/ttff/runner.mjs --check",
100
102
  "bench:ttff:update-baseline": "node ../bench/ttff/runner.mjs --update-baseline",
103
+ "bench:memory": "node ../bench/memory/runner.mjs",
104
+ "bench:memory:check": "node ../bench/memory/runner.mjs --check",
105
+ "bench:memory:update-baseline": "node ../bench/memory/runner.mjs --update-baseline",
101
106
  "bench:self-scan": "node ../bench/self-scan/measure.mjs",
102
107
  "bench:self-scan:check": "node ../bench/self-scan/check.mjs",
103
108
  "bench:mutation:check": "node ../bench/mutation/runner.mjs",
@@ -113,6 +118,9 @@
113
118
  "bench:layer-recall": "node ../bench/layer-recall/runner.mjs",
114
119
  "bench:layer-recall:check": "node ../bench/layer-recall/runner.mjs --check",
115
120
  "bench:layer-recall:update-baseline": "node ../bench/layer-recall/runner.mjs --update-baseline",
121
+ "bench:privacy-recall": "node ../bench/privacy-recall/measure.mjs",
122
+ "bench:privacy-recall:check": "node ../bench/privacy-recall/check.mjs",
123
+ "bench:privacy-recall:update-baseline": "node ../bench/privacy-recall/check.mjs --update-baseline",
116
124
  "bench:self-scan:update-baseline": "node ../bench/self-scan/check.mjs --update-baseline",
117
125
  "bench:engine-recall": "node ../bench/engine-recall/measure.mjs",
118
126
  "scorecard": "node ../scripts/scorecard.mjs",
@@ -151,4 +159,4 @@
151
159
  "bugs": {
152
160
  "url": "https://github.com/Clear-Capabilities/agentic-security/issues"
153
161
  }
154
- }
162
+ }
@@ -18,6 +18,7 @@ import { runIfdsTaintEngine } from './ifds.js';
18
18
  import { proveExploits } from './exploit-prover.js';
19
19
  import { applyStubAwareFilter } from './stub-aware-filter.js';
20
20
  import { loadProjectStubs } from '../ir/type-stubs.js';
21
+ import { runPrivacyTaintEngine } from './privacy-deep-walker.js';
21
22
 
22
23
  export function runDeepAnalysis(perFileIR, callGraph, opts = {}) {
23
24
  // Path-feasibility pass over every function before the taint walk.
@@ -121,6 +122,23 @@ export function runDeepAnalysis(perFileIR, callGraph, opts = {}) {
121
122
  }
122
123
  } catch { /* IFDS failure should not fail the scan */ }
123
124
  }
125
+ // FR-403 step 3 (assurance-hardening PRD, D-0047): opt-in, isolated
126
+ // privacy-taint walker. OFF by default. Mirrors the IFDS wiring above
127
+ // exactly -- an additional pass whose findings are merged in, never
128
+ // replacing the primary engine's output, and whose failure must never
129
+ // fail the scan. See dataflow/privacy-deep-walker.js's own header for why
130
+ // this is a wholly separate module rather than a change to runTaintEngine/
131
+ // step/exprTaint above.
132
+ if (process.env.AGENTIC_SECURITY_PRIVACY_DEEP === '1') {
133
+ try {
134
+ const privacyFindings = runPrivacyTaintEngine(callGraph, opts);
135
+ const existing = new Set(findings.map(f => `${f.file}:${f.line}:${f.sink?.label || f.cwe || ''}`));
136
+ for (const f of privacyFindings) {
137
+ const key = `${f.file}:${f.line}:${f.sink?.label || f.cwe || ''}`;
138
+ if (!existing.has(key)) findings.push(f);
139
+ }
140
+ } catch { /* privacy-deep failure should not fail the scan */ }
141
+ }
124
142
  for (const f of findings) f._pathFeasibilityPruned = totalPruned;
125
143
  if (preSeededCache) {
126
144
  Object.defineProperty(findings, '_incrementalStats', {
@@ -0,0 +1,290 @@
1
+ // FR-403 (assurance-hardening PRD), Steps 1-2 of D-0041's decomposition plan:
2
+ // "Track flows through ... storage, logs, responses, analytics, email,
3
+ // files, object storage, queues, and outbound APIs."
4
+ //
5
+ // STEP 1: A catalog of PRIVACY-LEAK (CWE-359) sink entries, in the EXACT shape
6
+ // `dataflow/catalog.js` already uses for every other family (see that
7
+ // module's own header for the full `{kind, id, language, framework, match,
8
+ // argIndex, vuln}` contract) — mirroring `privacy-taint.js`'s existing
9
+ // `SINK_PATTERNS` (log/response/outboundHttp/thirdPartySdk/fileWrite/
10
+ // s3Upload/emailSend) plus the 2 sink categories FR-403 names that had no
11
+ // coverage anywhere (generic storage/DB, queues).
12
+ //
13
+ // DELIBERATELY NOT MERGED INTO `catalog.js`'s own `CATALOG` EXPORT, and NOT
14
+ // wired into `matchSinkOrSanitizer`/`runTaintEngine` — this is the single
15
+ // most important design decision in this file, found DURING implementation
16
+ // (not anticipated by D-0041's original grounding pass, which characterized
17
+ // step 1 as "zero behavior change"). `CATALOG` is a shared, always-active
18
+ // singleton: matchSinkOrSanitizer/matchSource read it unconditionally for
19
+ // EVERY scan, with no per-family or per-source filtering at the taint-state
20
+ // level (a tainted value's origin family is never recorded — only whether
21
+ // it is tainted at all). Appending these sink entries directly to `CATALOG`
22
+ // would therefore make them reachable by every ALREADY-ACTIVE general
23
+ // security source (`req.body`, `req.query`, etc.) immediately, producing
24
+ // spurious CWE-359 "privacy-leak" findings for ordinary attacker-input
25
+ // flows that have nothing to do with data actually classified PII/PHI/PCI
26
+ // by `privacy-taxonomy.js` — a real, immediate false-positive class, not
27
+ // the "zero risk" step D-0041's own step 1 was meant to be. Keeping this
28
+ // catalog in its own file, imported by nothing yet, is what makes it
29
+ // genuinely inert: these entries do not affect any scan's output until a
30
+ // future step (D-0041 step 3) builds a privacy-specific matcher that
31
+ // consults ONLY the sources `privacy-taxonomy.js` actually classifies,
32
+ // never the general engine's already-active source set.
33
+ //
34
+ // Registered in `test/no-dead-modules.test.js`'s ALLOWLIST — a deliberate,
35
+ // staged capability with no consumer yet, not an oversight.
36
+
37
+ import { classifyFieldAgainst } from './privacy-taxonomy.js';
38
+ import { _languageFamilyExtensions } from './catalog.js';
39
+
40
+ export const PRIVACY_SINK_CATALOG = [
41
+ // ── log ──────────────────────────────────────────────────────────────────
42
+ { kind: 'sink', id: 'privacy-js-console-log', language: 'js', framework: 'node', category: 'log',
43
+ match: { type: 'call', callee: 'log', receiverTypeIn: ['console'] }, argIndex: 'all',
44
+ vuln: { name: 'Privacy Leak (console.log)', severity: 'medium', cwe: 'CWE-359',
45
+ remediation: 'Do not log regulated data (PII/PHI/PCI). Redact or hash the field before logging.' } },
46
+ { kind: 'sink', id: 'privacy-js-console-error', language: 'js', framework: 'node', category: 'log',
47
+ match: { type: 'call', callee: 'error', receiverTypeIn: ['console'] }, argIndex: 'all',
48
+ vuln: { name: 'Privacy Leak (console.error)', severity: 'medium', cwe: 'CWE-359',
49
+ remediation: 'Do not log regulated data (PII/PHI/PCI). Redact or hash the field before logging.' } },
50
+ { kind: 'sink', id: 'privacy-js-logger-info', language: 'js', framework: 'node', category: 'log',
51
+ match: { type: 'call', callee: 'info', receiverTypeIn: ['log|logger|Logger'] }, argIndex: 'all',
52
+ vuln: { name: 'Privacy Leak (logger.info)', severity: 'medium', cwe: 'CWE-359',
53
+ remediation: 'Do not log regulated data (PII/PHI/PCI). Redact or hash the field before logging.' } },
54
+ { kind: 'sink', id: 'privacy-js-logger-warn', language: 'js', framework: 'node', category: 'log',
55
+ match: { type: 'call', callee: 'warn', receiverTypeIn: ['log|logger|Logger'] }, argIndex: 'all',
56
+ vuln: { name: 'Privacy Leak (logger.warn)', severity: 'medium', cwe: 'CWE-359',
57
+ remediation: 'Do not log regulated data (PII/PHI/PCI). Redact or hash the field before logging.' } },
58
+
59
+ // ── response ─────────────────────────────────────────────────────────────
60
+ { kind: 'sink', id: 'privacy-js-res-send', language: 'js', framework: 'express', category: 'response',
61
+ match: { type: 'call', callee: 'send', receiverTypeIn: ['res|response'] }, argIndex: 0,
62
+ vuln: { name: 'Privacy Leak (response body)', severity: 'high', cwe: 'CWE-359',
63
+ remediation: 'Do not return regulated data (PII/PHI/PCI) in an API response unless the requester is authorized and the field is required.' } },
64
+ { kind: 'sink', id: 'privacy-js-res-json', language: 'js', framework: 'express', category: 'response',
65
+ match: { type: 'call', callee: 'json', receiverTypeIn: ['res|response'] }, argIndex: 0,
66
+ vuln: { name: 'Privacy Leak (response body)', severity: 'high', cwe: 'CWE-359',
67
+ remediation: 'Do not return regulated data (PII/PHI/PCI) in an API response unless the requester is authorized and the field is required.' } },
68
+
69
+ // ── outboundHttp ─────────────────────────────────────────────────────────
70
+ { kind: 'sink', id: 'privacy-js-fetch', language: 'js', framework: 'browser', category: 'outboundHttp',
71
+ match: { type: 'call', callee: 'fetch' }, argIndex: 1,
72
+ vuln: { name: 'Privacy Leak (outbound HTTP request)', severity: 'high', cwe: 'CWE-359',
73
+ remediation: 'Do not send regulated data (PII/PHI/PCI) to a third-party endpoint without a documented data-processing agreement.' } },
74
+ { kind: 'sink', id: 'privacy-js-axios-post', language: 'js', framework: 'axios', category: 'outboundHttp',
75
+ match: { type: 'call', callee: 'post', receiverTypeIn: ['axios'] }, argIndex: 1,
76
+ vuln: { name: 'Privacy Leak (outbound HTTP request)', severity: 'high', cwe: 'CWE-359',
77
+ remediation: 'Do not send regulated data (PII/PHI/PCI) to a third-party endpoint without a documented data-processing agreement.' } },
78
+
79
+ // ── thirdPartySdk (analytics) ────────────────────────────────────────────
80
+ { kind: 'sink', id: 'privacy-js-analytics-track', language: 'js', framework: 'analytics', category: 'thirdPartySdk',
81
+ match: { type: 'call', callee: 'track', receiverTypeIn: ['stripe|sentry|datadog|segment|amplitude|mixpanel|posthog|braze|intercom|analytics'] }, argIndex: 'all',
82
+ vuln: { name: 'Privacy Leak (third-party analytics)', severity: 'high', cwe: 'CWE-359',
83
+ remediation: 'Do not send regulated data (PII/PHI/PCI) to an analytics/SDK provider — pseudonymize identifiers instead of raw fields.' } },
84
+ { kind: 'sink', id: 'privacy-js-analytics-identify', language: 'js', framework: 'analytics', category: 'thirdPartySdk',
85
+ match: { type: 'call', callee: 'identify', receiverTypeIn: ['stripe|sentry|datadog|segment|amplitude|mixpanel|posthog|braze|intercom|analytics'] }, argIndex: 'all',
86
+ vuln: { name: 'Privacy Leak (third-party analytics)', severity: 'high', cwe: 'CWE-359',
87
+ remediation: 'Do not send regulated data (PII/PHI/PCI) to an analytics/SDK provider — pseudonymize identifiers instead of raw fields.' } },
88
+
89
+ // ── fileWrite ────────────────────────────────────────────────────────────
90
+ { kind: 'sink', id: 'privacy-js-fs-writeFile', language: 'js', framework: 'node', category: 'fileWrite',
91
+ match: { type: 'call', callee: 'writeFile', receiverTypeIn: ['fs'] }, argIndex: 1,
92
+ vuln: { name: 'Privacy Leak (file write)', severity: 'medium', cwe: 'CWE-359',
93
+ remediation: 'Do not write regulated data (PII/PHI/PCI) to an unencrypted file without a documented retention and access-control policy.' } },
94
+ { kind: 'sink', id: 'privacy-js-fs-writeFileSync', language: 'js', framework: 'node', category: 'fileWrite',
95
+ match: { type: 'call', callee: 'writeFileSync', receiverTypeIn: ['fs'] }, argIndex: 1,
96
+ vuln: { name: 'Privacy Leak (file write)', severity: 'medium', cwe: 'CWE-359',
97
+ remediation: 'Do not write regulated data (PII/PHI/PCI) to an unencrypted file without a documented retention and access-control policy.' } },
98
+
99
+ // ── s3Upload (object storage) ────────────────────────────────────────────
100
+ { kind: 'sink', id: 'privacy-js-s3-putObject', language: 'js', framework: 'aws-sdk', category: 's3Upload',
101
+ match: { type: 'call', callee: 'putObject', receiverTypeIn: ['s3|S3Client|aws'] }, argIndex: 0,
102
+ vuln: { name: 'Privacy Leak (object storage upload)', severity: 'high', cwe: 'CWE-359',
103
+ remediation: 'Do not upload regulated data (PII/PHI/PCI) to object storage without server-side encryption and a documented access policy.' } },
104
+
105
+ // ── emailSend ────────────────────────────────────────────────────────────
106
+ { kind: 'sink', id: 'privacy-js-sendMail', language: 'js', framework: 'nodemailer', category: 'emailSend',
107
+ match: { type: 'call', callee: 'sendMail' }, argIndex: 0,
108
+ vuln: { name: 'Privacy Leak (outbound email)', severity: 'medium', cwe: 'CWE-359',
109
+ remediation: 'Do not include regulated data (PII/PHI/PCI) in an outbound email body beyond what the recipient is authorized to receive.' } },
110
+
111
+ // ── storage (generic DB — the first of FR-403's 2 previously-uncovered categories) ──
112
+ { kind: 'sink', id: 'privacy-js-mongo-insertOne', language: 'js', framework: 'mongodb', category: 'storage',
113
+ match: { type: 'call', callee: 'insertOne', receiverTypeIn: ['collection|db|mongo'] }, argIndex: 0,
114
+ vuln: { name: 'Privacy Leak (database write)', severity: 'medium', cwe: 'CWE-359',
115
+ remediation: 'Regulated data (PII/PHI/PCI) written to a database must be covered by a documented retention, access-control, and encryption-at-rest policy.' } },
116
+ { kind: 'sink', id: 'privacy-js-mongo-insertMany', language: 'js', framework: 'mongodb', category: 'storage',
117
+ match: { type: 'call', callee: 'insertMany', receiverTypeIn: ['collection|db|mongo'] }, argIndex: 0,
118
+ vuln: { name: 'Privacy Leak (database write)', severity: 'medium', cwe: 'CWE-359',
119
+ remediation: 'Regulated data (PII/PHI/PCI) written to a database must be covered by a documented retention, access-control, and encryption-at-rest policy.' } },
120
+
121
+ // ── queues (the second of FR-403's 2 previously-uncovered categories) ────
122
+ { kind: 'sink', id: 'privacy-js-queue-sendMessage', language: 'js', framework: 'aws-sdk', category: 'queues',
123
+ match: { type: 'call', callee: 'sendMessage', receiverTypeIn: ['queue|sqs|SQS'] }, argIndex: 0,
124
+ vuln: { name: 'Privacy Leak (message queue)', severity: 'medium', cwe: 'CWE-359',
125
+ remediation: 'Regulated data (PII/PHI/PCI) placed on a queue is readable by every consumer with queue access — document who can subscribe before sending it.' } },
126
+ { kind: 'sink', id: 'privacy-js-queue-publish', language: 'js', framework: 'pubsub', category: 'queues',
127
+ match: { type: 'call', callee: 'publish', receiverTypeIn: ['queue|sns|SNS|topic|pubsub|kafka|producer'] }, argIndex: 0,
128
+ vuln: { name: 'Privacy Leak (message queue)', severity: 'medium', cwe: 'CWE-359',
129
+ remediation: 'Regulated data (PII/PHI/PCI) placed on a queue is readable by every consumer with queue access — document who can subscribe before sending it.' } },
130
+ ];
131
+
132
+ // All 9 sink categories FR-403 names (7 already covered by privacy-taint.js's
133
+ // own SINK_PATTERNS, plus storage/queues) — used by this module's own tests
134
+ // to assert coverage is genuinely complete, not just "some entries exist."
135
+ export const PRIVACY_SINK_CATEGORIES = Object.freeze([
136
+ 'log', 'response', 'outboundHttp', 'thirdPartySdk', 'fileWrite', 's3Upload', 'emailSend', 'storage', 'queues',
137
+ ]);
138
+
139
+ // ─── D-0041 STEP 2: a "declared-as" source matcher ─────────────────────────
140
+ //
141
+ // The one genuine gap `catalog.js`'s existing `match.type` shapes (`call`,
142
+ // `member`, `global`, `annotation`) leave uncovered: every one of them
143
+ // matches an EXPRESSION PATTERN (a specific callee, a specific property
144
+ // read, a specific decorator). A privacy source is a different kind of
145
+ // fact entirely — "this declaration's NAME classifies as PII/PHI/PCI/etc."
146
+ // — decided by `privacy-taxonomy.js`'s regex-per-class taxonomy, not by
147
+ // what expression touches it. There is no fixed name to put in a `match`
148
+ // object; the classification must run against WHATEVER name a declaration
149
+ // actually has.
150
+ //
151
+ // Modeled after `matchAnnotationParams` (catalog.js) rather than the CFG-
152
+ // node matchers: that function is also NOT consulted mid-walk against an
153
+ // expression — it runs ONCE PER FUNCTION against the IR's own
154
+ // `fn.paramAnnotations` side-channel, before the walk starts, and its
155
+ // result is unioned into the entry taint-state (see `dataflow/CLAUDE.md`'s
156
+ // own note that annotation matching "is not consulted during the CFG
157
+ // walk"). `matchPrivacyDeclSource(s)` below follow that same shape:
158
+ // pure, pre-walk, name-classification functions a future step 3 will call
159
+ // once per function/file against real declaration names, unioning any hit
160
+ // into a privacy-specific entry taint-state — never against `catalog.js`'s
161
+ // own `CATALOG`, for the exact isolation reason this file's header
162
+ // explains for the sink half.
163
+
164
+ /**
165
+ * Classify one declaration name. Returns `{name, classes}` when it matches
166
+ * at least one taxonomy class, or `null` when it matches none — `null`
167
+ * rather than `{name, classes: []}` so a caller can filter with a plain
168
+ * truthiness check, matching `classifyFieldAgainst`'s own "empty array
169
+ * means no match" contract one level up.
170
+ */
171
+ export function matchPrivacyDeclSource(declName, compiled) {
172
+ if (!declName) return null;
173
+ const classes = classifyFieldAgainst(declName, compiled);
174
+ return classes.length ? { name: declName, classes } : null;
175
+ }
176
+
177
+ /**
178
+ * Classify every name in a list of declarations (variable names, function
179
+ * params, object-literal keys — whatever a caller's IR extraction
180
+ * produces) against the taxonomy. Returns a Map<name, classes[]> so a
181
+ * caller gets both "which names are sources" (the key set) and "which
182
+ * classes triggered it" (the value) in one pass, without re-classifying.
183
+ * Never throws on malformed input — a non-array degrades to an empty Map.
184
+ */
185
+ export function matchPrivacyDeclSources(declNames, compiled) {
186
+ const out = new Map();
187
+ for (const name of Array.isArray(declNames) ? declNames : []) {
188
+ const hit = matchPrivacyDeclSource(name, compiled);
189
+ if (hit) out.set(hit.name, hit.classes);
190
+ }
191
+ return out;
192
+ }
193
+
194
+ // D-0046/D-0047 (FR-403 step 3, grounding): mirrors `catalog.js`'s
195
+ // matchSinkOrSanitizer(calleeExpr, file, receiverType) shape/contract
196
+ // exactly (array of hits, or null) so a future isolated privacy walker can
197
+ // consult it the same way the general walker consults the general catalog
198
+ // -- but reads ONLY PRIVACY_SINK_CATALOG, never CATALOG. This is a linear
199
+ // scan, not `catalog.js`'s CALLEE_INDEX Map lookup: at ~20 entries a scan is
200
+ // plenty fast, and avoiding the shared index machinery (which is private/
201
+ // unexported, and keyed by a shared module-level Map) keeps the two
202
+ // catalogs fully decoupled -- exactly the isolation D-0042 requires, now
203
+ // extended to the sink-matching CODE PATH, not just the entry DATA.
204
+ // `_languageFamilyExtensions` is the one thing imported from catalog.js: a
205
+ // pure, stateless helper with no shared mutable state and no coupling to
206
+ // the general taint engine's behavior, reused to avoid duplicating the
207
+ // language-extension regex table.
208
+
209
+ function _privacyLanguageAllowed(entry, file) {
210
+ if (!file) return true;
211
+ const res = _languageFamilyExtensions(entry.language);
212
+ if (!res.length) return true; // unmapped language stays permissive
213
+ return res.some((re) => re.test(file));
214
+ }
215
+
216
+ function _privacyReceiverTypeAllowed(entry, receiverType) {
217
+ const pats = entry.match && entry.match.receiverTypeIn;
218
+ if (!pats || !pats.length) return true;
219
+ if (!receiverType) return true;
220
+ return pats.some((p) => new RegExp(p, 'i').test(String(receiverType)));
221
+ }
222
+
223
+ // Duplicated from catalog.js's private (unexported) `_calleeIndexHits`
224
+ // name-extraction logic rather than exporting it from catalog.js -- keeping
225
+ // this file's only catalog.js dependency to the one pure helper above.
226
+ function _privacyCalleeNames(calleeExpr) {
227
+ let last = null;
228
+ let full = null;
229
+ if (typeof calleeExpr === 'string') {
230
+ full = calleeExpr;
231
+ last = calleeExpr.includes('.') ? calleeExpr.slice(calleeExpr.lastIndexOf('.') + 1) : calleeExpr;
232
+ } else if (calleeExpr && calleeExpr.kind === 'member' && calleeExpr.prop) {
233
+ last = calleeExpr.prop;
234
+ if (calleeExpr.object && calleeExpr.object.kind === 'ident') full = `${calleeExpr.object.name}.${calleeExpr.prop}`;
235
+ } else if (calleeExpr && calleeExpr.kind === 'ident') {
236
+ last = calleeExpr.name || null;
237
+ }
238
+ return { last, full };
239
+ }
240
+
241
+ export function matchPrivacySink(calleeExpr, file, receiverType) {
242
+ if (!calleeExpr) return null;
243
+ const { last, full } = _privacyCalleeNames(calleeExpr);
244
+ if (!last && !full) return null;
245
+ const hits = PRIVACY_SINK_CATALOG.filter((e) => {
246
+ if (!e || e.kind !== 'sink') return false;
247
+ const cName = e.match && e.match.callee;
248
+ if (!cName) return false;
249
+ if (cName !== last && cName !== full) return false;
250
+ if (!_privacyLanguageAllowed(e, file)) return false;
251
+ if (!_privacyReceiverTypeAllowed(e, receiverType)) return false;
252
+ return true;
253
+ });
254
+ return hits.length ? hits : null;
255
+ }
256
+
257
+ // FR-403 step 3, item (d): safe-transformation recognition. Mirrors
258
+ // dataflow/CLAUDE.md's own documented philosophy for the general engine's
259
+ // sanitizers EXACTLY -- "Sanitizer entries are RECORDED, never trusted to
260
+ // kill taint" -- for the identical reason: a value hashed/masked/encrypted
261
+ // might still be REVERSIBLE (a weak hash, a partial mask, a home-rolled
262
+ // "encrypt" that's really just base64) or applied on only ONE branch. A
263
+ // privacy-deep finding that passed through one of these callees is
264
+ // DEMOTED (confidence, never severity/existence), not deleted -- the same
265
+ // recall-preserving direction every other precision annotator in this
266
+ // codebase takes. A named list, not exhaustive, mirroring engine.js's own
267
+ // _UNSANITIZER_CALLEES precedent (dataflow/CLAUDE.md: "modelled, but only
268
+ // for a named list... a project-local decoder is not recognised").
269
+ const PRIVACY_TRANSFORM_CALLEES = new Set([
270
+ // Cryptographic hash (one-way, not generally reversible)
271
+ 'createHash', 'sha256', 'sha512', 'md5', 'hash', 'hashSync', 'hashPassword',
272
+ // Password/key-derivation hashing
273
+ 'bcrypt', 'scrypt', 'pbkdf2', 'pbkdf2Sync', 'argon2',
274
+ // Symmetric/asymmetric encryption
275
+ 'createCipher', 'createCipheriv', 'encrypt', 'seal',
276
+ // Masking / redaction / anonymization -- named, not pattern-matched, since
277
+ // a bare substring match on "mask" would over-fire on unrelated code
278
+ // (e.g. a bitmask helper named `applyMask`).
279
+ 'mask', 'redact', 'anonymize', 'pseudonymize', 'tokenize',
280
+ ]);
281
+
282
+ /**
283
+ * True when `calleeExpr` is one of the named privacy-transform callees.
284
+ * Mirrors matchPrivacySink's own bare/dotted callee-name matching.
285
+ */
286
+ export function isPrivacyTransformCallee(calleeExpr) {
287
+ if (!calleeExpr) return false;
288
+ const { last, full } = _privacyCalleeNames(calleeExpr);
289
+ return PRIVACY_TRANSFORM_CALLEES.has(last) || PRIVACY_TRANSFORM_CALLEES.has(full);
290
+ }