@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,219 @@
|
|
|
1
|
+
// Cross-repo taint via service contracts (v0.73).
|
|
2
|
+
//
|
|
3
|
+
// Most SAST tools are repo-bound: scan service A, scan service B
|
|
4
|
+
// separately, never connect the dots. Real attacks chain across repos —
|
|
5
|
+
// service A's response field flows through service B's request handler
|
|
6
|
+
// into B's SQL query.
|
|
7
|
+
//
|
|
8
|
+
// This module is the federation layer. Given a collection of OpenAPI /
|
|
9
|
+
// gRPC specs from multiple repos, it:
|
|
10
|
+
//
|
|
11
|
+
// 1. Parses each spec, extracts endpoints + their response shapes
|
|
12
|
+
// 2. Resolves which endpoints are CONSUMED across the set (one repo's
|
|
13
|
+
// client call → another repo's server route)
|
|
14
|
+
// 3. Emits cross-service findings tagged with both producer + consumer
|
|
15
|
+
// repo identities + the federated path
|
|
16
|
+
//
|
|
17
|
+
// v1 scope: OpenAPI only (the cross-lang-openapi.js module already
|
|
18
|
+
// handles intra-repo flow; this is the inter-repo lift). gRPC + GraphQL
|
|
19
|
+
// federation are deferred — the gRPC posture module exists but its
|
|
20
|
+
// schema-extraction layer needs cross-repo alignment first.
|
|
21
|
+
//
|
|
22
|
+
// Each input is a `{ repo, specPath, specContent }` triple. The repo
|
|
23
|
+
// name is the GitHub slug ('owner/name'); specPath is relative within
|
|
24
|
+
// that repo; specContent is the raw YAML/JSON.
|
|
25
|
+
|
|
26
|
+
import * as yaml from 'js-yaml';
|
|
27
|
+
|
|
28
|
+
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
|
|
29
|
+
|
|
30
|
+
function _parseSpec(spec) {
|
|
31
|
+
if (!spec || typeof spec.specContent !== 'string') return null;
|
|
32
|
+
try {
|
|
33
|
+
const doc = /\.json$/i.test(spec.specPath || '')
|
|
34
|
+
? JSON.parse(spec.specContent)
|
|
35
|
+
: yaml.load(spec.specContent);
|
|
36
|
+
if (!doc || !doc.paths) return null;
|
|
37
|
+
return doc;
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _endpointsFor(doc) {
|
|
42
|
+
if (!doc || !doc.paths) return [];
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const [p, methods] of Object.entries(doc.paths)) {
|
|
45
|
+
for (const m of Object.keys(methods)) {
|
|
46
|
+
if (!METHODS.includes(m.toLowerCase())) continue;
|
|
47
|
+
const op = methods[m];
|
|
48
|
+
out.push({
|
|
49
|
+
method: m.toLowerCase(),
|
|
50
|
+
path: p,
|
|
51
|
+
responseFields: _responseFields(op),
|
|
52
|
+
requestFields: _requestFields(op),
|
|
53
|
+
operationId: op.operationId,
|
|
54
|
+
summary: op.summary || op.description || '',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Extract leaf field paths from an OpenAPI schema. Recursively walks
|
|
62
|
+
// `properties` returning dotted access paths ('user.email', 'items.0.name').
|
|
63
|
+
function _leafPathsOf(schema, prefix = '', depth = 0) {
|
|
64
|
+
if (!schema || depth > 6) return [];
|
|
65
|
+
if (schema.properties) {
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const [k, v] of Object.entries(schema.properties)) {
|
|
68
|
+
const p = prefix ? `${prefix}.${k}` : k;
|
|
69
|
+
out.push(...(_leafPathsOf(v, p, depth + 1) || [p]));
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
if (schema.type === 'array' && schema.items) {
|
|
74
|
+
return _leafPathsOf(schema.items, prefix ? `${prefix}[]` : '[]', depth + 1);
|
|
75
|
+
}
|
|
76
|
+
return prefix ? [prefix] : [];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _responseFields(op) {
|
|
80
|
+
if (!op || !op.responses) return [];
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const [code, resp] of Object.entries(op.responses)) {
|
|
83
|
+
if (!/^2/.test(code) && code !== 'default') continue;
|
|
84
|
+
const content = resp.content || {};
|
|
85
|
+
for (const mt of Object.values(content)) {
|
|
86
|
+
if (mt.schema) out.push(..._leafPathsOf(mt.schema));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [...new Set(out)];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _requestFields(op) {
|
|
93
|
+
if (!op) return [];
|
|
94
|
+
const out = [];
|
|
95
|
+
if (op.requestBody && op.requestBody.content) {
|
|
96
|
+
for (const mt of Object.values(op.requestBody.content)) {
|
|
97
|
+
if (mt.schema) out.push(..._leafPathsOf(mt.schema));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (Array.isArray(op.parameters)) {
|
|
101
|
+
for (const p of op.parameters) if (p && p.name) out.push(p.name);
|
|
102
|
+
}
|
|
103
|
+
return [...new Set(out)];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build the federated endpoint graph across a set of specs.
|
|
108
|
+
*
|
|
109
|
+
* Input:
|
|
110
|
+
* specs: Array<{ repo: 'owner/name', specPath, specContent }>
|
|
111
|
+
*
|
|
112
|
+
* Output:
|
|
113
|
+
* {
|
|
114
|
+
* producers: Map<'METHOD PATH', { repo, endpoint }[]>,
|
|
115
|
+
* consumers: Map<'METHOD PATH', { repo, endpoint }[]>,
|
|
116
|
+
* federatedEdges: [
|
|
117
|
+
* {
|
|
118
|
+
* from: { repo, method, path, fields: string[] },
|
|
119
|
+
* to: { repo, method, path, fields: string[] },
|
|
120
|
+
* sharedFields: string[],
|
|
121
|
+
* }
|
|
122
|
+
* ]
|
|
123
|
+
* }
|
|
124
|
+
*
|
|
125
|
+
* A producer is anyone who DEFINES an endpoint (responses field). A
|
|
126
|
+
* consumer is anyone who DECLARES a client dependency on that endpoint
|
|
127
|
+
* via an `x-consumes` extension OR a matching `operationId` in a
|
|
128
|
+
* `dependsOn` block. v1 uses a simpler heuristic: every pair of specs
|
|
129
|
+
* containing the same `(method, path)` is an edge — the assumption is
|
|
130
|
+
* that distinct repos publishing the same path means one consumes from
|
|
131
|
+
* the other.
|
|
132
|
+
*/
|
|
133
|
+
export function buildFederatedGraph(specs) {
|
|
134
|
+
const producers = new Map(); // 'GET /users/:id' → endpoints[]
|
|
135
|
+
for (const spec of (specs || [])) {
|
|
136
|
+
const doc = _parseSpec(spec);
|
|
137
|
+
if (!doc) continue;
|
|
138
|
+
for (const ep of _endpointsFor(doc)) {
|
|
139
|
+
const key = `${ep.method.toUpperCase()} ${ep.path}`;
|
|
140
|
+
if (!producers.has(key)) producers.set(key, []);
|
|
141
|
+
producers.get(key).push({ repo: spec.repo, endpoint: ep });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Federated edges: every pair of repos publishing the same (method, path)
|
|
145
|
+
// is treated as a producer→consumer edge. The producer side is the spec
|
|
146
|
+
// that declares the response shape; the consumer the one that declares
|
|
147
|
+
// a matching request.
|
|
148
|
+
const federatedEdges = [];
|
|
149
|
+
for (const [key, entries] of producers) {
|
|
150
|
+
if (entries.length < 2) continue;
|
|
151
|
+
for (let i = 0; i < entries.length; i++) {
|
|
152
|
+
for (let j = 0; j < entries.length; j++) {
|
|
153
|
+
if (i === j) continue;
|
|
154
|
+
const from = entries[i];
|
|
155
|
+
const to = entries[j];
|
|
156
|
+
// Heuristic: if `from` declares responseFields and `to` declares
|
|
157
|
+
// requestFields with overlap, an edge exists.
|
|
158
|
+
const shared = from.endpoint.responseFields.filter(f =>
|
|
159
|
+
to.endpoint.requestFields.includes(f));
|
|
160
|
+
if (shared.length === 0) continue;
|
|
161
|
+
federatedEdges.push({
|
|
162
|
+
from: {
|
|
163
|
+
repo: from.repo, method: from.endpoint.method, path: from.endpoint.path,
|
|
164
|
+
fields: from.endpoint.responseFields,
|
|
165
|
+
},
|
|
166
|
+
to: {
|
|
167
|
+
repo: to.repo, method: to.endpoint.method, path: to.endpoint.path,
|
|
168
|
+
fields: to.endpoint.requestFields,
|
|
169
|
+
},
|
|
170
|
+
sharedFields: shared,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { producers, federatedEdges };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Render a list of cross-service findings from the federated graph. Each
|
|
180
|
+
* edge becomes one finding tagged `family: 'cross-repo-taint'` with both
|
|
181
|
+
* repos + the shared fields in the trace.
|
|
182
|
+
*/
|
|
183
|
+
export function federatedFindings(graph) {
|
|
184
|
+
if (!graph || !Array.isArray(graph.federatedEdges)) return [];
|
|
185
|
+
const findings = [];
|
|
186
|
+
for (const edge of graph.federatedEdges) {
|
|
187
|
+
findings.push({
|
|
188
|
+
id: `cross-repo:${edge.from.repo}:${edge.from.method}:${edge.from.path}->${edge.to.repo}`,
|
|
189
|
+
file: `(cross-repo: ${edge.from.repo})`,
|
|
190
|
+
line: 0,
|
|
191
|
+
vuln: `Cross-service data flow: ${edge.from.repo} → ${edge.to.repo}`,
|
|
192
|
+
severity: 'medium',
|
|
193
|
+
cwe: 'CWE-829', // Inclusion of Functionality from Untrusted Control Sphere
|
|
194
|
+
family: 'cross-repo-taint',
|
|
195
|
+
parser: 'CROSS-REPO',
|
|
196
|
+
confidence: 0.65,
|
|
197
|
+
description:
|
|
198
|
+
`${edge.from.repo} declares the endpoint ${edge.from.method.toUpperCase()} ${edge.from.path} ` +
|
|
199
|
+
`whose response carries ${edge.sharedFields.join(', ')}. ` +
|
|
200
|
+
`${edge.to.repo} consumes the same endpoint and feeds those fields into its request shape. ` +
|
|
201
|
+
`Any taint in the producer's response surfaces in the consumer's input — sanitization MUST happen at the consumer boundary.`,
|
|
202
|
+
remediation:
|
|
203
|
+
'Add a contract-test fixture in the consumer that asserts each shared field passes its sanitizer at request-handler entry. ' +
|
|
204
|
+
'If the producer is third-party, treat the response as untrusted: validate length/charset before downstream use.',
|
|
205
|
+
trace: [
|
|
206
|
+
{ kind: 'producer', label: `${edge.from.repo}: ${edge.from.method} ${edge.from.path}`, line: 0 },
|
|
207
|
+
{ kind: 'consumer', label: `${edge.to.repo}: ${edge.to.method} ${edge.to.path}`, line: 0 },
|
|
208
|
+
],
|
|
209
|
+
crossRepo: {
|
|
210
|
+
from: edge.from.repo,
|
|
211
|
+
to: edge.to.repo,
|
|
212
|
+
sharedFields: edge.sharedFields,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return findings;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export const _internal = { _parseSpec, _endpointsFor, _leafPathsOf, _responseFields, _requestFields };
|