@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,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Types
|
|
3
|
+
* Types for baseline/diff mode functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VulnerabilityCategory, VulnerabilitySeverity, SeverityCounts, ScanDepth } from '../types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A finding stored in the baseline
|
|
10
|
+
* Contains enough information to identify and display the finding
|
|
11
|
+
*/
|
|
12
|
+
export interface BaselineFinding {
|
|
13
|
+
/** Finding hash (from computeFindingHash) */
|
|
14
|
+
hash: string
|
|
15
|
+
/** File path relative to project root */
|
|
16
|
+
filePath: string
|
|
17
|
+
/** Line number in the file */
|
|
18
|
+
lineNumber: number
|
|
19
|
+
/** Vulnerability category */
|
|
20
|
+
category: VulnerabilityCategory
|
|
21
|
+
/** Severity level */
|
|
22
|
+
severity: VulnerabilitySeverity
|
|
23
|
+
/** Finding title */
|
|
24
|
+
title: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Baseline data stored in .oculum/baseline.json
|
|
29
|
+
*/
|
|
30
|
+
export interface BaselineData {
|
|
31
|
+
/** Schema version for forward compatibility */
|
|
32
|
+
version: 1
|
|
33
|
+
/** ISO 8601 timestamp when baseline was created */
|
|
34
|
+
createdAt: string
|
|
35
|
+
/** Git commit SHA when baseline was created (optional) */
|
|
36
|
+
commit?: string
|
|
37
|
+
/** Git branch name when baseline was created (optional) */
|
|
38
|
+
branch?: string
|
|
39
|
+
/** Scan depth used when creating baseline */
|
|
40
|
+
scanDepth?: ScanDepth
|
|
41
|
+
/** List of findings in the baseline */
|
|
42
|
+
findings: BaselineFinding[]
|
|
43
|
+
/** Summary statistics */
|
|
44
|
+
stats: {
|
|
45
|
+
total: number
|
|
46
|
+
critical: number
|
|
47
|
+
high: number
|
|
48
|
+
medium: number
|
|
49
|
+
low: number
|
|
50
|
+
info: number
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Result of comparing current findings against baseline
|
|
56
|
+
*/
|
|
57
|
+
export interface DiffResult {
|
|
58
|
+
/** Findings in current scan but NOT in baseline (new issues) */
|
|
59
|
+
new: import('../types').Vulnerability[]
|
|
60
|
+
/** Findings in baseline but NOT in current scan (fixed issues) */
|
|
61
|
+
fixed: BaselineFinding[]
|
|
62
|
+
/** Findings in both current scan AND baseline (existing issues) */
|
|
63
|
+
existing: import('../types').Vulnerability[]
|
|
64
|
+
/** Summary statistics */
|
|
65
|
+
stats: {
|
|
66
|
+
newCount: number
|
|
67
|
+
fixedCount: number
|
|
68
|
+
existingCount: number
|
|
69
|
+
newBySeverity: SeverityCounts
|
|
70
|
+
fixedBySeverity: SeverityCounts
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Baseline diff metadata attached to ScanResult
|
|
76
|
+
* Only present when --new flag is used
|
|
77
|
+
*/
|
|
78
|
+
export interface BaselineDiff {
|
|
79
|
+
/** When the baseline was created */
|
|
80
|
+
baselineCreatedAt: string
|
|
81
|
+
/** Git commit of the baseline (if available) */
|
|
82
|
+
baselineCommit?: string
|
|
83
|
+
/** Number of new findings (not in baseline) */
|
|
84
|
+
newCount: number
|
|
85
|
+
/** Number of fixed findings (in baseline, not in current) */
|
|
86
|
+
fixedCount: number
|
|
87
|
+
/** Number of existing findings (in both) */
|
|
88
|
+
existingCount: number
|
|
89
|
+
/** Details of fixed findings for display */
|
|
90
|
+
fixedFindings: BaselineFinding[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Default baseline file path relative to project root */
|
|
94
|
+
export const BASELINE_FILE_PATH = '.oculum/baseline.json'
|
|
95
|
+
|
|
96
|
+
/** Directory for oculum files */
|
|
97
|
+
export const OCULUM_DIR = '.oculum'
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ScanResult, Vulnerability, VulnerabilitySeverity } from '../types'
|
|
7
7
|
import { groupByTheme, getBlockingIssues, GroupedFindings, THEME_CONFIG } from './grouping'
|
|
8
|
+
import { computeFindingHash } from '../suppression/hash'
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* ANSI color codes
|
|
@@ -57,28 +58,101 @@ function severityBadge(severity: VulnerabilitySeverity): string {
|
|
|
57
58
|
return c(style.color, `${style.symbol} ${style.label}`)
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Format options for single finding
|
|
63
|
+
*/
|
|
64
|
+
interface FormatFindingOptions {
|
|
65
|
+
indent?: string
|
|
66
|
+
compact?: boolean
|
|
67
|
+
verbose?: boolean
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
/**
|
|
61
71
|
* Format a single finding for terminal
|
|
72
|
+
* Default: Actionable output with impact, code, and fix steps
|
|
73
|
+
* Compact: Severity + title + location only
|
|
74
|
+
* Verbose: All of the above plus references and validation notes
|
|
62
75
|
*/
|
|
63
|
-
function formatFinding(finding: Vulnerability,
|
|
76
|
+
function formatFinding(finding: Vulnerability, options: FormatFindingOptions = {}): string {
|
|
77
|
+
const { indent = ' ', compact = false, verbose = false } = options
|
|
64
78
|
const badge = severityBadge(finding.severity)
|
|
65
79
|
const location = c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
|
|
80
|
+
const hash = computeFindingHash(finding)
|
|
66
81
|
|
|
82
|
+
// Compact mode: just severity, title, and location
|
|
83
|
+
if (compact) {
|
|
84
|
+
return `${indent}${badge} ${c(colors.bold, finding.title)} ${location}\n`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default actionable output
|
|
67
88
|
let output = `${indent}${badge} ${c(colors.bold, finding.title)}\n`
|
|
68
89
|
output += `${indent} ${location}\n`
|
|
69
|
-
output +=
|
|
90
|
+
output += '\n'
|
|
91
|
+
|
|
92
|
+
// Impact (why this matters) - shown by default
|
|
93
|
+
if (finding.impact) {
|
|
94
|
+
output += `${indent} ${c(colors.yellow + colors.bold, 'Impact:')} ${finding.impact}\n`
|
|
95
|
+
output += '\n'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Code snippet
|
|
99
|
+
if (finding.lineContent && finding.lineContent.trim()) {
|
|
100
|
+
output += `${indent} ${c(colors.dim, 'Code:')} ${c(colors.white, finding.lineContent.trim().substring(0, 80))}${finding.lineContent.trim().length > 80 ? '...' : ''}\n`
|
|
101
|
+
output += '\n'
|
|
102
|
+
}
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
// Fix steps - shown by default (numbered list)
|
|
105
|
+
if (finding.fixSteps && finding.fixSteps.length > 0) {
|
|
106
|
+
output += `${indent} ${c(colors.green + colors.bold, 'Fix:')}\n`
|
|
107
|
+
finding.fixSteps.forEach((step, i) => {
|
|
108
|
+
output += `${indent} ${c(colors.green, `${i + 1}. ${step}`)}\n`
|
|
109
|
+
})
|
|
110
|
+
output += '\n'
|
|
111
|
+
} else if (finding.suggestedFix) {
|
|
112
|
+
// Fallback to legacy suggestedFix field
|
|
72
113
|
output += `${indent} ${c(colors.green, '💡 ' + finding.suggestedFix)}\n`
|
|
114
|
+
output += '\n'
|
|
73
115
|
}
|
|
74
116
|
|
|
117
|
+
// Verbose mode: show additional details
|
|
118
|
+
if (verbose) {
|
|
119
|
+
// Description
|
|
120
|
+
output += `${indent} ${c(colors.dim, finding.description)}\n`
|
|
121
|
+
|
|
122
|
+
// References (OWASP/CWE links)
|
|
123
|
+
if (finding.references && finding.references.length > 0) {
|
|
124
|
+
output += `${indent} ${c(colors.blue, 'References:')}\n`
|
|
125
|
+
finding.references.forEach(ref => {
|
|
126
|
+
output += `${indent} ${c(colors.blue, ` • ${ref}`)}\n`
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Validation notes (if AI validated)
|
|
131
|
+
if (finding.validationNotes) {
|
|
132
|
+
output += `${indent} ${c(colors.dim, `[AI] ${finding.validationNotes}`)}\n`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// AI enhanced indicator
|
|
136
|
+
if (finding.aiEnhanced) {
|
|
137
|
+
output += `${indent} ${c(colors.magenta, '✨ AI-enhanced fix suggestion')}\n`
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Suppress command - always shown
|
|
142
|
+
output += `${indent} ${c(colors.dim, `Suppress: oculum ignore ${hash} --file "${finding.filePath}:${finding.lineNumber}" --reason "..."`)}\n`
|
|
143
|
+
|
|
75
144
|
return output
|
|
76
145
|
}
|
|
77
146
|
|
|
78
147
|
/**
|
|
79
148
|
* Format a group of findings
|
|
80
149
|
*/
|
|
81
|
-
function formatGroup(group: GroupedFindings,
|
|
150
|
+
function formatGroup(group: GroupedFindings, options: {
|
|
151
|
+
maxFindings?: number
|
|
152
|
+
compact?: boolean
|
|
153
|
+
verbose?: boolean
|
|
154
|
+
} = {}): string {
|
|
155
|
+
const { maxFindings = 10, compact = false, verbose = false } = options
|
|
82
156
|
const { theme, themeName, findings, severityCounts } = group
|
|
83
157
|
const config = THEME_CONFIG[theme]
|
|
84
158
|
|
|
@@ -96,7 +170,7 @@ function formatGroup(group: GroupedFindings, maxFindings: number = 10): string {
|
|
|
96
170
|
// Show findings
|
|
97
171
|
const shown = findings.slice(0, maxFindings)
|
|
98
172
|
for (const finding of shown) {
|
|
99
|
-
output += formatFinding(finding) + '\n'
|
|
173
|
+
output += formatFinding(finding, { compact, verbose }) + '\n'
|
|
100
174
|
}
|
|
101
175
|
|
|
102
176
|
// Truncation notice
|
|
@@ -107,6 +181,32 @@ function formatGroup(group: GroupedFindings, maxFindings: number = 10): string {
|
|
|
107
181
|
return output
|
|
108
182
|
}
|
|
109
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Format baseline diff summary
|
|
186
|
+
*/
|
|
187
|
+
function formatDiffSummary(baselineDiff: NonNullable<ScanResult['baselineDiff']>): string {
|
|
188
|
+
let output = ''
|
|
189
|
+
|
|
190
|
+
output += c(colors.bold, 'Baseline Comparison') + '\n'
|
|
191
|
+
output += c(colors.dim, '─'.repeat(40)) + '\n'
|
|
192
|
+
output += ` 🆕 ${c(colors.yellow, `${baselineDiff.newCount} new`)} findings\n`
|
|
193
|
+
output += ` ✅ ${c(colors.green, `${baselineDiff.fixedCount} fixed`)} since baseline\n`
|
|
194
|
+
output += ` 📋 ${c(colors.dim, `${baselineDiff.existingCount} existing`)} (in baseline)\n`
|
|
195
|
+
output += '\n'
|
|
196
|
+
|
|
197
|
+
// Format baseline date
|
|
198
|
+
const baselineDate = new Date(baselineDiff.baselineCreatedAt)
|
|
199
|
+
const dateStr = baselineDate.toLocaleDateString('en-US', {
|
|
200
|
+
year: 'numeric',
|
|
201
|
+
month: 'short',
|
|
202
|
+
day: 'numeric',
|
|
203
|
+
})
|
|
204
|
+
const commitStr = baselineDiff.baselineCommit ? ` (${baselineDiff.baselineCommit})` : ''
|
|
205
|
+
output += c(colors.dim, `Baseline from ${dateStr}${commitStr}`) + '\n\n'
|
|
206
|
+
|
|
207
|
+
return output
|
|
208
|
+
}
|
|
209
|
+
|
|
110
210
|
/**
|
|
111
211
|
* Format full scan result for terminal
|
|
112
212
|
*/
|
|
@@ -114,13 +214,17 @@ export function formatTerminalOutput(result: ScanResult, options: {
|
|
|
114
214
|
maxFindingsPerGroup?: number
|
|
115
215
|
showAllFindings?: boolean
|
|
116
216
|
noColor?: boolean
|
|
217
|
+
compact?: boolean
|
|
218
|
+
verbose?: boolean
|
|
117
219
|
} = {}): string {
|
|
118
220
|
const {
|
|
119
221
|
maxFindingsPerGroup = 10,
|
|
120
222
|
showAllFindings = false,
|
|
223
|
+
compact = false,
|
|
224
|
+
verbose = false,
|
|
121
225
|
} = options
|
|
122
226
|
|
|
123
|
-
const { vulnerabilities, severityCounts, hasBlockingIssues, filesScanned, scanDuration } = result
|
|
227
|
+
const { vulnerabilities, severityCounts, hasBlockingIssues, filesScanned, scanDuration, baselineDiff } = result
|
|
124
228
|
|
|
125
229
|
let output = '\n'
|
|
126
230
|
|
|
@@ -129,6 +233,11 @@ export function formatTerminalOutput(result: ScanResult, options: {
|
|
|
129
233
|
output += c(colors.bold, ' OCULUM SECURITY SCAN RESULTS') + '\n'
|
|
130
234
|
output += c(colors.bold, '═'.repeat(60)) + '\n\n'
|
|
131
235
|
|
|
236
|
+
// Baseline diff summary (if present)
|
|
237
|
+
if (baselineDiff) {
|
|
238
|
+
output += formatDiffSummary(baselineDiff)
|
|
239
|
+
}
|
|
240
|
+
|
|
132
241
|
// Status
|
|
133
242
|
if (hasBlockingIssues) {
|
|
134
243
|
const blocking = severityCounts.critical + severityCounts.high
|
|
@@ -157,7 +266,7 @@ export function formatTerminalOutput(result: ScanResult, options: {
|
|
|
157
266
|
output += c(colors.red, 'These must be fixed before merging:') + '\n\n'
|
|
158
267
|
|
|
159
268
|
for (const finding of blockingIssues.slice(0, 10)) {
|
|
160
|
-
output += formatFinding(finding)
|
|
269
|
+
output += formatFinding(finding, { compact, verbose })
|
|
161
270
|
output += '\n'
|
|
162
271
|
}
|
|
163
272
|
|
|
@@ -182,7 +291,47 @@ export function formatTerminalOutput(result: ScanResult, options: {
|
|
|
182
291
|
if (nonBlocking.length === 0 && blockingIssues.length > 0) continue
|
|
183
292
|
}
|
|
184
293
|
|
|
185
|
-
output += formatGroup(group, maxFindingsPerGroup)
|
|
294
|
+
output += formatGroup(group, { maxFindings: maxFindingsPerGroup, compact, verbose })
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Suppressed findings section (if any)
|
|
298
|
+
if (result.suppressedVulnerabilities && result.suppressedVulnerabilities.length > 0) {
|
|
299
|
+
output += '\n' + c(colors.dim, '─'.repeat(60)) + '\n'
|
|
300
|
+
output += c(colors.dim + colors.bold, 'SUPPRESSED FINDINGS') + '\n'
|
|
301
|
+
output += c(colors.dim, `${result.suppressedVulnerabilities.length} findings suppressed`) + '\n\n'
|
|
302
|
+
|
|
303
|
+
for (const suppressed of result.suppressedVulnerabilities.slice(0, 5)) {
|
|
304
|
+
const typeLabel = suppressed.suppressionType === 'inline' ? 'inline'
|
|
305
|
+
: suppressed.suppressionType === 'config-finding' ? 'config'
|
|
306
|
+
: 'rule'
|
|
307
|
+
output += c(colors.dim, ` ${suppressed.hash.slice(0, 8)} ${suppressed.filePath}:${suppressed.lineNumber}`) + '\n'
|
|
308
|
+
output += c(colors.dim, ` ${suppressed.title}`) + '\n'
|
|
309
|
+
output += c(colors.dim, ` [${typeLabel}] ${suppressed.suppressionReason}`) + '\n'
|
|
310
|
+
if (suppressed.expires) {
|
|
311
|
+
output += c(colors.dim, ` Expires: ${suppressed.expires}`) + '\n'
|
|
312
|
+
}
|
|
313
|
+
output += '\n'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (result.suppressedVulnerabilities.length > 5) {
|
|
317
|
+
output += c(colors.dim, ` ... and ${result.suppressedVulnerabilities.length - 5} more suppressed\n`)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Suppression stats (if any)
|
|
322
|
+
if (result.suppressionStats && (result.suppressionStats.inlineSuppressed > 0 ||
|
|
323
|
+
result.suppressionStats.configFindingSuppressed > 0 ||
|
|
324
|
+
result.suppressionStats.configRuleSuppressed > 0)) {
|
|
325
|
+
const stats = result.suppressionStats
|
|
326
|
+
const parts: string[] = []
|
|
327
|
+
if (stats.inlineSuppressed > 0) parts.push(`${stats.inlineSuppressed} inline`)
|
|
328
|
+
if (stats.configFindingSuppressed > 0) parts.push(`${stats.configFindingSuppressed} config`)
|
|
329
|
+
if (stats.configRuleSuppressed > 0) parts.push(`${stats.configRuleSuppressed} rule`)
|
|
330
|
+
if (stats.expired > 0) parts.push(`${stats.expired} expired`)
|
|
331
|
+
|
|
332
|
+
if (!result.suppressedVulnerabilities) {
|
|
333
|
+
output += '\n' + c(colors.dim, `Suppressed: ${parts.join(', ')}`) + '\n'
|
|
334
|
+
}
|
|
186
335
|
}
|
|
187
336
|
|
|
188
337
|
// Footer
|
|
@@ -192,6 +341,222 @@ export function formatTerminalOutput(result: ScanResult, options: {
|
|
|
192
341
|
return output
|
|
193
342
|
}
|
|
194
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Compact summary options
|
|
346
|
+
*/
|
|
347
|
+
export interface CompactSummaryOptions {
|
|
348
|
+
/** Number findings for reference with show command */
|
|
349
|
+
showNumbers?: boolean
|
|
350
|
+
/** Limit shown per severity (default: 5) */
|
|
351
|
+
maxPerSeverity?: number
|
|
352
|
+
/** Show "Run oculum show..." hint */
|
|
353
|
+
showHint?: boolean
|
|
354
|
+
/** Disable colors */
|
|
355
|
+
noColor?: boolean
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Format compact summary grouped by severity
|
|
360
|
+
* Output format:
|
|
361
|
+
* HIGH (2)
|
|
362
|
+
* 1. SQL injection in src/api/users.ts:42
|
|
363
|
+
* 2. Missing auth in src/api/admin.ts:15
|
|
364
|
+
*/
|
|
365
|
+
export function formatCompactSummary(
|
|
366
|
+
vulnerabilities: Vulnerability[],
|
|
367
|
+
options: CompactSummaryOptions = {}
|
|
368
|
+
): string {
|
|
369
|
+
const {
|
|
370
|
+
showNumbers = true,
|
|
371
|
+
maxPerSeverity = 5,
|
|
372
|
+
showHint = true,
|
|
373
|
+
noColor = false,
|
|
374
|
+
} = options
|
|
375
|
+
|
|
376
|
+
if (vulnerabilities.length === 0) {
|
|
377
|
+
return noColor
|
|
378
|
+
? 'No security issues found.'
|
|
379
|
+
: c(colors.green, '✓ No security issues found.')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Group by severity
|
|
383
|
+
const bySeverity: Record<VulnerabilitySeverity, Vulnerability[]> = {
|
|
384
|
+
critical: [],
|
|
385
|
+
high: [],
|
|
386
|
+
medium: [],
|
|
387
|
+
low: [],
|
|
388
|
+
info: [],
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const v of vulnerabilities) {
|
|
392
|
+
bySeverity[v.severity].push(v)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Build output
|
|
396
|
+
let output = ''
|
|
397
|
+
let globalIndex = 1
|
|
398
|
+
|
|
399
|
+
const severityOrder: VulnerabilitySeverity[] = ['critical', 'high', 'medium', 'low', 'info']
|
|
400
|
+
const severityColors: Record<VulnerabilitySeverity, string> = {
|
|
401
|
+
critical: colors.bgRed + colors.white,
|
|
402
|
+
high: colors.red,
|
|
403
|
+
medium: colors.yellow,
|
|
404
|
+
low: colors.blue,
|
|
405
|
+
info: colors.gray,
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
for (const severity of severityOrder) {
|
|
409
|
+
const findings = bySeverity[severity]
|
|
410
|
+
if (findings.length === 0) continue
|
|
411
|
+
|
|
412
|
+
// Severity header
|
|
413
|
+
const label = severity.toUpperCase()
|
|
414
|
+
const header = noColor
|
|
415
|
+
? `${label} (${findings.length})`
|
|
416
|
+
: c(severityColors[severity] + colors.bold, `${label} (${findings.length})`)
|
|
417
|
+
output += `\n ${header}\n`
|
|
418
|
+
|
|
419
|
+
// Show findings
|
|
420
|
+
const shown = findings.slice(0, maxPerSeverity)
|
|
421
|
+
for (const finding of shown) {
|
|
422
|
+
const num = showNumbers ? `${globalIndex}. ` : ''
|
|
423
|
+
const location = noColor
|
|
424
|
+
? `${finding.filePath}:${finding.lineNumber}`
|
|
425
|
+
: c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
|
|
426
|
+
|
|
427
|
+
output += noColor
|
|
428
|
+
? ` ${num}${finding.title} in ${location}\n`
|
|
429
|
+
: ` ${c(colors.dim, num)}${finding.title} ${c(colors.dim, 'in')} ${location}\n`
|
|
430
|
+
|
|
431
|
+
globalIndex++
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Show truncation notice
|
|
435
|
+
if (findings.length > maxPerSeverity) {
|
|
436
|
+
const more = findings.length - maxPerSeverity
|
|
437
|
+
const truncated = noColor
|
|
438
|
+
? ` ... and ${more} more\n`
|
|
439
|
+
: c(colors.dim, ` ... and ${more} more\n`)
|
|
440
|
+
output += truncated
|
|
441
|
+
globalIndex += more // Increment for hidden findings
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Hint at bottom
|
|
446
|
+
if (showHint && vulnerabilities.length > 0) {
|
|
447
|
+
output += '\n'
|
|
448
|
+
output += noColor
|
|
449
|
+
? "Run 'oculum show 1' for details · 'oculum fix' for suggestions\n"
|
|
450
|
+
: c(colors.dim, "Run 'oculum show 1' for details · 'oculum fix' for suggestions\n")
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return output
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Format a numbered finding list for the show command
|
|
458
|
+
* Returns findings with their numbers for reference
|
|
459
|
+
*/
|
|
460
|
+
export function getNumberedFindings(vulnerabilities: Vulnerability[]): Array<{ number: number; finding: Vulnerability }> {
|
|
461
|
+
return vulnerabilities.map((finding, index) => ({
|
|
462
|
+
number: index + 1,
|
|
463
|
+
finding,
|
|
464
|
+
}))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Format a single finding detail view for the show command
|
|
469
|
+
*/
|
|
470
|
+
export function formatFindingDetail(
|
|
471
|
+
finding: Vulnerability,
|
|
472
|
+
number: number,
|
|
473
|
+
options: { verbose?: boolean; noColor?: boolean } = {}
|
|
474
|
+
): string {
|
|
475
|
+
const { verbose = false, noColor = false } = options
|
|
476
|
+
|
|
477
|
+
let output = ''
|
|
478
|
+
|
|
479
|
+
// Header
|
|
480
|
+
const badge = noColor
|
|
481
|
+
? `[${finding.severity.toUpperCase()}]`
|
|
482
|
+
: severityBadge(finding.severity)
|
|
483
|
+
const title = noColor ? finding.title : c(colors.bold, finding.title)
|
|
484
|
+
output += `\n#${number} ${badge} ${title}\n`
|
|
485
|
+
|
|
486
|
+
// Location
|
|
487
|
+
const location = noColor
|
|
488
|
+
? finding.filePath + ':' + finding.lineNumber
|
|
489
|
+
: c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
|
|
490
|
+
output += ` ${location}\n`
|
|
491
|
+
output += '\n'
|
|
492
|
+
|
|
493
|
+
// Impact
|
|
494
|
+
if (finding.impact) {
|
|
495
|
+
const impactLabel = noColor ? 'Impact:' : c(colors.yellow + colors.bold, 'Impact:')
|
|
496
|
+
output += ` ${impactLabel} ${finding.impact}\n`
|
|
497
|
+
output += '\n'
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Code snippet
|
|
501
|
+
if (finding.lineContent && finding.lineContent.trim()) {
|
|
502
|
+
const codeLabel = noColor ? 'Code:' : c(colors.dim, 'Code:')
|
|
503
|
+
const code = finding.lineContent.trim().substring(0, 100)
|
|
504
|
+
const codeText = noColor ? code : c(colors.white, code)
|
|
505
|
+
output += ` ${codeLabel} ${codeText}${finding.lineContent.trim().length > 100 ? '...' : ''}\n`
|
|
506
|
+
output += '\n'
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Description
|
|
510
|
+
output += noColor
|
|
511
|
+
? ` ${finding.description}\n`
|
|
512
|
+
: ` ${c(colors.dim, finding.description)}\n`
|
|
513
|
+
output += '\n'
|
|
514
|
+
|
|
515
|
+
// Fix steps
|
|
516
|
+
if (finding.fixSteps && finding.fixSteps.length > 0) {
|
|
517
|
+
const fixLabel = noColor ? 'How to fix:' : c(colors.green + colors.bold, 'How to fix:')
|
|
518
|
+
output += ` ${fixLabel}\n`
|
|
519
|
+
finding.fixSteps.forEach((step, i) => {
|
|
520
|
+
const stepText = noColor ? `${i + 1}. ${step}` : c(colors.green, `${i + 1}. ${step}`)
|
|
521
|
+
output += ` ${stepText}\n`
|
|
522
|
+
})
|
|
523
|
+
output += '\n'
|
|
524
|
+
} else if (finding.suggestedFix) {
|
|
525
|
+
const fixLabel = noColor ? 'Suggested fix:' : c(colors.green + colors.bold, 'Suggested fix:')
|
|
526
|
+
output += ` ${fixLabel} ${finding.suggestedFix}\n`
|
|
527
|
+
output += '\n'
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Verbose mode: additional details
|
|
531
|
+
if (verbose) {
|
|
532
|
+
// References
|
|
533
|
+
if (finding.references && finding.references.length > 0) {
|
|
534
|
+
const refLabel = noColor ? 'References:' : c(colors.blue, 'References:')
|
|
535
|
+
output += ` ${refLabel}\n`
|
|
536
|
+
finding.references.forEach(ref => {
|
|
537
|
+
output += noColor
|
|
538
|
+
? ` - ${ref}\n`
|
|
539
|
+
: ` ${c(colors.blue, `- ${ref}`)}\n`
|
|
540
|
+
})
|
|
541
|
+
output += '\n'
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Validation notes
|
|
545
|
+
if (finding.validationNotes) {
|
|
546
|
+
const notesLabel = noColor ? '[AI]' : c(colors.magenta, '[AI]')
|
|
547
|
+
output += ` ${notesLabel} ${finding.validationNotes}\n`
|
|
548
|
+
output += '\n'
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Category and confidence
|
|
552
|
+
output += noColor
|
|
553
|
+
? ` Category: ${finding.category} · Confidence: ${finding.confidence || 'medium'} · Layer: ${finding.layer}\n`
|
|
554
|
+
: c(colors.dim, ` Category: ${finding.category} · Confidence: ${finding.confidence || 'medium'} · Layer: ${finding.layer}\n`)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return output
|
|
558
|
+
}
|
|
559
|
+
|
|
195
560
|
/**
|
|
196
561
|
* Format as simple list (no grouping, no colors)
|
|
197
562
|
*/
|
|
@@ -317,6 +682,76 @@ const RULE_METADATA: Record<string, { name: string; description: string; helpUri
|
|
|
317
682
|
* For integration with GitHub Code Scanning
|
|
318
683
|
*/
|
|
319
684
|
export function formatSARIF(result: ScanResult): object {
|
|
685
|
+
// Build results from active vulnerabilities
|
|
686
|
+
const activeResults = result.vulnerabilities.map((v, index) => ({
|
|
687
|
+
ruleId: v.category,
|
|
688
|
+
ruleIndex: getRuleIndex(result.vulnerabilities, v.category),
|
|
689
|
+
level: mapSeverityToSARIF(v.severity),
|
|
690
|
+
message: {
|
|
691
|
+
text: v.description,
|
|
692
|
+
},
|
|
693
|
+
locations: [{
|
|
694
|
+
physicalLocation: {
|
|
695
|
+
artifactLocation: {
|
|
696
|
+
uri: v.filePath,
|
|
697
|
+
uriBaseId: '%SRCROOT%',
|
|
698
|
+
},
|
|
699
|
+
region: {
|
|
700
|
+
startLine: v.lineNumber,
|
|
701
|
+
startColumn: 1,
|
|
702
|
+
snippet: v.lineContent ? { text: v.lineContent } : undefined,
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
}],
|
|
706
|
+
fingerprints: {
|
|
707
|
+
'oculum/v1': `${v.category}:${v.filePath}:${v.lineNumber}`,
|
|
708
|
+
},
|
|
709
|
+
fixes: v.suggestedFix ? [{
|
|
710
|
+
description: {
|
|
711
|
+
text: v.suggestedFix,
|
|
712
|
+
},
|
|
713
|
+
}] : undefined,
|
|
714
|
+
properties: {
|
|
715
|
+
confidence: v.confidence,
|
|
716
|
+
layer: v.layer,
|
|
717
|
+
},
|
|
718
|
+
}))
|
|
719
|
+
|
|
720
|
+
// Build results from suppressed vulnerabilities (with SARIF suppression state)
|
|
721
|
+
const suppressedResults = (result.suppressedVulnerabilities || []).map((s) => ({
|
|
722
|
+
ruleId: s.category,
|
|
723
|
+
ruleIndex: 0, // Will be resolved by GitHub
|
|
724
|
+
level: mapSeverityToSARIF(s.severity),
|
|
725
|
+
message: {
|
|
726
|
+
text: s.title,
|
|
727
|
+
},
|
|
728
|
+
locations: [{
|
|
729
|
+
physicalLocation: {
|
|
730
|
+
artifactLocation: {
|
|
731
|
+
uri: s.filePath,
|
|
732
|
+
uriBaseId: '%SRCROOT%',
|
|
733
|
+
},
|
|
734
|
+
region: {
|
|
735
|
+
startLine: s.lineNumber,
|
|
736
|
+
startColumn: 1,
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
}],
|
|
740
|
+
fingerprints: {
|
|
741
|
+
'oculum/v1': `${s.category}:${s.filePath}:${s.lineNumber}`,
|
|
742
|
+
'oculum/hash': s.hash,
|
|
743
|
+
},
|
|
744
|
+
suppressions: [{
|
|
745
|
+
kind: s.suppressionType === 'inline' ? 'inSource' : 'external',
|
|
746
|
+
justification: s.suppressionReason,
|
|
747
|
+
state: 'accepted',
|
|
748
|
+
}],
|
|
749
|
+
properties: {
|
|
750
|
+
suppressionType: s.suppressionType,
|
|
751
|
+
expires: s.expires,
|
|
752
|
+
},
|
|
753
|
+
}))
|
|
754
|
+
|
|
320
755
|
return {
|
|
321
756
|
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
322
757
|
version: '2.1.0',
|
|
@@ -330,39 +765,7 @@ export function formatSARIF(result: ScanResult): object {
|
|
|
330
765
|
rules: getUniqueRules(result.vulnerabilities),
|
|
331
766
|
},
|
|
332
767
|
},
|
|
333
|
-
results:
|
|
334
|
-
ruleId: v.category,
|
|
335
|
-
ruleIndex: getRuleIndex(result.vulnerabilities, v.category),
|
|
336
|
-
level: mapSeverityToSARIF(v.severity),
|
|
337
|
-
message: {
|
|
338
|
-
text: v.description,
|
|
339
|
-
},
|
|
340
|
-
locations: [{
|
|
341
|
-
physicalLocation: {
|
|
342
|
-
artifactLocation: {
|
|
343
|
-
uri: v.filePath,
|
|
344
|
-
uriBaseId: '%SRCROOT%',
|
|
345
|
-
},
|
|
346
|
-
region: {
|
|
347
|
-
startLine: v.lineNumber,
|
|
348
|
-
startColumn: 1,
|
|
349
|
-
snippet: v.lineContent ? { text: v.lineContent } : undefined,
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
}],
|
|
353
|
-
fingerprints: {
|
|
354
|
-
'oculum/v1': `${v.category}:${v.filePath}:${v.lineNumber}`,
|
|
355
|
-
},
|
|
356
|
-
fixes: v.suggestedFix ? [{
|
|
357
|
-
description: {
|
|
358
|
-
text: v.suggestedFix,
|
|
359
|
-
},
|
|
360
|
-
}] : undefined,
|
|
361
|
-
properties: {
|
|
362
|
-
confidence: v.confidence,
|
|
363
|
-
layer: v.layer,
|
|
364
|
-
},
|
|
365
|
-
})),
|
|
768
|
+
results: [...activeResults, ...suppressedResults],
|
|
366
769
|
columnKind: 'utf16CodeUnits',
|
|
367
770
|
}],
|
|
368
771
|
}
|