@clear-capabilities/agentic-security-scanner 0.74.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.
- package/CHANGELOG.md +1580 -0
- package/bin/.agentic-security/findings.json +1577 -0
- package/bin/.agentic-security/last-scan.json +1577 -0
- package/bin/.agentic-security/last-scan.json.sig +1 -0
- package/bin/.agentic-security/scan-history.json +465 -0
- package/bin/.agentic-security/streak.json +25 -0
- package/bin/agentic-security-audit.js +198 -0
- package/bin/agentic-security-consistency.js +80 -0
- package/bin/agentic-security-diff.js +136 -0
- package/bin/agentic-security-lsp.js +12 -0
- package/bin/agentic-security-mcp.js +40 -0
- package/bin/agentic-security-rule.js +153 -0
- package/bin/agentic-security.js +1683 -0
- package/dist/117.index.js +207 -0
- package/dist/178.index.js +250 -0
- package/dist/218.index.js +793 -0
- package/dist/227.index.js +192 -0
- package/dist/301.index.js +167 -0
- package/dist/384.index.js +18 -0
- package/dist/476.index.js +126 -0
- package/dist/513.index.js +373 -0
- package/dist/520.index.js +13 -0
- package/dist/601.index.js +1038 -0
- package/dist/634.index.js +1892 -0
- package/dist/637.index.js +216 -0
- package/dist/660.index.js +131 -0
- package/dist/675.index.js +451 -0
- package/dist/826.index.js +188 -0
- package/dist/830.index.js +133 -0
- package/dist/agentic-security.mjs +272 -0
- package/dist/agentic-security.mjs.sha256 +1 -0
- package/dist/calibration-seed.json +27 -0
- package/package.json +77 -0
- package/src/.agentic-security/findings.json +80844 -0
- package/src/.agentic-security/last-scan.json +80844 -0
- package/src/.agentic-security/last-scan.json.sig +1 -0
- package/src/.agentic-security/scan-history.json +8408 -0
- package/src/.agentic-security/streak.json +26 -0
- package/src/badge.js +188 -0
- package/src/compare.js +203 -0
- package/src/dataflow/.agentic-security/findings.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json +3487 -0
- package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
- package/src/dataflow/.agentic-security/scan-history.json +735 -0
- package/src/dataflow/.agentic-security/streak.json +24 -0
- package/src/dataflow/CLAUDE.md +38 -0
- package/src/dataflow/access-paths.js +172 -0
- package/src/dataflow/async-sequencing.js +177 -0
- package/src/dataflow/backward.js +201 -0
- package/src/dataflow/catalog-expanded.js +485 -0
- package/src/dataflow/catalog.js +659 -0
- package/src/dataflow/cross-repo.js +219 -0
- package/src/dataflow/engine.js +588 -0
- package/src/dataflow/exception-flow.js +116 -0
- package/src/dataflow/exploit-prover.js +187 -0
- package/src/dataflow/higher-order.js +221 -0
- package/src/dataflow/ifds.js +347 -0
- package/src/dataflow/implicit-flow.js +129 -0
- package/src/dataflow/incremental.js +229 -0
- package/src/dataflow/index.js +181 -0
- package/src/dataflow/numeric-domain.js +192 -0
- package/src/dataflow/path-feasibility.js +114 -0
- package/src/dataflow/points-to.js +337 -0
- package/src/dataflow/polyglot.js +190 -0
- package/src/dataflow/proven-clean.js +159 -0
- package/src/dataflow/receiver-context.js +76 -0
- package/src/dataflow/sanitizer-proof.js +154 -0
- package/src/dataflow/soft-taint.js +140 -0
- package/src/dataflow/string-domain.js +234 -0
- package/src/dataflow/stub-aware-filter.js +100 -0
- package/src/dataflow/summaries.js +132 -0
- package/src/dataflow/symbolic-exec.js +238 -0
- package/src/dataflow/tabulation.js +135 -0
- package/src/engine.js +7763 -0
- package/src/history-scan.js +229 -0
- package/src/index.js +3 -0
- package/src/integrations/.agentic-security/findings.json +1504 -0
- package/src/integrations/.agentic-security/last-scan.json +1504 -0
- package/src/integrations/.agentic-security/scan-history.json +40 -0
- package/src/integrations/.agentic-security/streak.json +21 -0
- package/src/integrations/index.js +321 -0
- package/src/integrations/tickets.js +200 -0
- package/src/ir/.agentic-security/findings.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json +3036 -0
- package/src/ir/.agentic-security/last-scan.json.sig +1 -0
- package/src/ir/.agentic-security/scan-history.json +364 -0
- package/src/ir/.agentic-security/streak.json +23 -0
- package/src/ir/CLAUDE.md +172 -0
- package/src/ir/callgraph.js +73 -0
- package/src/ir/class-hierarchy.js +195 -0
- package/src/ir/index.js +152 -0
- package/src/ir/parser-cs.js +260 -0
- package/src/ir/parser-java.js +286 -0
- package/src/ir/parser-js.js +413 -0
- package/src/ir/parser-kt.js +258 -0
- package/src/ir/parser-py-cst.js +136 -0
- package/src/ir/parser-py.helper.py +501 -0
- package/src/ir/parser-py.js +312 -0
- package/src/ir/ssa.js +315 -0
- package/src/ir/type-stubs.js +288 -0
- package/src/leaderboard.js +152 -0
- package/src/llm-validator/.agentic-security/findings.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json +1891 -0
- package/src/llm-validator/.agentic-security/last-scan.json.sig +1 -0
- package/src/llm-validator/.agentic-security/scan-history.json +168 -0
- package/src/llm-validator/.agentic-security/streak.json +20 -0
- package/src/llm-validator/consistency.js +141 -0
- package/src/llm-validator/index.js +437 -0
- package/src/lsp/.agentic-security/findings.json +28 -0
- package/src/lsp/.agentic-security/last-scan.json +28 -0
- package/src/lsp/.agentic-security/scan-history.json +79 -0
- package/src/lsp/.agentic-security/streak.json +22 -0
- package/src/lsp/server.js +275 -0
- package/src/mcp/.agentic-security/findings.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json +8358 -0
- package/src/mcp/.agentic-security/last-scan.json.sig +1 -0
- package/src/mcp/.agentic-security/scan-history.json +1125 -0
- package/src/mcp/.agentic-security/streak.json +22 -0
- package/src/mcp/CLAUDE.md +54 -0
- package/src/mcp/audit.js +136 -0
- package/src/mcp/redact.js +75 -0
- package/src/mcp/server.js +158 -0
- package/src/mcp/stdio.js +83 -0
- package/src/mcp/tools.js +940 -0
- package/src/mcp/validate.js +49 -0
- package/src/personality.js +164 -0
- package/src/poc-video.js +239 -0
- package/src/posture/.agentic-security/findings.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json +51239 -0
- package/src/posture/.agentic-security/last-scan.json.sig +1 -0
- package/src/posture/.agentic-security/scan-history.json +5557 -0
- package/src/posture/.agentic-security/streak.json +24 -0
- package/src/posture/CLAUDE.md +42 -0
- package/src/posture/adversarial-self-test.js +114 -0
- package/src/posture/adversary-agent.js +204 -0
- package/src/posture/agents-memory.js +135 -0
- package/src/posture/ai-code-fingerprint.js +171 -0
- package/src/posture/aibom.js +284 -0
- package/src/posture/api-inventory.js +96 -0
- package/src/posture/attack-playbooks.js +305 -0
- package/src/posture/auditor-agent.js +115 -0
- package/src/posture/auth-posture-import.js +135 -0
- package/src/posture/baseline-compare.js +114 -0
- package/src/posture/blast-radius.js +836 -0
- package/src/posture/bounty-prediction.js +141 -0
- package/src/posture/business-logic.js +239 -0
- package/src/posture/calibration-drift.js +93 -0
- package/src/posture/calibration-seed.json +27 -0
- package/src/posture/calibration.js +204 -0
- package/src/posture/clustering.js +75 -0
- package/src/posture/concurrency-checker.js +265 -0
- package/src/posture/confidence.js +65 -0
- package/src/posture/container-runtime.js +149 -0
- package/src/posture/counterfactual.js +109 -0
- package/src/posture/cross-lang-graphql.js +165 -0
- package/src/posture/cross-lang-grpc.js +166 -0
- package/src/posture/cross-lang-meta.js +101 -0
- package/src/posture/cross-lang-openapi.js +187 -0
- package/src/posture/cross-lang-orm.js +153 -0
- package/src/posture/cross-lang-queues.js +210 -0
- package/src/posture/crown-jewels.js +110 -0
- package/src/posture/custom-rules.js +361 -0
- package/src/posture/cve-alert-daemon.js +433 -0
- package/src/posture/cve-lookup.js +129 -0
- package/src/posture/dead-code.js +430 -0
- package/src/posture/defender-agent.js +158 -0
- package/src/posture/deploy-platform.js +204 -0
- package/src/posture/detector-fuzz.js +61 -0
- package/src/posture/deterministic.js +99 -0
- package/src/posture/drift.js +165 -0
- package/src/posture/epss.js +156 -0
- package/src/posture/exploitability-probability.js +212 -0
- package/src/posture/exploitability.js +121 -0
- package/src/posture/feature-flags.js +110 -0
- package/src/posture/finding-defaults.js +132 -0
- package/src/posture/fix-history.js +411 -0
- package/src/posture/fix-plan.js +121 -0
- package/src/posture/fix-verify-loop.js +157 -0
- package/src/posture/fix-verify.js +130 -0
- package/src/posture/flow-narration.js +105 -0
- package/src/posture/grader-calibration.js +156 -0
- package/src/posture/harness-discovery.js +113 -0
- package/src/posture/holdout-eval.js +144 -0
- package/src/posture/iac-reachability.js +163 -0
- package/src/posture/iam-policy.js +128 -0
- package/src/posture/integrity.js +97 -0
- package/src/posture/learning.js +166 -0
- package/src/posture/license-policy.js +109 -0
- package/src/posture/llm-redteam-prompts.js +418 -0
- package/src/posture/llm-redteam.js +303 -0
- package/src/posture/material-change.js +163 -0
- package/src/posture/mitigation-composite.js +55 -0
- package/src/posture/mttr.js +91 -0
- package/src/posture/network-policy-import.js +126 -0
- package/src/posture/path-predicates.js +99 -0
- package/src/posture/persona-prioritization.js +153 -0
- package/src/posture/poc-cwe-map.js +51 -0
- package/src/posture/poc-generator.js +500 -0
- package/src/posture/policy-gate.js +174 -0
- package/src/posture/pre-incident-archaeology.js +110 -0
- package/src/posture/profile.js +93 -0
- package/src/posture/reachability-filter.js +42 -0
- package/src/posture/regression-test-gen.js +200 -0
- package/src/posture/reverse-blast-radius.js +110 -0
- package/src/posture/router.js +109 -0
- package/src/posture/rule-overrides.js +198 -0
- package/src/posture/rule-pack-signing.js +209 -0
- package/src/posture/rule-packs.js +143 -0
- package/src/posture/rule-synthesis.js +108 -0
- package/src/posture/ruleset-version.js +71 -0
- package/src/posture/sbom.js +129 -0
- package/src/posture/schema-aware-bridge.js +207 -0
- package/src/posture/security-trend.js +87 -0
- package/src/posture/semantic-clone.js +114 -0
- package/src/posture/specification-mining.js +170 -0
- package/src/posture/stable-id.js +75 -0
- package/src/posture/stack-playbook.js +229 -0
- package/src/posture/streak.js +249 -0
- package/src/posture/suppressions.js +135 -0
- package/src/posture/telemetry-ingest.js +112 -0
- package/src/posture/threat-model.js +145 -0
- package/src/posture/three-agent-pipeline.js +74 -0
- package/src/posture/triage.js +146 -0
- package/src/posture/trust-boundary-diagram.js +115 -0
- package/src/posture/type-narrowing.js +129 -0
- package/src/posture/validator-metrics.js +179 -0
- package/src/posture/verifier-ephemeral.js +118 -0
- package/src/posture/verifier-target.js +147 -0
- package/src/posture/verifier.js +257 -0
- package/src/posture/version.js +75 -0
- package/src/posture/waf-ingest.js +200 -0
- package/src/posture/why-fired.js +141 -0
- package/src/pr-comment.js +172 -0
- package/src/pr-delta.js +198 -0
- package/src/report/.agentic-security/findings.json +79 -0
- package/src/report/.agentic-security/last-scan.json +79 -0
- package/src/report/.agentic-security/last-scan.json.sig +1 -0
- package/src/report/.agentic-security/scan-history.json +332 -0
- package/src/report/.agentic-security/streak.json +23 -0
- package/src/report/index.js +1136 -0
- package/src/report/mascot.js +42 -0
- package/src/runScan.js +141 -0
- package/src/sast/.agentic-security/findings.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json +5051 -0
- package/src/sast/.agentic-security/last-scan.json.sig +1 -0
- package/src/sast/.agentic-security/scan-history.json +788 -0
- package/src/sast/.agentic-security/streak.json +23 -0
- package/src/sast/CLAUDE.md +39 -0
- package/src/sast/_comment-strip.js +46 -0
- package/src/sast/agent-tool-escalation.js +131 -0
- package/src/sast/auth-provider.js +171 -0
- package/src/sast/authz.js +236 -0
- package/src/sast/bench-shape/.agentic-security/findings.json +28 -0
- package/src/sast/bench-shape/.agentic-security/last-scan.json +28 -0
- package/src/sast/bench-shape/.agentic-security/scan-history.json +24 -0
- package/src/sast/bench-shape/.agentic-security/streak.json +22 -0
- package/src/sast/bench-shape/index.js +62 -0
- package/src/sast/claude-hook-injection.js +199 -0
- package/src/sast/claude-md-prompt-injection.js +170 -0
- package/src/sast/claude-settings.js +165 -0
- package/src/sast/client-side.js +149 -0
- package/src/sast/cpp-bench-extras.js +122 -0
- package/src/sast/cpp-dataflow.js +430 -0
- package/src/sast/cpp.js +248 -0
- package/src/sast/csharp.js +152 -0
- package/src/sast/csrf.js +82 -0
- package/src/sast/dart-flutter.js +173 -0
- package/src/sast/db-rls.js +147 -0
- package/src/sast/db-taint.js +215 -0
- package/src/sast/defi-deep.js +242 -0
- package/src/sast/deserialization-gadgets.js +113 -0
- package/src/sast/django-hardening.js +230 -0
- package/src/sast/env-hygiene.js +125 -0
- package/src/sast/fastapi-hardening.js +145 -0
- package/src/sast/go-extended.js +84 -0
- package/src/sast/host-header.js +106 -0
- package/src/sast/index.js +17 -0
- package/src/sast/java-ast-folding.js +561 -0
- package/src/sast/java-bench-extras.js +708 -0
- package/src/sast/java-collection-passthrough.js +178 -0
- package/src/sast/java-constant-fold.js +244 -0
- package/src/sast/java-deserialization.js +125 -0
- package/src/sast/jndi.js +104 -0
- package/src/sast/juliet-shape.js +324 -0
- package/src/sast/jwt-exp.js +104 -0
- package/src/sast/kotlin.js +82 -0
- package/src/sast/laravel-hardening.js +198 -0
- package/src/sast/ldap-injection.js +100 -0
- package/src/sast/llm-owasp.js +465 -0
- package/src/sast/llm-stored-prompt.js +103 -0
- package/src/sast/llm-trading-agent.js +161 -0
- package/src/sast/llm.js +308 -0
- package/src/sast/logic.js +140 -0
- package/src/sast/mass-assignment.js +101 -0
- package/src/sast/mcp-audit.js +242 -0
- package/src/sast/mobile-manifest.js +195 -0
- package/src/sast/model-load.js +164 -0
- package/src/sast/mutation-xss.js +87 -0
- package/src/sast/nosql-injection.js +82 -0
- package/src/sast/open-redirect.js +119 -0
- package/src/sast/php.js +91 -0
- package/src/sast/pipeline.js +122 -0
- package/src/sast/primary-cwe-java.js +155 -0
- package/src/sast/prompt-firewall.js +151 -0
- package/src/sast/prompt-template.js +157 -0
- package/src/sast/prototype-pollution.js +112 -0
- package/src/sast/python-sinks.js +195 -0
- package/src/sast/quarkus-hardening.js +102 -0
- package/src/sast/rag-poisoning.js +118 -0
- package/src/sast/rate-limit.js +128 -0
- package/src/sast/response-splitting.js +138 -0
- package/src/sast/ruby.js +108 -0
- package/src/sast/rust.js +105 -0
- package/src/sast/solidity.js +167 -0
- package/src/sast/springboot-hardening.js +186 -0
- package/src/sast/ssrf-cloud-metadata.js +80 -0
- package/src/sast/ssti.js +116 -0
- package/src/sast/swift.js +162 -0
- package/src/sast/toctou.js +95 -0
- package/src/sast/webhook.js +101 -0
- package/src/sast/xpath-injection.js +51 -0
- package/src/sast/xxe.js +140 -0
- package/src/sast/zip-slip.js +200 -0
- package/src/sca/base-images.json +45 -0
- package/src/sca/container.js +107 -0
- package/src/sca/dep-confusion.js +134 -0
- package/src/sca/index.js +6 -0
- package/src/sca/popular-packages.json +41 -0
- package/src/sca/sarif-ingest.js +187 -0
- package/src/sca/vuln-function-hints.json +89 -0
- package/src/secrets/index.js +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a1c1494ded2872530fa2cf6558148e7bc5f680b69e56ad96435f859f2d8c3078
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"timestamp": "2026-05-18T18:07:37.257Z",
|
|
4
|
+
"label": "scan",
|
|
5
|
+
"total": 0,
|
|
6
|
+
"critical": 0,
|
|
7
|
+
"high": 0,
|
|
8
|
+
"medium": 0,
|
|
9
|
+
"low": 0,
|
|
10
|
+
"kev": 0,
|
|
11
|
+
"ids": []
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"timestamp": "2026-05-18T18:07:52.302Z",
|
|
15
|
+
"label": "scan",
|
|
16
|
+
"total": 0,
|
|
17
|
+
"critical": 0,
|
|
18
|
+
"high": 0,
|
|
19
|
+
"medium": 0,
|
|
20
|
+
"low": 0,
|
|
21
|
+
"kev": 0,
|
|
22
|
+
"ids": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"timestamp": "2026-05-18T18:07:55.308Z",
|
|
26
|
+
"label": "scan",
|
|
27
|
+
"total": 0,
|
|
28
|
+
"critical": 0,
|
|
29
|
+
"high": 0,
|
|
30
|
+
"medium": 0,
|
|
31
|
+
"low": 0,
|
|
32
|
+
"kev": 0,
|
|
33
|
+
"ids": []
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"timestamp": "2026-05-19T15:08:17.723Z",
|
|
37
|
+
"label": "scan",
|
|
38
|
+
"total": 0,
|
|
39
|
+
"critical": 0,
|
|
40
|
+
"high": 0,
|
|
41
|
+
"medium": 0,
|
|
42
|
+
"low": 0,
|
|
43
|
+
"kev": 0,
|
|
44
|
+
"ids": []
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"timestamp": "2026-05-19T15:13:36.271Z",
|
|
48
|
+
"label": "scan",
|
|
49
|
+
"total": 0,
|
|
50
|
+
"critical": 0,
|
|
51
|
+
"high": 0,
|
|
52
|
+
"medium": 0,
|
|
53
|
+
"low": 0,
|
|
54
|
+
"kev": 0,
|
|
55
|
+
"ids": []
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"timestamp": "2026-05-19T15:14:33.487Z",
|
|
59
|
+
"label": "scan",
|
|
60
|
+
"total": 0,
|
|
61
|
+
"critical": 0,
|
|
62
|
+
"high": 0,
|
|
63
|
+
"medium": 0,
|
|
64
|
+
"low": 0,
|
|
65
|
+
"kev": 0,
|
|
66
|
+
"ids": []
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"timestamp": "2026-05-19T15:15:32.497Z",
|
|
70
|
+
"label": "scan",
|
|
71
|
+
"total": 0,
|
|
72
|
+
"critical": 0,
|
|
73
|
+
"high": 0,
|
|
74
|
+
"medium": 0,
|
|
75
|
+
"low": 0,
|
|
76
|
+
"kev": 0,
|
|
77
|
+
"ids": []
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"timestamp": "2026-05-19T15:16:14.591Z",
|
|
81
|
+
"label": "scan",
|
|
82
|
+
"total": 0,
|
|
83
|
+
"critical": 0,
|
|
84
|
+
"high": 0,
|
|
85
|
+
"medium": 0,
|
|
86
|
+
"low": 0,
|
|
87
|
+
"kev": 0,
|
|
88
|
+
"ids": []
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"timestamp": "2026-05-19T15:16:47.095Z",
|
|
92
|
+
"label": "scan",
|
|
93
|
+
"total": 0,
|
|
94
|
+
"critical": 0,
|
|
95
|
+
"high": 0,
|
|
96
|
+
"medium": 0,
|
|
97
|
+
"low": 0,
|
|
98
|
+
"kev": 0,
|
|
99
|
+
"ids": []
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"timestamp": "2026-05-19T15:39:01.083Z",
|
|
103
|
+
"label": "scan",
|
|
104
|
+
"total": 0,
|
|
105
|
+
"critical": 0,
|
|
106
|
+
"high": 0,
|
|
107
|
+
"medium": 0,
|
|
108
|
+
"low": 0,
|
|
109
|
+
"kev": 0,
|
|
110
|
+
"ids": []
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"timestamp": "2026-05-19T20:23:03.902Z",
|
|
114
|
+
"label": "scan",
|
|
115
|
+
"total": 0,
|
|
116
|
+
"critical": 0,
|
|
117
|
+
"high": 0,
|
|
118
|
+
"medium": 0,
|
|
119
|
+
"low": 0,
|
|
120
|
+
"kev": 0,
|
|
121
|
+
"ids": []
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"timestamp": "2026-05-19T20:39:09.197Z",
|
|
125
|
+
"label": "scan",
|
|
126
|
+
"total": 0,
|
|
127
|
+
"critical": 0,
|
|
128
|
+
"high": 0,
|
|
129
|
+
"medium": 0,
|
|
130
|
+
"low": 0,
|
|
131
|
+
"kev": 0,
|
|
132
|
+
"ids": []
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"timestamp": "2026-05-19T20:39:21.000Z",
|
|
136
|
+
"label": "scan",
|
|
137
|
+
"total": 0,
|
|
138
|
+
"critical": 0,
|
|
139
|
+
"high": 0,
|
|
140
|
+
"medium": 0,
|
|
141
|
+
"low": 0,
|
|
142
|
+
"kev": 0,
|
|
143
|
+
"ids": []
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"timestamp": "2026-05-20T03:49:13.475Z",
|
|
147
|
+
"label": "scan",
|
|
148
|
+
"total": 0,
|
|
149
|
+
"critical": 0,
|
|
150
|
+
"high": 0,
|
|
151
|
+
"medium": 0,
|
|
152
|
+
"low": 0,
|
|
153
|
+
"kev": 0,
|
|
154
|
+
"ids": []
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"timestamp": "2026-05-20T03:50:06.549Z",
|
|
158
|
+
"label": "scan",
|
|
159
|
+
"total": 1,
|
|
160
|
+
"critical": 0,
|
|
161
|
+
"high": 0,
|
|
162
|
+
"medium": 1,
|
|
163
|
+
"low": 0,
|
|
164
|
+
"kev": 0,
|
|
165
|
+
"ids": [
|
|
166
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"timestamp": "2026-05-20T03:50:48.754Z",
|
|
171
|
+
"label": "scan",
|
|
172
|
+
"total": 1,
|
|
173
|
+
"critical": 0,
|
|
174
|
+
"high": 0,
|
|
175
|
+
"medium": 1,
|
|
176
|
+
"low": 0,
|
|
177
|
+
"kev": 0,
|
|
178
|
+
"ids": [
|
|
179
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
180
|
+
]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"timestamp": "2026-05-20T03:52:59.627Z",
|
|
184
|
+
"label": "scan",
|
|
185
|
+
"total": 1,
|
|
186
|
+
"critical": 0,
|
|
187
|
+
"high": 0,
|
|
188
|
+
"medium": 1,
|
|
189
|
+
"low": 0,
|
|
190
|
+
"kev": 0,
|
|
191
|
+
"ids": [
|
|
192
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"timestamp": "2026-05-20T12:32:01.638Z",
|
|
197
|
+
"label": "scan",
|
|
198
|
+
"total": 2,
|
|
199
|
+
"critical": 0,
|
|
200
|
+
"high": 1,
|
|
201
|
+
"medium": 1,
|
|
202
|
+
"low": 0,
|
|
203
|
+
"kev": 0,
|
|
204
|
+
"ids": [
|
|
205
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
206
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"timestamp": "2026-05-20T12:32:06.661Z",
|
|
211
|
+
"label": "scan",
|
|
212
|
+
"total": 2,
|
|
213
|
+
"critical": 0,
|
|
214
|
+
"high": 1,
|
|
215
|
+
"medium": 1,
|
|
216
|
+
"low": 0,
|
|
217
|
+
"kev": 0,
|
|
218
|
+
"ids": [
|
|
219
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
220
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"timestamp": "2026-05-20T12:32:15.761Z",
|
|
225
|
+
"label": "scan",
|
|
226
|
+
"total": 2,
|
|
227
|
+
"critical": 0,
|
|
228
|
+
"high": 1,
|
|
229
|
+
"medium": 1,
|
|
230
|
+
"low": 0,
|
|
231
|
+
"kev": 0,
|
|
232
|
+
"ids": [
|
|
233
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
234
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
235
|
+
]
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"timestamp": "2026-05-20T12:32:20.916Z",
|
|
239
|
+
"label": "scan",
|
|
240
|
+
"total": 2,
|
|
241
|
+
"critical": 0,
|
|
242
|
+
"high": 1,
|
|
243
|
+
"medium": 1,
|
|
244
|
+
"low": 0,
|
|
245
|
+
"kev": 0,
|
|
246
|
+
"ids": [
|
|
247
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
248
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"timestamp": "2026-05-20T12:32:26.044Z",
|
|
253
|
+
"label": "scan",
|
|
254
|
+
"total": 2,
|
|
255
|
+
"critical": 0,
|
|
256
|
+
"high": 1,
|
|
257
|
+
"medium": 1,
|
|
258
|
+
"low": 0,
|
|
259
|
+
"kev": 0,
|
|
260
|
+
"ids": [
|
|
261
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
262
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"timestamp": "2026-05-20T12:34:20.136Z",
|
|
267
|
+
"label": "scan",
|
|
268
|
+
"total": 3,
|
|
269
|
+
"critical": 0,
|
|
270
|
+
"high": 2,
|
|
271
|
+
"medium": 1,
|
|
272
|
+
"low": 0,
|
|
273
|
+
"kev": 0,
|
|
274
|
+
"ids": [
|
|
275
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
276
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
277
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"timestamp": "2026-05-20T12:34:25.000Z",
|
|
282
|
+
"label": "scan",
|
|
283
|
+
"total": 3,
|
|
284
|
+
"critical": 0,
|
|
285
|
+
"high": 2,
|
|
286
|
+
"medium": 1,
|
|
287
|
+
"low": 0,
|
|
288
|
+
"kev": 0,
|
|
289
|
+
"ids": [
|
|
290
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
291
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
292
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
293
|
+
]
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"timestamp": "2026-05-20T12:34:32.674Z",
|
|
297
|
+
"label": "scan",
|
|
298
|
+
"total": 3,
|
|
299
|
+
"critical": 0,
|
|
300
|
+
"high": 2,
|
|
301
|
+
"medium": 1,
|
|
302
|
+
"low": 0,
|
|
303
|
+
"kev": 0,
|
|
304
|
+
"ids": [
|
|
305
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
306
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
307
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
308
|
+
]
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"timestamp": "2026-05-20T12:34:40.056Z",
|
|
312
|
+
"label": "scan",
|
|
313
|
+
"total": 3,
|
|
314
|
+
"critical": 0,
|
|
315
|
+
"high": 2,
|
|
316
|
+
"medium": 1,
|
|
317
|
+
"low": 0,
|
|
318
|
+
"kev": 0,
|
|
319
|
+
"ids": [
|
|
320
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
321
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
322
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
323
|
+
]
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"timestamp": "2026-05-20T12:34:45.128Z",
|
|
327
|
+
"label": "scan",
|
|
328
|
+
"total": 3,
|
|
329
|
+
"critical": 0,
|
|
330
|
+
"high": 2,
|
|
331
|
+
"medium": 1,
|
|
332
|
+
"low": 0,
|
|
333
|
+
"kev": 0,
|
|
334
|
+
"ids": [
|
|
335
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
336
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
337
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)"
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"timestamp": "2026-05-20T17:01:27.551Z",
|
|
342
|
+
"label": "scan",
|
|
343
|
+
"total": 12,
|
|
344
|
+
"critical": 0,
|
|
345
|
+
"high": 2,
|
|
346
|
+
"medium": 10,
|
|
347
|
+
"low": 0,
|
|
348
|
+
"kev": 0,
|
|
349
|
+
"ids": [
|
|
350
|
+
"struct:parser-cs.js:208:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
351
|
+
"struct:parser-kt.js:207:Mass_Assignment_(req.body_Direct_to_Model)",
|
|
352
|
+
"struct:parser-py-cst.js:91:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
353
|
+
"struct:type-stubs.js:190:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
354
|
+
"struct:type-stubs.js:198:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
355
|
+
"struct:type-stubs.js:216:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
356
|
+
"struct:type-stubs.js:245:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
357
|
+
"struct:type-stubs.js:48:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
358
|
+
"struct:type-stubs.js:57:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
359
|
+
"struct:type-stubs.js:58:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
360
|
+
"struct:type-stubs.js:79:Synchronous_Blocking_I/O_(DoS_Risk_in_Server_Context)",
|
|
361
|
+
"toctou-fs:type-stubs.js:48"
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"firstScanDate": "2026-05-18T18:07:37.263Z",
|
|
3
|
+
"lastScanDate": "2026-05-20T17:01:27.572Z",
|
|
4
|
+
"totalScans": 28,
|
|
5
|
+
"daysCleanCritical": 3,
|
|
6
|
+
"lastCleanDate": "2026-05-20",
|
|
7
|
+
"lastCriticalDate": null,
|
|
8
|
+
"hasEverHadCritical": false,
|
|
9
|
+
"bestDaysCleanCritical": 3,
|
|
10
|
+
"totalFindingsAtFirstScan": 0,
|
|
11
|
+
"totalFindingsAtLastScan": 13,
|
|
12
|
+
"totalFixesInferred": 0,
|
|
13
|
+
"lastGrade": "A-",
|
|
14
|
+
"bestGrade": "A+",
|
|
15
|
+
"launchCheckPassedAt": null,
|
|
16
|
+
"achievements": [
|
|
17
|
+
"first-scan",
|
|
18
|
+
"grade-a",
|
|
19
|
+
"grade-a-plus",
|
|
20
|
+
"scan-veteran-25"
|
|
21
|
+
],
|
|
22
|
+
"previousGrade": "A-"
|
|
23
|
+
}
|
package/src/ir/CLAUDE.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# scanner/src/ir/
|
|
2
|
+
|
|
3
|
+
Layer-1 intermediate representation. Per-file IR + cross-file call graph;
|
|
4
|
+
consumed by `scanner/src/dataflow/` for taint analysis.
|
|
5
|
+
|
|
6
|
+
## Parsers
|
|
7
|
+
|
|
8
|
+
| Language | Module | Backend |
|
|
9
|
+
|----------|-----------------------|--------------------------------------------------|
|
|
10
|
+
| JS / TS | `parser-js.js` | `@babel/parser` |
|
|
11
|
+
| Python | `parser-py-cst.js` | Python 3.8+ stdlib `ast` via subprocess (default when available) |
|
|
12
|
+
| Python | `parser-py.js` | Hand-rolled regex parser (fallback when python3 missing) |
|
|
13
|
+
| Java | `parser-java.js` | `java-parser` npm package (async) |
|
|
14
|
+
|
|
15
|
+
## Python parser — dual-path with auto fallback
|
|
16
|
+
|
|
17
|
+
The Python pipeline has two implementations. The dispatcher in `index.js`
|
|
18
|
+
picks one at scan time:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
AGENTIC_SECURITY_PY_PARSER=auto (default) — try CST, fall back to regex
|
|
22
|
+
AGENTIC_SECURITY_PY_PARSER=cst force CST; error if python3 missing
|
|
23
|
+
AGENTIC_SECURITY_PY_PARSER=regex force regex
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Why two
|
|
27
|
+
|
|
28
|
+
The regex parser (`parser-py.js`) was the original v1. It's a hand-rolled
|
|
29
|
+
indentation walker with a balanced-paren expression matcher. It admits in
|
|
30
|
+
its own comments to dropping:
|
|
31
|
+
|
|
32
|
+
- list / dict / set / generator comprehensions
|
|
33
|
+
- decorators (parsed but body never lowered)
|
|
34
|
+
- `match` statements
|
|
35
|
+
- `async` / `await` (modeled as transparent unwrap)
|
|
36
|
+
- lambda bodies (collapsed to opaque)
|
|
37
|
+
|
|
38
|
+
Real-world Python is full of these. The taint engine silently no-ops on
|
|
39
|
+
every dropped function.
|
|
40
|
+
|
|
41
|
+
The CST parser (`parser-py-cst.js`) shells out to a small Python helper
|
|
42
|
+
script (`parser-py.helper.py`) that uses the stdlib `ast` module — zero
|
|
43
|
+
external dependencies, ships with every Python 3.8+ install. The helper
|
|
44
|
+
emits the same IR shape (`{functions[{qid,name,line,params,cfg,file}],
|
|
45
|
+
topLevel}`) as the regex parser. The CFG is built from the real AST, so:
|
|
46
|
+
|
|
47
|
+
- decorators don't drop the function record
|
|
48
|
+
- async def is recognized
|
|
49
|
+
- match statements don't drop the function (the body's `case` arms are a
|
|
50
|
+
noop placeholder for now — future work — but the function is captured)
|
|
51
|
+
- comprehensions surface their elt expression so taint propagates through
|
|
52
|
+
`[x for x in untrusted]`
|
|
53
|
+
- nested function defs become separate entries in `functions[]`
|
|
54
|
+
- `def f(x=Foo(1,2))` and `db.execute(sanitize(x))` parse correctly
|
|
55
|
+
|
|
56
|
+
### Cost
|
|
57
|
+
|
|
58
|
+
- One `python3` subprocess **per `runScan`** (NOT per file). All Python
|
|
59
|
+
files in the project go in a single batched stdin payload, parsed in a
|
|
60
|
+
single linear loop on the Python side.
|
|
61
|
+
- Capability probe (`python3 --version`) runs ONCE per process and is
|
|
62
|
+
cached.
|
|
63
|
+
- When the helper crashes mid-batch, the dispatcher silently falls back
|
|
64
|
+
to the regex parser. Set `AGENTIC_SECURITY_PY_PARSER_DEBUG=1` to see
|
|
65
|
+
the failure on stderr.
|
|
66
|
+
|
|
67
|
+
### When `auto` falls back to regex
|
|
68
|
+
|
|
69
|
+
- `python3` / `python` not on PATH
|
|
70
|
+
- Python version < 3.8
|
|
71
|
+
- helper script's stdin JSON corruption
|
|
72
|
+
- helper subprocess timeout (10 s for the whole batch — generous)
|
|
73
|
+
- helper output isn't parseable JSON
|
|
74
|
+
|
|
75
|
+
Each of these is a real failure mode; the regex fallback keeps the scan
|
|
76
|
+
producing findings instead of returning empty.
|
|
77
|
+
|
|
78
|
+
### What CST still doesn't model
|
|
79
|
+
|
|
80
|
+
The helper builds a CFG only for shapes the dataflow engine actually
|
|
81
|
+
consumes. The following still emit `kind: 'noop'` nodes (future work):
|
|
82
|
+
|
|
83
|
+
- `match` case bodies (the function is captured; the per-case taint flow
|
|
84
|
+
isn't lowered)
|
|
85
|
+
- destructuring assignment (`a, b = req.body`) — only single-target
|
|
86
|
+
assignments get a precise `target` field
|
|
87
|
+
- comprehension generators (`for x in iter`) — the iter expression is
|
|
88
|
+
visible via the elt, but the generator's own `if` filters aren't yet
|
|
89
|
+
modeled
|
|
90
|
+
- walrus `:=` at expression position — the RHS flows forward, but the
|
|
91
|
+
named binding isn't tracked as a separate variable
|
|
92
|
+
|
|
93
|
+
These are deliberate to keep the CFG bounded; the regex parser's behavior
|
|
94
|
+
was strictly worse (it dropped the entire function).
|
|
95
|
+
|
|
96
|
+
## IR shape contract
|
|
97
|
+
|
|
98
|
+
Every parser must produce this shape:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
{
|
|
102
|
+
file: 'rel/path.py',
|
|
103
|
+
functions: [
|
|
104
|
+
{
|
|
105
|
+
qid: 'file.py::name@line#sha', // stable cross-file identifier
|
|
106
|
+
name: 'function_name',
|
|
107
|
+
line: 42,
|
|
108
|
+
params: ['arg1', 'arg2', ...],
|
|
109
|
+
file: 'file.py',
|
|
110
|
+
cfg: {
|
|
111
|
+
entry: 'nodeId',
|
|
112
|
+
exit: 'nodeId',
|
|
113
|
+
nodes: {
|
|
114
|
+
[nodeId]: {
|
|
115
|
+
kind: 'entry'|'exit'|'noop'|'loop-header'|'assign'|'call'
|
|
116
|
+
|'if'|'return'|'throw'|'unknown',
|
|
117
|
+
line: number,
|
|
118
|
+
succ: [nodeId, ...],
|
|
119
|
+
pred: [nodeId, ...],
|
|
120
|
+
// kind-specific:
|
|
121
|
+
// assign: target (string or null), source (expr)
|
|
122
|
+
// call: callee (string or expr), args ([expr])
|
|
123
|
+
// if: cond (expr)
|
|
124
|
+
// return: value (expr or null)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
topLevel: null, // reserved for module-level code; not yet used
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Expression shape (returned by `source`, `cond`, `args[i]`, etc.):
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
{ kind: 'literal', value: any }
|
|
138
|
+
{ kind: 'ident', name: 'x' }
|
|
139
|
+
{ kind: 'member', object: expr, prop: 'attr' }
|
|
140
|
+
{ kind: 'binary', op: '+', left: expr, right: expr }
|
|
141
|
+
{ kind: 'logical', op: 'and'|'or', left: expr, right: expr }
|
|
142
|
+
{ kind: 'tpl', parts: [expr, ...] }
|
|
143
|
+
{ kind: 'call', callee: string|expr, args: [expr, ...] }
|
|
144
|
+
{ kind: 'array', elements: [expr, ...] }
|
|
145
|
+
{ kind: 'object', props: [{value: expr}, ...] }
|
|
146
|
+
{ kind: 'union', branches: [expr, expr] }
|
|
147
|
+
{ kind: 'unknown' }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Any change to this shape must update **all** parsers AND the dataflow
|
|
151
|
+
engine — `scanner/src/dataflow/engine.js` is the contract reader.
|
|
152
|
+
|
|
153
|
+
## Adding a Python construct
|
|
154
|
+
|
|
155
|
+
1. Add a recognizer + lowerer branch in `parser-py.helper.py`'s
|
|
156
|
+
`_lower_stmt` or `_lower_expr` function.
|
|
157
|
+
2. Add a fixture-style test in `scanner/test/parser-py-cst.test.js`.
|
|
158
|
+
3. Run `npm run test:dataflow` to confirm no IR-consumer regressions.
|
|
159
|
+
4. Optionally: add a corresponding case to `parser-py.js` so the regex
|
|
160
|
+
fallback handles the same shape (but don't block on this — the regex
|
|
161
|
+
parser is a fallback, not a target).
|
|
162
|
+
|
|
163
|
+
## When to retire the regex parser
|
|
164
|
+
|
|
165
|
+
The regex parser stays as long as some customers run scans on machines
|
|
166
|
+
without Python 3.8+. Realistic CI / dev environments have Python; locked-
|
|
167
|
+
down enterprise runners sometimes don't. Targets for retirement:
|
|
168
|
+
|
|
169
|
+
- Two minor releases with zero `parser-py-cst → regex` fallbacks reported
|
|
170
|
+
via the optional telemetry surface.
|
|
171
|
+
- Or, the `AGENTIC_SECURITY_PY_PARSER` env defaults to `cst` (strict
|
|
172
|
+
mode) for one release with no customer complaint tickets filed.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Cross-file call graph from the JS/TS IR.
|
|
2
|
+
//
|
|
3
|
+
// Resolves call sites to callee function IDs. Resolution rules (best-effort):
|
|
4
|
+
// 1. Direct function call to a name defined in the same file → resolve to that fn.
|
|
5
|
+
// 2. Method call `obj.foo()` where obj is a class instance with a known
|
|
6
|
+
// method `foo` in the same file → resolve to that method.
|
|
7
|
+
// 3. Module-imported name: look up in another file's exports.
|
|
8
|
+
// 4. Anything else → unresolved; the dataflow engine treats the callee as
|
|
9
|
+
// an opaque sink for taint.
|
|
10
|
+
|
|
11
|
+
export function buildCallGraph(perFileIR) {
|
|
12
|
+
// perFileIR is { [file]: parseJsFile output }
|
|
13
|
+
const functions = new Map(); // qid → FunctionIR
|
|
14
|
+
const byNameInFile = new Map(); // file → Map<name, qid>
|
|
15
|
+
const classMethods = new Map(); // 'ClassName.method' → qid
|
|
16
|
+
|
|
17
|
+
for (const file of Object.keys(perFileIR || {})) {
|
|
18
|
+
const ir = perFileIR[file];
|
|
19
|
+
if (!ir || !ir.functions) continue;
|
|
20
|
+
byNameInFile.set(file, new Map());
|
|
21
|
+
for (const fn of ir.functions) {
|
|
22
|
+
functions.set(fn.qid, fn);
|
|
23
|
+
byNameInFile.get(file).set(fn.name, fn.qid);
|
|
24
|
+
// Class methods: qid carries the class name as the scope.
|
|
25
|
+
const m = fn.qid.match(/::([A-Z]\w*)::(\w+)@/);
|
|
26
|
+
if (m) classMethods.set(`${m[1]}.${m[2]}`, fn.qid);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Resolve each call site.
|
|
31
|
+
const edges = []; // { caller, site, callee, ambiguous? }
|
|
32
|
+
for (const fn of functions.values()) {
|
|
33
|
+
for (const c of (fn.calls || [])) {
|
|
34
|
+
if (!c.callee) { edges.push({ caller: fn.qid, site: c.site, callee: null, line: c.line }); continue; }
|
|
35
|
+
// 1. Direct name in same file
|
|
36
|
+
const sameFileMap = byNameInFile.get(fn.file);
|
|
37
|
+
const resolved = sameFileMap?.get(c.callee) ||
|
|
38
|
+
classMethods.get(c.callee) ||
|
|
39
|
+
// 2. ClassName.method form
|
|
40
|
+
(c.callee.includes('.') ? classMethods.get(c.callee) : null) ||
|
|
41
|
+
null;
|
|
42
|
+
edges.push({ caller: fn.qid, site: c.site, callee: resolved, calleeName: c.callee, line: c.line });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Reverse index: callee → callers.
|
|
47
|
+
const callersOf = new Map();
|
|
48
|
+
for (const e of edges) {
|
|
49
|
+
if (!e.callee) continue;
|
|
50
|
+
if (!callersOf.has(e.callee)) callersOf.set(e.callee, []);
|
|
51
|
+
callersOf.get(e.callee).push(e);
|
|
52
|
+
}
|
|
53
|
+
// Premortem #7: expose a name→qid resolver so the taint engine can ask
|
|
54
|
+
// the call graph for the callee's qid at the assign-from-call site.
|
|
55
|
+
// Same precedence as the edge resolution above (same-file ident wins,
|
|
56
|
+
// ClassName.method falls back).
|
|
57
|
+
function resolve(name) {
|
|
58
|
+
if (!name || typeof name !== 'string') return null;
|
|
59
|
+
// Direct ident match — search every file's same-file map.
|
|
60
|
+
for (const m of byNameInFile.values()) {
|
|
61
|
+
if (m.has(name)) return m.get(name);
|
|
62
|
+
}
|
|
63
|
+
if (classMethods.has(name)) return classMethods.get(name);
|
|
64
|
+
if (name.includes('.')) {
|
|
65
|
+
const tail = name.split('.').slice(-1)[0];
|
|
66
|
+
for (const m of byNameInFile.values()) {
|
|
67
|
+
if (m.has(tail)) return m.get(tail);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return { functions, edges, callersOf, resolve };
|
|
73
|
+
}
|