@oculum/scanner 1.0.9 → 1.0.11
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/baseline/diff.d.ts +32 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +119 -0
- package/dist/baseline/diff.js.map +1 -0
- package/dist/baseline/index.d.ts +9 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/baseline/index.js +19 -0
- package/dist/baseline/index.js.map +1 -0
- package/dist/baseline/manager.d.ts +67 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +180 -0
- package/dist/baseline/manager.js.map +1 -0
- package/dist/baseline/types.d.ts +91 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +12 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/formatters/cli-terminal.d.ts +38 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -1
- package/dist/formatters/cli-terminal.js +365 -42
- package/dist/formatters/cli-terminal.js.map +1 -1
- package/dist/formatters/github-comment.d.ts +1 -1
- package/dist/formatters/github-comment.d.ts.map +1 -1
- package/dist/formatters/github-comment.js +75 -11
- package/dist/formatters/github-comment.js.map +1 -1
- package/dist/formatters/index.d.ts +1 -1
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +4 -1
- package/dist/formatters/index.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -16
- package/dist/index.js.map +1 -1
- package/dist/layer1/config-audit.d.ts.map +1 -1
- package/dist/layer1/config-audit.js +20 -3
- package/dist/layer1/config-audit.js.map +1 -1
- package/dist/layer1/config-mcp-audit.d.ts +20 -0
- package/dist/layer1/config-mcp-audit.d.ts.map +1 -0
- package/dist/layer1/config-mcp-audit.js +239 -0
- package/dist/layer1/config-mcp-audit.js.map +1 -0
- package/dist/layer1/index.d.ts +1 -0
- package/dist/layer1/index.d.ts.map +1 -1
- package/dist/layer1/index.js +9 -1
- package/dist/layer1/index.js.map +1 -1
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
- package/dist/layer2/ai-agent-tools.js +303 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -1
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
- package/dist/layer2/ai-endpoint-protection.js +17 -3
- package/dist/layer2/ai-endpoint-protection.js.map +1 -1
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
- package/dist/layer2/ai-execution-sinks.js +462 -12
- package/dist/layer2/ai-execution-sinks.js.map +1 -1
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
- package/dist/layer2/ai-fingerprinting.js +3 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -1
- package/dist/layer2/ai-mcp-security.d.ts +17 -0
- package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
- package/dist/layer2/ai-mcp-security.js +679 -0
- package/dist/layer2/ai-mcp-security.js.map +1 -0
- package/dist/layer2/ai-package-hallucination.d.ts +19 -0
- package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
- package/dist/layer2/ai-package-hallucination.js +696 -0
- package/dist/layer2/ai-package-hallucination.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.js +495 -9
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
- package/dist/layer2/ai-rag-safety.js +372 -1
- package/dist/layer2/ai-rag-safety.js.map +1 -1
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
- package/dist/layer2/auth-antipatterns.js +4 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -1
- package/dist/layer2/byok-patterns.d.ts.map +1 -1
- package/dist/layer2/byok-patterns.js +3 -0
- package/dist/layer2/byok-patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/child-process.d.ts +16 -0
- package/dist/layer2/dangerous-functions/child-process.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/child-process.js +74 -0
- package/dist/layer2/dangerous-functions/child-process.js.map +1 -0
- package/dist/layer2/dangerous-functions/dom-xss.d.ts +29 -0
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/dom-xss.js +179 -0
- package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
- package/dist/layer2/dangerous-functions/index.d.ts +13 -0
- package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/index.js +621 -0
- package/dist/layer2/dangerous-functions/index.js.map +1 -0
- package/dist/layer2/dangerous-functions/json-parse.d.ts +31 -0
- package/dist/layer2/dangerous-functions/json-parse.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/json-parse.js +319 -0
- package/dist/layer2/dangerous-functions/json-parse.js.map +1 -0
- package/dist/layer2/dangerous-functions/math-random.d.ts +61 -0
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/math-random.js +459 -0
- package/dist/layer2/dangerous-functions/math-random.js.map +1 -0
- package/dist/layer2/dangerous-functions/patterns.d.ts +21 -0
- package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/patterns.js +161 -0
- package/dist/layer2/dangerous-functions/patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions/request-validation.d.ts +13 -0
- package/dist/layer2/dangerous-functions/request-validation.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/request-validation.js +119 -0
- package/dist/layer2/dangerous-functions/request-validation.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +23 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.js +149 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts +31 -0
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/helpers.js +124 -0
- package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/index.d.ts +9 -0
- package/dist/layer2/dangerous-functions/utils/index.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/index.js +23 -0
- package/dist/layer2/dangerous-functions/utils/index.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +22 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.js +89 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -1
- package/dist/layer2/data-exposure.js +3 -0
- package/dist/layer2/data-exposure.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +3 -0
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +3 -0
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +61 -2
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/logic-gates.d.ts.map +1 -1
- package/dist/layer2/logic-gates.js +4 -0
- package/dist/layer2/logic-gates.js.map +1 -1
- package/dist/layer2/model-supply-chain.d.ts +20 -0
- package/dist/layer2/model-supply-chain.d.ts.map +1 -0
- package/dist/layer2/model-supply-chain.js +376 -0
- package/dist/layer2/model-supply-chain.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -1
- package/dist/layer2/risky-imports.js +4 -0
- package/dist/layer2/risky-imports.js.map +1 -1
- package/dist/layer2/variables.d.ts.map +1 -1
- package/dist/layer2/variables.js +4 -0
- package/dist/layer2/variables.js.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.d.ts +24 -0
- package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -0
- package/dist/layer3/anthropic/auto-dismiss.js +188 -0
- package/dist/layer3/anthropic/auto-dismiss.js.map +1 -0
- package/dist/layer3/anthropic/clients.d.ts +44 -0
- package/dist/layer3/anthropic/clients.d.ts.map +1 -0
- package/dist/layer3/anthropic/clients.js +81 -0
- package/dist/layer3/anthropic/clients.js.map +1 -0
- package/dist/layer3/anthropic/index.d.ts +41 -0
- package/dist/layer3/anthropic/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/index.js +141 -0
- package/dist/layer3/anthropic/index.js.map +1 -0
- package/dist/layer3/anthropic/prompts/index.d.ts +8 -0
- package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/index.js +14 -0
- package/dist/layer3/anthropic/prompts/index.js.map +1 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +15 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.js +169 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +1 -0
- package/dist/layer3/anthropic/prompts/validation.d.ts +12 -0
- package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/validation.js +421 -0
- package/dist/layer3/anthropic/prompts/validation.js.map +1 -0
- package/dist/layer3/anthropic/providers/anthropic.d.ts +21 -0
- package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/anthropic.js +266 -0
- package/dist/layer3/anthropic/providers/anthropic.js.map +1 -0
- package/dist/layer3/anthropic/providers/index.d.ts +8 -0
- package/dist/layer3/anthropic/providers/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/index.js +15 -0
- package/dist/layer3/anthropic/providers/index.js.map +1 -0
- package/dist/layer3/anthropic/providers/openai.d.ts +18 -0
- package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/openai.js +340 -0
- package/dist/layer3/anthropic/providers/openai.js.map +1 -0
- package/dist/layer3/anthropic/request-builder.d.ts +20 -0
- package/dist/layer3/anthropic/request-builder.d.ts.map +1 -0
- package/dist/layer3/anthropic/request-builder.js +134 -0
- package/dist/layer3/anthropic/request-builder.js.map +1 -0
- package/dist/layer3/anthropic/types.d.ts +88 -0
- package/dist/layer3/anthropic/types.d.ts.map +1 -0
- package/dist/layer3/anthropic/types.js +38 -0
- package/dist/layer3/anthropic/types.js.map +1 -0
- package/dist/layer3/anthropic/utils/index.d.ts +9 -0
- package/dist/layer3/anthropic/utils/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/index.js +24 -0
- package/dist/layer3/anthropic/utils/index.js.map +1 -0
- package/dist/layer3/anthropic/utils/path-helpers.d.ts +21 -0
- package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/path-helpers.js +69 -0
- package/dist/layer3/anthropic/utils/path-helpers.js.map +1 -0
- package/dist/layer3/anthropic/utils/response-parser.d.ts +40 -0
- package/dist/layer3/anthropic/utils/response-parser.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/response-parser.js +285 -0
- package/dist/layer3/anthropic/utils/response-parser.js.map +1 -0
- package/dist/layer3/anthropic/utils/retry.d.ts +15 -0
- package/dist/layer3/anthropic/utils/retry.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/retry.js +62 -0
- package/dist/layer3/anthropic/utils/retry.js.map +1 -0
- package/dist/layer3/index.d.ts +1 -0
- package/dist/layer3/index.d.ts.map +1 -1
- package/dist/layer3/index.js +16 -6
- package/dist/layer3/index.js.map +1 -1
- package/dist/layer3/osv-check.d.ts +75 -0
- package/dist/layer3/osv-check.d.ts.map +1 -0
- package/dist/layer3/osv-check.js +308 -0
- package/dist/layer3/osv-check.js.map +1 -0
- package/dist/rules/framework-fixes.d.ts +48 -0
- package/dist/rules/framework-fixes.d.ts.map +1 -0
- package/dist/rules/framework-fixes.js +439 -0
- package/dist/rules/framework-fixes.js.map +1 -0
- package/dist/rules/index.d.ts +8 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +18 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/metadata.d.ts +43 -0
- package/dist/rules/metadata.d.ts.map +1 -0
- package/dist/rules/metadata.js +734 -0
- package/dist/rules/metadata.js.map +1 -0
- package/dist/suppression/config-loader.d.ts +74 -0
- package/dist/suppression/config-loader.d.ts.map +1 -0
- package/dist/suppression/config-loader.js +424 -0
- package/dist/suppression/config-loader.js.map +1 -0
- package/dist/suppression/hash.d.ts +48 -0
- package/dist/suppression/hash.d.ts.map +1 -0
- package/dist/suppression/hash.js +88 -0
- package/dist/suppression/hash.js.map +1 -0
- package/dist/suppression/index.d.ts +11 -0
- package/dist/suppression/index.d.ts.map +1 -0
- package/dist/suppression/index.js +39 -0
- package/dist/suppression/index.js.map +1 -0
- package/dist/suppression/inline-parser.d.ts +39 -0
- package/dist/suppression/inline-parser.d.ts.map +1 -0
- package/dist/suppression/inline-parser.js +218 -0
- package/dist/suppression/inline-parser.js.map +1 -0
- package/dist/suppression/manager.d.ts +94 -0
- package/dist/suppression/manager.d.ts.map +1 -0
- package/dist/suppression/manager.js +292 -0
- package/dist/suppression/manager.js.map +1 -0
- package/dist/suppression/types.d.ts +151 -0
- package/dist/suppression/types.d.ts.map +1 -0
- package/dist/suppression/types.js +28 -0
- package/dist/suppression/types.js.map +1 -0
- package/dist/tiers.d.ts +1 -1
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +27 -0
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +62 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/context-helpers.d.ts +4 -0
- package/dist/utils/context-helpers.d.ts.map +1 -1
- package/dist/utils/context-helpers.js +13 -9
- package/dist/utils/context-helpers.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/benchmark/fixtures/layer1/mcp-config-audit.json +31 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1489 -82
- package/src/__tests__/benchmark/fixtures/layer2/ai-mcp-security.ts +495 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-package-hallucination.ts +255 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +300 -1
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +139 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +7 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +63 -0
- package/src/__tests__/benchmark/fixtures/layer2/excessive-agency.ts +221 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +18 -0
- package/src/__tests__/benchmark/fixtures/layer2/model-supply-chain.ts +204 -0
- package/src/__tests__/benchmark/fixtures/layer2/phase1-enhancements.ts +157 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +758 -0
- package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
- package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
- package/src/baseline/__tests__/diff.test.ts +261 -0
- package/src/baseline/__tests__/manager.test.ts +225 -0
- package/src/baseline/diff.ts +135 -0
- package/src/baseline/index.ts +29 -0
- package/src/baseline/manager.ts +230 -0
- package/src/baseline/types.ts +97 -0
- package/src/formatters/cli-terminal.ts +444 -41
- package/src/formatters/github-comment.ts +79 -11
- package/src/formatters/index.ts +4 -0
- package/src/index.ts +197 -14
- package/src/layer1/config-audit.ts +24 -3
- package/src/layer1/config-mcp-audit.ts +276 -0
- package/src/layer1/index.ts +16 -6
- package/src/layer2/ai-agent-tools.ts +336 -0
- package/src/layer2/ai-endpoint-protection.ts +16 -3
- package/src/layer2/ai-execution-sinks.ts +516 -12
- package/src/layer2/ai-fingerprinting.ts +5 -1
- package/src/layer2/ai-mcp-security.ts +730 -0
- package/src/layer2/ai-package-hallucination.ts +791 -0
- package/src/layer2/ai-prompt-hygiene.ts +547 -9
- package/src/layer2/ai-rag-safety.ts +382 -3
- package/src/layer2/auth-antipatterns.ts +5 -0
- package/src/layer2/byok-patterns.ts +5 -1
- package/src/layer2/dangerous-functions/child-process.ts +98 -0
- package/src/layer2/dangerous-functions/dom-xss.ts +220 -0
- package/src/layer2/dangerous-functions/index.ts +949 -0
- package/src/layer2/dangerous-functions/json-parse.ts +385 -0
- package/src/layer2/dangerous-functions/math-random.ts +537 -0
- package/src/layer2/dangerous-functions/patterns.ts +174 -0
- package/src/layer2/dangerous-functions/request-validation.ts +145 -0
- package/src/layer2/dangerous-functions/utils/control-flow.ts +162 -0
- package/src/layer2/dangerous-functions/utils/helpers.ts +170 -0
- package/src/layer2/dangerous-functions/utils/index.ts +25 -0
- package/src/layer2/dangerous-functions/utils/schema-validation.ts +91 -0
- package/src/layer2/data-exposure.ts +5 -1
- package/src/layer2/framework-checks.ts +5 -0
- package/src/layer2/index.ts +63 -1
- package/src/layer2/logic-gates.ts +5 -0
- package/src/layer2/model-supply-chain.ts +456 -0
- package/src/layer2/risky-imports.ts +5 -0
- package/src/layer2/variables.ts +5 -0
- package/src/layer3/__tests__/osv-check.test.ts +384 -0
- package/src/layer3/anthropic/auto-dismiss.ts +212 -0
- package/src/layer3/anthropic/clients.ts +84 -0
- package/src/layer3/anthropic/index.ts +170 -0
- package/src/layer3/anthropic/prompts/index.ts +14 -0
- package/src/layer3/anthropic/prompts/semantic-analysis.ts +173 -0
- package/src/layer3/anthropic/prompts/validation.ts +419 -0
- package/src/layer3/anthropic/providers/anthropic.ts +310 -0
- package/src/layer3/anthropic/providers/index.ts +8 -0
- package/src/layer3/anthropic/providers/openai.ts +384 -0
- package/src/layer3/anthropic/request-builder.ts +150 -0
- package/src/layer3/anthropic/types.ts +148 -0
- package/src/layer3/anthropic/utils/index.ts +26 -0
- package/src/layer3/anthropic/utils/path-helpers.ts +68 -0
- package/src/layer3/anthropic/utils/response-parser.ts +322 -0
- package/src/layer3/anthropic/utils/retry.ts +75 -0
- package/src/layer3/index.ts +18 -5
- package/src/layer3/osv-check.ts +420 -0
- package/src/rules/__tests__/framework-fixes.test.ts +689 -0
- package/src/rules/__tests__/metadata.test.ts +218 -0
- package/src/rules/framework-fixes.ts +470 -0
- package/src/rules/index.ts +21 -0
- package/src/rules/metadata.ts +831 -0
- package/src/suppression/__tests__/config-loader.test.ts +382 -0
- package/src/suppression/__tests__/hash.test.ts +166 -0
- package/src/suppression/__tests__/inline-parser.test.ts +212 -0
- package/src/suppression/__tests__/manager.test.ts +415 -0
- package/src/suppression/config-loader.ts +462 -0
- package/src/suppression/hash.ts +95 -0
- package/src/suppression/index.ts +51 -0
- package/src/suppression/inline-parser.ts +273 -0
- package/src/suppression/manager.ts +379 -0
- package/src/suppression/types.ts +174 -0
- package/src/tiers.ts +36 -0
- package/src/types.ts +90 -0
- package/src/utils/context-helpers.ts +13 -9
- package/dist/layer2/dangerous-functions.d.ts +0 -7
- package/dist/layer2/dangerous-functions.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions.js +0 -1701
- package/dist/layer2/dangerous-functions.js.map +0 -1
- package/dist/layer3/anthropic.d.ts +0 -87
- package/dist/layer3/anthropic.d.ts.map +0 -1
- package/dist/layer3/anthropic.js +0 -1948
- package/dist/layer3/anthropic.js.map +0 -1
- package/dist/layer3/openai.d.ts +0 -25
- package/dist/layer3/openai.d.ts.map +0 -1
- package/dist/layer3/openai.js +0 -238
- package/dist/layer3/openai.js.map +0 -1
- package/src/layer2/dangerous-functions.ts +0 -1940
- package/src/layer3/anthropic.ts +0 -2257
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Provider Implementation
|
|
3
|
+
*
|
|
4
|
+
* Validation using OpenAI GPT-5-mini model.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Vulnerability, ScanFile, ValidationStatus } from '../../../types'
|
|
8
|
+
import type { ProjectContext } from '../../../utils/project-context-builder'
|
|
9
|
+
import { buildProjectContext } from '../../../utils/project-context-builder'
|
|
10
|
+
import type { ValidationStats, AIValidationResult, StatsAccumulator } from '../types'
|
|
11
|
+
import { createStatsAccumulator } from '../types'
|
|
12
|
+
import { getOpenAIClient, GPT5_MINI_PRICING, FILES_PER_API_BATCH, PARALLEL_API_BATCHES } from '../clients'
|
|
13
|
+
import { makeOpenAIRequestWithRetry } from '../utils/retry'
|
|
14
|
+
import { parseMultiFileValidationResponse, parseValidationResponse, applyValidationResults } from '../utils/response-parser'
|
|
15
|
+
import { buildMultiFileValidationRequest } from '../request-builder'
|
|
16
|
+
import { HIGH_CONTEXT_VALIDATION_PROMPT } from '../prompts/validation'
|
|
17
|
+
|
|
18
|
+
// Cache for project context (built once per scan)
|
|
19
|
+
let cachedProjectContext: ProjectContext | null = null
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate findings using OpenAI GPT-5-mini
|
|
23
|
+
* This mirrors the Anthropic validation flow but uses OpenAI's API
|
|
24
|
+
*/
|
|
25
|
+
export async function validateWithOpenAI(
|
|
26
|
+
findings: Vulnerability[],
|
|
27
|
+
files: ScanFile[],
|
|
28
|
+
projectContext: ProjectContext | undefined,
|
|
29
|
+
stats: ValidationStats
|
|
30
|
+
): Promise<AIValidationResult> {
|
|
31
|
+
const client = getOpenAIClient()
|
|
32
|
+
|
|
33
|
+
// Build or use cached project context
|
|
34
|
+
const context = projectContext || cachedProjectContext || buildProjectContext(files)
|
|
35
|
+
if (!projectContext && !cachedProjectContext) {
|
|
36
|
+
cachedProjectContext = context
|
|
37
|
+
console.log('[OpenAI Validation] Built project context:', {
|
|
38
|
+
hasAuthMiddleware: context.auth.hasGlobalMiddleware,
|
|
39
|
+
authProvider: context.auth.authProvider,
|
|
40
|
+
orm: context.dataAccess.orm,
|
|
41
|
+
framework: context.frameworks.primary,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Group findings by file for efficient validation
|
|
46
|
+
const findingsByFile = new Map<string, Vulnerability[]>()
|
|
47
|
+
for (const finding of findings) {
|
|
48
|
+
const existing = findingsByFile.get(finding.filePath) || []
|
|
49
|
+
existing.push(finding)
|
|
50
|
+
findingsByFile.set(finding.filePath, existing)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const validatedFindings: Vulnerability[] = []
|
|
54
|
+
const fileEntries = Array.from(findingsByFile.entries())
|
|
55
|
+
|
|
56
|
+
// Track metrics (thread-safe accumulator)
|
|
57
|
+
let totalApiBatches = 0
|
|
58
|
+
const statsLock: StatsAccumulator = createStatsAccumulator()
|
|
59
|
+
|
|
60
|
+
const totalFileBatches = Math.ceil(fileEntries.length / FILES_PER_API_BATCH)
|
|
61
|
+
console.log(`[OpenAI Validation] Processing ${fileEntries.length} files in ${totalFileBatches} API batch(es) (${PARALLEL_API_BATCHES} parallel)`)
|
|
62
|
+
|
|
63
|
+
// Create all batch definitions
|
|
64
|
+
const allBatches: Array<{
|
|
65
|
+
batchNum: number
|
|
66
|
+
fileBatch: Array<[string, Vulnerability[]]>
|
|
67
|
+
}> = []
|
|
68
|
+
|
|
69
|
+
for (let batchStart = 0; batchStart < fileEntries.length; batchStart += FILES_PER_API_BATCH) {
|
|
70
|
+
const fileBatch = fileEntries.slice(batchStart, batchStart + FILES_PER_API_BATCH)
|
|
71
|
+
const batchNum = Math.floor(batchStart / FILES_PER_API_BATCH) + 1
|
|
72
|
+
allBatches.push({ batchNum, fileBatch })
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Process a single batch - returns validated findings for that batch
|
|
76
|
+
const processBatch = async (
|
|
77
|
+
batchDef: { batchNum: number; fileBatch: Array<[string, Vulnerability[]]> }
|
|
78
|
+
): Promise<Vulnerability[]> => {
|
|
79
|
+
const { batchNum, fileBatch } = batchDef
|
|
80
|
+
const batchFindings: Vulnerability[] = []
|
|
81
|
+
|
|
82
|
+
// Prepare file data for batch request
|
|
83
|
+
const fileDataList: Array<{ file: ScanFile; findings: Vulnerability[]; filePath: string }> = []
|
|
84
|
+
const filesWithoutContent: Array<{ filePath: string; findings: Vulnerability[] }> = []
|
|
85
|
+
|
|
86
|
+
for (const [filePath, fileFindings] of fileBatch) {
|
|
87
|
+
const file = files.find(f => f.path === filePath)
|
|
88
|
+
if (!file) {
|
|
89
|
+
filesWithoutContent.push({ filePath, findings: fileFindings })
|
|
90
|
+
} else {
|
|
91
|
+
fileDataList.push({ file, findings: fileFindings, filePath })
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle files without content
|
|
96
|
+
for (const { findings: fileFindings } of filesWithoutContent) {
|
|
97
|
+
for (const f of fileFindings) {
|
|
98
|
+
batchFindings.push({
|
|
99
|
+
...f,
|
|
100
|
+
validatedByAI: false,
|
|
101
|
+
validationStatus: 'not_validated' as ValidationStatus,
|
|
102
|
+
validationNotes: 'File content not available for validation',
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (fileDataList.length === 0) {
|
|
108
|
+
return batchFindings
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Build multi-file validation request
|
|
113
|
+
const validationRequest = buildMultiFileValidationRequest(
|
|
114
|
+
fileDataList.map(({ file, findings: fileFindings }) => ({ file, findings: fileFindings })),
|
|
115
|
+
context
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Call OpenAI GPT-5-mini with retry logic
|
|
119
|
+
const response = await makeOpenAIRequestWithRetry(async () =>
|
|
120
|
+
client.chat.completions.create({
|
|
121
|
+
model: 'gpt-5-mini-2025-08-07',
|
|
122
|
+
messages: [
|
|
123
|
+
{ role: 'system', content: HIGH_CONTEXT_VALIDATION_PROMPT },
|
|
124
|
+
{ role: 'user', content: validationRequest },
|
|
125
|
+
],
|
|
126
|
+
max_completion_tokens: 4096, // Sufficient for larger batches with many findings
|
|
127
|
+
response_format: {
|
|
128
|
+
type: 'json_schema',
|
|
129
|
+
json_schema: {
|
|
130
|
+
name: 'validation_response',
|
|
131
|
+
strict: true,
|
|
132
|
+
schema: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
properties: {
|
|
135
|
+
validations: {
|
|
136
|
+
type: 'array',
|
|
137
|
+
items: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
file: { type: 'string' },
|
|
141
|
+
validations: {
|
|
142
|
+
type: 'array',
|
|
143
|
+
items: {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {
|
|
146
|
+
index: { type: 'number' },
|
|
147
|
+
keep: { type: 'boolean' },
|
|
148
|
+
notes: {
|
|
149
|
+
type: ['string', 'null'],
|
|
150
|
+
default: null
|
|
151
|
+
},
|
|
152
|
+
adjustedSeverity: {
|
|
153
|
+
type: ['string', 'null'],
|
|
154
|
+
enum: ['critical', 'high', 'medium', 'low', 'info', null],
|
|
155
|
+
default: null
|
|
156
|
+
},
|
|
157
|
+
impact: {
|
|
158
|
+
type: ['string', 'null'],
|
|
159
|
+
description: 'Context-aware explanation of why this matters (1-2 sentences)',
|
|
160
|
+
default: null
|
|
161
|
+
},
|
|
162
|
+
fixSuggestion: {
|
|
163
|
+
type: ['string', 'null'],
|
|
164
|
+
description: 'Specific, actionable fix for this code context',
|
|
165
|
+
default: null
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
required: ['index', 'keep', 'notes', 'adjustedSeverity', 'impact', 'fixSuggestion'],
|
|
169
|
+
additionalProperties: false
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
required: ['file', 'validations'],
|
|
174
|
+
additionalProperties: false
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
required: ['validations'],
|
|
179
|
+
additionalProperties: false
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Track API call stats (accumulate to shared stats)
|
|
187
|
+
statsLock.apiCalls++
|
|
188
|
+
|
|
189
|
+
// Extract token usage from OpenAI response
|
|
190
|
+
const usage = response.usage
|
|
191
|
+
if (usage) {
|
|
192
|
+
const promptTokens = usage.prompt_tokens || 0
|
|
193
|
+
const completionTokens = usage.completion_tokens || 0
|
|
194
|
+
const cachedTokens = (usage as any).prompt_tokens_details?.cached_tokens || 0
|
|
195
|
+
const freshInputTokens = promptTokens - cachedTokens
|
|
196
|
+
|
|
197
|
+
statsLock.estimatedInputTokens += freshInputTokens
|
|
198
|
+
statsLock.estimatedOutputTokens += completionTokens
|
|
199
|
+
statsLock.cacheReadTokens += cachedTokens
|
|
200
|
+
|
|
201
|
+
console.log(`[OpenAI] Batch ${batchNum} tokens: ${promptTokens} input (${cachedTokens} cached), ${completionTokens} output`)
|
|
202
|
+
|
|
203
|
+
const freshCost = (freshInputTokens * GPT5_MINI_PRICING.input) / 1_000_000
|
|
204
|
+
const cachedCost = (cachedTokens * GPT5_MINI_PRICING.cached) / 1_000_000
|
|
205
|
+
const outputCost = (completionTokens * GPT5_MINI_PRICING.output) / 1_000_000
|
|
206
|
+
statsLock.estimatedCost += freshCost + cachedCost + outputCost
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Parse response content
|
|
210
|
+
const content = response.choices[0]?.message?.content
|
|
211
|
+
if (!content) {
|
|
212
|
+
for (const { findings: fileFindings } of fileDataList) {
|
|
213
|
+
for (const f of fileFindings) {
|
|
214
|
+
batchFindings.push({
|
|
215
|
+
...f,
|
|
216
|
+
validatedByAI: false,
|
|
217
|
+
validationStatus: 'not_validated' as ValidationStatus,
|
|
218
|
+
validationNotes: 'No valid response from OpenAI',
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return batchFindings
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Parse structured JSON response (with validations wrapper from response_format)
|
|
226
|
+
let parsedContent: any
|
|
227
|
+
try {
|
|
228
|
+
parsedContent = JSON.parse(content)
|
|
229
|
+
console.log(`[OpenAI Debug] Raw parsed content keys:`, Object.keys(parsedContent))
|
|
230
|
+
// Unwrap the validations array if present (from structured output)
|
|
231
|
+
if (parsedContent.validations && Array.isArray(parsedContent.validations)) {
|
|
232
|
+
console.log(`[OpenAI Debug] Unwrapping 'validations' array with ${parsedContent.validations.length} items`)
|
|
233
|
+
parsedContent = parsedContent.validations
|
|
234
|
+
} else if (Array.isArray(parsedContent)) {
|
|
235
|
+
console.log(`[OpenAI Debug] Content is already an array with ${parsedContent.length} items`)
|
|
236
|
+
} else {
|
|
237
|
+
console.log(`[OpenAI Debug] Content structure:`, typeof parsedContent, Array.isArray(parsedContent))
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.warn('[OpenAI] Failed to parse JSON response:', e)
|
|
241
|
+
parsedContent = content
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Parse multi-file response
|
|
245
|
+
const expectedFiles = fileDataList.map(({ filePath }) => filePath)
|
|
246
|
+
const validationResultsMap = parseMultiFileValidationResponse(
|
|
247
|
+
typeof parsedContent === 'string' ? parsedContent : JSON.stringify(parsedContent),
|
|
248
|
+
expectedFiles
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
console.log(`[OpenAI] Batch ${batchNum} parsed ${validationResultsMap.size} file results from ${fileDataList.length} files`)
|
|
252
|
+
if (validationResultsMap.size === 0) {
|
|
253
|
+
console.warn(`[OpenAI] WARNING: No file results parsed! Content type: ${typeof parsedContent}, isArray: ${Array.isArray(parsedContent)}`)
|
|
254
|
+
if (Array.isArray(parsedContent) && parsedContent.length > 0) {
|
|
255
|
+
console.log(`[OpenAI] First item structure:`, Object.keys(parsedContent[0]))
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Log any missing files from the response (these will be REJECTED)
|
|
260
|
+
if (validationResultsMap.size !== fileDataList.length) {
|
|
261
|
+
const missing = fileDataList
|
|
262
|
+
.filter(({ filePath }) => !validationResultsMap.has(filePath))
|
|
263
|
+
.map(({ filePath }) => filePath)
|
|
264
|
+
if (missing.length > 0) {
|
|
265
|
+
console.warn(`[OpenAI] Missing ${missing.length} files from response (will be REJECTED): ${missing.join(', ')}`)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Apply results per file
|
|
270
|
+
for (const { filePath, findings: fileFindings } of fileDataList) {
|
|
271
|
+
const fileResults = validationResultsMap.get(filePath)
|
|
272
|
+
console.log(`[OpenAI] File ${filePath}: ${fileResults?.length || 0} validation results for ${fileFindings.length} findings`)
|
|
273
|
+
|
|
274
|
+
if (!fileResults || fileResults.length === 0) {
|
|
275
|
+
const singleFileResults = parseValidationResponse(content)
|
|
276
|
+
if (singleFileResults.length > 0 && fileDataList.length === 1) {
|
|
277
|
+
const { processed: processedFindings, dismissedCount } = applyValidationResults(fileFindings, singleFileResults)
|
|
278
|
+
statsLock.validatedFindings += processedFindings.length + dismissedCount
|
|
279
|
+
statsLock.dismissedFindings += dismissedCount
|
|
280
|
+
for (const processed of processedFindings) {
|
|
281
|
+
if (processed.validationStatus === 'confirmed') statsLock.confirmedFindings++
|
|
282
|
+
else if (processed.validationStatus === 'downgraded') statsLock.downgradedFindings++
|
|
283
|
+
batchFindings.push(processed)
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// No validation results - REJECT all findings for this file (conservative approach)
|
|
287
|
+
console.warn(`[OpenAI] No validation results for ${filePath} - REJECTING ${fileFindings.length} findings`)
|
|
288
|
+
statsLock.validatedFindings += fileFindings.length
|
|
289
|
+
statsLock.dismissedFindings += fileFindings.length
|
|
290
|
+
// Don't add to batchFindings - findings are rejected
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
const { processed: processedFindings, dismissedCount } = applyValidationResults(fileFindings, fileResults)
|
|
294
|
+
statsLock.validatedFindings += processedFindings.length + dismissedCount
|
|
295
|
+
statsLock.dismissedFindings += dismissedCount
|
|
296
|
+
for (const processed of processedFindings) {
|
|
297
|
+
if (processed.validationStatus === 'confirmed') statsLock.confirmedFindings++
|
|
298
|
+
else if (processed.validationStatus === 'downgraded') statsLock.downgradedFindings++
|
|
299
|
+
batchFindings.push(processed)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(`[OpenAI Validation] Error in batch ${batchNum}:`, error)
|
|
306
|
+
for (const { findings: fileFindings } of fileDataList) {
|
|
307
|
+
for (const f of fileFindings) {
|
|
308
|
+
batchFindings.push({
|
|
309
|
+
...f,
|
|
310
|
+
validatedByAI: false,
|
|
311
|
+
validationStatus: 'not_validated' as ValidationStatus,
|
|
312
|
+
validationNotes: 'Validation failed due to API error',
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return batchFindings
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Process batches in parallel groups
|
|
322
|
+
const startTime = Date.now()
|
|
323
|
+
for (let i = 0; i < allBatches.length; i += PARALLEL_API_BATCHES) {
|
|
324
|
+
const parallelGroup = allBatches.slice(i, i + PARALLEL_API_BATCHES)
|
|
325
|
+
const batchNums = parallelGroup.map(b => b.batchNum).join(', ')
|
|
326
|
+
console.log(`[OpenAI Validation] Processing batches ${batchNums} in parallel`)
|
|
327
|
+
|
|
328
|
+
const results = await Promise.all(parallelGroup.map(processBatch))
|
|
329
|
+
for (const batchResults of results) {
|
|
330
|
+
validatedFindings.push(...batchResults)
|
|
331
|
+
}
|
|
332
|
+
totalApiBatches += parallelGroup.length
|
|
333
|
+
}
|
|
334
|
+
const totalDuration = Date.now() - startTime
|
|
335
|
+
|
|
336
|
+
// Copy accumulated stats back
|
|
337
|
+
stats.apiCalls = statsLock.apiCalls
|
|
338
|
+
stats.estimatedInputTokens = statsLock.estimatedInputTokens
|
|
339
|
+
stats.estimatedOutputTokens = statsLock.estimatedOutputTokens
|
|
340
|
+
stats.cacheReadTokens = statsLock.cacheReadTokens
|
|
341
|
+
stats.estimatedCost = statsLock.estimatedCost
|
|
342
|
+
stats.validatedFindings = statsLock.validatedFindings
|
|
343
|
+
stats.confirmedFindings = statsLock.confirmedFindings
|
|
344
|
+
stats.dismissedFindings = statsLock.dismissedFindings
|
|
345
|
+
stats.downgradedFindings = statsLock.downgradedFindings
|
|
346
|
+
|
|
347
|
+
// Calculate cache hit rate
|
|
348
|
+
const totalCacheableTokens = stats.cacheCreationTokens + stats.cacheReadTokens
|
|
349
|
+
stats.cacheHitRate = totalCacheableTokens > 0
|
|
350
|
+
? stats.cacheReadTokens / totalCacheableTokens
|
|
351
|
+
: 0
|
|
352
|
+
|
|
353
|
+
// Log validation stats
|
|
354
|
+
const avgTimePerFileMs = fileEntries.length > 0
|
|
355
|
+
? (totalDuration / fileEntries.length).toFixed(0)
|
|
356
|
+
: '0'
|
|
357
|
+
const totalDurationSec = (totalDuration / 1000).toFixed(1)
|
|
358
|
+
|
|
359
|
+
console.log(`[OpenAI Validation] Stats:`)
|
|
360
|
+
console.log(` - Total findings: ${stats.totalFindings}`)
|
|
361
|
+
console.log(` - AI validated: ${stats.validatedFindings}`)
|
|
362
|
+
console.log(` - Confirmed: ${stats.confirmedFindings}`)
|
|
363
|
+
console.log(` - Dismissed: ${stats.dismissedFindings}`)
|
|
364
|
+
console.log(` - Downgraded: ${stats.downgradedFindings}`)
|
|
365
|
+
console.log(` - API calls: ${stats.apiCalls}`)
|
|
366
|
+
console.log(` - Performance:`)
|
|
367
|
+
console.log(` - Total duration: ${totalDurationSec}s`)
|
|
368
|
+
console.log(` - Total API batches: ${totalApiBatches}`)
|
|
369
|
+
console.log(` - Avg time per file: ${avgTimePerFileMs}ms`)
|
|
370
|
+
console.log(` - Token usage:`)
|
|
371
|
+
console.log(` - Input (fresh): ${stats.estimatedInputTokens} tokens`)
|
|
372
|
+
console.log(` - Cached: ${stats.cacheReadTokens} tokens`)
|
|
373
|
+
console.log(` - Output: ${stats.estimatedOutputTokens} tokens`)
|
|
374
|
+
console.log(` - Estimated cost: $${stats.estimatedCost.toFixed(4)}`)
|
|
375
|
+
|
|
376
|
+
return { vulnerabilities: validatedFindings, stats }
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Clear cached project context (called after validation complete)
|
|
381
|
+
*/
|
|
382
|
+
export function clearOpenAICache(): void {
|
|
383
|
+
cachedProjectContext = null
|
|
384
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Builders for AI Validation
|
|
3
|
+
*
|
|
4
|
+
* Functions for building validation requests with full file context.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Vulnerability, ScanFile } from '../../types'
|
|
8
|
+
import type { ProjectContext } from '../../utils/project-context-builder'
|
|
9
|
+
import { getFileValidationContext } from '../../utils/project-context-builder'
|
|
10
|
+
import { getLanguageFromPath } from './utils/path-helpers'
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Single-File Request Builder
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a high-context validation request with full file content
|
|
18
|
+
*/
|
|
19
|
+
export function buildHighContextValidationRequest(
|
|
20
|
+
file: ScanFile,
|
|
21
|
+
findings: Vulnerability[],
|
|
22
|
+
projectContext: ProjectContext
|
|
23
|
+
): string {
|
|
24
|
+
// Add line numbers to full file content
|
|
25
|
+
const numberedContent = file.content
|
|
26
|
+
.split('\n')
|
|
27
|
+
.map((line, i) => `${String(i + 1).padStart(4, ' ')} | ${line}`)
|
|
28
|
+
.join('\n')
|
|
29
|
+
|
|
30
|
+
// Build candidate findings list
|
|
31
|
+
const candidatesText = findings.map((f, idx) => {
|
|
32
|
+
return `### Candidate ${idx}
|
|
33
|
+
- **Rule**: ${f.title}
|
|
34
|
+
- **Category**: ${f.category}
|
|
35
|
+
- **Original Severity**: ${f.severity}
|
|
36
|
+
- **Line**: ${f.lineNumber}
|
|
37
|
+
- **Detection Layer**: ${f.layer}
|
|
38
|
+
- **Description**: ${f.description}
|
|
39
|
+
- **Flagged Code**: \`${f.lineContent.trim()}\``
|
|
40
|
+
}).join('\n\n')
|
|
41
|
+
|
|
42
|
+
// Get file-specific context
|
|
43
|
+
const fileContext = getFileValidationContext(file, projectContext)
|
|
44
|
+
|
|
45
|
+
return `## Project Context
|
|
46
|
+
${projectContext.summary}
|
|
47
|
+
|
|
48
|
+
${fileContext}
|
|
49
|
+
|
|
50
|
+
## Full File Content
|
|
51
|
+
\`\`\`${file.language || getLanguageFromPath(file.path)}
|
|
52
|
+
${numberedContent}
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
## Candidate Findings to Validate (${findings.length} total)
|
|
56
|
+
|
|
57
|
+
${candidatesText}
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
Please validate each candidate finding. Return a JSON array with your decision for each.
|
|
62
|
+
Remember: Be AGGRESSIVE in rejecting false positives. Use the full file context and project architecture to make informed decisions.`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Multi-File Request Builder (Phase 2 Optimization)
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build a multi-file validation request (Phase 2 optimization)
|
|
71
|
+
* Batches multiple files into a single API call to reduce overhead
|
|
72
|
+
*/
|
|
73
|
+
export function buildMultiFileValidationRequest(
|
|
74
|
+
fileDataList: Array<{ file: ScanFile; findings: Vulnerability[] }>,
|
|
75
|
+
projectContext: ProjectContext
|
|
76
|
+
): string {
|
|
77
|
+
const filesContent = fileDataList.map(({ file, findings }, fileIndex) => {
|
|
78
|
+
// Add line numbers to full file content
|
|
79
|
+
const numberedContent = file.content
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((line, i) => `${String(i + 1).padStart(4, ' ')} | ${line}`)
|
|
82
|
+
.join('\n')
|
|
83
|
+
|
|
84
|
+
// Build candidate findings list with file-specific indices
|
|
85
|
+
const candidatesText = findings.map((f, idx) => {
|
|
86
|
+
return `### Candidate ${idx}
|
|
87
|
+
- **Rule**: ${f.title}
|
|
88
|
+
- **Category**: ${f.category}
|
|
89
|
+
- **Original Severity**: ${f.severity}
|
|
90
|
+
- **Line**: ${f.lineNumber}
|
|
91
|
+
- **Detection Layer**: ${f.layer}
|
|
92
|
+
- **Description**: ${f.description}
|
|
93
|
+
- **Flagged Code**: \`${f.lineContent.trim()}\``
|
|
94
|
+
}).join('\n\n')
|
|
95
|
+
|
|
96
|
+
// Get file-specific context
|
|
97
|
+
const fileContext = getFileValidationContext(file, projectContext)
|
|
98
|
+
|
|
99
|
+
return `
|
|
100
|
+
================================================================================
|
|
101
|
+
FILE ${fileIndex + 1}: ${file.path}
|
|
102
|
+
================================================================================
|
|
103
|
+
|
|
104
|
+
${fileContext}
|
|
105
|
+
|
|
106
|
+
### Full File Content
|
|
107
|
+
\`\`\`${file.language || getLanguageFromPath(file.path)}
|
|
108
|
+
${numberedContent}
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
### Candidate Findings to Validate (${findings.length} total)
|
|
112
|
+
|
|
113
|
+
${candidatesText}`
|
|
114
|
+
}).join('\n\n')
|
|
115
|
+
|
|
116
|
+
return `## Project Context
|
|
117
|
+
${projectContext.summary}
|
|
118
|
+
|
|
119
|
+
${filesContent}
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Response Format
|
|
124
|
+
|
|
125
|
+
For EACH file, provide a JSON object with the file path and validation results.
|
|
126
|
+
Return a JSON array where each element has:
|
|
127
|
+
- "file": the file path (e.g., "${fileDataList[0]?.file.path || 'path/to/file.ts'}")
|
|
128
|
+
- "validations": array of validation results for that file's candidates
|
|
129
|
+
|
|
130
|
+
Example response format:
|
|
131
|
+
\`\`\`json
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
"file": "src/auth.ts",
|
|
135
|
+
"validations": [
|
|
136
|
+
{ "index": 0, "keep": true, "adjustedSeverity": "medium", "notes": "Protected by middleware" },
|
|
137
|
+
{ "index": 1, "keep": false }
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"file": "src/api.ts",
|
|
142
|
+
"validations": [
|
|
143
|
+
{ "index": 0, "keep": true, "notes": "User input flows to SQL query" }
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
\`\`\`
|
|
148
|
+
|
|
149
|
+
Remember: Be AGGRESSIVE in rejecting false positives. Use the full file context and project architecture to make informed decisions.`
|
|
150
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for AI validation module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Vulnerability, VulnerabilitySeverity, VulnerabilityCategory, ValidationStatus } from '../../types'
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Cost Monitoring Types
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export interface ValidationStats {
|
|
12
|
+
/** Total findings processed (input) */
|
|
13
|
+
totalFindings: number
|
|
14
|
+
/** Findings that went through AI validation */
|
|
15
|
+
validatedFindings: number
|
|
16
|
+
/** Findings confirmed as true positives */
|
|
17
|
+
confirmedFindings: number
|
|
18
|
+
/** Findings dismissed as false positives */
|
|
19
|
+
dismissedFindings: number
|
|
20
|
+
/** Findings with severity adjusted down */
|
|
21
|
+
downgradedFindings: number
|
|
22
|
+
/** Findings auto-dismissed before AI (test files, etc.) */
|
|
23
|
+
autoDismissedFindings: number
|
|
24
|
+
/** Estimated input tokens used */
|
|
25
|
+
estimatedInputTokens: number
|
|
26
|
+
/** Estimated output tokens used */
|
|
27
|
+
estimatedOutputTokens: number
|
|
28
|
+
/** Estimated cost in USD (based on Haiku pricing) */
|
|
29
|
+
estimatedCost: number
|
|
30
|
+
/** Number of API calls made */
|
|
31
|
+
apiCalls: number
|
|
32
|
+
/** Cache creation tokens (first write to cache) */
|
|
33
|
+
cacheCreationTokens: number
|
|
34
|
+
/** Cache read tokens (subsequent reads from cache) */
|
|
35
|
+
cacheReadTokens: number
|
|
36
|
+
/** Cache hit rate (0-1) */
|
|
37
|
+
cacheHitRate: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AIValidationResult {
|
|
41
|
+
vulnerabilities: Vulnerability[]
|
|
42
|
+
stats: ValidationStats
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Validation Types
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
export interface ValidationResult {
|
|
50
|
+
index: number
|
|
51
|
+
keep: boolean
|
|
52
|
+
// Optimized format: single notes field (replaces reason + validationNotes)
|
|
53
|
+
notes?: string // Only for keep=true, concise explanation
|
|
54
|
+
adjustedSeverity?: VulnerabilitySeverity | null
|
|
55
|
+
// Legacy fields for backward compatibility during parsing
|
|
56
|
+
reason?: string
|
|
57
|
+
validationNotes?: string
|
|
58
|
+
// Actionable output fields (PRO-82) - only for kept findings
|
|
59
|
+
impact?: string // Context-aware explanation of why this matters
|
|
60
|
+
fixSuggestion?: string // Specific fix recommendation for this code context
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AIFinding {
|
|
64
|
+
lineNumber: number
|
|
65
|
+
severity: VulnerabilitySeverity
|
|
66
|
+
category: VulnerabilityCategory
|
|
67
|
+
title: string
|
|
68
|
+
description: string
|
|
69
|
+
suggestedFix: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Context Types
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
export interface Layer3Context {
|
|
77
|
+
/** Middleware configuration from project scan */
|
|
78
|
+
middlewareConfig?: {
|
|
79
|
+
hasAuthMiddleware: boolean
|
|
80
|
+
authType?: string
|
|
81
|
+
protectedPaths: string[]
|
|
82
|
+
}
|
|
83
|
+
/** Auth helper context */
|
|
84
|
+
authHelpers?: {
|
|
85
|
+
hasThrowingHelpers: boolean
|
|
86
|
+
summary: string
|
|
87
|
+
}
|
|
88
|
+
/** Additional context string */
|
|
89
|
+
additionalContext?: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// Auto-Dismiss Types
|
|
94
|
+
// ============================================================================
|
|
95
|
+
|
|
96
|
+
export interface AutoDismissRule {
|
|
97
|
+
name: string
|
|
98
|
+
check: (finding: Vulnerability, fileContent?: string) => boolean
|
|
99
|
+
reason: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Stats Accumulator (for thread-safe parallel processing)
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export interface StatsAccumulator {
|
|
107
|
+
apiCalls: number
|
|
108
|
+
estimatedInputTokens: number
|
|
109
|
+
estimatedOutputTokens: number
|
|
110
|
+
cacheReadTokens: number
|
|
111
|
+
estimatedCost: number
|
|
112
|
+
validatedFindings: number
|
|
113
|
+
confirmedFindings: number
|
|
114
|
+
dismissedFindings: number
|
|
115
|
+
downgradedFindings: number
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createStatsAccumulator(): StatsAccumulator {
|
|
119
|
+
return {
|
|
120
|
+
apiCalls: 0,
|
|
121
|
+
estimatedInputTokens: 0,
|
|
122
|
+
estimatedOutputTokens: 0,
|
|
123
|
+
cacheReadTokens: 0,
|
|
124
|
+
estimatedCost: 0,
|
|
125
|
+
validatedFindings: 0,
|
|
126
|
+
confirmedFindings: 0,
|
|
127
|
+
dismissedFindings: 0,
|
|
128
|
+
downgradedFindings: 0,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function createInitialStats(totalFindings: number): ValidationStats {
|
|
133
|
+
return {
|
|
134
|
+
totalFindings,
|
|
135
|
+
validatedFindings: 0,
|
|
136
|
+
confirmedFindings: 0,
|
|
137
|
+
dismissedFindings: 0,
|
|
138
|
+
downgradedFindings: 0,
|
|
139
|
+
autoDismissedFindings: 0,
|
|
140
|
+
estimatedInputTokens: 0,
|
|
141
|
+
estimatedOutputTokens: 0,
|
|
142
|
+
estimatedCost: 0,
|
|
143
|
+
apiCalls: 0,
|
|
144
|
+
cacheCreationTokens: 0,
|
|
145
|
+
cacheReadTokens: 0,
|
|
146
|
+
cacheHitRate: 0,
|
|
147
|
+
}
|
|
148
|
+
}
|