@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,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2: AI Package Hallucination Detection
|
|
3
|
+
* Detects AI-hallucinated and potentially fake package names in imports
|
|
4
|
+
*
|
|
5
|
+
* Background: USENIX research shows ~20% of AI-generated code references
|
|
6
|
+
* packages that don't exist, creating supply chain attack vectors. Attackers
|
|
7
|
+
* can register these fake package names and inject malicious code.
|
|
8
|
+
*
|
|
9
|
+
* Detection Strategy:
|
|
10
|
+
* 1. Known hallucinations database (verified fake packages)
|
|
11
|
+
* 2. Heuristic patterns (suspicious naming conventions)
|
|
12
|
+
* 3. Generic unscoped names that are too vague to be real
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
16
|
+
import {
|
|
17
|
+
isComment,
|
|
18
|
+
isTestOrMockFile,
|
|
19
|
+
isDocumentationFile,
|
|
20
|
+
isScannerOrFixtureFile,
|
|
21
|
+
isExampleDirectory,
|
|
22
|
+
} from '../utils/context-helpers'
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Known Hallucinated Package Database
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Verified fake package names from research and real-world observations
|
|
30
|
+
* These are packages that LLMs frequently suggest but don't exist (or are typosquats)
|
|
31
|
+
*/
|
|
32
|
+
const KNOWN_HALLUCINATED_PACKAGES: Set<string> = new Set([
|
|
33
|
+
// JavaScript/TypeScript - from USENIX research and observation
|
|
34
|
+
'react-charts', // Real: recharts, react-chartjs-2
|
|
35
|
+
'mongo-client', // Real: mongodb
|
|
36
|
+
'postgres-client', // Real: pg, postgres
|
|
37
|
+
'fast-json', // Real: fast-json-stringify
|
|
38
|
+
'node-helpers', // Doesn't exist
|
|
39
|
+
'node-utils', // Doesn't exist
|
|
40
|
+
'easy-utils', // Doesn't exist
|
|
41
|
+
'simple-tools', // Doesn't exist
|
|
42
|
+
'csv-parser-pro', // Real: csv-parser, papaparse
|
|
43
|
+
'express-jwt-auth', // Real: express-jwt, passport-jwt
|
|
44
|
+
'mongoose-connect', // Real: mongoose
|
|
45
|
+
'redis-connect', // Real: redis, ioredis
|
|
46
|
+
'graphql-tools-schema', // Real: @graphql-tools/schema
|
|
47
|
+
'aws-s3-client', // Real: @aws-sdk/client-s3
|
|
48
|
+
'google-cloud-storage', // Real: @google-cloud/storage (scoped)
|
|
49
|
+
'firebase-auth-client', // Real: firebase, firebase-admin
|
|
50
|
+
'stripe-payments', // Real: stripe
|
|
51
|
+
'twilio-sms', // Real: twilio
|
|
52
|
+
'sendgrid-email', // Real: @sendgrid/mail
|
|
53
|
+
'mailchimp-api', // Real: @mailchimp/mailchimp_marketing
|
|
54
|
+
'slack-bot', // Real: @slack/bolt, @slack/web-api
|
|
55
|
+
'discord-bot', // Real: discord.js
|
|
56
|
+
'telegram-bot', // Real: telegraf, node-telegram-bot-api
|
|
57
|
+
'jwt-decode-verify', // Real: jsonwebtoken, jose
|
|
58
|
+
'bcrypt-hash', // Real: bcrypt, bcryptjs
|
|
59
|
+
'uuid-generate', // Real: uuid
|
|
60
|
+
'date-formatter', // Real: date-fns, dayjs, moment
|
|
61
|
+
'image-resize', // Real: sharp, jimp
|
|
62
|
+
'pdf-generator', // Real: pdfkit, pdf-lib
|
|
63
|
+
'excel-parser', // Real: xlsx, exceljs
|
|
64
|
+
'xml-parser-pro', // Real: fast-xml-parser, xml2js
|
|
65
|
+
'yaml-parser', // Real: js-yaml, yaml
|
|
66
|
+
|
|
67
|
+
// Python hallucinated packages
|
|
68
|
+
'easy-flask', // Real: flask
|
|
69
|
+
'simple-api', // Real: fastapi, flask
|
|
70
|
+
'fast-db', // Real: sqlalchemy, databases
|
|
71
|
+
'python-helpers', // Doesn't exist
|
|
72
|
+
'django-helpers', // Doesn't exist
|
|
73
|
+
'flask-helpers', // Doesn't exist
|
|
74
|
+
'numpy-utils', // Doesn't exist
|
|
75
|
+
'pandas-helpers', // Doesn't exist
|
|
76
|
+
'data-parser', // Real: pandas
|
|
77
|
+
'ml-utils', // Doesn't exist
|
|
78
|
+
'ai-tools', // Doesn't exist
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Well-known legitimate packages to avoid false positives
|
|
83
|
+
*/
|
|
84
|
+
const KNOWN_LEGITIMATE_PACKAGES: Set<string> = new Set([
|
|
85
|
+
// Core/popular JS packages
|
|
86
|
+
'react', 'vue', 'angular', 'svelte', 'solid-js',
|
|
87
|
+
'express', 'fastify', 'hono', 'koa', 'nest',
|
|
88
|
+
'next', 'nuxt', 'remix', 'astro', 'gatsby',
|
|
89
|
+
'axios', 'fetch', 'got', 'ky', 'superagent',
|
|
90
|
+
'lodash', 'underscore', 'radash', 'ramda',
|
|
91
|
+
'zod', 'joi', 'yup', 'ajv', 'valibot',
|
|
92
|
+
'dayjs', 'date-fns', 'moment', 'luxon',
|
|
93
|
+
'mongodb', 'mongoose', 'pg', 'mysql2', 'better-sqlite3',
|
|
94
|
+
'prisma', 'drizzle-orm', 'typeorm', 'sequelize', 'knex',
|
|
95
|
+
'redis', 'ioredis',
|
|
96
|
+
'bcrypt', 'bcryptjs', 'argon2',
|
|
97
|
+
'jsonwebtoken', 'jose', 'passport',
|
|
98
|
+
'uuid', 'nanoid', 'cuid', 'ulid',
|
|
99
|
+
'sharp', 'jimp', 'canvas',
|
|
100
|
+
'pdfkit', 'pdf-lib',
|
|
101
|
+
'xlsx', 'exceljs',
|
|
102
|
+
'cheerio', 'puppeteer', 'playwright',
|
|
103
|
+
'winston', 'pino', 'bunyan',
|
|
104
|
+
'dotenv', 'config', 'convict',
|
|
105
|
+
'chalk', 'picocolors', 'kleur',
|
|
106
|
+
'commander', 'yargs', 'meow', 'cac',
|
|
107
|
+
'inquirer', 'prompts',
|
|
108
|
+
'glob', 'fast-glob', 'globby',
|
|
109
|
+
'chokidar', 'nodemon',
|
|
110
|
+
'esbuild', 'vite', 'webpack', 'rollup', 'parcel',
|
|
111
|
+
'jest', 'vitest', 'mocha', 'ava', 'tape',
|
|
112
|
+
'eslint', 'prettier', 'biome',
|
|
113
|
+
'typescript', 'ts-node', 'tsx',
|
|
114
|
+
'openai', 'anthropic',
|
|
115
|
+
'stripe', 'paypal',
|
|
116
|
+
'twilio', 'nodemailer',
|
|
117
|
+
'aws-sdk',
|
|
118
|
+
'firebase', 'firebase-admin',
|
|
119
|
+
'supabase',
|
|
120
|
+
'graphql', 'apollo-server', 'urql',
|
|
121
|
+
'socket.io', 'ws',
|
|
122
|
+
'bullmq', 'bee-queue',
|
|
123
|
+
'csv-parser', 'papaparse',
|
|
124
|
+
'fast-xml-parser', 'xml2js',
|
|
125
|
+
'js-yaml', 'yaml',
|
|
126
|
+
// Real 'simple-' packages
|
|
127
|
+
'simple-git', 'simpl-schema',
|
|
128
|
+
// Real 'fast-' packages
|
|
129
|
+
'fast-json-stringify', 'fastify', 'fast-glob', 'fast-deep-equal', 'fast-xml-parser',
|
|
130
|
+
|
|
131
|
+
// Python packages
|
|
132
|
+
'flask', 'django', 'fastapi', 'starlette', 'tornado',
|
|
133
|
+
'requests', 'httpx', 'aiohttp',
|
|
134
|
+
'numpy', 'pandas', 'scipy', 'matplotlib',
|
|
135
|
+
'scikit-learn', 'tensorflow', 'pytorch', 'keras',
|
|
136
|
+
'sqlalchemy', 'alembic', 'psycopg2', 'asyncpg',
|
|
137
|
+
'celery', 'redis', 'dramatiq',
|
|
138
|
+
'pydantic', 'marshmallow',
|
|
139
|
+
'pytest', 'unittest', 'nose',
|
|
140
|
+
'black', 'flake8', 'mypy', 'ruff',
|
|
141
|
+
'boto3', 'botocore',
|
|
142
|
+
'pillow', 'opencv-python',
|
|
143
|
+
'beautifulsoup4', 'lxml', 'scrapy',
|
|
144
|
+
])
|
|
145
|
+
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// Typosquatting Detection
|
|
148
|
+
// ============================================================================
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Popular packages to check for typosquatting
|
|
152
|
+
*/
|
|
153
|
+
const POPULAR_PACKAGES_FOR_TYPOSQUAT: string[] = [
|
|
154
|
+
// JavaScript/TypeScript core
|
|
155
|
+
'react', 'vue', 'angular', 'svelte', 'solid',
|
|
156
|
+
'express', 'fastify', 'koa', 'hono', 'nest',
|
|
157
|
+
'next', 'nuxt', 'remix', 'gatsby', 'astro',
|
|
158
|
+
'lodash', 'axios', 'moment', 'dayjs', 'zod',
|
|
159
|
+
'mongoose', 'sequelize', 'prisma', 'typeorm', 'knex',
|
|
160
|
+
'webpack', 'rollup', 'vite', 'esbuild', 'parcel',
|
|
161
|
+
'jest', 'vitest', 'mocha', 'chai', 'cypress',
|
|
162
|
+
'typescript', 'eslint', 'prettier', 'babel',
|
|
163
|
+
'redux', 'mobx', 'zustand', 'jotai', 'recoil',
|
|
164
|
+
'tailwindcss', 'bootstrap', 'antd', 'material-ui',
|
|
165
|
+
'socket', 'graphql', 'apollo', 'trpc',
|
|
166
|
+
// Python core
|
|
167
|
+
'requests', 'flask', 'django', 'fastapi', 'tornado',
|
|
168
|
+
'numpy', 'pandas', 'scipy', 'matplotlib', 'seaborn',
|
|
169
|
+
'tensorflow', 'pytorch', 'keras', 'scikit-learn',
|
|
170
|
+
'sqlalchemy', 'celery', 'redis', 'boto3',
|
|
171
|
+
'pydantic', 'pytest', 'black', 'ruff',
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Common character substitutions used in typosquatting
|
|
176
|
+
*/
|
|
177
|
+
const TYPOSQUAT_SUBSTITUTIONS: Array<[string, string]> = [
|
|
178
|
+
['0', 'o'], ['o', '0'],
|
|
179
|
+
['1', 'l'], ['l', '1'], ['1', 'i'], ['i', '1'],
|
|
180
|
+
['5', 's'], ['s', '5'],
|
|
181
|
+
['a', '@'], ['@', 'a'],
|
|
182
|
+
['e', '3'], ['3', 'e'],
|
|
183
|
+
['rn', 'm'], ['m', 'rn'],
|
|
184
|
+
['vv', 'w'], ['w', 'vv'],
|
|
185
|
+
['cl', 'd'], ['d', 'cl'],
|
|
186
|
+
['ii', 'u'], ['u', 'ii'],
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Calculate Levenshtein distance between two strings
|
|
191
|
+
*/
|
|
192
|
+
function levenshteinDistance(a: string, b: string): number {
|
|
193
|
+
const matrix: number[][] = []
|
|
194
|
+
|
|
195
|
+
for (let i = 0; i <= b.length; i++) {
|
|
196
|
+
matrix[i] = [i]
|
|
197
|
+
}
|
|
198
|
+
for (let j = 0; j <= a.length; j++) {
|
|
199
|
+
matrix[0][j] = j
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (let i = 1; i <= b.length; i++) {
|
|
203
|
+
for (let j = 1; j <= a.length; j++) {
|
|
204
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
205
|
+
matrix[i][j] = matrix[i - 1][j - 1]
|
|
206
|
+
} else {
|
|
207
|
+
matrix[i][j] = Math.min(
|
|
208
|
+
matrix[i - 1][j - 1] + 1,
|
|
209
|
+
matrix[i][j - 1] + 1,
|
|
210
|
+
matrix[i - 1][j] + 1
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return matrix[b.length][a.length]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check if package name has character substitutions matching a popular package
|
|
221
|
+
*/
|
|
222
|
+
function hasCharacterSubstitution(packageName: string, popularPackage: string): boolean {
|
|
223
|
+
// Apply each substitution to the popular package and check for match
|
|
224
|
+
for (const [from, to] of TYPOSQUAT_SUBSTITUTIONS) {
|
|
225
|
+
const substituted = popularPackage.replace(new RegExp(from, 'g'), to)
|
|
226
|
+
if (substituted.toLowerCase() === packageName.toLowerCase() && substituted !== popularPackage) {
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Check if package is a potential typosquat of a popular package
|
|
235
|
+
* Returns the popular package it resembles and the reason
|
|
236
|
+
*/
|
|
237
|
+
function checkTyposquatting(packageName: string): { isTyposquat: boolean; similarTo?: string; reason?: string } {
|
|
238
|
+
const name = packageName.toLowerCase()
|
|
239
|
+
|
|
240
|
+
for (const popular of POPULAR_PACKAGES_FOR_TYPOSQUAT) {
|
|
241
|
+
const popularLower = popular.toLowerCase()
|
|
242
|
+
|
|
243
|
+
// Skip exact match
|
|
244
|
+
if (name === popularLower) continue
|
|
245
|
+
|
|
246
|
+
// Check Levenshtein distance (1-2 chars difference)
|
|
247
|
+
const distance = levenshteinDistance(name, popularLower)
|
|
248
|
+
|
|
249
|
+
if (distance === 1) {
|
|
250
|
+
return {
|
|
251
|
+
isTyposquat: true,
|
|
252
|
+
similarTo: popular,
|
|
253
|
+
reason: `differs by only 1 character from "${popular}"`,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (distance === 2 && name.length >= 5 && Math.abs(name.length - popularLower.length) <= 1) {
|
|
258
|
+
return {
|
|
259
|
+
isTyposquat: true,
|
|
260
|
+
similarTo: popular,
|
|
261
|
+
reason: `very similar to "${popular}" (2 char difference)`,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Check character substitution
|
|
266
|
+
if (hasCharacterSubstitution(name, popularLower)) {
|
|
267
|
+
return {
|
|
268
|
+
isTyposquat: true,
|
|
269
|
+
similarTo: popular,
|
|
270
|
+
reason: `uses character substitution similar to "${popular}" (e.g., 0↔o, 1↔l)`,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check for doubled characters (lodaash vs lodash)
|
|
275
|
+
const doubledPattern = new RegExp(`^${popularLower.split('').join('+')}+$`)
|
|
276
|
+
if (doubledPattern.test(name) && name !== popularLower) {
|
|
277
|
+
return {
|
|
278
|
+
isTyposquat: true,
|
|
279
|
+
similarTo: popular,
|
|
280
|
+
reason: `contains doubled characters similar to "${popular}"`,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check for missing vowels (rqsts vs requests)
|
|
285
|
+
const noVowels = popularLower.replace(/[aeiou]/g, '')
|
|
286
|
+
const nameNoVowels = name.replace(/[aeiou]/g, '')
|
|
287
|
+
if (noVowels === nameNoVowels && noVowels.length >= 4 && name !== popularLower) {
|
|
288
|
+
return {
|
|
289
|
+
isTyposquat: true,
|
|
290
|
+
similarTo: popular,
|
|
291
|
+
reason: `missing vowels similar to "${popular}"`,
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Check for common prefixes/suffixes that create confusion
|
|
296
|
+
if (name === `${popularLower}-js` || name === `${popularLower}js` ||
|
|
297
|
+
name === `node-${popularLower}` || name === `${popularLower}-node`) {
|
|
298
|
+
return {
|
|
299
|
+
isTyposquat: true,
|
|
300
|
+
similarTo: popular,
|
|
301
|
+
reason: `adds common suffix/prefix that could be confused with "${popular}"`,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { isTyposquat: false }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ============================================================================
|
|
310
|
+
// Suspicious Pattern Definitions
|
|
311
|
+
// ============================================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Unscoped generic names that are too vague to be real packages
|
|
315
|
+
* Real packages have specific names, not generic utility words
|
|
316
|
+
*/
|
|
317
|
+
const GENERIC_UNSCOPED_NAMES: Set<string> = new Set([
|
|
318
|
+
'utils',
|
|
319
|
+
'helpers',
|
|
320
|
+
'common',
|
|
321
|
+
'tools',
|
|
322
|
+
'shared',
|
|
323
|
+
'lib',
|
|
324
|
+
'core',
|
|
325
|
+
'base',
|
|
326
|
+
'main',
|
|
327
|
+
'app',
|
|
328
|
+
'api',
|
|
329
|
+
'data',
|
|
330
|
+
'models',
|
|
331
|
+
'services',
|
|
332
|
+
'modules',
|
|
333
|
+
'components',
|
|
334
|
+
])
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Prefixes that are frequently hallucinated when combined with generic suffixes
|
|
338
|
+
*/
|
|
339
|
+
const SUSPICIOUS_PREFIXES = [
|
|
340
|
+
'easy-',
|
|
341
|
+
'simple-',
|
|
342
|
+
'fast-', // Note: some real packages use this, checked against allowlist
|
|
343
|
+
'quick-',
|
|
344
|
+
'basic-',
|
|
345
|
+
'super-',
|
|
346
|
+
'mega-',
|
|
347
|
+
'ultra-',
|
|
348
|
+
'awesome-',
|
|
349
|
+
'better-',
|
|
350
|
+
'node-', // Note: some real packages use this, but often hallucinated for non-core modules
|
|
351
|
+
'react-', // Note: many real packages, but also many hallucinated
|
|
352
|
+
'vue-', // Note: many real packages, but also many hallucinated
|
|
353
|
+
'express-', // Note: many real packages, but also many hallucinated
|
|
354
|
+
'python-',
|
|
355
|
+
'django-', // Note: some real, but often hallucinated
|
|
356
|
+
'flask-', // Note: some real, but often hallucinated
|
|
357
|
+
]
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Suffixes that indicate potential hallucination when combined with suspicious prefixes
|
|
361
|
+
*/
|
|
362
|
+
const SUSPICIOUS_SUFFIXES = [
|
|
363
|
+
'-utils',
|
|
364
|
+
'-helpers',
|
|
365
|
+
'-tools',
|
|
366
|
+
'-lib',
|
|
367
|
+
'-client', // Often hallucinated for already-named services
|
|
368
|
+
'-sdk', // Often hallucinated
|
|
369
|
+
'-api',
|
|
370
|
+
'-wrapper',
|
|
371
|
+
'-connector',
|
|
372
|
+
'-adapter',
|
|
373
|
+
'-handler',
|
|
374
|
+
'-manager',
|
|
375
|
+
'-service',
|
|
376
|
+
'-pro',
|
|
377
|
+
'-plus',
|
|
378
|
+
'-enhanced',
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Context Detection
|
|
383
|
+
// ============================================================================
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Check if package name is scoped (@org/package)
|
|
387
|
+
* Scoped packages are less likely to be hallucinated (requires npm org)
|
|
388
|
+
*/
|
|
389
|
+
function isScopedPackage(packageName: string): boolean {
|
|
390
|
+
return packageName.startsWith('@')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Check if this is a relative import (./path or ../path)
|
|
395
|
+
*/
|
|
396
|
+
function isRelativeImport(importPath: string): boolean {
|
|
397
|
+
return importPath.startsWith('./') || importPath.startsWith('../') || importPath.startsWith('/')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Check if this is an alias import (@/, ~/, #)
|
|
402
|
+
*/
|
|
403
|
+
function isAliasImport(importPath: string): boolean {
|
|
404
|
+
return /^[@~#]\//.test(importPath)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Check if this is a Node.js built-in module
|
|
409
|
+
*/
|
|
410
|
+
function isNodeBuiltin(packageName: string): boolean {
|
|
411
|
+
const builtins = new Set([
|
|
412
|
+
'fs', 'path', 'http', 'https', 'crypto', 'os', 'url', 'util', 'stream',
|
|
413
|
+
'events', 'buffer', 'querystring', 'child_process', 'cluster', 'dgram',
|
|
414
|
+
'dns', 'net', 'readline', 'repl', 'tls', 'tty', 'v8', 'vm', 'zlib',
|
|
415
|
+
'assert', 'async_hooks', 'console', 'constants', 'domain', 'inspector',
|
|
416
|
+
'module', 'perf_hooks', 'process', 'punycode', 'string_decoder',
|
|
417
|
+
'timers', 'trace_events', 'worker_threads',
|
|
418
|
+
// Node: prefixed
|
|
419
|
+
'node:fs', 'node:path', 'node:http', 'node:https', 'node:crypto',
|
|
420
|
+
'node:os', 'node:url', 'node:util', 'node:stream', 'node:events',
|
|
421
|
+
'node:buffer', 'node:querystring', 'node:child_process', 'node:test',
|
|
422
|
+
])
|
|
423
|
+
return builtins.has(packageName) || packageName.startsWith('node:')
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Check if file is a package manifest
|
|
428
|
+
*/
|
|
429
|
+
function isPackageManifest(filePath: string): boolean {
|
|
430
|
+
const manifestFiles = [
|
|
431
|
+
'package.json',
|
|
432
|
+
'requirements.txt',
|
|
433
|
+
'Pipfile',
|
|
434
|
+
'pyproject.toml',
|
|
435
|
+
'setup.py',
|
|
436
|
+
'Gemfile',
|
|
437
|
+
'go.mod',
|
|
438
|
+
'Cargo.toml',
|
|
439
|
+
'composer.json',
|
|
440
|
+
]
|
|
441
|
+
return manifestFiles.some(f => filePath.endsWith(f))
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Check if package name matches suspicious patterns
|
|
446
|
+
*/
|
|
447
|
+
function isSuspiciousPattern(packageName: string): { suspicious: boolean; reason: string } {
|
|
448
|
+
// Check known hallucinated packages first
|
|
449
|
+
if (KNOWN_HALLUCINATED_PACKAGES.has(packageName)) {
|
|
450
|
+
return { suspicious: true, reason: 'Known hallucinated package from research' }
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Skip known legitimate packages
|
|
454
|
+
if (KNOWN_LEGITIMATE_PACKAGES.has(packageName)) {
|
|
455
|
+
return { suspicious: false, reason: '' }
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check unscoped generic names
|
|
459
|
+
if (GENERIC_UNSCOPED_NAMES.has(packageName)) {
|
|
460
|
+
return { suspicious: true, reason: 'Generic unscoped name - real packages have specific names' }
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Check suspicious prefix + suffix combinations
|
|
464
|
+
for (const prefix of SUSPICIOUS_PREFIXES) {
|
|
465
|
+
if (packageName.startsWith(prefix)) {
|
|
466
|
+
// Check if it has a suspicious suffix too
|
|
467
|
+
for (const suffix of SUSPICIOUS_SUFFIXES) {
|
|
468
|
+
if (packageName.endsWith(suffix)) {
|
|
469
|
+
// Double suspicious - prefix AND suffix
|
|
470
|
+
return {
|
|
471
|
+
suspicious: true,
|
|
472
|
+
reason: `Suspicious pattern: "${prefix}" prefix with "${suffix}" suffix`,
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Just prefix is lower confidence
|
|
478
|
+
const baseName = packageName.slice(prefix.length)
|
|
479
|
+
if (GENERIC_UNSCOPED_NAMES.has(baseName) || baseName.length < 3) {
|
|
480
|
+
return {
|
|
481
|
+
suspicious: true,
|
|
482
|
+
reason: `Suspicious pattern: "${prefix}" prefix with generic name`,
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check if it's just prefix + generic suffix
|
|
489
|
+
for (const suffix of SUSPICIOUS_SUFFIXES) {
|
|
490
|
+
if (packageName.endsWith(suffix)) {
|
|
491
|
+
const baseName = packageName.slice(0, -suffix.length)
|
|
492
|
+
// If the base is very short or generic, flag it
|
|
493
|
+
if (baseName.length <= 2 || GENERIC_UNSCOPED_NAMES.has(baseName)) {
|
|
494
|
+
return {
|
|
495
|
+
suspicious: true,
|
|
496
|
+
reason: `Suspicious pattern: generic name with "${suffix}" suffix`,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { suspicious: false, reason: '' }
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Extract package name from import/require path
|
|
507
|
+
*/
|
|
508
|
+
function extractPackageName(importPath: string): string | null {
|
|
509
|
+
// Skip relative and alias imports
|
|
510
|
+
if (isRelativeImport(importPath) || isAliasImport(importPath)) {
|
|
511
|
+
return null
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Handle scoped packages (@org/package)
|
|
515
|
+
if (importPath.startsWith('@')) {
|
|
516
|
+
const parts = importPath.split('/')
|
|
517
|
+
if (parts.length >= 2) {
|
|
518
|
+
return `${parts[0]}/${parts[1]}`
|
|
519
|
+
}
|
|
520
|
+
return null
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Regular package - get the first part before any /
|
|
524
|
+
const parts = importPath.split('/')
|
|
525
|
+
return parts[0]
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// ============================================================================
|
|
529
|
+
// Import Pattern Matchers
|
|
530
|
+
// ============================================================================
|
|
531
|
+
|
|
532
|
+
interface ImportMatch {
|
|
533
|
+
packageName: string
|
|
534
|
+
lineNumber: number
|
|
535
|
+
lineContent: string
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Extract imports from JavaScript/TypeScript code
|
|
540
|
+
*/
|
|
541
|
+
function extractJSImports(content: string): ImportMatch[] {
|
|
542
|
+
const imports: ImportMatch[] = []
|
|
543
|
+
const lines = content.split('\n')
|
|
544
|
+
|
|
545
|
+
// ES6 import patterns
|
|
546
|
+
const es6ImportRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g
|
|
547
|
+
// require() patterns
|
|
548
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
549
|
+
// Dynamic import
|
|
550
|
+
const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
551
|
+
|
|
552
|
+
let match: RegExpExecArray | null
|
|
553
|
+
|
|
554
|
+
while ((match = es6ImportRegex.exec(content)) !== null) {
|
|
555
|
+
const lineNumber = content.substring(0, match.index).split('\n').length
|
|
556
|
+
const packageName = extractPackageName(match[1])
|
|
557
|
+
if (packageName && !isNodeBuiltin(packageName)) {
|
|
558
|
+
imports.push({
|
|
559
|
+
packageName,
|
|
560
|
+
lineNumber,
|
|
561
|
+
lineContent: lines[lineNumber - 1]?.trim() || '',
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
567
|
+
const lineNumber = content.substring(0, match.index).split('\n').length
|
|
568
|
+
const packageName = extractPackageName(match[1])
|
|
569
|
+
if (packageName && !isNodeBuiltin(packageName)) {
|
|
570
|
+
imports.push({
|
|
571
|
+
packageName,
|
|
572
|
+
lineNumber,
|
|
573
|
+
lineContent: lines[lineNumber - 1]?.trim() || '',
|
|
574
|
+
})
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
579
|
+
const lineNumber = content.substring(0, match.index).split('\n').length
|
|
580
|
+
const packageName = extractPackageName(match[1])
|
|
581
|
+
if (packageName && !isNodeBuiltin(packageName)) {
|
|
582
|
+
imports.push({
|
|
583
|
+
packageName,
|
|
584
|
+
lineNumber,
|
|
585
|
+
lineContent: lines[lineNumber - 1]?.trim() || '',
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return imports
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Extract dependencies from package.json
|
|
595
|
+
*/
|
|
596
|
+
function extractPackageJsonDeps(content: string, lines: string[]): ImportMatch[] {
|
|
597
|
+
const imports: ImportMatch[] = []
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const pkg = JSON.parse(content)
|
|
601
|
+
const allDeps = {
|
|
602
|
+
...pkg.dependencies,
|
|
603
|
+
...pkg.devDependencies,
|
|
604
|
+
...pkg.peerDependencies,
|
|
605
|
+
...pkg.optionalDependencies,
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (const packageName of Object.keys(allDeps)) {
|
|
609
|
+
// Find the line number where this package appears
|
|
610
|
+
const lineIndex = lines.findIndex(line => line.includes(`"${packageName}"`))
|
|
611
|
+
if (lineIndex !== -1) {
|
|
612
|
+
imports.push({
|
|
613
|
+
packageName,
|
|
614
|
+
lineNumber: lineIndex + 1,
|
|
615
|
+
lineContent: lines[lineIndex].trim(),
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
} catch {
|
|
620
|
+
// Invalid JSON, skip
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return imports
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Extract dependencies from requirements.txt
|
|
628
|
+
*/
|
|
629
|
+
function extractRequirementsDeps(_content: string, lines: string[]): ImportMatch[] {
|
|
630
|
+
const imports: ImportMatch[] = []
|
|
631
|
+
|
|
632
|
+
for (let i = 0; i < lines.length; i++) {
|
|
633
|
+
const line = lines[i].trim()
|
|
634
|
+
|
|
635
|
+
// Skip comments and empty lines
|
|
636
|
+
if (!line || line.startsWith('#') || line.startsWith('-')) continue
|
|
637
|
+
|
|
638
|
+
// Extract package name (before ==, >=, <=, ~=, etc.)
|
|
639
|
+
const match = line.match(/^([a-zA-Z0-9_-]+)/)
|
|
640
|
+
if (match) {
|
|
641
|
+
imports.push({
|
|
642
|
+
packageName: match[1].toLowerCase().replace(/_/g, '-'),
|
|
643
|
+
lineNumber: i + 1,
|
|
644
|
+
lineContent: line,
|
|
645
|
+
})
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return imports
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ============================================================================
|
|
653
|
+
// Main Detection Function
|
|
654
|
+
// ============================================================================
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Main detection function for AI package hallucination
|
|
658
|
+
*/
|
|
659
|
+
export function detectAIPackageHallucination(
|
|
660
|
+
content: string,
|
|
661
|
+
filePath: string
|
|
662
|
+
): Vulnerability[] {
|
|
663
|
+
const vulnerabilities: Vulnerability[] = []
|
|
664
|
+
|
|
665
|
+
// Skip non-applicable files
|
|
666
|
+
if (isScannerOrFixtureFile(filePath)) return vulnerabilities
|
|
667
|
+
if (isDocumentationFile(filePath)) return vulnerabilities
|
|
668
|
+
|
|
669
|
+
const lines = content.split('\n')
|
|
670
|
+
const isTestFile = isTestOrMockFile(filePath)
|
|
671
|
+
const isExample = isExampleDirectory(filePath)
|
|
672
|
+
const isManifest = isPackageManifest(filePath)
|
|
673
|
+
|
|
674
|
+
// Extract imports based on file type
|
|
675
|
+
let imports: ImportMatch[] = []
|
|
676
|
+
|
|
677
|
+
if (filePath.endsWith('package.json')) {
|
|
678
|
+
imports = extractPackageJsonDeps(content, lines)
|
|
679
|
+
} else if (filePath.endsWith('requirements.txt')) {
|
|
680
|
+
imports = extractRequirementsDeps(content, lines)
|
|
681
|
+
} else if (/\.(js|jsx|ts|tsx|mjs|cjs)$/.test(filePath)) {
|
|
682
|
+
imports = extractJSImports(content)
|
|
683
|
+
} else {
|
|
684
|
+
// Not a file we can analyze for imports
|
|
685
|
+
return vulnerabilities
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Track already-flagged packages to avoid duplicates
|
|
689
|
+
const flaggedPackages = new Set<string>()
|
|
690
|
+
|
|
691
|
+
for (const imp of imports) {
|
|
692
|
+
// Skip if we've already flagged this package
|
|
693
|
+
if (flaggedPackages.has(imp.packageName)) continue
|
|
694
|
+
|
|
695
|
+
// Skip scoped packages (less likely to be hallucinated)
|
|
696
|
+
if (isScopedPackage(imp.packageName)) continue
|
|
697
|
+
|
|
698
|
+
// Skip comments
|
|
699
|
+
if (isComment(imp.lineContent)) continue
|
|
700
|
+
|
|
701
|
+
// Skip known legitimate packages for typosquat check
|
|
702
|
+
if (KNOWN_LEGITIMATE_PACKAGES.has(imp.packageName)) continue
|
|
703
|
+
|
|
704
|
+
// Check for typosquatting first (higher priority - supply chain attack)
|
|
705
|
+
const typosquatResult = checkTyposquatting(imp.packageName)
|
|
706
|
+
if (typosquatResult.isTyposquat) {
|
|
707
|
+
flaggedPackages.add(imp.packageName)
|
|
708
|
+
|
|
709
|
+
let severity: VulnerabilitySeverity = 'high' // Typosquats are always high priority
|
|
710
|
+
|
|
711
|
+
// Package manifests in production are critical
|
|
712
|
+
if (isManifest) {
|
|
713
|
+
severity = 'critical'
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Test files and examples get downgraded
|
|
717
|
+
if (isTestFile || isExample) {
|
|
718
|
+
severity = 'low'
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const description = `Package "${imp.packageName}" ${typosquatResult.reason}. This could be a typosquatting attack where attackers register similar package names to steal credentials or inject malicious code.`
|
|
722
|
+
const suggestedFix = `Verify you meant to use "${typosquatResult.similarTo}". Run "npm view ${imp.packageName}" to check if this package exists. If it doesn't, update to "${typosquatResult.similarTo}".`
|
|
723
|
+
|
|
724
|
+
vulnerabilities.push({
|
|
725
|
+
id: `ai-pkg-typosquat-${filePath}-${imp.lineNumber}-${imp.packageName}`,
|
|
726
|
+
filePath,
|
|
727
|
+
lineNumber: imp.lineNumber,
|
|
728
|
+
lineContent: imp.lineContent,
|
|
729
|
+
severity,
|
|
730
|
+
category: 'ai_package_typosquat',
|
|
731
|
+
title: `Potential typosquat: ${imp.packageName} (similar to ${typosquatResult.similarTo})`,
|
|
732
|
+
description,
|
|
733
|
+
suggestedFix,
|
|
734
|
+
confidence: 'high',
|
|
735
|
+
layer: 2,
|
|
736
|
+
requiresAIValidation: false, // Typosquats don't need AI validation - pattern is clear
|
|
737
|
+
})
|
|
738
|
+
continue // Don't also flag as hallucination
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Check if package is suspicious (hallucination patterns)
|
|
742
|
+
const { suspicious, reason } = isSuspiciousPattern(imp.packageName)
|
|
743
|
+
|
|
744
|
+
if (suspicious) {
|
|
745
|
+
flaggedPackages.add(imp.packageName)
|
|
746
|
+
|
|
747
|
+
// Determine severity based on context
|
|
748
|
+
let severity: VulnerabilitySeverity = 'medium'
|
|
749
|
+
|
|
750
|
+
// Known hallucinations are higher severity
|
|
751
|
+
if (KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)) {
|
|
752
|
+
severity = 'high'
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Package manifests are higher severity (direct dependency)
|
|
756
|
+
if (isManifest && severity === 'medium') {
|
|
757
|
+
severity = 'high'
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Test files and examples get downgraded
|
|
761
|
+
if (isTestFile || isExample) {
|
|
762
|
+
severity = 'info'
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const description = KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)
|
|
766
|
+
? `Package "${imp.packageName}" is a known AI-hallucinated package that doesn't exist. This creates a supply chain attack vector where attackers register the fake package name.`
|
|
767
|
+
: `Package "${imp.packageName}" matches suspicious hallucination patterns: ${reason}. Verify this package exists on npm/PyPI before using.`
|
|
768
|
+
|
|
769
|
+
const suggestedFix = KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName)
|
|
770
|
+
? `Remove "${imp.packageName}" and use the correct package. Search npm/PyPI for the real package that provides this functionality.`
|
|
771
|
+
: `Verify "${imp.packageName}" exists: run "npm view ${imp.packageName}" or check https://www.npmjs.com/package/${imp.packageName}. If it doesn't exist, find the correct package name.`
|
|
772
|
+
|
|
773
|
+
vulnerabilities.push({
|
|
774
|
+
id: `ai-pkg-hallucination-${filePath}-${imp.lineNumber}-${imp.packageName}`,
|
|
775
|
+
filePath,
|
|
776
|
+
lineNumber: imp.lineNumber,
|
|
777
|
+
lineContent: imp.lineContent,
|
|
778
|
+
severity,
|
|
779
|
+
category: 'ai_package_hallucination',
|
|
780
|
+
title: `Potentially hallucinated package: ${imp.packageName}`,
|
|
781
|
+
description,
|
|
782
|
+
suggestedFix,
|
|
783
|
+
confidence: KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName) ? 'high' : 'medium',
|
|
784
|
+
layer: 2,
|
|
785
|
+
requiresAIValidation: severity !== 'info' && !KNOWN_HALLUCINATED_PACKAGES.has(imp.packageName),
|
|
786
|
+
})
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return vulnerabilities
|
|
791
|
+
}
|