@oculum/scanner 1.0.12 → 1.0.13
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/dist/detect/ai-code/agent-tools.d.ts +22 -0
- package/dist/detect/ai-code/agent-tools.d.ts.map +1 -0
- package/dist/detect/ai-code/agent-tools.js +1509 -0
- package/dist/detect/ai-code/agent-tools.js.map +1 -0
- package/dist/detect/ai-code/byok-patterns.d.ts +15 -0
- package/dist/detect/ai-code/byok-patterns.d.ts.map +1 -0
- package/dist/detect/ai-code/byok-patterns.js +313 -0
- package/dist/detect/ai-code/byok-patterns.js.map +1 -0
- package/dist/detect/ai-code/endpoint-protection.d.ts +38 -0
- package/dist/detect/ai-code/endpoint-protection.d.ts.map +1 -0
- package/dist/detect/ai-code/endpoint-protection.js +349 -0
- package/dist/detect/ai-code/endpoint-protection.js.map +1 -0
- package/dist/detect/ai-code/execution-sinks.d.ts +21 -0
- package/dist/detect/ai-code/execution-sinks.d.ts.map +1 -0
- package/dist/detect/ai-code/execution-sinks.js +1158 -0
- package/dist/detect/ai-code/execution-sinks.js.map +1 -0
- package/dist/detect/ai-code/fingerprinting.d.ts +10 -0
- package/dist/detect/ai-code/fingerprinting.d.ts.map +1 -0
- package/dist/detect/ai-code/fingerprinting.js +665 -0
- package/dist/detect/ai-code/fingerprinting.js.map +1 -0
- package/dist/detect/ai-code/index.d.ts +12 -0
- package/dist/detect/ai-code/index.d.ts.map +1 -0
- package/dist/detect/ai-code/index.js +26 -0
- package/dist/detect/ai-code/index.js.map +1 -0
- package/dist/detect/ai-code/mcp-security.d.ts +20 -0
- package/dist/detect/ai-code/mcp-security.d.ts.map +1 -0
- package/dist/detect/ai-code/mcp-security.js +880 -0
- package/dist/detect/ai-code/mcp-security.js.map +1 -0
- package/dist/detect/ai-code/model-supply-chain.d.ts +23 -0
- package/dist/detect/ai-code/model-supply-chain.d.ts.map +1 -0
- package/dist/detect/ai-code/model-supply-chain.js +447 -0
- package/dist/detect/ai-code/model-supply-chain.js.map +1 -0
- package/dist/detect/ai-code/package-hallucination.d.ts +22 -0
- package/dist/detect/ai-code/package-hallucination.d.ts.map +1 -0
- package/dist/detect/ai-code/package-hallucination.js +841 -0
- package/dist/detect/ai-code/package-hallucination.js.map +1 -0
- package/dist/detect/ai-code/prompt-hygiene.d.ts +22 -0
- package/dist/detect/ai-code/prompt-hygiene.d.ts.map +1 -0
- package/dist/detect/ai-code/prompt-hygiene.js +1177 -0
- package/dist/detect/ai-code/prompt-hygiene.js.map +1 -0
- package/dist/detect/ai-code/rag-safety.d.ts +24 -0
- package/dist/detect/ai-code/rag-safety.d.ts.map +1 -0
- package/dist/detect/ai-code/rag-safety.js +913 -0
- package/dist/detect/ai-code/rag-safety.js.map +1 -0
- package/dist/detect/ai-code/schema-validation.d.ts +28 -0
- package/dist/detect/ai-code/schema-validation.d.ts.map +1 -0
- package/dist/detect/ai-code/schema-validation.js +378 -0
- package/dist/detect/ai-code/schema-validation.js.map +1 -0
- package/dist/detect/config/agent-skill-injection.d.ts +27 -0
- package/dist/detect/config/agent-skill-injection.d.ts.map +1 -0
- package/dist/detect/config/agent-skill-injection.js +472 -0
- package/dist/detect/config/agent-skill-injection.js.map +1 -0
- package/dist/detect/config/comments.d.ts +11 -0
- package/dist/detect/config/comments.d.ts.map +1 -0
- package/dist/detect/config/comments.js +206 -0
- package/dist/detect/config/comments.js.map +1 -0
- package/dist/detect/config/file-flags.d.ts +10 -0
- package/dist/detect/config/file-flags.d.ts.map +1 -0
- package/dist/detect/config/file-flags.js +124 -0
- package/dist/detect/config/file-flags.js.map +1 -0
- package/dist/detect/config/index.d.ts +7 -0
- package/dist/detect/config/index.d.ts.map +1 -0
- package/dist/detect/config/index.js +17 -0
- package/dist/detect/config/index.js.map +1 -0
- package/dist/detect/config/osv-check.d.ts +75 -0
- package/dist/detect/config/osv-check.d.ts.map +1 -0
- package/dist/detect/config/osv-check.js +309 -0
- package/dist/detect/config/osv-check.js.map +1 -0
- package/dist/detect/config/package-check.d.ts +63 -0
- package/dist/detect/config/package-check.d.ts.map +1 -0
- package/dist/detect/config/package-check.js +509 -0
- package/dist/detect/config/package-check.js.map +1 -0
- package/dist/detect/config/urls.d.ts +11 -0
- package/dist/detect/config/urls.d.ts.map +1 -0
- package/dist/detect/config/urls.js +450 -0
- package/dist/detect/config/urls.js.map +1 -0
- package/dist/detect/index.d.ts +37 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +77 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/secrets/config-audit.d.ts +11 -0
- package/dist/detect/secrets/config-audit.d.ts.map +1 -0
- package/dist/detect/secrets/config-audit.js +315 -0
- package/dist/detect/secrets/config-audit.js.map +1 -0
- package/dist/detect/secrets/config-mcp-audit.d.ts +23 -0
- package/dist/detect/secrets/config-mcp-audit.d.ts.map +1 -0
- package/dist/detect/secrets/config-mcp-audit.js +243 -0
- package/dist/detect/secrets/config-mcp-audit.js.map +1 -0
- package/dist/detect/secrets/entropy.d.ts +11 -0
- package/dist/detect/secrets/entropy.d.ts.map +1 -0
- package/dist/detect/secrets/entropy.js +751 -0
- package/dist/detect/secrets/entropy.js.map +1 -0
- package/dist/detect/secrets/index.d.ts +36 -0
- package/dist/detect/secrets/index.d.ts.map +1 -0
- package/dist/detect/secrets/index.js +174 -0
- package/dist/detect/secrets/index.js.map +1 -0
- package/dist/detect/secrets/patterns.d.ts +11 -0
- package/dist/detect/secrets/patterns.d.ts.map +1 -0
- package/dist/detect/secrets/patterns.js +518 -0
- package/dist/detect/secrets/patterns.js.map +1 -0
- package/dist/detect/secrets/weak-crypto.d.ts +10 -0
- package/dist/detect/secrets/weak-crypto.d.ts.map +1 -0
- package/dist/detect/secrets/weak-crypto.js +432 -0
- package/dist/detect/secrets/weak-crypto.js.map +1 -0
- package/dist/detect/structural/auth-patterns.d.ts +22 -0
- package/dist/detect/structural/auth-patterns.d.ts.map +1 -0
- package/dist/detect/structural/auth-patterns.js +533 -0
- package/dist/detect/structural/auth-patterns.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/child-process.d.ts +16 -0
- package/dist/detect/structural/dangerous-functions/child-process.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/child-process.js +74 -0
- package/dist/detect/structural/dangerous-functions/child-process.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/dom-xss.d.ts +34 -0
- package/dist/detect/structural/dangerous-functions/dom-xss.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/dom-xss.js +230 -0
- package/dist/detect/structural/dangerous-functions/dom-xss.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/index.d.ts +16 -0
- package/dist/detect/structural/dangerous-functions/index.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/index.js +1193 -0
- package/dist/detect/structural/dangerous-functions/index.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/json-parse.d.ts +31 -0
- package/dist/detect/structural/dangerous-functions/json-parse.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/json-parse.js +326 -0
- package/dist/detect/structural/dangerous-functions/json-parse.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/math-random.d.ts +111 -0
- package/dist/detect/structural/dangerous-functions/math-random.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/math-random.js +684 -0
- package/dist/detect/structural/dangerous-functions/math-random.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/patterns.d.ts +21 -0
- package/dist/detect/structural/dangerous-functions/patterns.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/patterns.js +163 -0
- package/dist/detect/structural/dangerous-functions/patterns.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/request-validation.d.ts +13 -0
- package/dist/detect/structural/dangerous-functions/request-validation.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/request-validation.js +126 -0
- package/dist/detect/structural/dangerous-functions/request-validation.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/control-flow.d.ts +24 -0
- package/dist/detect/structural/dangerous-functions/utils/control-flow.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/control-flow.js +70 -0
- package/dist/detect/structural/dangerous-functions/utils/control-flow.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/helpers.d.ts +31 -0
- package/dist/detect/structural/dangerous-functions/utils/helpers.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/helpers.js +147 -0
- package/dist/detect/structural/dangerous-functions/utils/helpers.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/index.d.ts +9 -0
- package/dist/detect/structural/dangerous-functions/utils/index.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/index.js +23 -0
- package/dist/detect/structural/dangerous-functions/utils/index.js.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.d.ts +22 -0
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.js +102 -0
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.js.map +1 -0
- package/dist/detect/structural/data-exposure.d.ts +19 -0
- package/dist/detect/structural/data-exposure.d.ts.map +1 -0
- package/dist/detect/structural/data-exposure.js +262 -0
- package/dist/detect/structural/data-exposure.js.map +1 -0
- package/dist/detect/structural/framework-checks.d.ts +10 -0
- package/dist/detect/structural/framework-checks.d.ts.map +1 -0
- package/dist/detect/structural/framework-checks.js +389 -0
- package/dist/detect/structural/framework-checks.js.map +1 -0
- package/dist/detect/structural/index.d.ts +71 -0
- package/dist/detect/structural/index.d.ts.map +1 -0
- package/dist/detect/structural/index.js +510 -0
- package/dist/detect/structural/index.js.map +1 -0
- package/dist/detect/structural/log-injection.d.ts +18 -0
- package/dist/detect/structural/log-injection.d.ts.map +1 -0
- package/dist/detect/structural/log-injection.js +217 -0
- package/dist/detect/structural/log-injection.js.map +1 -0
- package/dist/detect/structural/logic-gates.d.ts +10 -0
- package/dist/detect/structural/logic-gates.d.ts.map +1 -0
- package/dist/detect/structural/logic-gates.js +227 -0
- package/dist/detect/structural/logic-gates.js.map +1 -0
- package/dist/detect/structural/risky-imports.d.ts +10 -0
- package/dist/detect/structural/risky-imports.d.ts.map +1 -0
- package/dist/detect/structural/risky-imports.js +168 -0
- package/dist/detect/structural/risky-imports.js.map +1 -0
- package/dist/detect/structural/security-headers.d.ts +18 -0
- package/dist/detect/structural/security-headers.d.ts.map +1 -0
- package/dist/detect/structural/security-headers.js +196 -0
- package/dist/detect/structural/security-headers.js.map +1 -0
- package/dist/detect/structural/ssrf-detection.d.ts +18 -0
- package/dist/detect/structural/ssrf-detection.d.ts.map +1 -0
- package/dist/detect/structural/ssrf-detection.js +263 -0
- package/dist/detect/structural/ssrf-detection.js.map +1 -0
- package/dist/detect/structural/variables.d.ts +11 -0
- package/dist/detect/structural/variables.d.ts.map +1 -0
- package/dist/detect/structural/variables.js +159 -0
- package/dist/detect/structural/variables.js.map +1 -0
- package/dist/detect/structural/xxe-detection.d.ts +18 -0
- package/dist/detect/structural/xxe-detection.d.ts.map +1 -0
- package/dist/detect/structural/xxe-detection.js +245 -0
- package/dist/detect/structural/xxe-detection.js.map +1 -0
- package/dist/index.d.ts +17 -64
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -1034
- package/dist/index.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +1 -8
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +4 -0
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +50 -1
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/log-injection.d.ts +18 -0
- package/dist/layer2/log-injection.d.ts.map +1 -0
- package/dist/layer2/log-injection.js +214 -0
- package/dist/layer2/log-injection.js.map +1 -0
- package/dist/layer2/security-headers.d.ts +18 -0
- package/dist/layer2/security-headers.d.ts.map +1 -0
- package/dist/layer2/security-headers.js +187 -0
- package/dist/layer2/security-headers.js.map +1 -0
- package/dist/layer2/ssrf-detection.d.ts +18 -0
- package/dist/layer2/ssrf-detection.d.ts.map +1 -0
- package/dist/layer2/ssrf-detection.js +252 -0
- package/dist/layer2/ssrf-detection.js.map +1 -0
- package/dist/layer2/xxe-detection.d.ts +18 -0
- package/dist/layer2/xxe-detection.d.ts.map +1 -0
- package/dist/layer2/xxe-detection.js +242 -0
- package/dist/layer2/xxe-detection.js.map +1 -0
- package/dist/layer3/anthropic/prompts/index.d.ts +1 -1
- package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -1
- package/dist/layer3/anthropic/prompts/index.js +3 -1
- package/dist/layer3/anthropic/prompts/index.js.map +1 -1
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.d.ts +19 -0
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.js +156 -0
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/auth-access.d.ts +9 -0
- package/dist/layer3/anthropic/prompts/modules/auth-access.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/auth-access.js +25 -0
- package/dist/layer3/anthropic/prompts/modules/auth-access.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/common.d.ts +11 -0
- package/dist/layer3/anthropic/prompts/modules/common.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/common.js +152 -0
- package/dist/layer3/anthropic/prompts/modules/common.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/index.d.ts +54 -0
- package/dist/layer3/anthropic/prompts/modules/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/index.js +185 -0
- package/dist/layer3/anthropic/prompts/modules/index.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.d.ts +8 -0
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.js +84 -0
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.d.ts +8 -0
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.js +68 -0
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.js.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.d.ts +8 -0
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.js +22 -0
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.js.map +1 -0
- package/dist/layer3/anthropic/prompts/validation.d.ts +9 -3
- package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -1
- package/dist/layer3/anthropic/prompts/validation.js +14 -410
- package/dist/layer3/anthropic/prompts/validation.js.map +1 -1
- package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -1
- package/dist/layer3/anthropic/providers/anthropic.js +6 -3
- package/dist/layer3/anthropic/providers/anthropic.js.map +1 -1
- package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -1
- package/dist/layer3/anthropic/providers/openai.js +6 -3
- package/dist/layer3/anthropic/providers/openai.js.map +1 -1
- package/dist/layer3/anthropic/request-builder.d.ts +11 -4
- package/dist/layer3/anthropic/request-builder.d.ts.map +1 -1
- package/dist/layer3/anthropic/request-builder.js +32 -16
- package/dist/layer3/anthropic/request-builder.js.map +1 -1
- package/dist/layer3/anthropic/utils/context-extractor.d.ts +55 -0
- package/dist/layer3/anthropic/utils/context-extractor.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/context-extractor.js +161 -0
- package/dist/layer3/anthropic/utils/context-extractor.js.map +1 -0
- package/dist/layer3/anthropic/utils/index.d.ts +2 -0
- package/dist/layer3/anthropic/utils/index.d.ts.map +1 -1
- package/dist/layer3/anthropic/utils/index.js +4 -1
- package/dist/layer3/anthropic/utils/index.js.map +1 -1
- package/dist/model/auth-helper-detector.d.ts +56 -0
- package/dist/model/auth-helper-detector.d.ts.map +1 -0
- package/dist/model/auth-helper-detector.js +360 -0
- package/dist/model/auth-helper-detector.js.map +1 -0
- package/dist/model/cross-file-taint.d.ts +40 -0
- package/dist/model/cross-file-taint.d.ts.map +1 -0
- package/dist/model/cross-file-taint.js +290 -0
- package/dist/model/cross-file-taint.js.map +1 -0
- package/dist/model/framework-models/django.d.ts +9 -0
- package/dist/model/framework-models/django.d.ts.map +1 -0
- package/dist/model/framework-models/django.js +82 -0
- package/dist/model/framework-models/django.js.map +1 -0
- package/dist/model/framework-models/express.d.ts +9 -0
- package/dist/model/framework-models/express.d.ts.map +1 -0
- package/dist/model/framework-models/express.js +52 -0
- package/dist/model/framework-models/express.js.map +1 -0
- package/dist/model/framework-models/index.d.ts +20 -0
- package/dist/model/framework-models/index.d.ts.map +1 -0
- package/dist/model/framework-models/index.js +102 -0
- package/dist/model/framework-models/index.js.map +1 -0
- package/dist/model/framework-models/nextjs.d.ts +9 -0
- package/dist/model/framework-models/nextjs.d.ts.map +1 -0
- package/dist/model/framework-models/nextjs.js +71 -0
- package/dist/model/framework-models/nextjs.js.map +1 -0
- package/dist/model/framework-models/prisma.d.ts +10 -0
- package/dist/model/framework-models/prisma.d.ts.map +1 -0
- package/dist/model/framework-models/prisma.js +54 -0
- package/dist/model/framework-models/prisma.js.map +1 -0
- package/dist/model/framework-models/react.d.ts +9 -0
- package/dist/model/framework-models/react.d.ts.map +1 -0
- package/dist/model/framework-models/react.js +67 -0
- package/dist/model/framework-models/react.js.map +1 -0
- package/dist/model/framework-models/sequelize.d.ts +9 -0
- package/dist/model/framework-models/sequelize.d.ts.map +1 -0
- package/dist/model/framework-models/sequelize.js +62 -0
- package/dist/model/framework-models/sequelize.js.map +1 -0
- package/dist/model/framework-models/types.d.ts +43 -0
- package/dist/model/framework-models/types.d.ts.map +1 -0
- package/dist/model/framework-models/types.js +10 -0
- package/dist/model/framework-models/types.js.map +1 -0
- package/dist/model/function-classifier.d.ts +32 -0
- package/dist/model/function-classifier.d.ts.map +1 -0
- package/dist/model/function-classifier.js +143 -0
- package/dist/model/function-classifier.js.map +1 -0
- package/dist/model/import-resolver.d.ts +45 -0
- package/dist/model/import-resolver.d.ts.map +1 -0
- package/dist/model/import-resolver.js +410 -0
- package/dist/model/import-resolver.js.map +1 -0
- package/dist/model/imported-auth-detector.d.ts +38 -0
- package/dist/model/imported-auth-detector.d.ts.map +1 -0
- package/dist/model/imported-auth-detector.js +199 -0
- package/dist/model/imported-auth-detector.js.map +1 -0
- package/dist/model/index.d.ts +63 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +272 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/middleware-detector.d.ts +55 -0
- package/dist/model/middleware-detector.d.ts.map +1 -0
- package/dist/model/middleware-detector.js +382 -0
- package/dist/model/middleware-detector.js.map +1 -0
- package/dist/model/module-graph.d.ts +46 -0
- package/dist/model/module-graph.d.ts.map +1 -0
- package/dist/model/module-graph.js +187 -0
- package/dist/model/module-graph.js.map +1 -0
- package/dist/model/oauth-flow-detector.d.ts +41 -0
- package/dist/model/oauth-flow-detector.d.ts.map +1 -0
- package/dist/model/oauth-flow-detector.js +202 -0
- package/dist/model/oauth-flow-detector.js.map +1 -0
- package/dist/model/project-context.d.ts +119 -0
- package/dist/model/project-context.d.ts.map +1 -0
- package/dist/model/project-context.js +534 -0
- package/dist/model/project-context.js.map +1 -0
- package/dist/model/route-auth-resolver.d.ts +27 -0
- package/dist/model/route-auth-resolver.d.ts.map +1 -0
- package/dist/model/route-auth-resolver.js +182 -0
- package/dist/model/route-auth-resolver.js.map +1 -0
- package/dist/model/route-discovery/express.d.ts +25 -0
- package/dist/model/route-discovery/express.d.ts.map +1 -0
- package/dist/model/route-discovery/express.js +225 -0
- package/dist/model/route-discovery/express.js.map +1 -0
- package/dist/model/route-discovery/index.d.ts +21 -0
- package/dist/model/route-discovery/index.d.ts.map +1 -0
- package/dist/model/route-discovery/index.js +67 -0
- package/dist/model/route-discovery/index.js.map +1 -0
- package/dist/model/route-discovery/nextjs.d.ts +16 -0
- package/dist/model/route-discovery/nextjs.d.ts.map +1 -0
- package/dist/model/route-discovery/nextjs.js +179 -0
- package/dist/model/route-discovery/nextjs.js.map +1 -0
- package/dist/model/route-discovery/python.d.ts +16 -0
- package/dist/model/route-discovery/python.d.ts.map +1 -0
- package/dist/model/route-discovery/python.js +181 -0
- package/dist/model/route-discovery/python.js.map +1 -0
- package/dist/model/route-discovery/types.d.ts +36 -0
- package/dist/model/route-discovery/types.d.ts.map +1 -0
- package/dist/model/route-discovery/types.js +16 -0
- package/dist/model/route-discovery/types.js.map +1 -0
- package/dist/model/route-discovery/utils.d.ts +18 -0
- package/dist/model/route-discovery/utils.d.ts.map +1 -0
- package/dist/model/route-discovery/utils.js +55 -0
- package/dist/model/route-discovery/utils.js.map +1 -0
- package/dist/model/route-hierarchy.d.ts +50 -0
- package/dist/model/route-hierarchy.d.ts.map +1 -0
- package/dist/model/route-hierarchy.js +226 -0
- package/dist/model/route-hierarchy.js.map +1 -0
- package/dist/model/sanitiser-detection.d.ts +27 -0
- package/dist/model/sanitiser-detection.d.ts.map +1 -0
- package/dist/model/sanitiser-detection.js +224 -0
- package/dist/model/sanitiser-detection.js.map +1 -0
- package/dist/model/sink-matcher.d.ts +17 -0
- package/dist/model/sink-matcher.d.ts.map +1 -0
- package/dist/model/sink-matcher.js +141 -0
- package/dist/model/sink-matcher.js.map +1 -0
- package/dist/model/sink-patterns.d.ts +19 -0
- package/dist/model/sink-patterns.d.ts.map +1 -0
- package/dist/model/sink-patterns.js +88 -0
- package/dist/model/sink-patterns.js.map +1 -0
- package/dist/model/source-discovery.d.ts +15 -0
- package/dist/model/source-discovery.d.ts.map +1 -0
- package/dist/model/source-discovery.js +170 -0
- package/dist/model/source-discovery.js.map +1 -0
- package/dist/model/taint-tracker.d.ts +21 -0
- package/dist/model/taint-tracker.d.ts.map +1 -0
- package/dist/model/taint-tracker.js +281 -0
- package/dist/model/taint-tracker.js.map +1 -0
- package/dist/model/taint-types.d.ts +74 -0
- package/dist/model/taint-types.d.ts.map +1 -0
- package/dist/model/taint-types.js +9 -0
- package/dist/model/taint-types.js.map +1 -0
- package/dist/model/trpc-analyzer.d.ts +78 -0
- package/dist/model/trpc-analyzer.d.ts.map +1 -0
- package/dist/model/trpc-analyzer.js +297 -0
- package/dist/model/trpc-analyzer.js.map +1 -0
- package/dist/parse/file-classifier.d.ts +228 -0
- package/dist/parse/file-classifier.d.ts.map +1 -0
- package/dist/parse/file-classifier.js +933 -0
- package/dist/parse/file-classifier.js.map +1 -0
- package/dist/parse/path-exclusions.d.ts +55 -0
- package/dist/parse/path-exclusions.d.ts.map +1 -0
- package/dist/parse/path-exclusions.js +224 -0
- package/dist/parse/path-exclusions.js.map +1 -0
- package/dist/pipeline/config.d.ts +39 -0
- package/dist/pipeline/config.d.ts.map +1 -0
- package/dist/pipeline/config.js +46 -0
- package/dist/pipeline/config.js.map +1 -0
- package/dist/pipeline/index.d.ts +34 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +377 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/modes/incremental.d.ts +66 -0
- package/dist/pipeline/modes/incremental.d.ts.map +1 -0
- package/dist/pipeline/modes/incremental.js +200 -0
- package/dist/pipeline/modes/incremental.js.map +1 -0
- package/dist/postprocess/aggregation.d.ts +14 -0
- package/dist/postprocess/aggregation.d.ts.map +1 -0
- package/dist/postprocess/aggregation.js +63 -0
- package/dist/postprocess/aggregation.js.map +1 -0
- package/dist/postprocess/contradictions.d.ts +18 -0
- package/dist/postprocess/contradictions.d.ts.map +1 -0
- package/dist/postprocess/contradictions.js +99 -0
- package/dist/postprocess/contradictions.js.map +1 -0
- package/dist/postprocess/dedup.d.ts +13 -0
- package/dist/postprocess/dedup.d.ts.map +1 -0
- package/dist/postprocess/dedup.js +58 -0
- package/dist/postprocess/dedup.js.map +1 -0
- package/dist/postprocess/filtering/context-adjustments.d.ts +23 -0
- package/dist/postprocess/filtering/context-adjustments.d.ts.map +1 -0
- package/dist/postprocess/filtering/context-adjustments.js +100 -0
- package/dist/postprocess/filtering/context-adjustments.js.map +1 -0
- package/dist/postprocess/filtering/index.d.ts +3 -0
- package/dist/postprocess/filtering/index.d.ts.map +1 -0
- package/dist/postprocess/filtering/index.js +8 -0
- package/dist/postprocess/filtering/index.js.map +1 -0
- package/dist/postprocess/filtering/pipeline.d.ts +48 -0
- package/dist/postprocess/filtering/pipeline.d.ts.map +1 -0
- package/dist/postprocess/filtering/pipeline.js +76 -0
- package/dist/postprocess/filtering/pipeline.js.map +1 -0
- package/dist/postprocess/index.d.ts +41 -0
- package/dist/postprocess/index.d.ts.map +1 -0
- package/dist/postprocess/index.js +85 -0
- package/dist/postprocess/index.js.map +1 -0
- package/dist/postprocess/suppression/config-loader.d.ts +74 -0
- package/dist/postprocess/suppression/config-loader.d.ts.map +1 -0
- package/dist/postprocess/suppression/config-loader.js +424 -0
- package/dist/postprocess/suppression/config-loader.js.map +1 -0
- package/dist/postprocess/suppression/hash.d.ts +48 -0
- package/dist/postprocess/suppression/hash.d.ts.map +1 -0
- package/dist/postprocess/suppression/hash.js +88 -0
- package/dist/postprocess/suppression/hash.js.map +1 -0
- package/dist/postprocess/suppression/index.d.ts +11 -0
- package/dist/postprocess/suppression/index.d.ts.map +1 -0
- package/dist/postprocess/suppression/index.js +39 -0
- package/dist/postprocess/suppression/index.js.map +1 -0
- package/dist/postprocess/suppression/inline-parser.d.ts +39 -0
- package/dist/postprocess/suppression/inline-parser.d.ts.map +1 -0
- package/dist/postprocess/suppression/inline-parser.js +218 -0
- package/dist/postprocess/suppression/inline-parser.js.map +1 -0
- package/dist/postprocess/suppression/manager.d.ts +94 -0
- package/dist/postprocess/suppression/manager.d.ts.map +1 -0
- package/dist/postprocess/suppression/manager.js +292 -0
- package/dist/postprocess/suppression/manager.js.map +1 -0
- package/dist/postprocess/suppression/types.d.ts +151 -0
- package/dist/postprocess/suppression/types.d.ts.map +1 -0
- package/dist/postprocess/suppression/types.js +28 -0
- package/dist/postprocess/suppression/types.js.map +1 -0
- package/dist/postprocess/validation-cap.d.ts +17 -0
- package/dist/postprocess/validation-cap.d.ts.map +1 -0
- package/dist/postprocess/validation-cap.js +64 -0
- package/dist/postprocess/validation-cap.js.map +1 -0
- package/dist/report/build-result.d.ts +33 -0
- package/dist/report/build-result.d.ts.map +1 -0
- package/dist/report/build-result.js +59 -0
- package/dist/report/build-result.js.map +1 -0
- package/dist/report/enrichment.d.ts +19 -0
- package/dist/report/enrichment.d.ts.map +1 -0
- package/dist/report/enrichment.js +44 -0
- package/dist/report/enrichment.js.map +1 -0
- package/dist/report/formatters/ai-context.d.ts +23 -0
- package/dist/report/formatters/ai-context.d.ts.map +1 -0
- package/dist/report/formatters/ai-context.js +238 -0
- package/dist/report/formatters/ai-context.js.map +1 -0
- package/dist/report/formatters/cli-terminal.d.ts +65 -0
- package/dist/report/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/report/formatters/cli-terminal.js +735 -0
- package/dist/report/formatters/cli-terminal.js.map +1 -0
- package/dist/report/formatters/github-comment.d.ts +41 -0
- package/dist/report/formatters/github-comment.d.ts.map +1 -0
- package/dist/report/formatters/github-comment.js +370 -0
- package/dist/report/formatters/github-comment.js.map +1 -0
- package/dist/report/formatters/grouping.d.ts +52 -0
- package/dist/report/formatters/grouping.d.ts.map +1 -0
- package/dist/report/formatters/grouping.js +152 -0
- package/dist/report/formatters/grouping.js.map +1 -0
- package/dist/report/formatters/ide/claude-code.d.ts +17 -0
- package/dist/report/formatters/ide/claude-code.d.ts.map +1 -0
- package/dist/report/formatters/ide/claude-code.js +94 -0
- package/dist/report/formatters/ide/claude-code.js.map +1 -0
- package/dist/report/formatters/ide/cursor.d.ts +13 -0
- package/dist/report/formatters/ide/cursor.d.ts.map +1 -0
- package/dist/report/formatters/ide/cursor.js +125 -0
- package/dist/report/formatters/ide/cursor.js.map +1 -0
- package/dist/report/formatters/ide/index.d.ts +62 -0
- package/dist/report/formatters/ide/index.d.ts.map +1 -0
- package/dist/report/formatters/ide/index.js +184 -0
- package/dist/report/formatters/ide/index.js.map +1 -0
- package/dist/report/formatters/ide/windsurf.d.ts +13 -0
- package/dist/report/formatters/ide/windsurf.d.ts.map +1 -0
- package/dist/report/formatters/ide/windsurf.js +117 -0
- package/dist/report/formatters/ide/windsurf.js.map +1 -0
- package/dist/report/formatters/index.d.ts +11 -0
- package/dist/report/formatters/index.d.ts.map +1 -0
- package/dist/report/formatters/index.js +54 -0
- package/dist/report/formatters/index.js.map +1 -0
- package/dist/report/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/report/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/report/formatters/vscode-diagnostic.js +151 -0
- package/dist/report/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/report/summary.d.ts +27 -0
- package/dist/report/summary.d.ts.map +1 -0
- package/dist/report/summary.js +57 -0
- package/dist/report/summary.js.map +1 -0
- package/dist/rules/metadata.d.ts.map +1 -1
- package/dist/rules/metadata.js +66 -0
- package/dist/rules/metadata.js.map +1 -1
- package/dist/score/adjustments.d.ts +22 -0
- package/dist/score/adjustments.d.ts.map +1 -0
- package/dist/score/adjustments.js +373 -0
- package/dist/score/adjustments.js.map +1 -0
- package/dist/score/auto-dismiss.d.ts +28 -0
- package/dist/score/auto-dismiss.d.ts.map +1 -0
- package/dist/score/auto-dismiss.js +200 -0
- package/dist/score/auto-dismiss.js.map +1 -0
- package/dist/score/confidence.d.ts +19 -0
- package/dist/score/confidence.d.ts.map +1 -0
- package/dist/score/confidence.js +52 -0
- package/dist/score/confidence.js.map +1 -0
- package/dist/score/index.d.ts +61 -0
- package/dist/score/index.d.ts.map +1 -0
- package/dist/score/index.js +250 -0
- package/dist/score/index.js.map +1 -0
- package/dist/score/types.d.ts +160 -0
- package/dist/score/types.d.ts.map +1 -0
- package/dist/score/types.js +14 -0
- package/dist/score/types.js.map +1 -0
- package/dist/shared/ai-context/index.d.ts +6 -0
- package/dist/shared/ai-context/index.d.ts.map +1 -0
- package/dist/shared/ai-context/index.js +13 -0
- package/dist/shared/ai-context/index.js.map +1 -0
- package/dist/shared/ai-context/manager.d.ts +67 -0
- package/dist/shared/ai-context/manager.d.ts.map +1 -0
- package/dist/shared/ai-context/manager.js +104 -0
- package/dist/shared/ai-context/manager.js.map +1 -0
- package/dist/shared/baseline/diff.d.ts +32 -0
- package/dist/shared/baseline/diff.d.ts.map +1 -0
- package/dist/shared/baseline/diff.js +119 -0
- package/dist/shared/baseline/diff.js.map +1 -0
- package/dist/shared/baseline/index.d.ts +9 -0
- package/dist/shared/baseline/index.d.ts.map +1 -0
- package/dist/shared/baseline/index.js +19 -0
- package/dist/shared/baseline/index.js.map +1 -0
- package/dist/shared/baseline/manager.d.ts +67 -0
- package/dist/shared/baseline/manager.d.ts.map +1 -0
- package/dist/shared/baseline/manager.js +180 -0
- package/dist/shared/baseline/manager.js.map +1 -0
- package/dist/shared/baseline/types.d.ts +91 -0
- package/dist/shared/baseline/types.d.ts.map +1 -0
- package/dist/shared/baseline/types.js +12 -0
- package/dist/shared/baseline/types.js.map +1 -0
- package/dist/shared/category-filter.d.ts +125 -0
- package/dist/shared/category-filter.d.ts.map +1 -0
- package/dist/shared/category-filter.js +360 -0
- package/dist/shared/category-filter.js.map +1 -0
- package/dist/shared/code-analysis.d.ts +39 -0
- package/dist/shared/code-analysis.d.ts.map +1 -0
- package/dist/shared/code-analysis.js +159 -0
- package/dist/shared/code-analysis.js.map +1 -0
- package/dist/shared/comment-analyzer.d.ts +38 -0
- package/dist/shared/comment-analyzer.d.ts.map +1 -0
- package/dist/shared/comment-analyzer.js +218 -0
- package/dist/shared/comment-analyzer.js.map +1 -0
- package/dist/shared/diff-detector.d.ts +53 -0
- package/dist/shared/diff-detector.d.ts.map +1 -0
- package/dist/shared/diff-detector.js +104 -0
- package/dist/shared/diff-detector.js.map +1 -0
- package/dist/shared/diff-parser.d.ts +80 -0
- package/dist/shared/diff-parser.d.ts.map +1 -0
- package/dist/shared/diff-parser.js +202 -0
- package/dist/shared/diff-parser.js.map +1 -0
- package/dist/shared/environment-context.d.ts +76 -0
- package/dist/shared/environment-context.d.ts.map +1 -0
- package/dist/shared/environment-context.js +271 -0
- package/dist/shared/environment-context.js.map +1 -0
- package/dist/shared/intent-detector.d.ts +66 -0
- package/dist/shared/intent-detector.d.ts.map +1 -0
- package/dist/shared/intent-detector.js +282 -0
- package/dist/shared/intent-detector.js.map +1 -0
- package/dist/shared/parsed-file.d.ts +51 -0
- package/dist/shared/parsed-file.d.ts.map +1 -0
- package/dist/shared/parsed-file.js +95 -0
- package/dist/shared/parsed-file.js.map +1 -0
- package/dist/shared/registry-clients.d.ts +93 -0
- package/dist/shared/registry-clients.d.ts.map +1 -0
- package/dist/shared/registry-clients.js +273 -0
- package/dist/shared/registry-clients.js.map +1 -0
- package/dist/shared/rules/framework-fixes.d.ts +48 -0
- package/dist/shared/rules/framework-fixes.d.ts.map +1 -0
- package/dist/shared/rules/framework-fixes.js +439 -0
- package/dist/shared/rules/framework-fixes.js.map +1 -0
- package/dist/shared/rules/index.d.ts +8 -0
- package/dist/shared/rules/index.d.ts.map +1 -0
- package/dist/shared/rules/index.js +18 -0
- package/dist/shared/rules/index.js.map +1 -0
- package/dist/shared/rules/metadata.d.ts +43 -0
- package/dist/shared/rules/metadata.d.ts.map +1 -0
- package/dist/shared/rules/metadata.js +819 -0
- package/dist/shared/rules/metadata.js.map +1 -0
- package/dist/shared/schema-semantics.d.ts +45 -0
- package/dist/shared/schema-semantics.d.ts.map +1 -0
- package/dist/shared/schema-semantics.js +193 -0
- package/dist/shared/schema-semantics.js.map +1 -0
- package/dist/shared/types.d.ts +337 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +126 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/tiers.d.ts +2 -2
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +10 -0
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validate/clients.d.ts +44 -0
- package/dist/validate/clients.d.ts.map +1 -0
- package/dist/validate/clients.js +81 -0
- package/dist/validate/clients.js.map +1 -0
- package/dist/validate/index.d.ts +41 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +141 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/prompts/index.d.ts +8 -0
- package/dist/validate/prompts/index.d.ts.map +1 -0
- package/dist/validate/prompts/index.js +16 -0
- package/dist/validate/prompts/index.js.map +1 -0
- package/dist/validate/prompts/modules/ai-patterns.d.ts +19 -0
- package/dist/validate/prompts/modules/ai-patterns.d.ts.map +1 -0
- package/dist/validate/prompts/modules/ai-patterns.js +156 -0
- package/dist/validate/prompts/modules/ai-patterns.js.map +1 -0
- package/dist/validate/prompts/modules/auth-access.d.ts +9 -0
- package/dist/validate/prompts/modules/auth-access.d.ts.map +1 -0
- package/dist/validate/prompts/modules/auth-access.js +25 -0
- package/dist/validate/prompts/modules/auth-access.js.map +1 -0
- package/dist/validate/prompts/modules/common.d.ts +11 -0
- package/dist/validate/prompts/modules/common.d.ts.map +1 -0
- package/dist/validate/prompts/modules/common.js +186 -0
- package/dist/validate/prompts/modules/common.js.map +1 -0
- package/dist/validate/prompts/modules/index.d.ts +54 -0
- package/dist/validate/prompts/modules/index.d.ts.map +1 -0
- package/dist/validate/prompts/modules/index.js +186 -0
- package/dist/validate/prompts/modules/index.js.map +1 -0
- package/dist/validate/prompts/modules/owasp-classic.d.ts +8 -0
- package/dist/validate/prompts/modules/owasp-classic.d.ts.map +1 -0
- package/dist/validate/prompts/modules/owasp-classic.js +84 -0
- package/dist/validate/prompts/modules/owasp-classic.js.map +1 -0
- package/dist/validate/prompts/modules/secrets-crypto.d.ts +8 -0
- package/dist/validate/prompts/modules/secrets-crypto.d.ts.map +1 -0
- package/dist/validate/prompts/modules/secrets-crypto.js +68 -0
- package/dist/validate/prompts/modules/secrets-crypto.js.map +1 -0
- package/dist/validate/prompts/modules/xss-prompt.d.ts +8 -0
- package/dist/validate/prompts/modules/xss-prompt.d.ts.map +1 -0
- package/dist/validate/prompts/modules/xss-prompt.js +22 -0
- package/dist/validate/prompts/modules/xss-prompt.js.map +1 -0
- package/dist/validate/prompts/semantic-analysis.d.ts +15 -0
- package/dist/validate/prompts/semantic-analysis.d.ts.map +1 -0
- package/dist/validate/prompts/semantic-analysis.js +169 -0
- package/dist/validate/prompts/semantic-analysis.js.map +1 -0
- package/dist/validate/prompts/validation.d.ts +18 -0
- package/dist/validate/prompts/validation.d.ts.map +1 -0
- package/dist/validate/prompts/validation.js +25 -0
- package/dist/validate/prompts/validation.js.map +1 -0
- package/dist/validate/providers/anthropic.d.ts +17 -0
- package/dist/validate/providers/anthropic.d.ts.map +1 -0
- package/dist/validate/providers/anthropic.js +260 -0
- package/dist/validate/providers/anthropic.js.map +1 -0
- package/dist/validate/providers/index.d.ts +8 -0
- package/dist/validate/providers/index.d.ts.map +1 -0
- package/dist/validate/providers/index.js +13 -0
- package/dist/validate/providers/index.js.map +1 -0
- package/dist/validate/providers/openai.d.ts +14 -0
- package/dist/validate/providers/openai.d.ts.map +1 -0
- package/dist/validate/providers/openai.js +336 -0
- package/dist/validate/providers/openai.js.map +1 -0
- package/dist/validate/request-builder.d.ts +61 -0
- package/dist/validate/request-builder.d.ts.map +1 -0
- package/dist/validate/request-builder.js +346 -0
- package/dist/validate/request-builder.js.map +1 -0
- package/dist/validate/types.d.ts +88 -0
- package/dist/validate/types.d.ts.map +1 -0
- package/dist/validate/types.js +38 -0
- package/dist/validate/types.js.map +1 -0
- package/dist/validate/utils/context-extractor.d.ts +55 -0
- package/dist/validate/utils/context-extractor.d.ts.map +1 -0
- package/dist/validate/utils/context-extractor.js +161 -0
- package/dist/validate/utils/context-extractor.js.map +1 -0
- package/dist/validate/utils/index.d.ts +11 -0
- package/dist/validate/utils/index.d.ts.map +1 -0
- package/dist/validate/utils/index.js +27 -0
- package/dist/validate/utils/index.js.map +1 -0
- package/dist/validate/utils/path-helpers.d.ts +21 -0
- package/dist/validate/utils/path-helpers.d.ts.map +1 -0
- package/dist/validate/utils/path-helpers.js +69 -0
- package/dist/validate/utils/path-helpers.js.map +1 -0
- package/dist/validate/utils/response-parser.d.ts +40 -0
- package/dist/validate/utils/response-parser.d.ts.map +1 -0
- package/dist/validate/utils/response-parser.js +286 -0
- package/dist/validate/utils/response-parser.js.map +1 -0
- package/dist/validate/utils/retry.d.ts +15 -0
- package/dist/validate/utils/retry.d.ts.map +1 -0
- package/dist/validate/utils/retry.js +62 -0
- package/dist/validate/utils/retry.js.map +1 -0
- package/package.json +8 -7
- package/src/__tests__/benchmark/fixtures/layer1/agent-skill-injection.ts +204 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +3 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +15 -0
- package/src/__tests__/benchmark/fixtures/layer2/log-injection.ts +147 -0
- package/src/__tests__/benchmark/fixtures/layer2/security-headers.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ssrf-detection.ts +210 -0
- package/src/__tests__/benchmark/fixtures/layer2/xxe-detection.ts +195 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +3 -3
- package/src/__tests__/benchmark/run-real-world-test.ts +4 -4
- package/src/__tests__/benchmark/types.ts +1 -1
- package/src/__tests__/benchmark/utils/test-runner.ts +3 -3
- package/src/__tests__/category-filter.test.ts +2 -2
- package/src/__tests__/context-engine/cross-file-taint.test.ts +284 -0
- package/src/__tests__/context-engine/framework-models.test.ts +457 -0
- package/src/__tests__/context-engine/function-classifier.test.ts +146 -0
- package/src/__tests__/context-engine/import-resolver.test.ts +328 -0
- package/src/__tests__/context-engine/integration.test.ts +320 -0
- package/src/__tests__/context-engine/module-graph.test.ts +159 -0
- package/src/__tests__/context-engine/route-discovery/auth-resolver.test.ts +353 -0
- package/src/__tests__/context-engine/route-discovery/express.test.ts +150 -0
- package/src/__tests__/context-engine/route-discovery/nextjs.test.ts +138 -0
- package/src/__tests__/context-engine/route-discovery/python.test.ts +95 -0
- package/src/__tests__/context-engine/sanitiser-detection.test.ts +187 -0
- package/src/__tests__/context-engine/sink-matcher.test.ts +251 -0
- package/src/__tests__/context-engine/source-discovery.test.ts +186 -0
- package/src/__tests__/context-engine/taint-tracker.test.ts +182 -0
- package/src/__tests__/regression/agent-skill-benign.test.ts +174 -0
- package/src/__tests__/regression/known-false-positives.test.ts +312 -4
- package/src/__tests__/score/adjustments.test.ts +385 -0
- package/src/__tests__/score/confidence.test.ts +283 -0
- package/src/__tests__/score/framework-scoring.test.ts +275 -0
- package/src/__tests__/score/route-scoring.test.ts +156 -0
- package/src/__tests__/score/scoring-integration.test.ts +165 -0
- package/src/__tests__/score/taint-adjustments.test.ts +244 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +37 -49
- package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +52 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +3 -3
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +2 -2
- package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +1 -1
- package/src/__tests__/snapshots/scan-depth.test.ts +3 -3
- package/src/__tests__/validate/route-annotations.test.ts +138 -0
- package/src/__tests__/validation/analyze-results.ts +1 -1
- package/src/__tests__/validation/extract-for-triage.ts +1 -1
- package/src/__tests__/validation/fp-deep-analysis.ts +1 -1
- package/src/{layer2/ai-agent-tools.ts → detect/ai-code/agent-tools.ts} +23 -3
- package/src/{layer2 → detect/ai-code}/byok-patterns.ts +17 -5
- package/src/{layer2/ai-endpoint-protection.ts → detect/ai-code/endpoint-protection.ts} +8 -4
- package/src/{layer2/ai-execution-sinks.ts → detect/ai-code/execution-sinks.ts} +8 -4
- package/src/{layer2/ai-fingerprinting.ts → detect/ai-code/fingerprinting.ts} +20 -4
- package/src/detect/ai-code/index.ts +11 -0
- package/src/{layer2/ai-mcp-security.ts → detect/ai-code/mcp-security.ts} +7 -3
- package/src/{layer2 → detect/ai-code}/model-supply-chain.ts +7 -3
- package/src/{layer2/ai-package-hallucination.ts → detect/ai-code/package-hallucination.ts} +18 -3
- package/src/{layer2/ai-prompt-hygiene.ts → detect/ai-code/prompt-hygiene.ts} +25 -3
- package/src/{layer2/ai-rag-safety.ts → detect/ai-code/rag-safety.ts} +7 -3
- package/src/{layer2/ai-schema-validation.ts → detect/ai-code/schema-validation.ts} +7 -3
- package/src/detect/config/agent-skill-injection.ts +551 -0
- package/src/{layer1 → detect/config}/comments.ts +6 -2
- package/src/{layer1 → detect/config}/file-flags.ts +9 -3
- package/src/detect/config/index.ts +6 -0
- package/src/{layer3 → detect/config}/osv-check.ts +3 -2
- package/src/{layer3 → detect/config}/package-check.ts +3 -2
- package/src/{layer1 → detect/config}/urls.ts +12 -5
- package/src/detect/index.ts +131 -0
- package/src/{layer1 → detect/secrets}/config-audit.ts +7 -2
- package/src/{layer1 → detect/secrets}/config-mcp-audit.ts +8 -3
- package/src/{layer1 → detect/secrets}/entropy.ts +23 -11
- package/src/{layer1 → detect/secrets}/index.ts +31 -30
- package/src/{layer1 → detect/secrets}/patterns.ts +10 -3
- package/src/{layer1 → detect/secrets}/weak-crypto.ts +7 -2
- package/src/{layer2/auth-antipatterns.ts → detect/structural/auth-patterns.ts} +23 -11
- package/src/{layer2 → detect/structural}/dangerous-functions/dom-xss.ts +1 -1
- package/src/{layer2 → detect/structural}/dangerous-functions/index.ts +47 -24
- package/src/{layer2 → detect/structural}/dangerous-functions/json-parse.ts +10 -2
- package/src/{layer2 → detect/structural}/dangerous-functions/math-random.ts +2 -2
- package/src/{layer2 → detect/structural}/dangerous-functions/patterns.ts +1 -1
- package/src/{layer2 → detect/structural}/dangerous-functions/request-validation.ts +10 -2
- package/src/{layer2 → detect/structural}/dangerous-functions/utils/control-flow.ts +2 -2
- package/src/{layer2 → detect/structural}/data-exposure.ts +11 -3
- package/src/{layer2 → detect/structural}/framework-checks.ts +10 -11
- package/src/{layer2 → detect/structural}/index.ts +80 -77
- package/src/detect/structural/log-injection.ts +254 -0
- package/src/{layer2 → detect/structural}/logic-gates.ts +13 -5
- package/src/{layer2 → detect/structural}/risky-imports.ts +7 -3
- package/src/detect/structural/security-headers.ts +231 -0
- package/src/detect/structural/ssrf-detection.ts +300 -0
- package/src/{layer2 → detect/structural}/variables.ts +7 -3
- package/src/detect/structural/xxe-detection.ts +295 -0
- package/src/index.ts +39 -1291
- package/src/{utils → model}/auth-helper-detector.ts +1 -1
- package/src/model/cross-file-taint.ts +374 -0
- package/src/model/framework-models/django.ts +82 -0
- package/src/model/framework-models/express.ts +54 -0
- package/src/model/framework-models/index.ts +116 -0
- package/src/model/framework-models/nextjs.ts +69 -0
- package/src/model/framework-models/prisma.ts +57 -0
- package/src/model/framework-models/react.ts +63 -0
- package/src/model/framework-models/sequelize.ts +63 -0
- package/src/model/framework-models/types.ts +46 -0
- package/src/model/function-classifier.ts +184 -0
- package/src/model/import-resolver.ts +453 -0
- package/src/{utils → model}/imported-auth-detector.ts +21 -85
- package/src/model/index.ts +353 -0
- package/src/{utils → model}/middleware-detector.ts +156 -17
- package/src/model/module-graph.ts +254 -0
- package/src/{utils → model}/oauth-flow-detector.ts +1 -1
- package/src/{utils/project-context-builder.ts → model/project-context.ts} +1 -1
- package/src/model/route-auth-resolver.ts +216 -0
- package/src/model/route-discovery/express.ts +251 -0
- package/src/model/route-discovery/index.ts +83 -0
- package/src/model/route-discovery/nextjs.ts +216 -0
- package/src/model/route-discovery/python.ts +214 -0
- package/src/model/route-discovery/types.ts +48 -0
- package/src/model/route-discovery/utils.ts +54 -0
- package/src/model/sanitiser-detection.ts +268 -0
- package/src/model/sink-matcher.ts +178 -0
- package/src/model/sink-patterns.ts +109 -0
- package/src/model/source-discovery.ts +209 -0
- package/src/model/taint-tracker.ts +333 -0
- package/src/model/taint-types.ts +149 -0
- package/src/{utils → model}/trpc-analyzer.ts +1 -1
- package/src/{utils/context-helpers.ts → parse/file-classifier.ts} +54 -0
- package/src/{utils → parse}/path-exclusions.ts +1 -1
- package/src/pipeline/config.ts +81 -0
- package/src/pipeline/index.ts +437 -0
- package/src/{modes → pipeline/modes}/incremental.ts +5 -5
- package/src/postprocess/aggregation.ts +74 -0
- package/src/postprocess/contradictions.ts +128 -0
- package/src/postprocess/dedup.ts +62 -0
- package/src/{filtering → postprocess/filtering}/__tests__/pipeline.test.ts +1 -1
- package/src/{filtering → postprocess/filtering}/context-adjustments.ts +2 -2
- package/src/{filtering → postprocess/filtering}/pipeline.ts +2 -2
- package/src/postprocess/index.ts +118 -0
- package/src/{suppression → postprocess/suppression}/config-loader.ts +1 -1
- package/src/{suppression → postprocess/suppression}/hash.ts +1 -1
- package/src/{suppression → postprocess/suppression}/inline-parser.ts +1 -1
- package/src/{suppression → postprocess/suppression}/manager.ts +1 -1
- package/src/{suppression → postprocess/suppression}/types.ts +2 -2
- package/src/postprocess/validation-cap.ts +66 -0
- package/src/report/build-result.ts +94 -0
- package/src/report/enrichment.ts +52 -0
- package/src/{formatters → report/formatters}/ai-context.ts +1 -1
- package/src/{formatters → report/formatters}/cli-terminal.ts +11 -11
- package/src/{formatters → report/formatters}/github-comment.ts +1 -1
- package/src/{formatters → report/formatters}/grouping.ts +8 -8
- package/src/{formatters → report/formatters}/ide/claude-code.ts +1 -1
- package/src/{formatters → report/formatters}/ide/cursor.ts +1 -1
- package/src/{formatters → report/formatters}/ide/windsurf.ts +1 -1
- package/src/{formatters → report/formatters}/vscode-diagnostic.ts +1 -1
- package/src/report/summary.ts +70 -0
- package/src/score/adjustments.ts +387 -0
- package/src/{layer3/anthropic → score}/auto-dismiss.ts +15 -14
- package/src/score/confidence.ts +66 -0
- package/src/score/index.ts +316 -0
- package/src/score/types.ts +187 -0
- package/src/{baseline → shared/baseline}/__tests__/diff.test.ts +2 -2
- package/src/{baseline → shared/baseline}/diff.ts +1 -1
- package/src/{baseline → shared/baseline}/manager.ts +1 -1
- package/src/{category-filter.ts → shared/category-filter.ts} +1 -1
- package/src/{utils → shared}/code-analysis.ts +1 -1
- package/src/{rules → shared/rules}/__tests__/metadata.test.ts +7 -0
- package/src/{rules → shared/rules}/framework-fixes.ts +1 -1
- package/src/{rules → shared/rules}/metadata.ts +94 -0
- package/src/{types.ts → shared/types.ts} +22 -5
- package/src/tiers.ts +18 -1
- package/src/validate/__tests__/context-extractor.test.ts +191 -0
- package/src/validate/__tests__/prompt-assembly.test.ts +233 -0
- package/src/validate/__tests__/request-builder.test.ts +347 -0
- package/src/{layer3/anthropic → validate}/index.ts +8 -7
- package/src/{layer3/anthropic → validate}/prompts/index.ts +2 -0
- package/src/validate/prompts/modules/ai-patterns.ts +153 -0
- package/src/validate/prompts/modules/auth-access.ts +22 -0
- package/src/validate/prompts/modules/common.ts +183 -0
- package/src/validate/prompts/modules/index.ts +204 -0
- package/src/validate/prompts/modules/owasp-classic.ts +81 -0
- package/src/validate/prompts/modules/secrets-crypto.ts +65 -0
- package/src/validate/prompts/modules/xss-prompt.ts +19 -0
- package/src/validate/prompts/validation.ts +20 -0
- package/src/{layer3/anthropic → validate}/providers/anthropic.ts +28 -27
- package/src/validate/providers/index.ts +8 -0
- package/src/{layer3/anthropic → validate}/providers/openai.ts +30 -25
- package/src/validate/request-builder.ts +448 -0
- package/src/{layer3/anthropic → validate}/types.ts +1 -1
- package/src/validate/utils/context-extractor.ts +220 -0
- package/src/{layer3/anthropic → validate}/utils/index.ts +10 -0
- package/src/{layer3/anthropic → validate}/utils/response-parser.ts +2 -1
- package/src/layer3/anthropic/prompts/validation.ts +0 -419
- package/src/layer3/anthropic/providers/index.ts +0 -8
- package/src/layer3/anthropic/request-builder.ts +0 -150
- package/src/layer3/index.ts +0 -168
- /package/src/{layer3 → detect/config}/__tests__/osv-check.test.ts +0 -0
- /package/src/{layer2 → detect/structural}/__tests__/math-random-enhanced.test.ts +0 -0
- /package/src/{layer2 → detect/structural}/dangerous-functions/child-process.ts +0 -0
- /package/src/{layer2 → detect/structural}/dangerous-functions/utils/helpers.ts +0 -0
- /package/src/{layer2 → detect/structural}/dangerous-functions/utils/index.ts +0 -0
- /package/src/{layer2 → detect/structural}/dangerous-functions/utils/schema-validation.ts +0 -0
- /package/src/{utils → model}/route-hierarchy.ts +0 -0
- /package/src/{filtering → postprocess/filtering}/index.ts +0 -0
- /package/src/{suppression → postprocess/suppression}/__tests__/config-loader.test.ts +0 -0
- /package/src/{suppression → postprocess/suppression}/__tests__/hash.test.ts +0 -0
- /package/src/{suppression → postprocess/suppression}/__tests__/inline-parser.test.ts +0 -0
- /package/src/{suppression → postprocess/suppression}/__tests__/manager.test.ts +0 -0
- /package/src/{suppression → postprocess/suppression}/index.ts +0 -0
- /package/src/{formatters → report/formatters}/__tests__/ai-context.test.ts +0 -0
- /package/src/{formatters → report/formatters}/ide/__tests__/ide.test.ts +0 -0
- /package/src/{formatters → report/formatters}/ide/index.ts +0 -0
- /package/src/{formatters → report/formatters}/index.ts +0 -0
- /package/src/{utils → shared}/__tests__/code-analysis.test.ts +0 -0
- /package/src/{utils → shared}/__tests__/parsed-file.test.ts +0 -0
- /package/src/{ai-context → shared/ai-context}/__tests__/manager.test.ts +0 -0
- /package/src/{ai-context → shared/ai-context}/index.ts +0 -0
- /package/src/{ai-context → shared/ai-context}/manager.ts +0 -0
- /package/src/{baseline → shared/baseline}/__tests__/manager.test.ts +0 -0
- /package/src/{baseline → shared/baseline}/index.ts +0 -0
- /package/src/{baseline → shared/baseline}/types.ts +0 -0
- /package/src/{utils → shared}/comment-analyzer.ts +0 -0
- /package/src/{utils → shared}/diff-detector.ts +0 -0
- /package/src/{utils → shared}/diff-parser.ts +0 -0
- /package/src/{utils → shared}/environment-context.ts +0 -0
- /package/src/{utils → shared}/intent-detector.ts +0 -0
- /package/src/{utils → shared}/parsed-file.ts +0 -0
- /package/src/{utils → shared}/registry-clients.ts +0 -0
- /package/src/{rules → shared/rules}/__tests__/framework-fixes.test.ts +0 -0
- /package/src/{rules → shared/rules}/index.ts +0 -0
- /package/src/{utils → shared}/schema-semantics.ts +0 -0
- /package/src/{layer3/anthropic → validate}/clients.ts +0 -0
- /package/src/{layer3/anthropic → validate}/prompts/semantic-analysis.ts +0 -0
- /package/src/{layer3/anthropic → validate}/utils/path-helpers.ts +0 -0
- /package/src/{layer3/anthropic → validate}/utils/retry.ts +0 -0
package/src/index.ts
CHANGED
|
@@ -1,1299 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Scanner
|
|
3
|
-
*
|
|
2
|
+
* Scanner Public API
|
|
3
|
+
*
|
|
4
|
+
* Re-exports from stage modules. All prior exports preserved.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import type {
|
|
7
|
-
ScanFile,
|
|
8
|
-
ScanResult,
|
|
9
|
-
Vulnerability,
|
|
10
|
-
SeverityCounts,
|
|
11
|
-
CategoryCounts,
|
|
12
|
-
VulnerabilityCategory,
|
|
13
|
-
ScanMode,
|
|
14
|
-
ScanModeConfig,
|
|
15
|
-
ScanDepth,
|
|
16
|
-
CancellationToken,
|
|
17
|
-
SuppressedVulnerabilitySummary,
|
|
18
|
-
DetectorContext,
|
|
19
|
-
} from './types'
|
|
20
|
-
import { createEmptyDetectorContext } from './types'
|
|
21
|
-
import { SCANNABLE_EXTENSIONS, SPECIAL_FILES, MAX_FILE_SIZE, SCAN_MODE_DEFAULTS } from './types'
|
|
22
|
-
import { runLayer1Scan } from './layer1'
|
|
23
|
-
import { runLayer2Scan } from './layer2'
|
|
24
|
-
import { runLayer3Scan } from './layer3'
|
|
25
|
-
import { validateFindingsWithAI, applyAutoDismissRules, type ValidationStats } from './layer3/anthropic'
|
|
26
|
-
import { aggregateLocalhostFindings } from './layer1/urls'
|
|
27
|
-
import { detectGlobalAuthMiddleware, type MiddlewareAuthConfig, getRoutePathFromFile, isRouteProtectedByMiddleware } from './utils/middleware-detector'
|
|
28
|
-
import { detectAuthHelpers } from './utils/auth-helper-detector'
|
|
29
|
-
import { buildFileAuthImports } from './utils/imported-auth-detector'
|
|
30
|
-
// Tier system imports for filtering by scan depth
|
|
31
|
-
import {
|
|
32
|
-
type DetectorTier,
|
|
33
|
-
type TierStats,
|
|
34
|
-
getTierForCategory,
|
|
35
|
-
isTierVisibleAtDepth,
|
|
36
|
-
shouldValidateWithAI,
|
|
37
|
-
computeTierStats,
|
|
38
|
-
formatTierStats,
|
|
39
|
-
} from './tiers'
|
|
40
|
-
// Suppression system
|
|
41
|
-
import { SuppressionManager } from './suppression'
|
|
42
|
-
// Rule metadata for actionable output (PRO-82)
|
|
43
|
-
import { getRuleMetadata } from './rules'
|
|
44
|
-
// Framework-aware fix suggestions (PRO-83)
|
|
45
|
-
import { getFrameworkFix } from './rules/framework-fixes'
|
|
46
|
-
// Project context for framework detection
|
|
47
|
-
import { buildProjectContext, type ProjectContext } from './utils/project-context-builder'
|
|
48
|
-
// File context helpers
|
|
49
|
-
import {
|
|
50
|
-
isServerOnlyFile,
|
|
51
|
-
isClientBundledFile,
|
|
52
|
-
isTestOrMockFile,
|
|
53
|
-
isToolingDirectory,
|
|
54
|
-
isLocaleFile,
|
|
55
|
-
} from './utils/context-helpers'
|
|
56
|
-
// Shared ranking utilities
|
|
57
|
-
import { severityRank, confidenceRank } from './utils/parsed-file'
|
|
58
|
-
// Centralized context-based filtering
|
|
59
|
-
import { applyContextAdjustments } from './filtering/context-adjustments'
|
|
60
|
-
import { FilterPipeline } from './filtering/pipeline'
|
|
61
|
-
|
|
62
|
-
// Maximum candidates per file to send to AI validation (cost control)
|
|
63
|
-
const MAX_VALIDATION_CANDIDATES_PER_FILE = 10
|
|
64
|
-
|
|
65
7
|
// ============================================================================
|
|
66
|
-
//
|
|
8
|
+
// Core Scanner API
|
|
67
9
|
// ============================================================================
|
|
68
10
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* This provides rich context to Layer 2 detectors for smarter decisions
|
|
72
|
-
*/
|
|
73
|
-
function buildDetectorContext(
|
|
74
|
-
projectContext: ProjectContext,
|
|
75
|
-
middlewareConfig: MiddlewareAuthConfig | undefined
|
|
76
|
-
): DetectorContext {
|
|
77
|
-
// Map ProjectContext to DetectorContext format
|
|
78
|
-
const context = createEmptyDetectorContext()
|
|
79
|
-
|
|
80
|
-
// Auth context
|
|
81
|
-
if (middlewareConfig?.hasAuthMiddleware) {
|
|
82
|
-
context.auth.middleware = {
|
|
83
|
-
hasAuthMiddleware: true,
|
|
84
|
-
authType: middlewareConfig.authType ?? null,
|
|
85
|
-
protectedPaths: middlewareConfig.protectedPaths,
|
|
86
|
-
publicPaths: middlewareConfig.publicPaths,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Auth helpers from project context
|
|
91
|
-
if (projectContext.auth.throwingAuthHelpers.length > 0) {
|
|
92
|
-
context.auth.authHelpers = {
|
|
93
|
-
hasThrowingHelpers: true,
|
|
94
|
-
throwingHelperNames: projectContext.auth.throwingAuthHelpers.map(h => h.name),
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Public endpoints
|
|
99
|
-
context.auth.publicEndpoints = projectContext.auth.publicPaths
|
|
100
|
-
|
|
101
|
-
// Framework context
|
|
102
|
-
context.framework = {
|
|
103
|
-
primary: projectContext.frameworks.primary ?? null,
|
|
104
|
-
frontend: projectContext.frameworks.frontend ?? null,
|
|
105
|
-
hasTypeScript: projectContext.frameworks.usesTypeScript,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Data access context
|
|
109
|
-
context.dataAccess = {
|
|
110
|
-
orm: mapOrmToDataAccessOrm(projectContext.dataAccess.orm),
|
|
111
|
-
hasRLS: projectContext.dataAccess.hasRLS,
|
|
112
|
-
validationLib: mapValidationLib(projectContext.dataAccess.validationLibrary),
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Note: File context is populated per-file in buildFileDetectorContext
|
|
116
|
-
|
|
117
|
-
return context
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Build file-specific context for a single file
|
|
122
|
-
* This is merged with the project-level DetectorContext
|
|
123
|
-
*/
|
|
124
|
-
function buildFileDetectorContext(
|
|
125
|
-
baseContext: DetectorContext,
|
|
126
|
-
filePath: string
|
|
127
|
-
): DetectorContext {
|
|
128
|
-
// Clone the base context and add file-specific info
|
|
129
|
-
return {
|
|
130
|
-
...baseContext,
|
|
131
|
-
file: {
|
|
132
|
-
isServerOnly: isServerOnlyFile(filePath),
|
|
133
|
-
isClientBundled: isClientBundledFile(filePath),
|
|
134
|
-
isTestFile: isTestOrMockFile(filePath),
|
|
135
|
-
isConfigFile: isConfigFile(filePath),
|
|
136
|
-
isToolingDir: isToolingDirectory(filePath),
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if file is a configuration file
|
|
143
|
-
*/
|
|
144
|
-
function isConfigFile(filePath: string): boolean {
|
|
145
|
-
const configPatterns = [
|
|
146
|
-
/config\.(ts|js|json|yaml|yml)$/i,
|
|
147
|
-
/settings\.(ts|js|json)$/i,
|
|
148
|
-
/constants\.(ts|js)$/i,
|
|
149
|
-
/\.config\.(ts|js|mjs|cjs)$/i,
|
|
150
|
-
/\.env/i,
|
|
151
|
-
/tsconfig\.json$/i,
|
|
152
|
-
/package\.json$/i,
|
|
153
|
-
/jest\.config/i,
|
|
154
|
-
/vitest\.config/i,
|
|
155
|
-
/eslint/i,
|
|
156
|
-
/prettier/i,
|
|
157
|
-
]
|
|
158
|
-
return configPatterns.some(p => p.test(filePath))
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Map ProjectContext ORM to DetectorContext ORM
|
|
163
|
-
*/
|
|
164
|
-
function mapOrmToDataAccessOrm(
|
|
165
|
-
orm: ProjectContext['dataAccess']['orm']
|
|
166
|
-
): DetectorContext['dataAccess']['orm'] {
|
|
167
|
-
if (!orm) return null
|
|
168
|
-
switch (orm) {
|
|
169
|
-
case 'prisma':
|
|
170
|
-
case 'drizzle':
|
|
171
|
-
case 'typeorm':
|
|
172
|
-
case 'sequelize':
|
|
173
|
-
case 'knex':
|
|
174
|
-
return orm
|
|
175
|
-
default:
|
|
176
|
-
return null
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Map validation library to DetectorContext format
|
|
182
|
-
*/
|
|
183
|
-
function mapValidationLib(
|
|
184
|
-
lib: string | undefined
|
|
185
|
-
): DetectorContext['dataAccess']['validationLib'] {
|
|
186
|
-
if (!lib) return null
|
|
187
|
-
switch (lib) {
|
|
188
|
-
case 'zod':
|
|
189
|
-
case 'yup':
|
|
190
|
-
case 'joi':
|
|
191
|
-
case 'valibot':
|
|
192
|
-
return lib
|
|
193
|
-
default:
|
|
194
|
-
return null
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Classify vulnerabilities into tier groups for filtering
|
|
200
|
-
*/
|
|
201
|
-
interface TierClassification {
|
|
202
|
-
/** Tier A (core): High-precision, always visible */
|
|
203
|
-
core: Vulnerability[]
|
|
204
|
-
/** Tier B (ai_assisted): Context-heavy, needs AI validation */
|
|
205
|
-
ai_assisted: Vulnerability[]
|
|
206
|
-
/** Tier C (experimental): Hidden from users, internal use only */
|
|
207
|
-
experimental: Vulnerability[]
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Classify vulnerabilities by their detector tier
|
|
212
|
-
*/
|
|
213
|
-
function classifyByTier(vulnerabilities: Vulnerability[]): TierClassification {
|
|
214
|
-
const result: TierClassification = {
|
|
215
|
-
core: [],
|
|
216
|
-
ai_assisted: [],
|
|
217
|
-
experimental: [],
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
for (const vuln of vulnerabilities) {
|
|
221
|
-
const tier = getTierForCategory(vuln.category, vuln.layer)
|
|
222
|
-
result[tier].push(vuln)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return result
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Filter vulnerabilities based on scan depth and tier
|
|
230
|
-
*
|
|
231
|
-
* - cheap: Only Tier A (core) findings are surfaced
|
|
232
|
-
* - validated: Tier A visible, Tier B goes through AI validation
|
|
233
|
-
* - deep: Same as validated, plus Layer 3 semantic analysis
|
|
234
|
-
*
|
|
235
|
-
* Tier C (experimental) is NEVER surfaced to users, regardless of depth.
|
|
236
|
-
*/
|
|
237
|
-
function filterByTierAndDepth(
|
|
238
|
-
vulnerabilities: Vulnerability[],
|
|
239
|
-
depth: ScanDepth
|
|
240
|
-
): {
|
|
241
|
-
/** Findings to surface directly (no AI validation needed) */
|
|
242
|
-
toSurface: Vulnerability[]
|
|
243
|
-
/** Findings to send through AI validation */
|
|
244
|
-
toValidate: Vulnerability[]
|
|
245
|
-
/** Findings hidden from users (Tier C) */
|
|
246
|
-
hidden: Vulnerability[]
|
|
247
|
-
/** Tier stats for logging */
|
|
248
|
-
tierStats: TierStats
|
|
249
|
-
} {
|
|
250
|
-
const classified = classifyByTier(vulnerabilities)
|
|
251
|
-
const tierStats = computeTierStats(
|
|
252
|
-
vulnerabilities.map(v => ({ category: v.category, layer: v.layer }))
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
switch (depth) {
|
|
256
|
-
case 'local':
|
|
257
|
-
// Only Tier A is visible, no AI validation
|
|
258
|
-
return {
|
|
259
|
-
toSurface: classified.core,
|
|
260
|
-
toValidate: [],
|
|
261
|
-
hidden: [...classified.ai_assisted, ...classified.experimental],
|
|
262
|
-
tierStats,
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
case 'verified':
|
|
266
|
-
case 'deep':
|
|
267
|
-
// Tier A always surfaces, Tier B goes to AI validation
|
|
268
|
-
// For findings that already require AI validation, only include Tier A/B
|
|
269
|
-
const coreRequiringValidation = classified.core.filter(v =>
|
|
270
|
-
v.requiresAIValidation ||
|
|
271
|
-
v.category === 'high_entropy_string' ||
|
|
272
|
-
v.category === 'hardcoded_secret' ||
|
|
273
|
-
(v.category === 'sensitive_url' && v.severity !== 'info')
|
|
274
|
-
)
|
|
275
|
-
const coreNotRequiringValidation = classified.core.filter(v =>
|
|
276
|
-
!coreRequiringValidation.includes(v)
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
return {
|
|
280
|
-
toSurface: coreNotRequiringValidation,
|
|
281
|
-
toValidate: [...coreRequiringValidation, ...classified.ai_assisted],
|
|
282
|
-
hidden: classified.experimental,
|
|
283
|
-
tierStats,
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
default:
|
|
287
|
-
// Fallback to local behavior for unknown depths
|
|
288
|
-
console.warn(`[Scanner] Unknown scan depth "${depth}", falling back to "local"`)
|
|
289
|
-
return {
|
|
290
|
-
toSurface: classified.core,
|
|
291
|
-
toValidate: [],
|
|
292
|
-
hidden: [...classified.ai_assisted, ...classified.experimental],
|
|
293
|
-
tierStats,
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export interface ScanOptions {
|
|
299
|
-
/** Enable AI-powered validation and analysis */
|
|
300
|
-
enableAI?: boolean
|
|
301
|
-
/** Maximum files to scan */
|
|
302
|
-
maxFiles?: number
|
|
303
|
-
/** Branch being scanned */
|
|
304
|
-
branch?: string
|
|
305
|
-
/** Scan mode configuration (full vs incremental) */
|
|
306
|
-
scanMode?: ScanMode | ScanModeConfig
|
|
307
|
-
/** Scan depth (cheap/validated/deep) - controls AI usage */
|
|
308
|
-
scanDepth?: ScanDepth
|
|
309
|
-
/** Suppress console.log output (for interactive CLI mode) */
|
|
310
|
-
quiet?: boolean
|
|
311
|
-
/** Cancellation token for aborting scans gracefully */
|
|
312
|
-
cancellationToken?: CancellationToken
|
|
313
|
-
/** Project path for loading suppression config (defaults to cwd) */
|
|
314
|
-
projectPath?: string
|
|
315
|
-
/** Include suppressed findings in output (for --show-suppressed) */
|
|
316
|
-
showSuppressed?: boolean
|
|
317
|
-
/** Include filter audit trail in output (for debugging/explaining dismissals) */
|
|
318
|
-
includeFilterAudit?: boolean
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export interface ScanProgress {
|
|
322
|
-
status: 'fetching' | 'layer1' | 'layer2' | 'layer3' | 'validating' | 'complete' | 'failed'
|
|
323
|
-
message: string
|
|
324
|
-
filesProcessed: number
|
|
325
|
-
totalFiles: number
|
|
326
|
-
vulnerabilitiesFound: number
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
export type ProgressCallback = (progress: ScanProgress) => void
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Resolve scan mode configuration from options
|
|
333
|
-
*
|
|
334
|
-
* Handles both scan mode (full/incremental) and depth (local/verified/deep).
|
|
335
|
-
* Depth controls AI usage:
|
|
336
|
-
* - local: skip AI validation + skip Layer 3
|
|
337
|
-
* - verified: run AI validation, skip Layer 3
|
|
338
|
-
* - deep: run AI validation + Layer 3
|
|
339
|
-
*/
|
|
340
|
-
function resolveScanModeConfig(options: ScanOptions): ScanModeConfig {
|
|
341
|
-
const scanModeOption = options.scanMode
|
|
342
|
-
|
|
343
|
-
// Determine base mode
|
|
344
|
-
const mode: ScanMode = !scanModeOption ? 'full'
|
|
345
|
-
: typeof scanModeOption === 'string' ? scanModeOption
|
|
346
|
-
: scanModeOption.mode
|
|
347
|
-
|
|
348
|
-
const defaults = SCAN_MODE_DEFAULTS[mode]
|
|
349
|
-
|
|
350
|
-
// Build config from defaults + explicit overrides
|
|
351
|
-
let config: ScanModeConfig = {
|
|
352
|
-
...defaults,
|
|
353
|
-
mode,
|
|
354
|
-
...(typeof scanModeOption === 'object' ? scanModeOption : {}),
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Apply depth mode (default: 'local')
|
|
358
|
-
const depth = options.scanDepth ?? 'local'
|
|
359
|
-
config.scanDepth = depth
|
|
360
|
-
|
|
361
|
-
// Map depth to skipAIValidation/skipLayer3 unless explicitly overridden
|
|
362
|
-
const hasExplicitSkipAI = typeof scanModeOption === 'object' && scanModeOption.skipAIValidation !== undefined
|
|
363
|
-
const hasExplicitSkipL3 = typeof scanModeOption === 'object' && scanModeOption.skipLayer3 !== undefined
|
|
364
|
-
|
|
365
|
-
if (!hasExplicitSkipAI) {
|
|
366
|
-
config.skipAIValidation = depth === 'local'
|
|
367
|
-
}
|
|
368
|
-
if (!hasExplicitSkipL3) {
|
|
369
|
-
config.skipLayer3 = depth !== 'deep'
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return config
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Run a complete security scan on the provided files
|
|
377
|
-
*
|
|
378
|
-
* Supports two scan modes:
|
|
379
|
-
* - full: Complete scan with AI validation on all files (initial onboarding, deep audits)
|
|
380
|
-
* - incremental: Focused scan on changed files only (CI/CD, fast feedback)
|
|
381
|
-
*/
|
|
382
|
-
export async function runScan(
|
|
383
|
-
files: ScanFile[],
|
|
384
|
-
repoInfo: { name: string; url: string; branch: string },
|
|
385
|
-
options: ScanOptions = {},
|
|
386
|
-
onProgress?: ProgressCallback
|
|
387
|
-
): Promise<ScanResult> {
|
|
388
|
-
const startTime = Date.now()
|
|
389
|
-
const allVulnerabilities: Vulnerability[] = []
|
|
390
|
-
|
|
391
|
-
// Filter audit pipeline (zero overhead when disabled)
|
|
392
|
-
const filterPipeline = new FilterPipeline(options.includeFilterAudit ?? false)
|
|
393
|
-
const fid = (v: Pick<Vulnerability, 'filePath' | 'lineNumber' | 'category'>) => `${v.filePath}:${v.lineNumber}:${v.category}`
|
|
394
|
-
|
|
395
|
-
// Resolve scan mode configuration
|
|
396
|
-
const scanModeConfig = resolveScanModeConfig(options)
|
|
397
|
-
const isIncremental = scanModeConfig.mode === 'incremental'
|
|
398
|
-
const depth = scanModeConfig.scanDepth || 'local'
|
|
399
|
-
const quiet = options.quiet ?? false
|
|
400
|
-
const cancellationToken = options.cancellationToken
|
|
401
|
-
|
|
402
|
-
// Conditional logging helper - suppresses output in quiet mode (interactive CLI)
|
|
403
|
-
const log = (message: string) => {
|
|
404
|
-
if (!quiet) {
|
|
405
|
-
console.log(message)
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Helper to check cancellation and throw if cancelled
|
|
410
|
-
const checkCancelled = () => {
|
|
411
|
-
if (cancellationToken?.cancelled) {
|
|
412
|
-
throw new Error(`Scan cancelled: ${cancellationToken.reason || 'user requested'}`)
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
log(`[Scanner] repo=${repoInfo.name} mode=${scanModeConfig.mode} depth=${depth} files=${files.length}`)
|
|
417
|
-
if (isIncremental && scanModeConfig.changedFiles) {
|
|
418
|
-
log(`[Scanner] repo=${repoInfo.name} incremental_files=${scanModeConfig.changedFiles.length}`)
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Filter out locale/i18n files - they contain translations, not code
|
|
422
|
-
// This dramatically reduces false positives and AI validation costs
|
|
423
|
-
const localeFileCount = files.filter(f => isLocaleFile(f.path)).length
|
|
424
|
-
if (localeFileCount > 0) {
|
|
425
|
-
files = files.filter(f => !isLocaleFile(f.path))
|
|
426
|
-
log(`[Scanner] repo=${repoInfo.name} skipped_locale_files=${localeFileCount} remaining_files=${files.length}`)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Report progress helper
|
|
430
|
-
const reportProgress = (
|
|
431
|
-
status: ScanProgress['status'],
|
|
432
|
-
message: string,
|
|
433
|
-
vulnerabilitiesFound: number = allVulnerabilities.length
|
|
434
|
-
) => {
|
|
435
|
-
if (onProgress) {
|
|
436
|
-
onProgress({
|
|
437
|
-
status,
|
|
438
|
-
message,
|
|
439
|
-
filesProcessed: files.length,
|
|
440
|
-
totalFiles: files.length,
|
|
441
|
-
vulnerabilitiesFound,
|
|
442
|
-
})
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// For incremental scans, filter to only changed files for AI-heavy operations
|
|
447
|
-
// but still run static analysis on all files for context
|
|
448
|
-
const filesForAI = isIncremental && scanModeConfig.changedFiles
|
|
449
|
-
? files.filter(f => scanModeConfig.changedFiles!.some(cf => f.path.endsWith(cf) || f.path.includes(cf)))
|
|
450
|
-
: files
|
|
451
|
-
|
|
452
|
-
// Declare variables that need to be accessible in catch block
|
|
453
|
-
let middlewareConfig: MiddlewareAuthConfig | undefined
|
|
454
|
-
let capturedValidationStats: ValidationStats | undefined
|
|
455
|
-
|
|
456
|
-
try {
|
|
457
|
-
checkCancelled()
|
|
458
|
-
|
|
459
|
-
// Detect global auth middleware before scanning (always on all files for context)
|
|
460
|
-
middlewareConfig = detectGlobalAuthMiddleware(files)
|
|
461
|
-
if (middlewareConfig.hasAuthMiddleware) {
|
|
462
|
-
log(`[Scanner] repo=${repoInfo.name} auth_middleware=${middlewareConfig.authType || 'unknown'} file=${middlewareConfig.middlewareFile}`)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Build imported auth registry for cross-file middleware detection
|
|
466
|
-
const fileAuthImports = buildFileAuthImports(files)
|
|
467
|
-
const filesWithImportedAuth = Array.from(fileAuthImports.values()).filter(f => f.usesImportedAuth).length
|
|
468
|
-
if (filesWithImportedAuth > 0) {
|
|
469
|
-
log(`[Scanner] repo=${repoInfo.name} files_with_imported_auth=${filesWithImportedAuth}`)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
checkCancelled()
|
|
473
|
-
|
|
474
|
-
// Phase timing tracking
|
|
475
|
-
const phaseTiming: { layer1?: number; layer2?: number; aiValidation?: number; layer3?: number } = {}
|
|
476
|
-
|
|
477
|
-
// Layer 1: Surface Scan
|
|
478
|
-
const layer1Start = Date.now()
|
|
479
|
-
reportProgress('layer1', 'Running surface scan (patterns, entropy, config)...')
|
|
480
|
-
let layer1Result = await runLayer1Scan(files, onProgress, cancellationToken)
|
|
481
|
-
|
|
482
|
-
// Aggregate repeated localhost findings
|
|
483
|
-
const layer1RawCount = layer1Result.vulnerabilities.length
|
|
484
|
-
const layer1BeforeAggregation = layer1Result.vulnerabilities
|
|
485
|
-
layer1Result = {
|
|
486
|
-
...layer1Result,
|
|
487
|
-
vulnerabilities: aggregateLocalhostFindings(layer1Result.vulnerabilities)
|
|
488
|
-
}
|
|
489
|
-
if (filterPipeline.isEnabled) {
|
|
490
|
-
const afterIds = new Set(layer1Result.vulnerabilities.map(fid))
|
|
491
|
-
for (const v of layer1BeforeAggregation) {
|
|
492
|
-
if (!afterIds.has(fid(v))) {
|
|
493
|
-
filterPipeline.record(fid(v), { stage: 'localhost_aggregation', action: 'aggregated', reason: 'Aggregated repeated localhost finding' })
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
phaseTiming.layer1 = Date.now() - layer1Start
|
|
498
|
-
log(`[Layer1] repo=${repoInfo.name} findings_raw=${layer1RawCount} findings_deduped=${layer1Result.vulnerabilities.length} duration=${phaseTiming.layer1}ms`)
|
|
499
|
-
|
|
500
|
-
checkCancelled()
|
|
501
|
-
|
|
502
|
-
// Build project context early - used by Layer 2 DetectorContext and framework-aware fixes
|
|
503
|
-
// This detects frameworks (Next.js, Express), ORMs (Prisma, Drizzle), and frontend libs (React, Vue)
|
|
504
|
-
const projectContext = buildProjectContext(files)
|
|
505
|
-
|
|
506
|
-
// Build DetectorContext for context-aware Layer 2 detection (Phase 2b)
|
|
507
|
-
const detectorContext = buildDetectorContext(projectContext, middlewareConfig)
|
|
508
|
-
|
|
509
|
-
// Layer 2: Structural Scan
|
|
510
|
-
const layer2Start = Date.now()
|
|
511
|
-
reportProgress('layer2', 'Running structural scan (variables, logic gates)...', layer1Result.vulnerabilities.length)
|
|
512
|
-
const layer2Result = await runLayer2Scan(
|
|
513
|
-
files,
|
|
514
|
-
{ middlewareConfig, fileAuthImports, detectorContext },
|
|
515
|
-
onProgress,
|
|
516
|
-
cancellationToken
|
|
517
|
-
)
|
|
518
|
-
|
|
519
|
-
// Format heuristic breakdown for logging
|
|
520
|
-
const heuristicBreakdown = Object.entries(layer2Result.stats.raw)
|
|
521
|
-
.filter(([, count]) => count > 0)
|
|
522
|
-
.map(([name, count]) => `${name}:${count}`)
|
|
523
|
-
.join(',')
|
|
524
|
-
phaseTiming.layer2 = Date.now() - layer2Start
|
|
525
|
-
log(`[Layer2] repo=${repoInfo.name} findings_raw=${Object.values(layer2Result.stats.raw).reduce((a, b) => a + b, 0)} findings_deduped=${layer2Result.vulnerabilities.length} duration=${phaseTiming.layer2}ms heuristic_breakdown={${heuristicBreakdown}}`)
|
|
526
|
-
|
|
527
|
-
// Combine Layer 1 and Layer 2 findings
|
|
528
|
-
const layer12Findings = [...layer1Result.vulnerabilities, ...layer2Result.vulnerabilities]
|
|
529
|
-
|
|
530
|
-
// Aggregate noisy findings BEFORE tier filtering to reduce noise
|
|
531
|
-
const beforeAggregationCount = layer12Findings.length
|
|
532
|
-
const aggregatedFindings = aggregateNoisyFindings(layer12Findings)
|
|
533
|
-
const aggregatedCount = beforeAggregationCount - aggregatedFindings.length
|
|
534
|
-
if (filterPipeline.isEnabled) {
|
|
535
|
-
const afterIds = new Set(aggregatedFindings.map(fid))
|
|
536
|
-
for (const v of layer12Findings) {
|
|
537
|
-
if (!afterIds.has(fid(v))) {
|
|
538
|
-
filterPipeline.record(fid(v), { stage: 'noisy_aggregation', action: 'aggregated', reason: 'Aggregated noisy finding (3+ similar per file)' })
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Enrich findings with metadata from rule registry (PRO-82)
|
|
544
|
-
// PRO-83: Uses projectContext for framework-specific fix suggestions
|
|
545
|
-
// This provides default impact, evidence, fixSteps, references for all findings
|
|
546
|
-
// AI validation can override these later with context-aware content
|
|
547
|
-
const enrichedFindings = enrichWithMetadata(aggregatedFindings, projectContext)
|
|
548
|
-
|
|
549
|
-
// Apply tier-based filtering based on scan depth
|
|
550
|
-
// This is the key integration point for the detector tier system
|
|
551
|
-
const tierFiltered = filterByTierAndDepth(enrichedFindings, depth)
|
|
552
|
-
|
|
553
|
-
if (filterPipeline.isEnabled) {
|
|
554
|
-
for (const v of tierFiltered.hidden) {
|
|
555
|
-
filterPipeline.record(fid(v), { stage: 'tier_filter', action: 'dismissed', reason: `Hidden by tier filter at depth=${depth}` })
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
// Log tier breakdown
|
|
559
|
-
log(`[Scanner] repo=${repoInfo.name} tier_breakdown=${formatTierStats(tierFiltered.tierStats)}`)
|
|
560
|
-
log(`[Scanner] repo=${repoInfo.name} depth=${depth} tier_routing: surface=${tierFiltered.toSurface.length} validate=${tierFiltered.toValidate.length} hidden=${tierFiltered.hidden.length}`)
|
|
561
|
-
|
|
562
|
-
// For cheap scans: Tier A surfaces directly, Tier B/C are hidden
|
|
563
|
-
// For validated/deep: Tier A surfaces, Tier B goes through AI validation, Tier C hidden
|
|
564
|
-
|
|
565
|
-
// Some Tier A findings still need AI validation (entropy, secrets, AI-specific)
|
|
566
|
-
const additionalValidation = tierFiltered.toSurface.filter(v =>
|
|
567
|
-
v.requiresAIValidation ||
|
|
568
|
-
v.category === 'ai_prompt_injection' || // Story B1: Prompt hygiene
|
|
569
|
-
v.category === 'ai_unsafe_execution' || // Story B2: LLM output execution
|
|
570
|
-
v.category === 'ai_overpermissive_tool' // Story B4: Agent tool permissions
|
|
571
|
-
)
|
|
572
|
-
|
|
573
|
-
// Surface findings that don't need validation (excluding those that do)
|
|
574
|
-
const noValidationNeededRaw = tierFiltered.toSurface.filter(v => !additionalValidation.includes(v))
|
|
575
|
-
|
|
576
|
-
// Apply auto-dismiss rules to direct-surface findings (mode='surface')
|
|
577
|
-
// Uses 'surface' mode to exclude cost-saving rules like 'info_severity_core_only'
|
|
578
|
-
// This ensures test/scanner/example files are dismissed, but info-severity findings still surface
|
|
579
|
-
const { toValidate: noValidationNeeded, dismissed: surfaceDismissed } = applyAutoDismissRules(noValidationNeededRaw, 'surface')
|
|
580
|
-
|
|
581
|
-
// Combine tier-filtered validation candidates with additional ones
|
|
582
|
-
const requiresValidation = [...tierFiltered.toValidate, ...additionalValidation]
|
|
11
|
+
// Pipeline orchestrator — main entry point
|
|
12
|
+
export { runScan, type ScanOptions } from './pipeline'
|
|
583
13
|
|
|
584
|
-
|
|
585
|
-
|
|
14
|
+
// Summary utilities (public API for backfilling legacy scans etc.)
|
|
15
|
+
export { computeIssueMixFromVulnerabilities } from './report/summary'
|
|
586
16
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
filterPipeline.record(fid(d.finding), { stage: 'auto_dismiss', action: 'dismissed', reason: d.reason, ruleName: d.rule })
|
|
591
|
-
}
|
|
592
|
-
for (const d of autoDismissed) {
|
|
593
|
-
filterPipeline.record(fid(d.finding), { stage: 'auto_dismiss', action: 'dismissed', reason: d.reason, ruleName: d.rule })
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Combine all dismissed findings for logging
|
|
598
|
-
const allDismissed = [...surfaceDismissed, ...autoDismissed]
|
|
599
|
-
|
|
600
|
-
// Track auto-dismiss by severity and category for logging
|
|
601
|
-
const autoDismissBySeverity: Record<string, number> = { info: 0, low: 0, medium: 0, high: 0, critical: 0 }
|
|
602
|
-
const autoDismissByCategory: Record<string, number> = {}
|
|
603
|
-
for (const d of allDismissed) {
|
|
604
|
-
autoDismissBySeverity[d.finding.severity] = (autoDismissBySeverity[d.finding.severity] || 0) + 1
|
|
605
|
-
autoDismissByCategory[d.finding.category] = (autoDismissByCategory[d.finding.category] || 0) + 1
|
|
606
|
-
}
|
|
607
|
-
if (allDismissed.length > 0) {
|
|
608
|
-
const categoryBreakdown = Object.entries(autoDismissByCategory)
|
|
609
|
-
.sort(([, a], [, b]) => b - a)
|
|
610
|
-
.map(([cat, count]) => `${cat}:${count}`)
|
|
611
|
-
.join(',')
|
|
612
|
-
log(`[AutoDismiss] repo=${repoInfo.name} total=${allDismissed.length} (surface=${surfaceDismissed.length} validation=${autoDismissed.length}) by_severity={info:${autoDismissBySeverity.info},low:${autoDismissBySeverity.low},medium:${autoDismissBySeverity.medium},high:${autoDismissBySeverity.high}} by_category={${categoryBreakdown}}`)
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Apply per-file cap to validation candidates (cost control)
|
|
616
|
-
// Use scan mode config for max files
|
|
617
|
-
const maxValidationFiles = scanModeConfig.maxAIValidationFiles || MAX_VALIDATION_CANDIDATES_PER_FILE
|
|
618
|
-
const cappedValidation = capValidationCandidatesPerFile(afterAutoDismiss, maxValidationFiles)
|
|
619
|
-
|
|
620
|
-
checkCancelled()
|
|
621
|
-
|
|
622
|
-
// AI Validation of selected findings (if AI is enabled and not skipped by scan mode)
|
|
623
|
-
let validatedFindings = cappedValidation
|
|
624
|
-
let capturedValidationStats: ValidationStats | undefined = undefined
|
|
625
|
-
const shouldValidate = options.enableAI !== false && !scanModeConfig.skipAIValidation && cappedValidation.length > 0
|
|
626
|
-
|
|
627
|
-
if (shouldValidate) {
|
|
628
|
-
checkCancelled()
|
|
629
|
-
const aiValidationStart = Date.now()
|
|
630
|
-
reportProgress('validating', 'AI validating findings (entropy, secrets, AI patterns)...', cappedValidation.length)
|
|
631
|
-
|
|
632
|
-
// For incremental scans, only validate findings in changed files
|
|
633
|
-
const findingsToValidate = isIncremental && scanModeConfig.changedFiles
|
|
634
|
-
? cappedValidation.filter(v => scanModeConfig.changedFiles!.some(cf => v.filePath.endsWith(cf) || v.filePath.includes(cf)))
|
|
635
|
-
: cappedValidation
|
|
636
|
-
|
|
637
|
-
if (findingsToValidate.length > 0) {
|
|
638
|
-
const validationResult = await validateFindingsWithAI(
|
|
639
|
-
findingsToValidate,
|
|
640
|
-
filesForAI,
|
|
641
|
-
undefined, // projectContext (uses default)
|
|
642
|
-
onProgress ? (progress) => {
|
|
643
|
-
// Convert AI validation progress to ScanProgress format
|
|
644
|
-
onProgress({
|
|
645
|
-
status: 'validating',
|
|
646
|
-
message: progress.status,
|
|
647
|
-
filesProcessed: progress.filesProcessed,
|
|
648
|
-
totalFiles: progress.totalFiles,
|
|
649
|
-
vulnerabilitiesFound: allVulnerabilities.length,
|
|
650
|
-
})
|
|
651
|
-
} : undefined
|
|
652
|
-
)
|
|
653
|
-
validatedFindings = validationResult.vulnerabilities
|
|
654
|
-
const { stats: validationStats } = validationResult
|
|
655
|
-
capturedValidationStats = validationStats // Capture for return
|
|
656
|
-
phaseTiming.aiValidation = Date.now() - aiValidationStart
|
|
657
|
-
|
|
658
|
-
if (filterPipeline.isEnabled) {
|
|
659
|
-
const validatedIds = new Set(validatedFindings.map(fid))
|
|
660
|
-
for (const v of findingsToValidate) {
|
|
661
|
-
if (!validatedIds.has(fid(v))) {
|
|
662
|
-
filterPipeline.record(fid(v), { stage: 'ai_validation', action: 'dismissed', reason: 'Dismissed by AI validation' })
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
for (const v of validatedFindings) {
|
|
666
|
-
if (v.validationNotes?.toLowerCase().includes('downgrad')) {
|
|
667
|
-
filterPipeline.record(fid(v), { stage: 'ai_validation', action: 'downgraded', reason: v.validationNotes || 'Downgraded by AI validation', originalSeverity: undefined, newSeverity: v.severity })
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} duration=${phaseTiming.aiValidation}ms candidates=${findingsToValidate.length} capped_from=${requiresValidation.length} auto_dismissed=${autoDismissed.length} kept=${validationStats.confirmedFindings} rejected=${validationStats.dismissedFindings} downgraded=${validationStats.downgradedFindings}`)
|
|
672
|
-
log(`[AI Validation] cost_estimate: input_tokens=${validationStats.estimatedInputTokens} output_tokens=${validationStats.estimatedOutputTokens} cost=$${validationStats.estimatedCost.toFixed(4)} api_calls=${validationStats.apiCalls}`)
|
|
673
|
-
|
|
674
|
-
// Add back findings that weren't validated (not in changed files)
|
|
675
|
-
// Mark them as skipped rather than failed validation
|
|
676
|
-
const notValidated = cappedValidation.filter(v => !findingsToValidate.includes(v)).map(v => ({
|
|
677
|
-
...v,
|
|
678
|
-
validatedByAI: false,
|
|
679
|
-
validationStatus: 'not_validated' as const,
|
|
680
|
-
validationNotes: 'Skipped validation (not in changed files for incremental scan)',
|
|
681
|
-
}))
|
|
682
|
-
validatedFindings.push(...notValidated)
|
|
683
|
-
}
|
|
684
|
-
} else if (scanModeConfig.skipAIValidation) {
|
|
685
|
-
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config findings_requiring_validation=${cappedValidation.length}`)
|
|
686
|
-
// In cheap mode, don't surface findings that require AI validation
|
|
687
|
-
// These are low-confidence without validation and would be noise
|
|
688
|
-
// Only surface high-confidence findings that don't need validation
|
|
689
|
-
validatedFindings = []
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Combine validated and non-validated findings
|
|
693
|
-
allVulnerabilities.push(...validatedFindings, ...noValidationNeeded)
|
|
694
|
-
|
|
695
|
-
// Layer 3: AI Semantic Analysis (new findings)
|
|
696
|
-
// Skip for incremental scans by default (configurable)
|
|
697
|
-
const shouldRunLayer3 = options.enableAI !== false && !scanModeConfig.skipLayer3
|
|
698
|
-
|
|
699
|
-
if (shouldRunLayer3) {
|
|
700
|
-
checkCancelled()
|
|
701
|
-
const layer3Start = Date.now()
|
|
702
|
-
reportProgress('layer3', 'Running AI semantic analysis...', allVulnerabilities.length)
|
|
703
|
-
|
|
704
|
-
// For incremental scans, only analyze changed files
|
|
705
|
-
const filesToAnalyze = isIncremental ? filesForAI : files
|
|
706
|
-
const maxLayer3Files = scanModeConfig.maxLayer3Files || 10
|
|
707
|
-
|
|
708
|
-
// Detect auth helpers for Layer 3 context
|
|
709
|
-
const authHelperContext = detectAuthHelpers(files)
|
|
710
|
-
|
|
711
|
-
const layer3Result = await runLayer3Scan(filesToAnalyze, {
|
|
712
|
-
enableAI: options.enableAI,
|
|
713
|
-
maxFiles: maxLayer3Files,
|
|
714
|
-
projectContext: {
|
|
715
|
-
middlewareConfig: middlewareConfig.hasAuthMiddleware ? {
|
|
716
|
-
hasAuthMiddleware: true,
|
|
717
|
-
authType: middlewareConfig.authType,
|
|
718
|
-
protectedPaths: middlewareConfig.protectedPaths,
|
|
719
|
-
} : undefined,
|
|
720
|
-
authHelpers: authHelperContext.hasThrowingHelpers ? {
|
|
721
|
-
hasThrowingHelpers: true,
|
|
722
|
-
summary: authHelperContext.summary,
|
|
723
|
-
} : undefined,
|
|
724
|
-
},
|
|
725
|
-
cancellationToken,
|
|
726
|
-
})
|
|
727
|
-
phaseTiming.layer3 = Date.now() - layer3Start
|
|
728
|
-
allVulnerabilities.push(...layer3Result.vulnerabilities)
|
|
729
|
-
log(`[Layer3] repo=${repoInfo.name} depth=${depth} duration=${phaseTiming.layer3}ms files_analyzed=${layer3Result.aiAnalyzed} findings=${layer3Result.vulnerabilities.length}`)
|
|
730
|
-
} else if (scanModeConfig.skipLayer3) {
|
|
731
|
-
log(`[Layer3] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config`)
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Log phase timing summary
|
|
735
|
-
const phaseTimingStr = Object.entries(phaseTiming)
|
|
736
|
-
.filter(([, ms]) => ms !== undefined)
|
|
737
|
-
.map(([phase, ms]) => `${phase}=${ms}ms`)
|
|
738
|
-
.join(' ')
|
|
739
|
-
if (phaseTimingStr) {
|
|
740
|
-
log(`[Scanner] repo=${repoInfo.name} phase_timing: ${phaseTimingStr}`)
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Deduplicate vulnerabilities
|
|
744
|
-
const uniqueVulnerabilities = deduplicateVulnerabilities(allVulnerabilities)
|
|
745
|
-
if (filterPipeline.isEnabled) {
|
|
746
|
-
const uniqueIds = new Set(uniqueVulnerabilities.map(fid))
|
|
747
|
-
for (const v of allVulnerabilities) {
|
|
748
|
-
if (!uniqueIds.has(fid(v))) {
|
|
749
|
-
filterPipeline.record(fid(v), { stage: 'deduplication', action: 'deduplicated', reason: 'Duplicate finding removed' })
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// Apply file context-based severity adjustments (Phase 2b)
|
|
755
|
-
// This applies to ALL findings from ALL layers (no FileContext = path-based adjustments only)
|
|
756
|
-
const contextAdjustedVulnerabilities = applyContextAdjustments(uniqueVulnerabilities)
|
|
757
|
-
if (filterPipeline.isEnabled) {
|
|
758
|
-
for (let i = 0; i < uniqueVulnerabilities.length; i++) {
|
|
759
|
-
const before = uniqueVulnerabilities[i]
|
|
760
|
-
const after = contextAdjustedVulnerabilities[i]
|
|
761
|
-
if (before.severity !== after.severity) {
|
|
762
|
-
filterPipeline.record(fid(after), { stage: 'global_context', action: 'downgraded', reason: after.validationNotes || 'Context-based severity downgrade', originalSeverity: before.severity, newSeverity: after.severity })
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Resolve contradictions (e.g., middleware-protected INFO vs missing-auth CRITICAL on same route)
|
|
768
|
-
const resolvedVulnerabilities = resolveContradictions(contextAdjustedVulnerabilities, middlewareConfig)
|
|
769
|
-
if (filterPipeline.isEnabled) {
|
|
770
|
-
const resolvedIds = new Set(resolvedVulnerabilities.map(fid))
|
|
771
|
-
for (const v of contextAdjustedVulnerabilities) {
|
|
772
|
-
if (!resolvedIds.has(fid(v))) {
|
|
773
|
-
filterPipeline.record(fid(v), { stage: 'contradiction_resolution', action: 'dismissed', reason: 'Removed by contradiction resolution' })
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
for (let i = 0; i < contextAdjustedVulnerabilities.length; i++) {
|
|
777
|
-
const before = contextAdjustedVulnerabilities[i]
|
|
778
|
-
const after = resolvedVulnerabilities.find(v => fid(v) === fid(before))
|
|
779
|
-
if (after && before.severity !== after.severity) {
|
|
780
|
-
filterPipeline.record(fid(after), { stage: 'contradiction_resolution', action: 'downgraded', reason: 'Downgraded by contradiction resolution', originalSeverity: before.severity, newSeverity: after.severity })
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// Apply suppressions (inline comments + config file)
|
|
786
|
-
const projectPath = options.projectPath || process.cwd()
|
|
787
|
-
const suppressionManager = new SuppressionManager({ projectPath })
|
|
788
|
-
const suppressionResult = suppressionManager.applySuppressions(resolvedVulnerabilities, files)
|
|
789
|
-
|
|
790
|
-
if (filterPipeline.isEnabled) {
|
|
791
|
-
for (const s of suppressionResult.suppressed) {
|
|
792
|
-
filterPipeline.record(fid(s.vulnerability), { stage: 'user_suppression', action: 'suppressed', reason: s.suppression.reason || `Suppressed by ${s.suppression.type}` })
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Log suppression stats if any were suppressed
|
|
797
|
-
if (suppressionResult.suppressed.length > 0 || suppressionResult.expiredSuppressions > 0) {
|
|
798
|
-
log(`[Suppression] repo=${repoInfo.name} suppressed=${suppressionResult.suppressed.length} (inline=${suppressionResult.stats.inlineSuppressed} config_finding=${suppressionResult.stats.configFindingSuppressed} config_rule=${suppressionResult.stats.configRuleSuppressed}) expired=${suppressionResult.expiredSuppressions}`)
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// Use the filtered findings (after suppression)
|
|
802
|
-
const afterSuppression = suppressionResult.findings
|
|
803
|
-
|
|
804
|
-
// Sort by severity
|
|
805
|
-
const sortedVulnerabilities = sortBySeverity(afterSuppression)
|
|
806
|
-
|
|
807
|
-
// Compute issue-mix counts (based on unsuppressed findings)
|
|
808
|
-
const severityCounts = computeSeverityCounts(sortedVulnerabilities)
|
|
809
|
-
const categoryCounts = computeCategoryCounts(sortedVulnerabilities)
|
|
810
|
-
const hasBlockingIssues = severityCounts.critical > 0 || severityCounts.high > 0
|
|
811
|
-
|
|
812
|
-
// Build suppressed vulnerabilities summary (for --show-suppressed)
|
|
813
|
-
const suppressedVulnerabilities: SuppressedVulnerabilitySummary[] | undefined = options.showSuppressed
|
|
814
|
-
? suppressionResult.suppressed.map(s => ({
|
|
815
|
-
hash: s.suppression.hash,
|
|
816
|
-
filePath: s.vulnerability.filePath,
|
|
817
|
-
lineNumber: s.vulnerability.lineNumber,
|
|
818
|
-
category: s.vulnerability.category,
|
|
819
|
-
severity: s.vulnerability.severity,
|
|
820
|
-
title: s.vulnerability.title,
|
|
821
|
-
suppressionType: s.suppression.type,
|
|
822
|
-
suppressionReason: s.suppression.reason,
|
|
823
|
-
expires: s.suppression.expires,
|
|
824
|
-
}))
|
|
825
|
-
: undefined
|
|
826
|
-
|
|
827
|
-
reportProgress('complete', 'Scan complete!', sortedVulnerabilities.length)
|
|
828
|
-
|
|
829
|
-
return {
|
|
830
|
-
repoName: repoInfo.name,
|
|
831
|
-
repoUrl: repoInfo.url,
|
|
832
|
-
branch: repoInfo.branch,
|
|
833
|
-
filesScanned: files.length,
|
|
834
|
-
filesSkipped: 0, // TODO: track skipped files
|
|
835
|
-
vulnerabilities: sortedVulnerabilities,
|
|
836
|
-
severityCounts,
|
|
837
|
-
categoryCounts,
|
|
838
|
-
hasBlockingIssues,
|
|
839
|
-
scanDuration: Date.now() - startTime,
|
|
840
|
-
timestamp: new Date().toISOString(),
|
|
841
|
-
validationStats: capturedValidationStats,
|
|
842
|
-
suppressionStats: suppressionResult.suppressed.length > 0 || suppressionResult.expiredSuppressions > 0
|
|
843
|
-
? suppressionResult.stats
|
|
844
|
-
: undefined,
|
|
845
|
-
suppressedVulnerabilities,
|
|
846
|
-
filterAuditTrail: filterPipeline.isEnabled ? filterPipeline.getAuditTrail() : undefined,
|
|
847
|
-
}
|
|
848
|
-
} catch (error) {
|
|
849
|
-
if (cancellationToken?.cancelled) {
|
|
850
|
-
// Return partial results on cancellation
|
|
851
|
-
reportProgress('failed', 'Scan cancelled')
|
|
852
|
-
|
|
853
|
-
// Compute partial results
|
|
854
|
-
const uniqueVulnerabilities = deduplicateVulnerabilities(allVulnerabilities)
|
|
855
|
-
const resolvedVulnerabilities = resolveContradictions(uniqueVulnerabilities, middlewareConfig)
|
|
856
|
-
const sortedVulnerabilities = sortBySeverity(resolvedVulnerabilities)
|
|
857
|
-
const severityCounts = computeSeverityCounts(sortedVulnerabilities)
|
|
858
|
-
const categoryCounts = computeCategoryCounts(sortedVulnerabilities)
|
|
859
|
-
|
|
860
|
-
return {
|
|
861
|
-
repoName: repoInfo.name,
|
|
862
|
-
repoUrl: repoInfo.url,
|
|
863
|
-
branch: repoInfo.branch,
|
|
864
|
-
filesScanned: files.length,
|
|
865
|
-
filesSkipped: 0,
|
|
866
|
-
vulnerabilities: sortedVulnerabilities,
|
|
867
|
-
severityCounts,
|
|
868
|
-
categoryCounts,
|
|
869
|
-
hasBlockingIssues: false, // Don't block on partial results
|
|
870
|
-
scanDuration: Date.now() - startTime,
|
|
871
|
-
timestamp: new Date().toISOString(),
|
|
872
|
-
validationStats: capturedValidationStats,
|
|
873
|
-
cancelled: true,
|
|
874
|
-
cancelReason: cancellationToken.reason,
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
reportProgress('failed', `Scan failed: ${error}`)
|
|
879
|
-
throw error
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Enrich findings with metadata from the rule registry (PRO-82)
|
|
885
|
-
* Sets default impact, evidence, fixSteps, and references from registry
|
|
886
|
-
*
|
|
887
|
-
* PRO-83: When projectContext is provided, uses framework-aware fix suggestions
|
|
888
|
-
* that are tailored to the user's detected tech stack (e.g., Prisma-specific
|
|
889
|
-
* SQL injection fixes instead of generic advice).
|
|
890
|
-
*
|
|
891
|
-
* These can be overridden later by AI-generated content
|
|
892
|
-
*/
|
|
893
|
-
function enrichWithMetadata(
|
|
894
|
-
findings: Vulnerability[],
|
|
895
|
-
projectContext?: ProjectContext
|
|
896
|
-
): Vulnerability[] {
|
|
897
|
-
return findings.map(f => {
|
|
898
|
-
const metadata = getRuleMetadata(f.category)
|
|
899
|
-
if (!metadata) return f
|
|
900
|
-
|
|
901
|
-
// PRO-83: Check for framework-specific fix suggestions
|
|
902
|
-
let fixSteps = metadata.fixSteps
|
|
903
|
-
if (projectContext) {
|
|
904
|
-
const frameworkFix = getFrameworkFix(
|
|
905
|
-
f.category,
|
|
906
|
-
projectContext.frameworks,
|
|
907
|
-
projectContext.dataAccess
|
|
908
|
-
)
|
|
909
|
-
if (frameworkFix) {
|
|
910
|
-
fixSteps = frameworkFix.fixSteps
|
|
911
|
-
// Optionally append code example to description if available
|
|
912
|
-
// This makes the fix more actionable
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
return {
|
|
917
|
-
...f,
|
|
918
|
-
// Set defaults from registry (AI can override later)
|
|
919
|
-
impact: f.impact || metadata.whyItMatters,
|
|
920
|
-
evidence: f.evidence || metadata.evidence,
|
|
921
|
-
fixSteps: f.fixSteps || fixSteps,
|
|
922
|
-
references: f.references || metadata.references,
|
|
923
|
-
}
|
|
924
|
-
})
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* Aggregate noisy findings in the same file to reduce clutter
|
|
929
|
-
* Groups repeated findings with same filePath + category + title
|
|
930
|
-
* Especially useful for AI pattern spam
|
|
931
|
-
*/
|
|
932
|
-
function aggregateNoisyFindings(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
933
|
-
// Group findings by file + category + title
|
|
934
|
-
const groups = new Map<string, Vulnerability[]>()
|
|
935
|
-
|
|
936
|
-
for (const vuln of vulnerabilities) {
|
|
937
|
-
// Create grouping key: same file, category, and base title (without line-specific info)
|
|
938
|
-
const baseTitle = vuln.title.replace(/\s*\(\d+ instances?\)/, '').trim()
|
|
939
|
-
const key = `${vuln.filePath}|${vuln.category}|${baseTitle}`
|
|
940
|
-
|
|
941
|
-
const existing = groups.get(key) || []
|
|
942
|
-
existing.push(vuln)
|
|
943
|
-
groups.set(key, existing)
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const result: Vulnerability[] = []
|
|
947
|
-
|
|
948
|
-
for (const [key, group] of groups) {
|
|
949
|
-
// If only 1-2 findings, keep them as-is
|
|
950
|
-
if (group.length <= 2) {
|
|
951
|
-
result.push(...group)
|
|
952
|
-
continue
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// For 3+ similar findings in same file, aggregate into one
|
|
956
|
-
const first = group[0]
|
|
957
|
-
const lineNumbers = group.map(v => v.lineNumber).sort((a, b) => a - b)
|
|
958
|
-
const uniqueLines = [...new Set(lineNumbers)]
|
|
959
|
-
|
|
960
|
-
// Format line numbers nicely (show first few, then "...")
|
|
961
|
-
const lineDisplay = uniqueLines.length > 5
|
|
962
|
-
? `${uniqueLines.slice(0, 5).join(', ')}... (${uniqueLines.length} total)`
|
|
963
|
-
: uniqueLines.join(', ')
|
|
964
|
-
|
|
965
|
-
// Keep highest severity from the group
|
|
966
|
-
const highestSeverity = group.reduce((max, v) =>
|
|
967
|
-
severityRank(v.severity) > severityRank(max.severity) ? v : max
|
|
968
|
-
, group[0]).severity
|
|
969
|
-
|
|
970
|
-
// Create aggregated finding
|
|
971
|
-
const aggregated: Vulnerability = {
|
|
972
|
-
id: `${first.id}-aggregated`,
|
|
973
|
-
filePath: first.filePath,
|
|
974
|
-
lineNumber: uniqueLines[0], // First occurrence
|
|
975
|
-
lineContent: `${group.length} instances across this file`,
|
|
976
|
-
severity: highestSeverity,
|
|
977
|
-
category: first.category,
|
|
978
|
-
title: `${first.title.replace(/\s*\(\d+ instances?\)/, '')} (${group.length} instances)`,
|
|
979
|
-
description: `${first.description}\n\nFound ${group.length} occurrences at lines: ${lineDisplay}`,
|
|
980
|
-
suggestedFix: first.suggestedFix,
|
|
981
|
-
confidence: first.confidence,
|
|
982
|
-
layer: first.layer,
|
|
983
|
-
requiresAIValidation: first.requiresAIValidation,
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
result.push(aggregated)
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
return result
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Cap validation candidates per file to control AI costs
|
|
994
|
-
* Prioritizes by:
|
|
995
|
-
* 1. Tier (ai_assisted findings need AI most, so they get priority)
|
|
996
|
-
* 2. Severity (critical > high > medium > low > info)
|
|
997
|
-
* 3. Category importance (secrets/URLs/auth before cosmetic patterns)
|
|
998
|
-
*/
|
|
999
|
-
function capValidationCandidatesPerFile(
|
|
1000
|
-
vulnerabilities: Vulnerability[],
|
|
1001
|
-
maxPerFile: number = MAX_VALIDATION_CANDIDATES_PER_FILE
|
|
1002
|
-
): Vulnerability[] {
|
|
1003
|
-
// Group by file
|
|
1004
|
-
const byFile = new Map<string, Vulnerability[]>()
|
|
1005
|
-
for (const vuln of vulnerabilities) {
|
|
1006
|
-
const existing = byFile.get(vuln.filePath) || []
|
|
1007
|
-
existing.push(vuln)
|
|
1008
|
-
byFile.set(vuln.filePath, existing)
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const result: Vulnerability[] = []
|
|
1012
|
-
|
|
1013
|
-
for (const [filePath, fileVulns] of byFile) {
|
|
1014
|
-
// Sort by priority: tier first (ai_assisted needs AI most), then severity, then category
|
|
1015
|
-
const sorted = [...fileVulns].sort((a, b) => {
|
|
1016
|
-
// Tier comparison: ai_assisted (needs AI) > core (high precision) > experimental (hidden anyway)
|
|
1017
|
-
const tierPriority = (v: Vulnerability): number => {
|
|
1018
|
-
const tier = getTierForCategory(v.category, v.layer)
|
|
1019
|
-
if (tier === 'ai_assisted') return 10 // Highest priority - these NEED AI validation
|
|
1020
|
-
if (tier === 'core') return 5 // High precision, but AI can still help
|
|
1021
|
-
return 1 // Experimental - shouldn't even be here
|
|
1022
|
-
}
|
|
1023
|
-
const tierDiff = tierPriority(b) - tierPriority(a)
|
|
1024
|
-
if (tierDiff !== 0) return tierDiff
|
|
1025
|
-
|
|
1026
|
-
// Severity comparison (higher severity = higher priority)
|
|
1027
|
-
const severityDiff = severityRank(b.severity) - severityRank(a.severity)
|
|
1028
|
-
if (severityDiff !== 0) return severityDiff
|
|
1029
|
-
|
|
1030
|
-
// Category importance (secrets/URLs/auth before AI patterns)
|
|
1031
|
-
const categoryPriority = (v: Vulnerability): number => {
|
|
1032
|
-
if (v.category === 'hardcoded_secret') return 10
|
|
1033
|
-
if (v.category === 'high_entropy_string') return 9
|
|
1034
|
-
if (v.category === 'sensitive_url') return 8
|
|
1035
|
-
if (v.category === 'missing_auth') return 7
|
|
1036
|
-
if (v.category === 'ai_pattern') return 3
|
|
1037
|
-
return 5
|
|
1038
|
-
}
|
|
1039
|
-
return categoryPriority(b) - categoryPriority(a)
|
|
1040
|
-
})
|
|
1041
|
-
|
|
1042
|
-
// Take top N per file
|
|
1043
|
-
const capped = sorted.slice(0, maxPerFile)
|
|
1044
|
-
result.push(...capped)
|
|
1045
|
-
|
|
1046
|
-
// Note: Capping log removed to support quiet mode - this is debug info only
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
return result
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// applyGlobalContextAdjustments consolidated into filtering/context-adjustments.ts
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Remove duplicate vulnerabilities (same file, line, category)
|
|
1056
|
-
* Also handles cross-layer URL duplicates (sensitive_url + ai_pattern)
|
|
1057
|
-
*/
|
|
1058
|
-
function deduplicateVulnerabilities(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
1059
|
-
const seen = new Map<string, Vulnerability>()
|
|
1060
|
-
const urlDedupMap = new Map<string, Vulnerability>()
|
|
1061
|
-
|
|
1062
|
-
for (const vuln of vulnerabilities) {
|
|
1063
|
-
// Special handling for URL duplicates across layers
|
|
1064
|
-
// (e.g., Layer 1 detects as sensitive_url, Layer 2 detects as ai_pattern)
|
|
1065
|
-
if ((vuln.category === 'sensitive_url' || vuln.category === 'ai_pattern') &&
|
|
1066
|
-
/url|localhost|127\.0\.0\.1|http/i.test(vuln.lineContent)) {
|
|
1067
|
-
|
|
1068
|
-
// Create compound key that ignores category differences for URLs
|
|
1069
|
-
const urlKey = `${vuln.filePath}:${vuln.lineNumber}:url_finding`
|
|
1070
|
-
const existing = urlDedupMap.get(urlKey)
|
|
1071
|
-
|
|
1072
|
-
if (!existing) {
|
|
1073
|
-
urlDedupMap.set(urlKey, vuln)
|
|
1074
|
-
} else {
|
|
1075
|
-
// Keep Layer 1 (more specific) over Layer 2 AI pattern
|
|
1076
|
-
// Or keep higher severity
|
|
1077
|
-
if (vuln.layer < existing.layer ||
|
|
1078
|
-
severityRank(vuln.severity) > severityRank(existing.severity)) {
|
|
1079
|
-
urlDedupMap.set(urlKey, vuln)
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
continue
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Standard deduplication for non-URL findings
|
|
1086
|
-
const key = `${vuln.filePath}:${vuln.lineNumber}:${vuln.category}`
|
|
1087
|
-
const existing = seen.get(key)
|
|
1088
|
-
|
|
1089
|
-
// Keep the higher severity or higher confidence finding
|
|
1090
|
-
if (!existing) {
|
|
1091
|
-
seen.set(key, vuln)
|
|
1092
|
-
} else if (severityRank(vuln.severity) > severityRank(existing.severity)) {
|
|
1093
|
-
seen.set(key, vuln)
|
|
1094
|
-
} else if (
|
|
1095
|
-
severityRank(vuln.severity) === severityRank(existing.severity) &&
|
|
1096
|
-
confidenceRank(vuln.confidence) > confidenceRank(existing.confidence)
|
|
1097
|
-
) {
|
|
1098
|
-
seen.set(key, vuln)
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Combine URL and non-URL findings
|
|
1103
|
-
return [...Array.from(seen.values()), ...Array.from(urlDedupMap.values())]
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Resolve contradictions in findings
|
|
1108
|
-
*
|
|
1109
|
-
* Key contradiction types:
|
|
1110
|
-
* 1. Same route has both "protected by middleware" (info) AND "missing auth" (high/critical)
|
|
1111
|
-
* → Keep only the info-level "protected by middleware" finding
|
|
1112
|
-
* 2. Same file has BYOK "transient use" (info) AND "key stored insecurely" (high)
|
|
1113
|
-
* → Keep only the most accurate one based on context
|
|
1114
|
-
* 3. Same line has conflicting severities from different layers
|
|
1115
|
-
* → Prefer the lower severity if one explicitly notes protection
|
|
1116
|
-
*/
|
|
1117
|
-
function resolveContradictions(
|
|
1118
|
-
vulnerabilities: Vulnerability[],
|
|
1119
|
-
middlewareConfig?: MiddlewareAuthConfig
|
|
1120
|
-
): Vulnerability[] {
|
|
1121
|
-
// Group findings by file path for contradiction analysis
|
|
1122
|
-
const byFile = new Map<string, Vulnerability[]>()
|
|
1123
|
-
for (const vuln of vulnerabilities) {
|
|
1124
|
-
const existing = byFile.get(vuln.filePath) || []
|
|
1125
|
-
existing.push(vuln)
|
|
1126
|
-
byFile.set(vuln.filePath, existing)
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
const result: Vulnerability[] = []
|
|
1130
|
-
|
|
1131
|
-
for (const [filePath, fileVulns] of byFile) {
|
|
1132
|
-
// Check for auth contradictions in this file
|
|
1133
|
-
const authFindings = fileVulns.filter(v => v.category === 'missing_auth')
|
|
1134
|
-
const otherFindings = fileVulns.filter(v => v.category !== 'missing_auth')
|
|
1135
|
-
|
|
1136
|
-
// Identify protected routes (middleware or auth helper)
|
|
1137
|
-
const protectedInfos = authFindings.filter(v =>
|
|
1138
|
-
v.severity === 'info' &&
|
|
1139
|
-
(v.validationNotes === 'MIDDLEWARE_PROTECTED' ||
|
|
1140
|
-
v.validationNotes === 'AUTH_HELPER_PROTECTED' ||
|
|
1141
|
-
v.title.includes('protected by middleware') ||
|
|
1142
|
-
v.title.includes('uses auth helper'))
|
|
1143
|
-
)
|
|
1144
|
-
|
|
1145
|
-
// NEW: Check if this file's route is protected by middleware (even without explicit info finding)
|
|
1146
|
-
// This catches Layer 3 findings that don't have the Layer 2 protection info
|
|
1147
|
-
const routePath = getRoutePathFromFile(filePath)
|
|
1148
|
-
const isAPIRouteProtected = routePath && middlewareConfig?.hasAuthMiddleware
|
|
1149
|
-
? isRouteProtectedByMiddleware(routePath, middlewareConfig).isProtected
|
|
1150
|
-
: false
|
|
1151
|
-
|
|
1152
|
-
// Also check if file is a client component calling protected API routes
|
|
1153
|
-
// Client components (components/, app/ without route.ts) calling /api/** are safe
|
|
1154
|
-
const isClientCallingProtectedAPI =
|
|
1155
|
-
middlewareConfig?.hasAuthMiddleware &&
|
|
1156
|
-
(filePath.includes('/components/') ||
|
|
1157
|
-
(filePath.includes('/app/') && !filePath.includes('route.ts')))
|
|
1158
|
-
|
|
1159
|
-
// If we have protected route info findings OR the route is protected by middleware
|
|
1160
|
-
if (protectedInfos.length > 0 || isAPIRouteProtected || isClientCallingProtectedAPI) {
|
|
1161
|
-
// Keep the protected info findings
|
|
1162
|
-
result.push(...protectedInfos)
|
|
1163
|
-
|
|
1164
|
-
// For other auth findings on same file, either drop or downgrade to info
|
|
1165
|
-
const otherAuthFindings = authFindings.filter(v => !protectedInfos.includes(v))
|
|
1166
|
-
|
|
1167
|
-
for (const vuln of otherAuthFindings) {
|
|
1168
|
-
// If it's high/critical missing auth on a protected route, drop it entirely
|
|
1169
|
-
// (the middleware/helper protection supersedes)
|
|
1170
|
-
if (vuln.severity === 'critical' || vuln.severity === 'high') {
|
|
1171
|
-
const reason = isAPIRouteProtected
|
|
1172
|
-
? 'API route protected by middleware'
|
|
1173
|
-
: isClientCallingProtectedAPI
|
|
1174
|
-
? 'client component calling protected API'
|
|
1175
|
-
: 'route is protected'
|
|
1176
|
-
// Note: Contradiction log removed to support quiet mode - this is debug info only
|
|
1177
|
-
continue // Skip this finding
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// Keep lower severity auth findings as-is
|
|
1181
|
-
result.push(vuln)
|
|
1182
|
-
}
|
|
1183
|
-
} else {
|
|
1184
|
-
// No protection detected - keep all auth findings as-is
|
|
1185
|
-
result.push(...authFindings)
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// Handle BYOK contradictions
|
|
1189
|
-
const byokFindings = otherFindings.filter(v =>
|
|
1190
|
-
v.category === 'ai_pattern' && v.title.toLowerCase().includes('byok')
|
|
1191
|
-
)
|
|
1192
|
-
const nonByokFindings = otherFindings.filter(v =>
|
|
1193
|
-
!(v.category === 'ai_pattern' && v.title.toLowerCase().includes('byok'))
|
|
1194
|
-
)
|
|
1195
|
-
|
|
1196
|
-
if (byokFindings.length > 0) {
|
|
1197
|
-
// Check if we have both transient (low) and storage (high) BYOK findings
|
|
1198
|
-
const transientByok = byokFindings.filter(v =>
|
|
1199
|
-
v.severity === 'low' || v.severity === 'info'
|
|
1200
|
-
)
|
|
1201
|
-
const storageByok = byokFindings.filter(v =>
|
|
1202
|
-
v.severity === 'high' || v.severity === 'medium'
|
|
1203
|
-
)
|
|
1204
|
-
|
|
1205
|
-
if (transientByok.length > 0 && storageByok.length > 0) {
|
|
1206
|
-
// If we detected transient usage, prefer the lower severity
|
|
1207
|
-
// The higher severity ones may be false positives
|
|
1208
|
-
result.push(...transientByok)
|
|
1209
|
-
// Mark high-severity BYOK for review
|
|
1210
|
-
for (const vuln of storageByok) {
|
|
1211
|
-
result.push({
|
|
1212
|
-
...vuln,
|
|
1213
|
-
severity: 'low',
|
|
1214
|
-
validationNotes: `${vuln.validationNotes || ''} (downgraded: transient BYOK usage detected in same file)`.trim(),
|
|
1215
|
-
})
|
|
1216
|
-
}
|
|
1217
|
-
} else {
|
|
1218
|
-
// Keep all BYOK findings as-is
|
|
1219
|
-
result.push(...byokFindings)
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
|
|
1223
|
-
// Add non-BYOK other findings
|
|
1224
|
-
result.push(...nonByokFindings)
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
return result
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Sort vulnerabilities by severity (critical first)
|
|
1232
|
-
*/
|
|
1233
|
-
function sortBySeverity(vulnerabilities: Vulnerability[]): Vulnerability[] {
|
|
1234
|
-
return [...vulnerabilities].sort((a, b) => {
|
|
1235
|
-
const severityDiff = severityRank(b.severity) - severityRank(a.severity)
|
|
1236
|
-
if (severityDiff !== 0) return severityDiff
|
|
1237
|
-
|
|
1238
|
-
// Secondary sort by confidence
|
|
1239
|
-
return confidenceRank(b.confidence) - confidenceRank(a.confidence)
|
|
1240
|
-
})
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
// severityRank and confidenceRank imported from utils/parsed-file
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
* Compute severity counts from vulnerabilities
|
|
1247
|
-
*/
|
|
1248
|
-
function computeSeverityCounts(vulnerabilities: Vulnerability[]): SeverityCounts {
|
|
1249
|
-
const counts: SeverityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
|
|
1250
|
-
|
|
1251
|
-
for (const vuln of vulnerabilities) {
|
|
1252
|
-
if (vuln.severity in counts) {
|
|
1253
|
-
counts[vuln.severity]++
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
return counts
|
|
1258
|
-
}
|
|
1259
|
-
|
|
1260
|
-
/**
|
|
1261
|
-
* Compute category counts from vulnerabilities
|
|
1262
|
-
*/
|
|
1263
|
-
function computeCategoryCounts(vulnerabilities: Vulnerability[]): CategoryCounts {
|
|
1264
|
-
const counts: CategoryCounts = {}
|
|
1265
|
-
|
|
1266
|
-
for (const vuln of vulnerabilities) {
|
|
1267
|
-
const category = vuln.category as VulnerabilityCategory
|
|
1268
|
-
counts[category] = (counts[category] || 0) + 1
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
return counts
|
|
1272
|
-
}
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Re-export types and utilities
|
|
19
|
+
// ============================================================================
|
|
1273
20
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
export
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
hasBlockingIssues: boolean
|
|
1281
|
-
} {
|
|
1282
|
-
const severityCounts = computeSeverityCounts(vulnerabilities)
|
|
1283
|
-
const categoryCounts = computeCategoryCounts(vulnerabilities)
|
|
1284
|
-
const hasBlockingIssues = severityCounts.critical > 0 || severityCounts.high > 0
|
|
1285
|
-
|
|
1286
|
-
return { severityCounts, categoryCounts, hasBlockingIssues }
|
|
1287
|
-
}
|
|
21
|
+
export * from './shared/types'
|
|
22
|
+
export { runLayer1Scan } from './detect/secrets'
|
|
23
|
+
export { runLayer2Scan } from './detect/structural'
|
|
24
|
+
export { buildProjectContext, type ProjectContext } from './model/project-context'
|
|
25
|
+
export { validateFindingsWithAI, type ValidationStats, type AIValidationResult } from './validate'
|
|
26
|
+
export { createCancellationToken } from './shared/types'
|
|
1288
27
|
|
|
1289
|
-
//
|
|
1290
|
-
export
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
28
|
+
// Confidence scoring exports (Phase 2B)
|
|
29
|
+
export {
|
|
30
|
+
scoreFindings,
|
|
31
|
+
buildAdjustmentContext,
|
|
32
|
+
buildConfidenceLog,
|
|
33
|
+
DEPTH_CONFIGS,
|
|
34
|
+
type ScoredFinding,
|
|
35
|
+
type ConfidenceComputation,
|
|
36
|
+
type ConfidenceAdjustment,
|
|
37
|
+
type ConfidenceLog,
|
|
38
|
+
type AdjustmentRule,
|
|
39
|
+
type AdjustmentContext,
|
|
40
|
+
type DepthConfig,
|
|
41
|
+
} from './score'
|
|
42
|
+
|
|
43
|
+
// Context Engine types
|
|
44
|
+
export type { ContextEngineResult } from './model/taint-types'
|
|
1297
45
|
|
|
1298
46
|
// Suppression system exports
|
|
1299
47
|
export {
|
|
@@ -1312,7 +60,7 @@ export {
|
|
|
1312
60
|
type RuleSuppression,
|
|
1313
61
|
type SuppressionResult,
|
|
1314
62
|
type SuppressedVulnerability,
|
|
1315
|
-
} from './suppression'
|
|
63
|
+
} from './postprocess/suppression'
|
|
1316
64
|
|
|
1317
65
|
// Baseline system exports
|
|
1318
66
|
export {
|
|
@@ -1330,7 +78,7 @@ export {
|
|
|
1330
78
|
type LoadBaselineResult,
|
|
1331
79
|
type SaveBaselineResult,
|
|
1332
80
|
type ClearBaselineResult,
|
|
1333
|
-
} from './baseline'
|
|
81
|
+
} from './shared/baseline'
|
|
1334
82
|
|
|
1335
83
|
// Rule metadata exports (PRO-82)
|
|
1336
84
|
export {
|
|
@@ -1339,7 +87,7 @@ export {
|
|
|
1339
87
|
getAllCategories,
|
|
1340
88
|
hasMetadata,
|
|
1341
89
|
type RuleMetadata,
|
|
1342
|
-
} from './rules'
|
|
90
|
+
} from './shared/rules'
|
|
1343
91
|
|
|
1344
92
|
// AI Context exports
|
|
1345
93
|
export {
|
|
@@ -1350,7 +98,7 @@ export {
|
|
|
1350
98
|
type SaveContextResult,
|
|
1351
99
|
type LoadContextResult,
|
|
1352
100
|
type ClearContextResult,
|
|
1353
|
-
} from './ai-context'
|
|
101
|
+
} from './shared/ai-context'
|
|
1354
102
|
|
|
1355
103
|
// Category filter exports (Phase 2: Category-Based Filtering)
|
|
1356
104
|
export {
|
|
@@ -1365,4 +113,4 @@ export {
|
|
|
1365
113
|
validateCategories,
|
|
1366
114
|
getAvailableCategoryGroups,
|
|
1367
115
|
getCategoryGroupCounts,
|
|
1368
|
-
} from './category-filter'
|
|
116
|
+
} from './shared/category-filter'
|