@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
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Execution Sinks Test Fixtures
|
|
3
3
|
* Tests for detecting unsafe execution of LLM-generated code
|
|
4
|
+
*
|
|
5
|
+
* OWASP Reference: LLM02 - Insecure Output Handling
|
|
6
|
+
* Attack Chain: Prompt Injection -> LLM generates malicious output -> Dangerous Sink -> RCE/SQLi/XSS/SSRF
|
|
7
|
+
*
|
|
8
|
+
* Test Groups:
|
|
9
|
+
* 1. Code Execution Sinks (eval, Function, vm)
|
|
10
|
+
* 2. Shell Command Sinks (exec, spawn, child_process)
|
|
11
|
+
* 3. SQL Injection Sinks (query, execute, raw)
|
|
12
|
+
* 4. DOM/XSS Sinks (innerHTML, dangerouslySetInnerHTML)
|
|
13
|
+
* 5. Network/SSRF Sinks (fetch, axios, HTTP clients)
|
|
14
|
+
* 6. Redirect Sinks (res.redirect, window.location)
|
|
15
|
+
* 7. Header Injection Sinks (setHeader, cookie)
|
|
16
|
+
* 8. File System Sinks (readFile, writeFile, path.join)
|
|
17
|
+
* 9. Dynamic Import Sinks (import(), require())
|
|
18
|
+
* 10. Python-Specific Sinks (eval, exec, pickle, subprocess)
|
|
4
19
|
*/
|
|
5
20
|
|
|
6
21
|
import type { TestGroup } from '../../types'
|
|
@@ -10,179 +25,1571 @@ export const aiExecutionSinksTests: TestGroup = {
|
|
|
10
25
|
tier: 'A',
|
|
11
26
|
layer: 2,
|
|
12
27
|
description: 'Detection of unsafe execution of LLM-generated code, SQL, and commands',
|
|
13
|
-
|
|
28
|
+
|
|
14
29
|
truePositives: [
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Test Group 1: Code Execution Sinks
|
|
32
|
+
// ============================================================================
|
|
33
|
+
{
|
|
34
|
+
name: 'Code Execution Sinks - True Positives',
|
|
35
|
+
expectFindings: true,
|
|
36
|
+
expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
|
|
37
|
+
description: 'Code execution sinks that MUST be detected',
|
|
38
|
+
file: {
|
|
39
|
+
path: 'src/api/ai/code-executor.ts',
|
|
40
|
+
content: `
|
|
41
|
+
import OpenAI from 'openai'
|
|
42
|
+
import vm from 'vm'
|
|
43
|
+
|
|
44
|
+
const openai = new OpenAI()
|
|
45
|
+
|
|
46
|
+
// 1. eval() with LLM output - CRITICAL
|
|
47
|
+
export async function evalAICode(prompt: string) {
|
|
48
|
+
const response = await openai.chat.completions.create({
|
|
49
|
+
model: 'gpt-4',
|
|
50
|
+
messages: [{ role: 'user', content: prompt }]
|
|
51
|
+
})
|
|
52
|
+
const code = response.choices[0].message.content
|
|
53
|
+
return eval(code) // CRITICAL: Direct code execution
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Function constructor - CRITICAL
|
|
57
|
+
export async function createAIFunction(prompt: string) {
|
|
58
|
+
const response = await openai.chat.completions.create({
|
|
59
|
+
model: 'gpt-4',
|
|
60
|
+
messages: [{ role: 'user', content: prompt }]
|
|
61
|
+
})
|
|
62
|
+
return new Function(response.choices[0].message.content)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 3. vm.runInContext without proper sandbox - HIGH
|
|
66
|
+
export async function runInVMContext(prompt: string) {
|
|
67
|
+
const response = await openai.chat.completions.create({
|
|
68
|
+
model: 'gpt-4',
|
|
69
|
+
messages: [{ role: 'user', content: prompt }]
|
|
70
|
+
})
|
|
71
|
+
const context = vm.createContext({ console })
|
|
72
|
+
return vm.runInContext(response.choices[0].message.content, context)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 4. setTimeout with string (indirect eval) - HIGH
|
|
76
|
+
export async function delayedExecution(prompt: string) {
|
|
77
|
+
const response = await openai.chat.completions.create({
|
|
78
|
+
model: 'gpt-4',
|
|
79
|
+
messages: [{ role: 'user', content: prompt }]
|
|
80
|
+
})
|
|
81
|
+
setTimeout(response.choices[0].message.content, 1000) // String arg = eval
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 5. Indirect eval via global - CRITICAL
|
|
85
|
+
export async function indirectEval(prompt: string) {
|
|
86
|
+
const response = await openai.chat.completions.create({
|
|
87
|
+
model: 'gpt-4',
|
|
88
|
+
messages: [{ role: 'user', content: prompt }]
|
|
89
|
+
})
|
|
90
|
+
globalThis.eval(response.choices[0].message.content)
|
|
91
|
+
}
|
|
92
|
+
`,
|
|
93
|
+
language: 'typescript',
|
|
94
|
+
size: 1600,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Test Group 2: Shell Command Sinks
|
|
100
|
+
// ============================================================================
|
|
101
|
+
{
|
|
102
|
+
name: 'Shell Command Sinks - True Positives',
|
|
103
|
+
expectFindings: true,
|
|
104
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
105
|
+
description: 'Shell command sinks that MUST be detected',
|
|
106
|
+
file: {
|
|
107
|
+
path: 'src/api/ai/shell-executor.ts',
|
|
108
|
+
content: `
|
|
109
|
+
import OpenAI from 'openai'
|
|
110
|
+
import { exec, execSync, spawn } from 'child_process'
|
|
111
|
+
import execa from 'execa'
|
|
112
|
+
|
|
113
|
+
const openai = new OpenAI()
|
|
114
|
+
|
|
115
|
+
// 1. exec() with LLM command - CRITICAL
|
|
116
|
+
export async function executeAICommand(task: string) {
|
|
117
|
+
const response = await openai.chat.completions.create({
|
|
118
|
+
model: 'gpt-4',
|
|
119
|
+
messages: [{ role: 'user', content: \`Generate a shell command to: \${task}\` }]
|
|
120
|
+
})
|
|
121
|
+
const command = response.choices[0].message.content
|
|
122
|
+
exec(command, (err, stdout) => console.log(stdout))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 2. spawn() with LLM command - CRITICAL
|
|
126
|
+
export async function spawnAIProcess(task: string) {
|
|
127
|
+
const response = await openai.chat.completions.create({
|
|
128
|
+
model: 'gpt-4',
|
|
129
|
+
messages: [{ role: 'user', content: task }]
|
|
130
|
+
})
|
|
131
|
+
const completion = response.choices[0].message
|
|
132
|
+
spawn(completion.content, [])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 3. execSync with interpolation - CRITICAL
|
|
136
|
+
export async function execSyncAI(userPath: string) {
|
|
137
|
+
const response = await openai.chat.completions.create({
|
|
138
|
+
model: 'gpt-4',
|
|
139
|
+
messages: [{ role: 'user', content: \`Process path: \${userPath}\` }]
|
|
140
|
+
})
|
|
141
|
+
execSync(\`ls \${response.choices[0].message.content}\`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 4. child_process.exec with AI - CRITICAL
|
|
145
|
+
export async function childProcessExec(prompt: string) {
|
|
146
|
+
const response = await openai.chat.completions.create({
|
|
147
|
+
model: 'gpt-4',
|
|
148
|
+
messages: [{ role: 'user', content: prompt }]
|
|
149
|
+
})
|
|
150
|
+
const child_process = require('child_process')
|
|
151
|
+
child_process.exec(response.choices[0].message.content)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 5. execa with LLM command - CRITICAL
|
|
155
|
+
export async function execaAI(task: string) {
|
|
156
|
+
const response = await openai.chat.completions.create({
|
|
157
|
+
model: 'gpt-4',
|
|
158
|
+
messages: [{ role: 'user', content: task }]
|
|
159
|
+
})
|
|
160
|
+
await execa(response.choices[0].message.content, [])
|
|
161
|
+
}
|
|
162
|
+
`,
|
|
163
|
+
language: 'typescript',
|
|
164
|
+
size: 1600,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// ============================================================================
|
|
169
|
+
// Test Group 3: SQL Injection Sinks
|
|
170
|
+
// ============================================================================
|
|
171
|
+
{
|
|
172
|
+
name: 'SQL Injection Sinks - True Positives',
|
|
173
|
+
expectFindings: true,
|
|
174
|
+
expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
|
|
175
|
+
description: 'SQL injection sinks that MUST be detected',
|
|
176
|
+
file: {
|
|
177
|
+
path: 'src/api/ai/sql-executor.ts',
|
|
178
|
+
content: `
|
|
179
|
+
import OpenAI from 'openai'
|
|
180
|
+
import { PrismaClient } from '@prisma/client'
|
|
181
|
+
import Sequelize from 'sequelize'
|
|
182
|
+
import knex from 'knex'
|
|
183
|
+
|
|
184
|
+
const openai = new OpenAI()
|
|
185
|
+
const prisma = new PrismaClient()
|
|
186
|
+
const sequelize = new Sequelize('database')
|
|
187
|
+
const db = knex({ client: 'pg' })
|
|
188
|
+
|
|
189
|
+
// 1. Raw query with LLM SQL - CRITICAL
|
|
190
|
+
export async function executeAIQuery(userQuestion: string) {
|
|
191
|
+
const response = await openai.chat.completions.create({
|
|
192
|
+
model: 'gpt-4',
|
|
193
|
+
messages: [{ role: 'system', content: 'Convert to SQL' }, { role: 'user', content: userQuestion }]
|
|
194
|
+
})
|
|
195
|
+
const sql = response.choices[0].message.content
|
|
196
|
+
return db.query(sql)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 2. Template interpolation - CRITICAL
|
|
200
|
+
export async function templateSQLInjection(prompt: string) {
|
|
201
|
+
const response = await openai.chat.completions.create({
|
|
202
|
+
model: 'gpt-4',
|
|
203
|
+
messages: [{ role: 'user', content: prompt }]
|
|
204
|
+
})
|
|
205
|
+
return db.execute(\`SELECT * FROM \${response.choices[0].message.content}\`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 3. Prisma $queryRaw - CRITICAL
|
|
209
|
+
export async function prismaRawQuery(prompt: string) {
|
|
210
|
+
const response = await openai.chat.completions.create({
|
|
211
|
+
model: 'gpt-4',
|
|
212
|
+
messages: [{ role: 'user', content: prompt }]
|
|
213
|
+
})
|
|
214
|
+
const completion = response.choices[0].message
|
|
215
|
+
return prisma.$queryRaw(completion.content)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 4. Sequelize literal - CRITICAL
|
|
219
|
+
export async function sequelizeQuery(prompt: string) {
|
|
220
|
+
const response = await openai.chat.completions.create({
|
|
221
|
+
model: 'gpt-4',
|
|
222
|
+
messages: [{ role: 'user', content: prompt }]
|
|
223
|
+
})
|
|
224
|
+
const generatedSql = response.choices[0].message.content
|
|
225
|
+
return sequelize.query(generatedSql)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 5. Knex raw - CRITICAL
|
|
229
|
+
export async function knexRawQuery(prompt: string) {
|
|
230
|
+
const response = await openai.chat.completions.create({
|
|
231
|
+
model: 'gpt-4',
|
|
232
|
+
messages: [{ role: 'user', content: prompt }]
|
|
233
|
+
})
|
|
234
|
+
const aiQuery = response.choices[0].message.content
|
|
235
|
+
return knex.raw(aiQuery)
|
|
236
|
+
}
|
|
237
|
+
`,
|
|
238
|
+
language: 'typescript',
|
|
239
|
+
size: 1800,
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// Test Group 4: DOM/XSS Sinks
|
|
245
|
+
// ============================================================================
|
|
246
|
+
{
|
|
247
|
+
name: 'DOM XSS Sinks - True Positives',
|
|
248
|
+
expectFindings: true,
|
|
249
|
+
expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
|
|
250
|
+
description: 'DOM/XSS sinks that MUST be detected',
|
|
251
|
+
file: {
|
|
252
|
+
path: 'src/components/ai/chat-renderer.tsx',
|
|
253
|
+
content: `
|
|
254
|
+
import OpenAI from 'openai'
|
|
255
|
+
|
|
256
|
+
const openai = new OpenAI()
|
|
257
|
+
|
|
258
|
+
// 1. innerHTML with LLM content - HIGH
|
|
259
|
+
export async function renderAIContent(prompt: string) {
|
|
260
|
+
const response = await openai.chat.completions.create({
|
|
261
|
+
model: 'gpt-4',
|
|
262
|
+
messages: [{ role: 'user', content: prompt }]
|
|
263
|
+
})
|
|
264
|
+
document.getElementById('content').innerHTML = response.choices[0].message.content
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 2. dangerouslySetInnerHTML - HIGH
|
|
268
|
+
export async function DangerousAIComponent({ prompt }) {
|
|
269
|
+
const response = await openai.chat.completions.create({
|
|
270
|
+
model: 'gpt-4',
|
|
271
|
+
messages: [{ role: 'user', content: prompt }]
|
|
272
|
+
})
|
|
273
|
+
const completion = response.choices[0].message
|
|
274
|
+
return <div dangerouslySetInnerHTML={{ __html: completion.content }} />
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 3. document.write - HIGH
|
|
278
|
+
export async function documentWriteAI(prompt: string) {
|
|
279
|
+
const response = await openai.chat.completions.create({
|
|
280
|
+
model: 'gpt-4',
|
|
281
|
+
messages: [{ role: 'user', content: prompt }]
|
|
282
|
+
})
|
|
283
|
+
document.write(response.choices[0].message.content)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 4. outerHTML - HIGH
|
|
287
|
+
export async function outerHTMLAI(prompt: string) {
|
|
288
|
+
const response = await openai.chat.completions.create({
|
|
289
|
+
model: 'gpt-4',
|
|
290
|
+
messages: [{ role: 'user', content: prompt }]
|
|
291
|
+
})
|
|
292
|
+
const generated = response.choices[0].message.content
|
|
293
|
+
document.getElementById('container').outerHTML = generated
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 5. insertAdjacentHTML - HIGH
|
|
297
|
+
export async function insertAdjacentAI(prompt: string) {
|
|
298
|
+
const response = await openai.chat.completions.create({
|
|
299
|
+
model: 'gpt-4',
|
|
300
|
+
messages: [{ role: 'user', content: prompt }]
|
|
301
|
+
})
|
|
302
|
+
document.body.insertAdjacentHTML('beforeend', response.choices[0].message.content)
|
|
303
|
+
}
|
|
304
|
+
`,
|
|
305
|
+
language: 'typescript',
|
|
306
|
+
size: 1600,
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Test Group 5: Network/SSRF Sinks
|
|
312
|
+
// ============================================================================
|
|
313
|
+
{
|
|
314
|
+
name: 'Network SSRF Sinks - True Positives',
|
|
315
|
+
expectFindings: true,
|
|
316
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
317
|
+
description: 'Network/SSRF sinks that MUST be detected',
|
|
318
|
+
file: {
|
|
319
|
+
path: 'src/api/ai/network-handler.ts',
|
|
320
|
+
content: `
|
|
321
|
+
import OpenAI from 'openai'
|
|
322
|
+
import axios from 'axios'
|
|
323
|
+
import got from 'got'
|
|
324
|
+
|
|
325
|
+
const openai = new OpenAI()
|
|
326
|
+
|
|
327
|
+
// 1. fetch() with LLM URL - CRITICAL
|
|
328
|
+
export async function fetchAIUrl(prompt: string) {
|
|
329
|
+
const response = await openai.chat.completions.create({
|
|
330
|
+
model: 'gpt-4',
|
|
331
|
+
messages: [{ role: 'user', content: prompt }]
|
|
332
|
+
})
|
|
333
|
+
return fetch(response.choices[0].message.content)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 2. axios with LLM URL - CRITICAL
|
|
337
|
+
export async function axiosAIGet(prompt: string) {
|
|
338
|
+
const response = await openai.chat.completions.create({
|
|
339
|
+
model: 'gpt-4',
|
|
340
|
+
messages: [{ role: 'user', content: prompt }]
|
|
341
|
+
})
|
|
342
|
+
const completion = response.choices[0].message
|
|
343
|
+
return axios.get(completion.content)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 3. axios.post with AI URL - CRITICAL
|
|
347
|
+
export async function axiosAIPost(prompt: string, data: any) {
|
|
348
|
+
const response = await openai.chat.completions.create({
|
|
349
|
+
model: 'gpt-4',
|
|
350
|
+
messages: [{ role: 'user', content: prompt }]
|
|
351
|
+
})
|
|
352
|
+
const aiUrl = response.choices[0].message.content
|
|
353
|
+
return axios.post(aiUrl, data)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// 4. got with LLM URL - CRITICAL
|
|
357
|
+
export async function gotAI(prompt: string) {
|
|
358
|
+
const response = await openai.chat.completions.create({
|
|
359
|
+
model: 'gpt-4',
|
|
360
|
+
messages: [{ role: 'user', content: prompt }]
|
|
361
|
+
})
|
|
362
|
+
return got(response.choices[0].message.content)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 5. URL in options object - CRITICAL
|
|
366
|
+
export async function axiosConfigAI(prompt: string) {
|
|
367
|
+
const response = await openai.chat.completions.create({
|
|
368
|
+
model: 'gpt-4',
|
|
369
|
+
messages: [{ role: 'user', content: prompt }]
|
|
370
|
+
})
|
|
371
|
+
const completion = response.choices[0].message
|
|
372
|
+
return axios({ url: completion.content, method: 'POST' })
|
|
373
|
+
}
|
|
374
|
+
`,
|
|
375
|
+
language: 'typescript',
|
|
376
|
+
size: 1500,
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// ============================================================================
|
|
381
|
+
// Test Group 6: Redirect Sinks
|
|
382
|
+
// ============================================================================
|
|
383
|
+
{
|
|
384
|
+
name: 'Redirect Sinks - True Positives',
|
|
385
|
+
expectFindings: true,
|
|
386
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
387
|
+
description: 'Redirect sinks that MUST be detected',
|
|
388
|
+
file: {
|
|
389
|
+
path: 'src/api/ai/redirect-handler.ts',
|
|
390
|
+
content: `
|
|
391
|
+
import OpenAI from 'openai'
|
|
392
|
+
import { redirect } from 'next/navigation'
|
|
393
|
+
|
|
394
|
+
const openai = new OpenAI()
|
|
395
|
+
|
|
396
|
+
// 1. Server redirect with LLM URL - HIGH
|
|
397
|
+
export async function handleAIRedirect(req, res, prompt: string) {
|
|
398
|
+
const response = await openai.chat.completions.create({
|
|
399
|
+
model: 'gpt-4',
|
|
400
|
+
messages: [{ role: 'user', content: prompt }]
|
|
401
|
+
})
|
|
402
|
+
res.redirect(response.choices[0].message.content)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 2. Client navigation - HIGH
|
|
406
|
+
export async function clientNavigateAI(prompt: string) {
|
|
407
|
+
const response = await openai.chat.completions.create({
|
|
408
|
+
model: 'gpt-4',
|
|
409
|
+
messages: [{ role: 'user', content: prompt }]
|
|
410
|
+
})
|
|
411
|
+
const completion = response.choices[0].message
|
|
412
|
+
window.location.href = completion.content
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// 3. location.assign - HIGH
|
|
416
|
+
export async function locationAssignAI(prompt: string) {
|
|
417
|
+
const response = await openai.chat.completions.create({
|
|
418
|
+
model: 'gpt-4',
|
|
419
|
+
messages: [{ role: 'user', content: prompt }]
|
|
420
|
+
})
|
|
421
|
+
const aiUrl = response.choices[0].message.content
|
|
422
|
+
location.assign(aiUrl)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// 4. location.replace - HIGH
|
|
426
|
+
export async function locationReplaceAI(prompt: string) {
|
|
427
|
+
const response = await openai.chat.completions.create({
|
|
428
|
+
model: 'gpt-4',
|
|
429
|
+
messages: [{ role: 'user', content: prompt }]
|
|
430
|
+
})
|
|
431
|
+
const generatedUrl = response.choices[0].message.content
|
|
432
|
+
location.replace(generatedUrl)
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// 5. Next.js redirect - HIGH
|
|
436
|
+
export async function nextRedirectAI(prompt: string) {
|
|
437
|
+
const response = await openai.chat.completions.create({
|
|
438
|
+
model: 'gpt-4',
|
|
439
|
+
messages: [{ role: 'user', content: prompt }]
|
|
440
|
+
})
|
|
441
|
+
redirect(response.choices[0].message.content)
|
|
442
|
+
}
|
|
443
|
+
`,
|
|
444
|
+
language: 'typescript',
|
|
445
|
+
size: 1400,
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
// ============================================================================
|
|
450
|
+
// Test Group 7: Header Injection Sinks
|
|
451
|
+
// ============================================================================
|
|
452
|
+
{
|
|
453
|
+
name: 'Header Injection Sinks - True Positives',
|
|
454
|
+
expectFindings: true,
|
|
455
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
456
|
+
description: 'Header injection sinks that MUST be detected',
|
|
457
|
+
file: {
|
|
458
|
+
path: 'src/api/ai/header-handler.ts',
|
|
459
|
+
content: `
|
|
460
|
+
import OpenAI from 'openai'
|
|
461
|
+
|
|
462
|
+
const openai = new OpenAI()
|
|
463
|
+
|
|
464
|
+
// 1. setHeader with LLM value - HIGH
|
|
465
|
+
export async function setAIHeader(req, res, prompt: string) {
|
|
466
|
+
const response = await openai.chat.completions.create({
|
|
467
|
+
model: 'gpt-4',
|
|
468
|
+
messages: [{ role: 'user', content: prompt }]
|
|
469
|
+
})
|
|
470
|
+
res.setHeader('X-Custom', response.choices[0].message.content)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 2. Set-Cookie with LLM value - HIGH
|
|
474
|
+
export async function setCookieAI(req, res, prompt: string) {
|
|
475
|
+
const response = await openai.chat.completions.create({
|
|
476
|
+
model: 'gpt-4',
|
|
477
|
+
messages: [{ role: 'user', content: prompt }]
|
|
478
|
+
})
|
|
479
|
+
const completion = response.choices[0].message
|
|
480
|
+
res.cookie('session', completion.content)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 3. Header with potential CRLF - HIGH
|
|
484
|
+
export async function headerCRLF(req, res, prompt: string) {
|
|
485
|
+
const response = await openai.chat.completions.create({
|
|
486
|
+
model: 'gpt-4',
|
|
487
|
+
messages: [{ role: 'user', content: prompt }]
|
|
488
|
+
})
|
|
489
|
+
const aiUrl = response.choices[0].message.content
|
|
490
|
+
res.set('Location', aiUrl) // CRLF can inject headers
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 4. Content-Type manipulation - HIGH
|
|
494
|
+
export async function contentTypeAI(req, res, prompt: string) {
|
|
495
|
+
const response = await openai.chat.completions.create({
|
|
496
|
+
model: 'gpt-4',
|
|
497
|
+
messages: [{ role: 'user', content: prompt }]
|
|
498
|
+
})
|
|
499
|
+
res.type(response.choices[0].message.content)
|
|
500
|
+
}
|
|
501
|
+
`,
|
|
502
|
+
language: 'typescript',
|
|
503
|
+
size: 1200,
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
// ============================================================================
|
|
508
|
+
// Test Group 8: File System Sinks
|
|
509
|
+
// ============================================================================
|
|
510
|
+
{
|
|
511
|
+
name: 'File System Sinks - True Positives',
|
|
512
|
+
expectFindings: true,
|
|
513
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
514
|
+
description: 'File system sinks that MUST be detected',
|
|
515
|
+
file: {
|
|
516
|
+
path: 'src/api/ai/file-handler.ts',
|
|
517
|
+
content: `
|
|
518
|
+
import OpenAI from 'openai'
|
|
519
|
+
import fs from 'fs'
|
|
520
|
+
import path from 'path'
|
|
521
|
+
|
|
522
|
+
const openai = new OpenAI()
|
|
523
|
+
|
|
524
|
+
// 1. Path from LLM - readFile - CRITICAL
|
|
525
|
+
export async function readAIFile(prompt: string) {
|
|
526
|
+
const response = await openai.chat.completions.create({
|
|
527
|
+
model: 'gpt-4',
|
|
528
|
+
messages: [{ role: 'user', content: prompt }]
|
|
529
|
+
})
|
|
530
|
+
return fs.readFileSync(response.choices[0].message.content, 'utf-8')
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// 2. writeFile with LLM path - CRITICAL
|
|
534
|
+
export async function writeAIFile(prompt: string, data: string) {
|
|
535
|
+
const response = await openai.chat.completions.create({
|
|
536
|
+
model: 'gpt-4',
|
|
537
|
+
messages: [{ role: 'user', content: prompt }]
|
|
538
|
+
})
|
|
539
|
+
const completion = response.choices[0].message
|
|
540
|
+
fs.writeFileSync(completion.content, data)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 3. Path traversal risk - path.join with LLM - HIGH
|
|
544
|
+
export async function pathJoinAI(prompt: string) {
|
|
545
|
+
const response = await openai.chat.completions.create({
|
|
546
|
+
model: 'gpt-4',
|
|
547
|
+
messages: [{ role: 'user', content: prompt }]
|
|
548
|
+
})
|
|
549
|
+
const file = path.join('/base', response.choices[0].message.content)
|
|
550
|
+
return fs.readFile(file, 'utf-8')
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// 4. Unlink with LLM path - CRITICAL
|
|
554
|
+
export async function deleteAIFile(prompt: string) {
|
|
555
|
+
const response = await openai.chat.completions.create({
|
|
556
|
+
model: 'gpt-4',
|
|
557
|
+
messages: [{ role: 'user', content: prompt }]
|
|
558
|
+
})
|
|
559
|
+
const aiPath = response.choices[0].message.content
|
|
560
|
+
fs.unlinkSync(aiPath)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 5. rmSync with LLM path - CRITICAL
|
|
564
|
+
export async function recursiveDeleteAI(prompt: string) {
|
|
565
|
+
const response = await openai.chat.completions.create({
|
|
566
|
+
model: 'gpt-4',
|
|
567
|
+
messages: [{ role: 'user', content: prompt }]
|
|
568
|
+
})
|
|
569
|
+
const generated = response.choices[0].message.content
|
|
570
|
+
fs.rmSync(generated, { recursive: true })
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 6. mkdir with LLM path - HIGH
|
|
574
|
+
export async function createAIDir(prompt: string) {
|
|
575
|
+
const response = await openai.chat.completions.create({
|
|
576
|
+
model: 'gpt-4',
|
|
577
|
+
messages: [{ role: 'user', content: prompt }]
|
|
578
|
+
})
|
|
579
|
+
fs.mkdirSync(response.choices[0].message.content)
|
|
580
|
+
}
|
|
581
|
+
`,
|
|
582
|
+
language: 'typescript',
|
|
583
|
+
size: 1800,
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
|
|
587
|
+
// ============================================================================
|
|
588
|
+
// Test Group 9: Dynamic Import Sinks
|
|
589
|
+
// ============================================================================
|
|
590
|
+
{
|
|
591
|
+
name: 'Dynamic Import Sinks - True Positives',
|
|
592
|
+
expectFindings: true,
|
|
593
|
+
expectedCategories: ['ai_unsafe_execution'],
|
|
594
|
+
description: 'Dynamic import sinks that MUST be detected',
|
|
595
|
+
file: {
|
|
596
|
+
path: 'src/api/ai/plugin-loader.ts',
|
|
597
|
+
content: `
|
|
598
|
+
import OpenAI from 'openai'
|
|
599
|
+
|
|
600
|
+
const openai = new OpenAI()
|
|
601
|
+
|
|
602
|
+
// 1. import() with LLM module - CRITICAL
|
|
603
|
+
export async function loadAIModule(prompt: string) {
|
|
604
|
+
const response = await openai.chat.completions.create({
|
|
605
|
+
model: 'gpt-4',
|
|
606
|
+
messages: [{ role: 'user', content: prompt }]
|
|
607
|
+
})
|
|
608
|
+
const moduleName = response.choices[0].message.content
|
|
609
|
+
return import(moduleName)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 2. require() with LLM path - CRITICAL
|
|
613
|
+
export async function requireAIPlugin(prompt: string) {
|
|
614
|
+
const response = await openai.chat.completions.create({
|
|
615
|
+
model: 'gpt-4',
|
|
616
|
+
messages: [{ role: 'user', content: prompt }]
|
|
617
|
+
})
|
|
618
|
+
const completion = response.choices[0].message
|
|
619
|
+
return require(completion.content)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// 3. require.resolve - HIGH
|
|
623
|
+
export async function resolveAIModule(prompt: string) {
|
|
624
|
+
const response = await openai.chat.completions.create({
|
|
625
|
+
model: 'gpt-4',
|
|
626
|
+
messages: [{ role: 'user', content: prompt }]
|
|
627
|
+
})
|
|
628
|
+
const moduleName = response.choices[0].message.content
|
|
629
|
+
return require.resolve(moduleName)
|
|
630
|
+
}
|
|
631
|
+
`,
|
|
632
|
+
language: 'typescript',
|
|
633
|
+
size: 900,
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
// ============================================================================
|
|
638
|
+
// Test Group 10: Python-Specific Sinks
|
|
639
|
+
// ============================================================================
|
|
640
|
+
{
|
|
641
|
+
name: 'Python Sinks - True Positives',
|
|
642
|
+
expectFindings: true,
|
|
643
|
+
expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
|
|
644
|
+
description: 'Python-specific sinks that MUST be detected',
|
|
645
|
+
file: {
|
|
646
|
+
path: 'src/api/ai/python_executor.py',
|
|
647
|
+
content: `
|
|
648
|
+
import openai
|
|
649
|
+
import pickle
|
|
650
|
+
import subprocess
|
|
651
|
+
import os
|
|
652
|
+
import sqlite3
|
|
653
|
+
|
|
654
|
+
client = openai.OpenAI()
|
|
655
|
+
|
|
656
|
+
# 1. eval with LLM code - CRITICAL
|
|
657
|
+
def eval_ai_code(prompt: str):
|
|
658
|
+
response = client.chat.completions.create(
|
|
659
|
+
model="gpt-4",
|
|
660
|
+
messages=[{"role": "user", "content": prompt}]
|
|
661
|
+
)
|
|
662
|
+
code = response.choices[0].message.content
|
|
663
|
+
return eval(code)
|
|
664
|
+
|
|
665
|
+
# 2. exec with LLM code - CRITICAL
|
|
666
|
+
def exec_ai_code(prompt: str):
|
|
667
|
+
response = client.chat.completions.create(
|
|
668
|
+
model="gpt-4",
|
|
669
|
+
messages=[{"role": "user", "content": prompt}]
|
|
670
|
+
)
|
|
671
|
+
completion = response.choices[0].message
|
|
672
|
+
exec(completion.content)
|
|
673
|
+
|
|
674
|
+
# 3. pickle.loads with LLM data - CRITICAL
|
|
675
|
+
def unpickle_ai_data(prompt: str):
|
|
676
|
+
response = client.chat.completions.create(
|
|
677
|
+
model="gpt-4",
|
|
678
|
+
messages=[{"role": "user", "content": prompt}]
|
|
679
|
+
)
|
|
680
|
+
serialized = response.choices[0].message.content.encode()
|
|
681
|
+
return pickle.loads(serialized)
|
|
682
|
+
|
|
683
|
+
# 4. subprocess with shell=True - CRITICAL
|
|
684
|
+
def subprocess_ai(prompt: str):
|
|
685
|
+
response = client.chat.completions.create(
|
|
686
|
+
model="gpt-4",
|
|
687
|
+
messages=[{"role": "user", "content": prompt}]
|
|
688
|
+
)
|
|
689
|
+
ai_command = response.choices[0].message.content
|
|
690
|
+
subprocess.run(ai_command, shell=True)
|
|
691
|
+
|
|
692
|
+
# 5. os.system - CRITICAL
|
|
693
|
+
def os_system_ai(prompt: str):
|
|
694
|
+
response = client.chat.completions.create(
|
|
695
|
+
model="gpt-4",
|
|
696
|
+
messages=[{"role": "user", "content": prompt}]
|
|
697
|
+
)
|
|
698
|
+
generated_cmd = response.choices[0].message.content
|
|
699
|
+
os.system(generated_cmd)
|
|
700
|
+
|
|
701
|
+
# 6. SQL with f-string - CRITICAL
|
|
702
|
+
def sql_fstring_ai(prompt: str):
|
|
703
|
+
response = client.chat.completions.create(
|
|
704
|
+
model="gpt-4",
|
|
705
|
+
messages=[{"role": "user", "content": prompt}]
|
|
706
|
+
)
|
|
707
|
+
conn = sqlite3.connect("db.sqlite")
|
|
708
|
+
cursor = conn.cursor()
|
|
709
|
+
cursor.execute(f"SELECT * FROM {response.choices[0].message.content}")
|
|
710
|
+
`,
|
|
711
|
+
language: 'python',
|
|
712
|
+
size: 1600,
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
|
|
717
|
+
falseNegatives: [
|
|
718
|
+
// ============================================================================
|
|
719
|
+
// False Negatives Group 1: Safe Code Execution
|
|
720
|
+
// ============================================================================
|
|
721
|
+
{
|
|
722
|
+
name: 'Safe Code Execution - False Negatives',
|
|
723
|
+
expectFindings: false,
|
|
724
|
+
description: 'Safe code execution patterns that should NOT be flagged',
|
|
725
|
+
allowedInfoFindings: [
|
|
726
|
+
{
|
|
727
|
+
category: 'suspicious_package',
|
|
728
|
+
maxCount: 2,
|
|
729
|
+
reason: 'vm2 and isolated-vm are security sandbox packages',
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
category: 'ai_pattern',
|
|
733
|
+
maxCount: 2,
|
|
734
|
+
reason: 'AI console.log debugging is info-level',
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
file: {
|
|
738
|
+
path: 'src/api/ai/safe-code-executor.ts',
|
|
739
|
+
content: `
|
|
740
|
+
import OpenAI from 'openai'
|
|
741
|
+
import { VM } from 'vm2'
|
|
742
|
+
import ivm from 'isolated-vm'
|
|
743
|
+
import * as acorn from 'acorn'
|
|
744
|
+
|
|
745
|
+
const openai = new OpenAI()
|
|
746
|
+
|
|
747
|
+
// 1. Sandboxed execution with vm2 - SAFE
|
|
748
|
+
export async function executeSandboxed(prompt: string) {
|
|
749
|
+
const response = await openai.chat.completions.create({
|
|
750
|
+
model: 'gpt-4',
|
|
751
|
+
messages: [{ role: 'user', content: prompt }]
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
const vm = new VM({
|
|
755
|
+
timeout: 1000,
|
|
756
|
+
sandbox: {}
|
|
757
|
+
})
|
|
758
|
+
return vm.run(response.choices[0].message.content)
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// 2. Sandboxed execution with isolated-vm - SAFE
|
|
762
|
+
export async function executeIsolated(prompt: string) {
|
|
763
|
+
const response = await openai.chat.completions.create({
|
|
764
|
+
model: 'gpt-4',
|
|
765
|
+
messages: [{ role: 'user', content: prompt }]
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
const isolate = new ivm.Isolate({ memoryLimit: 128 })
|
|
769
|
+
const context = await isolate.createContext()
|
|
770
|
+
const script = await isolate.compileScript(response.choices[0].message.content)
|
|
771
|
+
return script.run(context)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// 3. AST-parsed code (safe transformation) - SAFE
|
|
775
|
+
export async function parseCode(prompt: string) {
|
|
776
|
+
const response = await openai.chat.completions.create({
|
|
777
|
+
model: 'gpt-4',
|
|
778
|
+
messages: [{ role: 'user', content: prompt }]
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
// Only parsing the AST, not executing
|
|
782
|
+
const ast = acorn.parse(response.choices[0].message.content, { ecmaVersion: 2020 })
|
|
783
|
+
return analyzeAST(ast)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// 4. Static strings that contain 'eval' - SAFE
|
|
787
|
+
export function getDocumentation() {
|
|
788
|
+
const docs = "Don't use eval() in production code"
|
|
789
|
+
const warning = "The eval function is dangerous"
|
|
790
|
+
return { docs, warning }
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// 5. Display only - SAFE
|
|
794
|
+
export async function displayAICode(prompt: string) {
|
|
795
|
+
const response = await openai.chat.completions.create({
|
|
796
|
+
model: 'gpt-4',
|
|
797
|
+
messages: [{ role: 'user', content: prompt }]
|
|
798
|
+
})
|
|
799
|
+
|
|
800
|
+
console.log('Generated code:', response.choices[0].message.content)
|
|
801
|
+
return { code: response.choices[0].message.content }
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function analyzeAST(ast: any) {
|
|
805
|
+
return { type: ast.type, bodyLength: ast.body?.length }
|
|
806
|
+
}
|
|
807
|
+
`,
|
|
808
|
+
language: 'typescript',
|
|
809
|
+
size: 1800,
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
|
|
813
|
+
// ============================================================================
|
|
814
|
+
// False Negatives Group 2: Safe Shell Execution
|
|
815
|
+
// ============================================================================
|
|
816
|
+
{
|
|
817
|
+
name: 'Safe Shell Execution - False Negatives',
|
|
818
|
+
expectFindings: false,
|
|
819
|
+
description: 'Safe shell execution patterns that should NOT be flagged',
|
|
820
|
+
allowedInfoFindings: [
|
|
821
|
+
{
|
|
822
|
+
category: 'ai_pattern',
|
|
823
|
+
maxCount: 1,
|
|
824
|
+
reason: 'AI context visible but execution is safe',
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
file: {
|
|
828
|
+
path: 'src/api/ai/safe-shell-executor.ts',
|
|
829
|
+
content: `
|
|
830
|
+
import OpenAI from 'openai'
|
|
831
|
+
import { execFile } from 'child_process'
|
|
832
|
+
|
|
833
|
+
const openai = new OpenAI()
|
|
834
|
+
|
|
835
|
+
// 1. Hardcoded command with AI args (allowlisted) - SAFE
|
|
836
|
+
export async function safeExecFile(prompt: string) {
|
|
837
|
+
const response = await openai.chat.completions.create({
|
|
838
|
+
model: 'gpt-4',
|
|
839
|
+
messages: [{ role: 'user', content: prompt }]
|
|
840
|
+
})
|
|
841
|
+
|
|
842
|
+
const allowedArgs = ['--verbose', '--dry-run', '--help']
|
|
843
|
+
const aiArg = response.choices[0].message.content.trim()
|
|
844
|
+
|
|
845
|
+
if (allowedArgs.includes(aiArg)) {
|
|
846
|
+
execFile('git', ['status', aiArg], (err, stdout) => console.log(stdout))
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// 2. Command palette display (not execution) - SAFE
|
|
851
|
+
export async function getCommandSuggestions(prompt: string) {
|
|
852
|
+
const response = await openai.chat.completions.create({
|
|
853
|
+
model: 'gpt-4',
|
|
854
|
+
messages: [{ role: 'user', content: prompt }]
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
const suggestions = response.choices[0].message.content.split('\\n')
|
|
858
|
+
return suggestions.map(cmd => ({
|
|
859
|
+
id: \`run-\${cmd}\`,
|
|
860
|
+
label: cmd,
|
|
861
|
+
description: 'Suggested command'
|
|
862
|
+
}))
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// 3. Sanitized command argument - SAFE
|
|
866
|
+
export async function sanitizedExec(prompt: string) {
|
|
867
|
+
const response = await openai.chat.completions.create({
|
|
868
|
+
model: 'gpt-4',
|
|
869
|
+
messages: [{ role: 'user', content: prompt }]
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
// Strict sanitization - only alphanumeric
|
|
873
|
+
const sanitized = response.choices[0].message.content.replace(/[^a-z0-9]/gi, '')
|
|
874
|
+
execFile('echo', [sanitized], (err, stdout) => console.log(stdout))
|
|
875
|
+
}
|
|
876
|
+
`,
|
|
877
|
+
language: 'typescript',
|
|
878
|
+
size: 1300,
|
|
879
|
+
},
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
// ============================================================================
|
|
883
|
+
// False Negatives Group 3: Safe SQL Queries
|
|
884
|
+
// ============================================================================
|
|
885
|
+
{
|
|
886
|
+
name: 'Safe SQL Queries - False Negatives',
|
|
887
|
+
expectFindings: false,
|
|
888
|
+
description: 'Safe SQL query patterns that should NOT be flagged',
|
|
889
|
+
allowedInfoFindings: [
|
|
890
|
+
{
|
|
891
|
+
category: 'dangerous_function',
|
|
892
|
+
maxCount: 1,
|
|
893
|
+
reason: 'Parameterized query with whitelist is safe',
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
category: 'ai_pattern',
|
|
897
|
+
maxCount: 1,
|
|
898
|
+
reason: 'AI context visible but query is parameterized',
|
|
899
|
+
},
|
|
900
|
+
],
|
|
901
|
+
file: {
|
|
902
|
+
path: 'src/api/ai/safe-sql-executor.ts',
|
|
903
|
+
content: `
|
|
904
|
+
import OpenAI from 'openai'
|
|
905
|
+
import { PrismaClient } from '@prisma/client'
|
|
906
|
+
|
|
907
|
+
const openai = new OpenAI()
|
|
908
|
+
const prisma = new PrismaClient()
|
|
909
|
+
|
|
910
|
+
// 1. Parameterized query - SAFE
|
|
911
|
+
export async function safeParameterized(userId: string) {
|
|
912
|
+
// AI not involved in query construction
|
|
913
|
+
const result = await prisma.$queryRaw\`SELECT * FROM users WHERE id = \${userId}\`
|
|
914
|
+
return result
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// 2. Allowlisted columns with AI - SAFE
|
|
918
|
+
export async function safeAIColumns(prompt: string) {
|
|
919
|
+
const response = await openai.chat.completions.create({
|
|
920
|
+
model: 'gpt-4',
|
|
921
|
+
messages: [{ role: 'user', content: prompt }]
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
const allowedColumns = ['id', 'name', 'email', 'created_at']
|
|
925
|
+
const requestedColumns = response.choices[0].message.content.split(',').map(c => c.trim())
|
|
926
|
+
const safeColumns = requestedColumns.filter(c => allowedColumns.includes(c))
|
|
927
|
+
|
|
928
|
+
if (safeColumns.length === 0) {
|
|
929
|
+
throw new Error('No valid columns requested')
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Using validated columns with parameterized WHERE
|
|
933
|
+
return db.query(\`SELECT \${safeColumns.join(', ')} FROM users WHERE id = $1\`, [userId])
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// 3. ORM methods (not raw SQL) - SAFE
|
|
937
|
+
export async function safeORMQuery(prompt: string) {
|
|
938
|
+
const response = await openai.chat.completions.create({
|
|
939
|
+
model: 'gpt-4',
|
|
940
|
+
messages: [{ role: 'user', content: prompt }]
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
// ORM handles escaping
|
|
944
|
+
const aiName = response.choices[0].message.content
|
|
945
|
+
return prisma.user.findMany({
|
|
946
|
+
where: { name: aiName }
|
|
947
|
+
})
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// 4. Schema-validated AI output - SAFE
|
|
951
|
+
export async function schemaValidatedQuery(prompt: string) {
|
|
952
|
+
const response = await openai.chat.completions.create({
|
|
953
|
+
model: 'gpt-4',
|
|
954
|
+
messages: [{ role: 'user', content: prompt }]
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
// Validate structure before use
|
|
958
|
+
const schema = z.object({
|
|
959
|
+
table: z.enum(['users', 'posts', 'comments']),
|
|
960
|
+
column: z.enum(['id', 'name', 'email']),
|
|
961
|
+
value: z.string().max(100)
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
const parsed = schema.parse(JSON.parse(response.choices[0].message.content))
|
|
965
|
+
return db.query(\`SELECT * FROM \${parsed.table} WHERE \${parsed.column} = $1\`, [parsed.value])
|
|
966
|
+
}
|
|
967
|
+
`,
|
|
968
|
+
language: 'typescript',
|
|
969
|
+
size: 1700,
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
// ============================================================================
|
|
974
|
+
// False Negatives Group 4: Safe DOM/XSS Handling
|
|
975
|
+
// ============================================================================
|
|
15
976
|
{
|
|
16
|
-
name: '
|
|
17
|
-
expectFindings:
|
|
18
|
-
|
|
19
|
-
|
|
977
|
+
name: 'Safe DOM XSS Handling - False Negatives',
|
|
978
|
+
expectFindings: false,
|
|
979
|
+
description: 'Safe DOM/XSS handling patterns that should NOT be flagged',
|
|
980
|
+
allowedInfoFindings: [
|
|
981
|
+
{
|
|
982
|
+
category: 'ai_pattern',
|
|
983
|
+
maxCount: 2,
|
|
984
|
+
reason: 'AI context visible but output is sanitized',
|
|
985
|
+
},
|
|
986
|
+
],
|
|
20
987
|
file: {
|
|
21
|
-
path: 'src/
|
|
988
|
+
path: 'src/components/ai/safe-chat-renderer.tsx',
|
|
22
989
|
content: `
|
|
23
990
|
import OpenAI from 'openai'
|
|
24
|
-
import
|
|
991
|
+
import DOMPurify from 'dompurify'
|
|
992
|
+
import ReactMarkdown from 'react-markdown'
|
|
25
993
|
|
|
26
994
|
const openai = new OpenAI()
|
|
27
995
|
|
|
28
|
-
//
|
|
29
|
-
export async function
|
|
996
|
+
// 1. DOMPurify sanitization - SAFE
|
|
997
|
+
export async function sanitizedInnerHTML(prompt: string) {
|
|
30
998
|
const response = await openai.chat.completions.create({
|
|
31
999
|
model: 'gpt-4',
|
|
32
1000
|
messages: [{ role: 'user', content: prompt }]
|
|
33
1001
|
})
|
|
34
1002
|
|
|
35
|
-
const
|
|
36
|
-
|
|
1003
|
+
const sanitized = DOMPurify.sanitize(response.choices[0].message.content)
|
|
1004
|
+
document.getElementById('content').innerHTML = sanitized
|
|
37
1005
|
}
|
|
38
1006
|
|
|
39
|
-
//
|
|
40
|
-
export async function
|
|
1007
|
+
// 2. textContent (safe) - SAFE
|
|
1008
|
+
export async function safeTextContent(prompt: string) {
|
|
41
1009
|
const response = await openai.chat.completions.create({
|
|
42
1010
|
model: 'gpt-4',
|
|
43
|
-
messages: [{ role: 'user', content:
|
|
1011
|
+
messages: [{ role: 'user', content: prompt }]
|
|
44
1012
|
})
|
|
45
1013
|
|
|
46
|
-
|
|
1014
|
+
document.getElementById('content').textContent = response.choices[0].message.content
|
|
47
1015
|
}
|
|
48
1016
|
|
|
49
|
-
//
|
|
50
|
-
export async function
|
|
1017
|
+
// 3. React auto-escaping - SAFE
|
|
1018
|
+
export async function SafeReactComponent({ prompt }) {
|
|
51
1019
|
const response = await openai.chat.completions.create({
|
|
52
1020
|
model: 'gpt-4',
|
|
53
|
-
messages: [{ role: 'user', content:
|
|
1021
|
+
messages: [{ role: 'user', content: prompt }]
|
|
54
1022
|
})
|
|
55
1023
|
|
|
56
|
-
|
|
57
|
-
|
|
1024
|
+
// React auto-escapes this
|
|
1025
|
+
return <div>{response.choices[0].message.content}</div>
|
|
58
1026
|
}
|
|
59
1027
|
|
|
60
|
-
//
|
|
61
|
-
export async function
|
|
1028
|
+
// 4. Markdown renderer with sanitization - SAFE
|
|
1029
|
+
export async function SafeMarkdownComponent({ prompt }) {
|
|
62
1030
|
const response = await openai.chat.completions.create({
|
|
63
1031
|
model: 'gpt-4',
|
|
64
|
-
messages: [{
|
|
65
|
-
role: 'system',
|
|
66
|
-
content: 'Convert natural language to SQL'
|
|
67
|
-
}, {
|
|
68
|
-
role: 'user',
|
|
69
|
-
content: userQuestion
|
|
70
|
-
}]
|
|
1032
|
+
messages: [{ role: 'user', content: prompt }]
|
|
71
1033
|
})
|
|
72
1034
|
|
|
73
|
-
|
|
74
|
-
return db.query(sql) // CRITICAL: AI-generated SQL executed directly!
|
|
1035
|
+
return <ReactMarkdown>{response.choices[0].message.content}</ReactMarkdown>
|
|
75
1036
|
}
|
|
76
1037
|
|
|
77
|
-
//
|
|
78
|
-
export async function
|
|
1038
|
+
// 5. Sanitized dangerouslySetInnerHTML - SAFE
|
|
1039
|
+
export async function SafeDangerousComponent({ prompt }) {
|
|
79
1040
|
const response = await openai.chat.completions.create({
|
|
80
1041
|
model: 'gpt-4',
|
|
81
1042
|
messages: [{ role: 'user', content: prompt }]
|
|
82
1043
|
})
|
|
83
1044
|
|
|
84
|
-
|
|
1045
|
+
return (
|
|
1046
|
+
<div
|
|
1047
|
+
dangerouslySetInnerHTML={{
|
|
1048
|
+
__html: DOMPurify.sanitize(response.choices[0].message.content)
|
|
1049
|
+
}}
|
|
1050
|
+
/>
|
|
1051
|
+
)
|
|
1052
|
+
}
|
|
1053
|
+
`,
|
|
1054
|
+
language: 'typescript',
|
|
1055
|
+
size: 1700,
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
// ============================================================================
|
|
1060
|
+
// False Negatives Group 5: Safe Network Requests
|
|
1061
|
+
// ============================================================================
|
|
1062
|
+
{
|
|
1063
|
+
name: 'Safe Network Requests - False Negatives',
|
|
1064
|
+
expectFindings: false,
|
|
1065
|
+
description: 'Safe network request patterns that should NOT be flagged',
|
|
1066
|
+
allowedInfoFindings: [
|
|
1067
|
+
{
|
|
1068
|
+
category: 'ai_pattern',
|
|
1069
|
+
maxCount: 1,
|
|
1070
|
+
reason: 'AI context visible but URL is validated',
|
|
1071
|
+
},
|
|
1072
|
+
],
|
|
1073
|
+
file: {
|
|
1074
|
+
path: 'src/api/ai/safe-network-handler.ts',
|
|
1075
|
+
content: `
|
|
1076
|
+
import OpenAI from 'openai'
|
|
1077
|
+
|
|
1078
|
+
const openai = new OpenAI()
|
|
1079
|
+
|
|
1080
|
+
// 1. URL validation with allowlist - SAFE
|
|
1081
|
+
export async function validatedFetch(prompt: string) {
|
|
1082
|
+
const response = await openai.chat.completions.create({
|
|
1083
|
+
model: 'gpt-4',
|
|
1084
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1085
|
+
})
|
|
1086
|
+
|
|
1087
|
+
const aiUrl = response.choices[0].message.content
|
|
1088
|
+
const allowedHosts = ['api.example.com', 'cdn.example.com']
|
|
1089
|
+
|
|
1090
|
+
if (allowedHosts.includes(new URL(aiUrl).host)) {
|
|
1091
|
+
return fetch(aiUrl)
|
|
1092
|
+
}
|
|
1093
|
+
throw new Error('Invalid host')
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// 2. Hardcoded URL with AI parameters - SAFE
|
|
1097
|
+
export async function safeApiCall(prompt: string) {
|
|
1098
|
+
const response = await openai.chat.completions.create({
|
|
1099
|
+
model: 'gpt-4',
|
|
1100
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1101
|
+
})
|
|
1102
|
+
|
|
1103
|
+
const query = encodeURIComponent(response.choices[0].message.content)
|
|
1104
|
+
return fetch(\`https://api.example.com/search?q=\${query}\`)
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// 3. Internal API with validated path - SAFE
|
|
1108
|
+
export async function validatedInternalAPI(prompt: string) {
|
|
1109
|
+
const response = await openai.chat.completions.create({
|
|
1110
|
+
model: 'gpt-4',
|
|
1111
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1112
|
+
})
|
|
1113
|
+
|
|
1114
|
+
const aiPath = response.choices[0].message.content
|
|
1115
|
+
// Only allow alphanumeric paths
|
|
1116
|
+
const safePath = aiPath.replace(/[^a-z0-9]/gi, '')
|
|
1117
|
+
return fetch(\`/api/internal/\${safePath}\`)
|
|
85
1118
|
}
|
|
86
1119
|
|
|
87
|
-
//
|
|
88
|
-
export async function
|
|
1120
|
+
// 4. Blocked private IPs - SAFE
|
|
1121
|
+
export async function blockedPrivateIPs(prompt: string) {
|
|
89
1122
|
const response = await openai.chat.completions.create({
|
|
90
1123
|
model: 'gpt-4',
|
|
91
1124
|
messages: [{ role: 'user', content: prompt }]
|
|
92
1125
|
})
|
|
93
1126
|
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
1127
|
+
const aiUrl = response.choices[0].message.content
|
|
1128
|
+
const url = new URL(aiUrl)
|
|
1129
|
+
const blockedHosts = ['localhost', '127.0.0.1', '0.0.0.0']
|
|
1130
|
+
const privateIpPatterns = [/^10\\./, /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./, /^192\\.168\\./]
|
|
1131
|
+
|
|
1132
|
+
if (blockedHosts.includes(url.hostname) ||
|
|
1133
|
+
privateIpPatterns.some(p => p.test(url.hostname))) {
|
|
1134
|
+
throw new Error('Private IP blocked')
|
|
1135
|
+
}
|
|
1136
|
+
return fetch(aiUrl)
|
|
97
1137
|
}
|
|
98
1138
|
`,
|
|
99
1139
|
language: 'typescript',
|
|
100
|
-
size:
|
|
1140
|
+
size: 1800,
|
|
101
1141
|
},
|
|
102
1142
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
1143
|
+
|
|
1144
|
+
// ============================================================================
|
|
1145
|
+
// False Negatives Group 6: Safe Redirects
|
|
1146
|
+
// ============================================================================
|
|
106
1147
|
{
|
|
107
|
-
name: '
|
|
1148
|
+
name: 'Safe Redirects - False Negatives',
|
|
108
1149
|
expectFindings: false,
|
|
109
|
-
description: 'Safe
|
|
1150
|
+
description: 'Safe redirect patterns that should NOT be flagged',
|
|
110
1151
|
allowedInfoFindings: [
|
|
111
1152
|
{
|
|
112
|
-
category: '
|
|
113
|
-
maxCount:
|
|
114
|
-
reason: '
|
|
1153
|
+
category: 'ai_pattern',
|
|
1154
|
+
maxCount: 1,
|
|
1155
|
+
reason: 'AI context visible but redirect is validated',
|
|
115
1156
|
},
|
|
1157
|
+
],
|
|
1158
|
+
file: {
|
|
1159
|
+
path: 'src/api/ai/safe-redirect-handler.ts',
|
|
1160
|
+
content: `
|
|
1161
|
+
import OpenAI from 'openai'
|
|
1162
|
+
|
|
1163
|
+
const openai = new OpenAI()
|
|
1164
|
+
|
|
1165
|
+
// 1. Relative URL validation - SAFE
|
|
1166
|
+
export async function safeRelativeRedirect(req, res, prompt: string) {
|
|
1167
|
+
const response = await openai.chat.completions.create({
|
|
1168
|
+
model: 'gpt-4',
|
|
1169
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1170
|
+
})
|
|
1171
|
+
|
|
1172
|
+
const aiUrl = response.choices[0].message.content
|
|
1173
|
+
if (aiUrl.startsWith('/') && !aiUrl.startsWith('//')) {
|
|
1174
|
+
res.redirect(aiUrl)
|
|
1175
|
+
} else {
|
|
1176
|
+
res.status(400).send('Invalid redirect URL')
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// 2. Same-origin check - SAFE
|
|
1181
|
+
export async function sameOriginRedirect(prompt: string) {
|
|
1182
|
+
const response = await openai.chat.completions.create({
|
|
1183
|
+
model: 'gpt-4',
|
|
1184
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
const aiUrl = response.choices[0].message.content
|
|
1188
|
+
const origin = window.location.origin
|
|
1189
|
+
const url = new URL(aiUrl, origin)
|
|
1190
|
+
|
|
1191
|
+
if (url.origin === origin) {
|
|
1192
|
+
location.href = aiUrl
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// 3. Allowlisted domains - SAFE
|
|
1197
|
+
export async function allowlistedRedirect(req, res, prompt: string) {
|
|
1198
|
+
const response = await openai.chat.completions.create({
|
|
1199
|
+
model: 'gpt-4',
|
|
1200
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1201
|
+
})
|
|
1202
|
+
|
|
1203
|
+
const aiUrl = response.choices[0].message.content
|
|
1204
|
+
const safeDomains = ['example.com', 'trusted.org', 'partner.net']
|
|
1205
|
+
|
|
1206
|
+
try {
|
|
1207
|
+
const url = new URL(aiUrl)
|
|
1208
|
+
if (safeDomains.includes(url.host)) {
|
|
1209
|
+
res.redirect(aiUrl)
|
|
1210
|
+
} else {
|
|
1211
|
+
res.status(400).send('Redirect blocked')
|
|
1212
|
+
}
|
|
1213
|
+
} catch {
|
|
1214
|
+
res.status(400).send('Invalid URL')
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
`,
|
|
1218
|
+
language: 'typescript',
|
|
1219
|
+
size: 1400,
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
|
|
1223
|
+
// ============================================================================
|
|
1224
|
+
// False Negatives Group 7: Safe Header Setting
|
|
1225
|
+
// ============================================================================
|
|
1226
|
+
{
|
|
1227
|
+
name: 'Safe Header Setting - False Negatives',
|
|
1228
|
+
expectFindings: false,
|
|
1229
|
+
description: 'Safe header setting patterns that should NOT be flagged',
|
|
1230
|
+
allowedInfoFindings: [
|
|
116
1231
|
{
|
|
117
|
-
category: '
|
|
1232
|
+
category: 'ai_pattern',
|
|
118
1233
|
maxCount: 1,
|
|
119
|
-
reason: '
|
|
1234
|
+
reason: 'AI context visible but headers are sanitized',
|
|
120
1235
|
},
|
|
1236
|
+
],
|
|
1237
|
+
file: {
|
|
1238
|
+
path: 'src/api/ai/safe-header-handler.ts',
|
|
1239
|
+
content: `
|
|
1240
|
+
import OpenAI from 'openai'
|
|
1241
|
+
import crypto from 'crypto'
|
|
1242
|
+
|
|
1243
|
+
const openai = new OpenAI()
|
|
1244
|
+
|
|
1245
|
+
// 1. Sanitized header value - SAFE
|
|
1246
|
+
export async function sanitizedHeader(req, res, prompt: string) {
|
|
1247
|
+
const response = await openai.chat.completions.create({
|
|
1248
|
+
model: 'gpt-4',
|
|
1249
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1250
|
+
})
|
|
1251
|
+
|
|
1252
|
+
// Remove CRLF injection characters
|
|
1253
|
+
const safeValue = response.choices[0].message.content.replace(/[\\r\\n]/g, '')
|
|
1254
|
+
res.setHeader('X-Custom', safeValue)
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// 2. Allowlisted content types - SAFE
|
|
1258
|
+
export async function allowlistedContentType(req, res, prompt: string) {
|
|
1259
|
+
const response = await openai.chat.completions.create({
|
|
1260
|
+
model: 'gpt-4',
|
|
1261
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1262
|
+
})
|
|
1263
|
+
|
|
1264
|
+
const allowedTypes = ['json', 'html', 'text', 'xml']
|
|
1265
|
+
const aiType = response.choices[0].message.content.trim().toLowerCase()
|
|
1266
|
+
|
|
1267
|
+
if (allowedTypes.includes(aiType)) {
|
|
1268
|
+
res.type(aiType)
|
|
1269
|
+
} else {
|
|
1270
|
+
res.type('text') // Default fallback
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
// 3. Server-generated tokens - SAFE
|
|
1275
|
+
export async function serverGeneratedCookie(req, res) {
|
|
1276
|
+
// Cookie value NOT from AI - generated server-side
|
|
1277
|
+
const sessionToken = crypto.randomUUID()
|
|
1278
|
+
res.cookie('session', sessionToken, {
|
|
1279
|
+
httpOnly: true,
|
|
1280
|
+
secure: true,
|
|
1281
|
+
sameSite: 'strict'
|
|
1282
|
+
})
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// 4. Validated header name and value - SAFE
|
|
1286
|
+
export async function validatedHeader(req, res, prompt: string) {
|
|
1287
|
+
const response = await openai.chat.completions.create({
|
|
1288
|
+
model: 'gpt-4',
|
|
1289
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
const aiValue = response.choices[0].message.content
|
|
1293
|
+
|
|
1294
|
+
// Validate: only alphanumeric and safe characters
|
|
1295
|
+
if (/^[a-zA-Z0-9\\-_. ]+$/.test(aiValue) && aiValue.length <= 256) {
|
|
1296
|
+
res.setHeader('X-AI-Response', aiValue)
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
`,
|
|
1300
|
+
language: 'typescript',
|
|
1301
|
+
size: 1600,
|
|
1302
|
+
},
|
|
1303
|
+
},
|
|
1304
|
+
|
|
1305
|
+
// ============================================================================
|
|
1306
|
+
// False Negatives Group 8: Safe File Operations
|
|
1307
|
+
// ============================================================================
|
|
1308
|
+
{
|
|
1309
|
+
name: 'Safe File Operations - False Negatives',
|
|
1310
|
+
expectFindings: false,
|
|
1311
|
+
description: 'Safe file operation patterns that should NOT be flagged',
|
|
1312
|
+
allowedInfoFindings: [
|
|
121
1313
|
{
|
|
122
1314
|
category: 'ai_pattern',
|
|
123
|
-
maxCount:
|
|
124
|
-
reason: 'AI
|
|
1315
|
+
maxCount: 1,
|
|
1316
|
+
reason: 'AI context visible but paths are validated',
|
|
125
1317
|
},
|
|
126
1318
|
],
|
|
127
1319
|
file: {
|
|
128
|
-
path: 'src/api/ai/safe-
|
|
1320
|
+
path: 'src/api/ai/safe-file-handler.ts',
|
|
129
1321
|
content: `
|
|
130
1322
|
import OpenAI from 'openai'
|
|
131
|
-
import
|
|
1323
|
+
import fs from 'fs'
|
|
1324
|
+
import path from 'path'
|
|
132
1325
|
|
|
133
1326
|
const openai = new OpenAI()
|
|
1327
|
+
const SAFE_BASE_DIR = '/app/data/user-files'
|
|
134
1328
|
|
|
135
|
-
//
|
|
136
|
-
export async function
|
|
1329
|
+
// 1. Path validation - SAFE
|
|
1330
|
+
export async function validatedFilePath(prompt: string) {
|
|
137
1331
|
const response = await openai.chat.completions.create({
|
|
138
1332
|
model: 'gpt-4',
|
|
139
1333
|
messages: [{ role: 'user', content: prompt }]
|
|
140
1334
|
})
|
|
141
1335
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1336
|
+
const aiPath = response.choices[0].message.content
|
|
1337
|
+
const resolved = path.resolve(SAFE_BASE_DIR, aiPath)
|
|
1338
|
+
|
|
1339
|
+
// Validate path is within safe directory
|
|
1340
|
+
if (!resolved.startsWith(SAFE_BASE_DIR)) {
|
|
1341
|
+
throw new Error('Invalid path - directory traversal attempt')
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
return fs.readFileSync(resolved, 'utf-8')
|
|
145
1345
|
}
|
|
146
1346
|
|
|
147
|
-
//
|
|
148
|
-
export async function
|
|
1347
|
+
// 2. Allowlisted extensions - SAFE
|
|
1348
|
+
export async function allowlistedExtensions(prompt: string, data: string) {
|
|
149
1349
|
const response = await openai.chat.completions.create({
|
|
150
1350
|
model: 'gpt-4',
|
|
151
1351
|
messages: [{ role: 'user', content: prompt }]
|
|
152
1352
|
})
|
|
153
1353
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
1354
|
+
const aiFile = response.choices[0].message.content
|
|
1355
|
+
const allowedExtensions = ['.txt', '.md', '.json', '.csv']
|
|
1356
|
+
const ext = path.extname(aiFile).toLowerCase()
|
|
1357
|
+
|
|
1358
|
+
if (allowedExtensions.includes(ext)) {
|
|
1359
|
+
const safePath = path.join(SAFE_BASE_DIR, path.basename(aiFile))
|
|
1360
|
+
fs.writeFileSync(safePath, data)
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// 3. Internal file operations (not AI-controlled) - SAFE
|
|
1365
|
+
export async function internalFileOps(prompt: string) {
|
|
1366
|
+
const response = await openai.chat.completions.create({
|
|
1367
|
+
model: 'gpt-4',
|
|
1368
|
+
messages: [{ role: 'user', content: prompt }]
|
|
157
1369
|
})
|
|
158
|
-
|
|
1370
|
+
|
|
1371
|
+
// File path is hardcoded, AI only provides content
|
|
1372
|
+
const filePath = '/app/logs/ai-responses.log'
|
|
1373
|
+
fs.appendFileSync(filePath, response.choices[0].message.content + '\\n')
|
|
159
1374
|
}
|
|
160
1375
|
|
|
161
|
-
//
|
|
162
|
-
export async function
|
|
1376
|
+
// 4. Sanitized filename - SAFE
|
|
1377
|
+
export async function sanitizedFilename(prompt: string, data: string) {
|
|
163
1378
|
const response = await openai.chat.completions.create({
|
|
164
1379
|
model: 'gpt-4',
|
|
165
|
-
messages: [{
|
|
166
|
-
role: 'system',
|
|
167
|
-
content: 'Return ONLY column names for the SELECT clause, comma-separated'
|
|
168
|
-
}, {
|
|
169
|
-
role: 'user',
|
|
170
|
-
content: userQuestion
|
|
171
|
-
}]
|
|
1380
|
+
messages: [{ role: 'user', content: prompt }]
|
|
172
1381
|
})
|
|
173
1382
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
const requestedColumns = columns.split(',').map(c => c.trim())
|
|
178
|
-
const safeColumns = requestedColumns.filter(c => allowedColumns.includes(c))
|
|
1383
|
+
// Remove all path traversal characters
|
|
1384
|
+
const aiFile = response.choices[0].message.content
|
|
1385
|
+
const sanitized = aiFile.replace(/[\\\\/\\x00-\\x1f]/g, '').replace(/\\.\\./g, '')
|
|
179
1386
|
|
|
180
|
-
|
|
181
|
-
|
|
1387
|
+
if (sanitized.length > 0 && sanitized.length <= 255) {
|
|
1388
|
+
const safePath = path.join(SAFE_BASE_DIR, sanitized)
|
|
1389
|
+
fs.writeFileSync(safePath, data)
|
|
1390
|
+
}
|
|
182
1391
|
}
|
|
183
1392
|
`,
|
|
184
1393
|
language: 'typescript',
|
|
185
|
-
size:
|
|
1394
|
+
size: 1900,
|
|
1395
|
+
},
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1398
|
+
// ============================================================================
|
|
1399
|
+
// False Negatives Group 9: Safe Dynamic Imports
|
|
1400
|
+
// ============================================================================
|
|
1401
|
+
{
|
|
1402
|
+
name: 'Safe Dynamic Imports - False Negatives',
|
|
1403
|
+
expectFindings: false,
|
|
1404
|
+
description: 'Safe dynamic import patterns that should NOT be flagged',
|
|
1405
|
+
allowedInfoFindings: [
|
|
1406
|
+
{
|
|
1407
|
+
category: 'ai_pattern',
|
|
1408
|
+
maxCount: 1,
|
|
1409
|
+
reason: 'AI context visible but imports are allowlisted',
|
|
1410
|
+
},
|
|
1411
|
+
],
|
|
1412
|
+
file: {
|
|
1413
|
+
path: 'src/api/ai/safe-plugin-loader.ts',
|
|
1414
|
+
content: `
|
|
1415
|
+
import OpenAI from 'openai'
|
|
1416
|
+
|
|
1417
|
+
const openai = new OpenAI()
|
|
1418
|
+
|
|
1419
|
+
// Plugin registry with allowlisted modules
|
|
1420
|
+
const ALLOWED_PLUGINS = {
|
|
1421
|
+
'lodash': () => import('lodash'),
|
|
1422
|
+
'moment': () => import('moment'),
|
|
1423
|
+
'date-fns': () => import('date-fns'),
|
|
1424
|
+
'uuid': () => import('uuid'),
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// 1. Allowlisted modules - SAFE
|
|
1428
|
+
export async function loadAllowlistedPlugin(prompt: string) {
|
|
1429
|
+
const response = await openai.chat.completions.create({
|
|
1430
|
+
model: 'gpt-4',
|
|
1431
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1432
|
+
})
|
|
1433
|
+
|
|
1434
|
+
const aiModule = response.choices[0].message.content.trim()
|
|
1435
|
+
const loader = ALLOWED_PLUGINS[aiModule]
|
|
1436
|
+
|
|
1437
|
+
if (loader) {
|
|
1438
|
+
return loader()
|
|
1439
|
+
}
|
|
1440
|
+
throw new Error(\`Plugin "\${aiModule}" not in allowlist\`)
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// 2. Hardcoded with AI config - SAFE
|
|
1444
|
+
export async function configuredPlugin(prompt: string) {
|
|
1445
|
+
const response = await openai.chat.completions.create({
|
|
1446
|
+
model: 'gpt-4',
|
|
1447
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1448
|
+
})
|
|
1449
|
+
|
|
1450
|
+
// AI only selects from predefined options
|
|
1451
|
+
const pluginName = response.choices[0].message.content.trim()
|
|
1452
|
+
|
|
1453
|
+
// Sanitize - only alphanumeric
|
|
1454
|
+
const sanitized = pluginName.replace(/[^a-z0-9-]/gi, '')
|
|
1455
|
+
|
|
1456
|
+
// Import from known safe directory
|
|
1457
|
+
const mod = await import(\`./plugins/\${sanitized}\`)
|
|
1458
|
+
return mod.default
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// 3. Import map resolution - SAFE
|
|
1462
|
+
export async function importMapPlugin(prompt: string) {
|
|
1463
|
+
const response = await openai.chat.completions.create({
|
|
1464
|
+
model: 'gpt-4',
|
|
1465
|
+
messages: [{ role: 'user', content: prompt }]
|
|
1466
|
+
})
|
|
1467
|
+
|
|
1468
|
+
const importMap = {
|
|
1469
|
+
'math': './lib/math.js',
|
|
1470
|
+
'string': './lib/string.js',
|
|
1471
|
+
'array': './lib/array.js',
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
const aiKey = response.choices[0].message.content.trim()
|
|
1475
|
+
|
|
1476
|
+
// AI key validated against map keys
|
|
1477
|
+
if (aiKey in importMap) {
|
|
1478
|
+
return require(importMap[aiKey])
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
`,
|
|
1482
|
+
language: 'typescript',
|
|
1483
|
+
size: 1600,
|
|
1484
|
+
},
|
|
1485
|
+
},
|
|
1486
|
+
|
|
1487
|
+
// ============================================================================
|
|
1488
|
+
// False Negatives Group 10: Safe Python Operations
|
|
1489
|
+
// ============================================================================
|
|
1490
|
+
{
|
|
1491
|
+
name: 'Safe Python Operations - False Negatives',
|
|
1492
|
+
expectFindings: false,
|
|
1493
|
+
description: 'Safe Python patterns that should NOT be flagged',
|
|
1494
|
+
allowedInfoFindings: [
|
|
1495
|
+
{
|
|
1496
|
+
category: 'ai_pattern',
|
|
1497
|
+
maxCount: 1,
|
|
1498
|
+
reason: 'AI context visible but operations are safe',
|
|
1499
|
+
},
|
|
1500
|
+
],
|
|
1501
|
+
file: {
|
|
1502
|
+
path: 'src/api/ai/safe_python_executor.py',
|
|
1503
|
+
content: `
|
|
1504
|
+
import openai
|
|
1505
|
+
import ast
|
|
1506
|
+
import yaml
|
|
1507
|
+
import subprocess
|
|
1508
|
+
import sqlite3
|
|
1509
|
+
|
|
1510
|
+
client = openai.OpenAI()
|
|
1511
|
+
|
|
1512
|
+
# 1. Parameterized SQL - SAFE
|
|
1513
|
+
def safe_parameterized_query(prompt: str):
|
|
1514
|
+
response = client.chat.completions.create(
|
|
1515
|
+
model="gpt-4",
|
|
1516
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1517
|
+
)
|
|
1518
|
+
|
|
1519
|
+
# Parameterized query - AI provides only values
|
|
1520
|
+
user_id = response.choices[0].message.content
|
|
1521
|
+
conn = sqlite3.connect("db.sqlite")
|
|
1522
|
+
cursor = conn.cursor()
|
|
1523
|
+
cursor.execute("SELECT * FROM users WHERE id = ?", [user_id])
|
|
1524
|
+
return cursor.fetchall()
|
|
1525
|
+
|
|
1526
|
+
# 2. ast.literal_eval (safe subset) - SAFE
|
|
1527
|
+
def safe_literal_eval(prompt: str):
|
|
1528
|
+
response = client.chat.completions.create(
|
|
1529
|
+
model="gpt-4",
|
|
1530
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1531
|
+
)
|
|
1532
|
+
|
|
1533
|
+
# ast.literal_eval only evaluates literals - no code execution
|
|
1534
|
+
data = ast.literal_eval(response.choices[0].message.content)
|
|
1535
|
+
return data
|
|
1536
|
+
|
|
1537
|
+
# 3. SafeLoader YAML - SAFE
|
|
1538
|
+
def safe_yaml_load(prompt: str):
|
|
1539
|
+
response = client.chat.completions.create(
|
|
1540
|
+
model="gpt-4",
|
|
1541
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
content = response.choices[0].message.content
|
|
1545
|
+
# SafeLoader prevents code execution
|
|
1546
|
+
data = yaml.load(content, Loader=yaml.SafeLoader)
|
|
1547
|
+
return data
|
|
1548
|
+
|
|
1549
|
+
# 4. Subprocess with list args (no shell) - SAFE
|
|
1550
|
+
def safe_subprocess(prompt: str):
|
|
1551
|
+
response = client.chat.completions.create(
|
|
1552
|
+
model="gpt-4",
|
|
1553
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1554
|
+
)
|
|
1555
|
+
|
|
1556
|
+
# shell=False with list args is safe
|
|
1557
|
+
# But we don't even use AI output in the command
|
|
1558
|
+
result = subprocess.run(['git', 'status', '--porcelain'], capture_output=True)
|
|
1559
|
+
return result.stdout.decode()
|
|
1560
|
+
|
|
1561
|
+
# 5. Display only - SAFE
|
|
1562
|
+
def display_ai_response(prompt: str):
|
|
1563
|
+
response = client.chat.completions.create(
|
|
1564
|
+
model="gpt-4",
|
|
1565
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
# Only printing, not executing
|
|
1569
|
+
print("AI Response:", response.choices[0].message.content)
|
|
1570
|
+
return {"response": response.choices[0].message.content}
|
|
1571
|
+
|
|
1572
|
+
# 6. Validated JSON parsing - SAFE
|
|
1573
|
+
def safe_json_parse(prompt: str):
|
|
1574
|
+
import json
|
|
1575
|
+
from pydantic import BaseModel
|
|
1576
|
+
|
|
1577
|
+
class SafeOutput(BaseModel):
|
|
1578
|
+
name: str
|
|
1579
|
+
value: int
|
|
1580
|
+
|
|
1581
|
+
response = client.chat.completions.create(
|
|
1582
|
+
model="gpt-4",
|
|
1583
|
+
messages=[{"role": "user", "content": prompt}]
|
|
1584
|
+
)
|
|
1585
|
+
|
|
1586
|
+
# Schema validation with Pydantic
|
|
1587
|
+
data = json.loads(response.choices[0].message.content)
|
|
1588
|
+
validated = SafeOutput(**data)
|
|
1589
|
+
return validated
|
|
1590
|
+
`,
|
|
1591
|
+
language: 'python',
|
|
1592
|
+
size: 2100,
|
|
186
1593
|
},
|
|
187
1594
|
},
|
|
188
1595
|
],
|