@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,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Security Test Fixtures
|
|
3
|
+
* Tests for detecting security issues in Model Context Protocol (MCP) tools
|
|
4
|
+
*
|
|
5
|
+
* Background: MCP enables AI agents to call external tools. Security risks include:
|
|
6
|
+
* - Tool Poisoning: External content returned without validation
|
|
7
|
+
* - Credential Issues: Credentials in tool parameters/responses
|
|
8
|
+
* - Confused Deputy: Operations without proper user context
|
|
9
|
+
*
|
|
10
|
+
* Reference: CVE-2025-6514, 13,000+ MCP servers deployed
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { TestGroup } from '../../types'
|
|
14
|
+
|
|
15
|
+
export const aiMcpSecurityTests: TestGroup = {
|
|
16
|
+
name: 'MCP Tool Security',
|
|
17
|
+
tier: 'A',
|
|
18
|
+
layer: 2,
|
|
19
|
+
description: 'Detection of security issues in Model Context Protocol tools',
|
|
20
|
+
|
|
21
|
+
truePositives: [
|
|
22
|
+
{
|
|
23
|
+
name: 'MCP Security - Tool Poisoning True Positives',
|
|
24
|
+
expectFindings: true,
|
|
25
|
+
expectedCategories: ['ai_mcp_tool_poisoning'],
|
|
26
|
+
description: 'MCP tools returning unvalidated external content',
|
|
27
|
+
file: {
|
|
28
|
+
path: 'src/mcp/tools/fetch-tool.ts',
|
|
29
|
+
content: `
|
|
30
|
+
import { McpServer, Tool } from '@modelcontextprotocol/sdk'
|
|
31
|
+
|
|
32
|
+
const server = new McpServer({ name: 'fetch-server' })
|
|
33
|
+
|
|
34
|
+
// Dangerous: Returns raw external content without sanitization
|
|
35
|
+
server.tool('fetch_webpage', async ({ url }: { url: string }) => {
|
|
36
|
+
const response = await fetch(url)
|
|
37
|
+
const html = await response.text()
|
|
38
|
+
// Returning raw HTML - could contain prompt injection payloads
|
|
39
|
+
return { content: html }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Dangerous: Raw file content returned without validation
|
|
43
|
+
server.tool('read_file', async ({ path }: { path: string }) => {
|
|
44
|
+
const content = await fs.readFile(path, 'utf-8')
|
|
45
|
+
// No validation of file content
|
|
46
|
+
return { content }
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Dangerous: Database query results returned directly
|
|
50
|
+
server.tool('query_database', async ({ query }: { query: string }) => {
|
|
51
|
+
const results = await db.query(query)
|
|
52
|
+
// Raw database content could contain injected instructions
|
|
53
|
+
return { data: results.rows }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Dangerous: Email content without sanitization
|
|
57
|
+
server.tool('get_email', async ({ id }: { id: string }) => {
|
|
58
|
+
const email = await emailClient.getMessage(id)
|
|
59
|
+
return {
|
|
60
|
+
subject: email.subject,
|
|
61
|
+
body: email.body, // Raw email body - could contain malicious instructions
|
|
62
|
+
attachments: email.attachments
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// Dangerous: RSS feed content
|
|
67
|
+
server.tool('fetch_rss', async ({ feedUrl }: { feedUrl: string }) => {
|
|
68
|
+
const feed = await parser.parseURL(feedUrl)
|
|
69
|
+
// Entire feed content returned - titles, descriptions could be poisoned
|
|
70
|
+
return { items: feed.items }
|
|
71
|
+
})
|
|
72
|
+
`,
|
|
73
|
+
language: 'typescript',
|
|
74
|
+
size: 1300,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'MCP Security - Credential Issues True Positives',
|
|
79
|
+
expectFindings: true,
|
|
80
|
+
expectedCategories: ['ai_mcp_credential_issue'],
|
|
81
|
+
description: 'MCP tools with credential exposure in parameters or responses',
|
|
82
|
+
file: {
|
|
83
|
+
path: 'src/mcp/tools/auth-tool.ts',
|
|
84
|
+
content: `
|
|
85
|
+
import { McpServer } from '@modelcontextprotocol/sdk'
|
|
86
|
+
|
|
87
|
+
const server = new McpServer({ name: 'auth-server' })
|
|
88
|
+
|
|
89
|
+
// Dangerous: API key passed as tool parameter
|
|
90
|
+
server.tool('call_api', async ({ apiKey, endpoint }: { apiKey: string; endpoint: string }) => {
|
|
91
|
+
const response = await fetch(endpoint, {
|
|
92
|
+
headers: { 'Authorization': \`Bearer \${apiKey}\` }
|
|
93
|
+
})
|
|
94
|
+
return { data: await response.json() }
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Dangerous: Returning credentials in response
|
|
98
|
+
server.tool('create_user', async ({ email }: { email: string }) => {
|
|
99
|
+
const user = await db.createUser(email)
|
|
100
|
+
const password = generatePassword()
|
|
101
|
+
await db.setPassword(user.id, password)
|
|
102
|
+
// Returning password to the model!
|
|
103
|
+
return { userId: user.id, password, apiKey: user.apiKey }
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Dangerous: Token in response
|
|
107
|
+
server.tool('authenticate', async ({ username, password }: { username: string; password: string }) => {
|
|
108
|
+
const result = await auth.login(username, password)
|
|
109
|
+
// JWT token exposed to model
|
|
110
|
+
return { token: result.jwt, refreshToken: result.refreshToken }
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// Dangerous: Database connection string in parameter
|
|
114
|
+
server.tool('connect_db', async ({ connectionString }: { connectionString: string }) => {
|
|
115
|
+
const client = await Database.connect(connectionString)
|
|
116
|
+
return { status: 'connected' }
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Dangerous: Private key parameter
|
|
120
|
+
server.tool('sign_message', async ({ privateKey, message }: { privateKey: string; message: string }) => {
|
|
121
|
+
const signature = crypto.sign(privateKey, message)
|
|
122
|
+
return { signature }
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Dangerous: Returning secrets from environment
|
|
126
|
+
server.tool('get_config', async () => {
|
|
127
|
+
return {
|
|
128
|
+
apiUrl: process.env.API_URL,
|
|
129
|
+
apiKey: process.env.API_KEY, // Secret exposed!
|
|
130
|
+
secretKey: process.env.SECRET_KEY, // Secret exposed!
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
`,
|
|
134
|
+
language: 'typescript',
|
|
135
|
+
size: 1500,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'MCP Security - Confused Deputy True Positives',
|
|
140
|
+
expectFindings: true,
|
|
141
|
+
expectedCategories: ['ai_mcp_confused_deputy'],
|
|
142
|
+
description: 'MCP tools performing operations without user context',
|
|
143
|
+
file: {
|
|
144
|
+
path: 'src/mcp/tools/data-tool.ts',
|
|
145
|
+
content: `
|
|
146
|
+
import { McpServer } from '@modelcontextprotocol/sdk'
|
|
147
|
+
|
|
148
|
+
const server = new McpServer({ name: 'data-server' })
|
|
149
|
+
|
|
150
|
+
// Dangerous: No user context - can access any user's files
|
|
151
|
+
server.tool('read_user_file', async ({ fileId }: { fileId: string }) => {
|
|
152
|
+
// No user authentication/authorization
|
|
153
|
+
const file = await db.files.findById(fileId)
|
|
154
|
+
return { content: file.content }
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Dangerous: Delete without user verification
|
|
158
|
+
server.tool('delete_record', async ({ recordId }: { recordId: string }) => {
|
|
159
|
+
// No check if user owns this record
|
|
160
|
+
await db.records.delete(recordId)
|
|
161
|
+
return { success: true }
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
// Dangerous: Update any user's data
|
|
165
|
+
server.tool('update_profile', async ({ userId, data }: { userId: string; data: any }) => {
|
|
166
|
+
// Can update ANY user's profile, not just the current user
|
|
167
|
+
await db.users.update(userId, data)
|
|
168
|
+
return { success: true }
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Dangerous: Cross-tenant data access
|
|
172
|
+
server.tool('get_organization_data', async ({ orgId }: { orgId: string }) => {
|
|
173
|
+
// No tenant verification - can access any org's data
|
|
174
|
+
const data = await db.organizations.findById(orgId)
|
|
175
|
+
return { data }
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Dangerous: Admin action without auth
|
|
179
|
+
server.tool('grant_admin', async ({ userId }: { userId: string }) => {
|
|
180
|
+
// No authorization check - any agent can make users admin
|
|
181
|
+
await db.users.update(userId, { role: 'admin' })
|
|
182
|
+
return { success: true }
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// Dangerous: Sending email as any user
|
|
186
|
+
server.tool('send_email', async ({ from, to, subject, body }: { from: string; to: string; subject: string; body: string }) => {
|
|
187
|
+
// No verification that 'from' is the actual user
|
|
188
|
+
await emailService.send({ from, to, subject, body })
|
|
189
|
+
return { sent: true }
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Dangerous: Accessing financial data without scope
|
|
193
|
+
server.tool('get_transactions', async ({ accountId }: { accountId: string }) => {
|
|
194
|
+
// Can access any account's transactions
|
|
195
|
+
const transactions = await financeService.getTransactions(accountId)
|
|
196
|
+
return { transactions }
|
|
197
|
+
})
|
|
198
|
+
`,
|
|
199
|
+
language: 'typescript',
|
|
200
|
+
size: 1700,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'MCP Security - Python MCP Tools True Positives',
|
|
205
|
+
expectFindings: true,
|
|
206
|
+
expectedCategories: ['ai_mcp_tool_poisoning', 'ai_mcp_credential_issue', 'ai_mcp_confused_deputy'],
|
|
207
|
+
description: 'Python MCP server with security issues',
|
|
208
|
+
file: {
|
|
209
|
+
path: 'src/mcp/server.py',
|
|
210
|
+
content: `
|
|
211
|
+
from mcp import McpServer
|
|
212
|
+
from mcp.server import Tool
|
|
213
|
+
|
|
214
|
+
server = McpServer(name="python-mcp")
|
|
215
|
+
|
|
216
|
+
# Tool poisoning: raw external content
|
|
217
|
+
@server.tool("fetch_url")
|
|
218
|
+
async def fetch_url(url: str) -> dict:
|
|
219
|
+
response = await httpx.get(url)
|
|
220
|
+
# Raw content returned without sanitization
|
|
221
|
+
return {"content": response.text}
|
|
222
|
+
|
|
223
|
+
# Credential issue: API key parameter
|
|
224
|
+
@server.tool("api_call")
|
|
225
|
+
async def api_call(api_key: str, endpoint: str) -> dict:
|
|
226
|
+
response = await httpx.get(endpoint, headers={"Authorization": f"Bearer {api_key}"})
|
|
227
|
+
return {"data": response.json()}
|
|
228
|
+
|
|
229
|
+
# Confused deputy: no user context
|
|
230
|
+
@server.tool("delete_file")
|
|
231
|
+
async def delete_file(file_id: str) -> dict:
|
|
232
|
+
# No user verification
|
|
233
|
+
await storage.delete(file_id)
|
|
234
|
+
return {"deleted": True}
|
|
235
|
+
|
|
236
|
+
# Tool poisoning: returning database content
|
|
237
|
+
@server.tool("search_documents")
|
|
238
|
+
async def search_documents(query: str) -> dict:
|
|
239
|
+
results = await db.search(query)
|
|
240
|
+
return {"documents": results}
|
|
241
|
+
|
|
242
|
+
# Credential in response
|
|
243
|
+
@server.tool("create_api_key")
|
|
244
|
+
async def create_api_key(name: str) -> dict:
|
|
245
|
+
key = await keys.create(name)
|
|
246
|
+
return {"api_key": key.secret} # Exposing secret!
|
|
247
|
+
`,
|
|
248
|
+
language: 'python',
|
|
249
|
+
size: 1100,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
|
|
254
|
+
falseNegatives: [
|
|
255
|
+
{
|
|
256
|
+
name: 'MCP Security - Safe Patterns',
|
|
257
|
+
expectFindings: false,
|
|
258
|
+
description: 'Safe MCP tool implementations that should NOT be flagged',
|
|
259
|
+
allowedInfoFindings: [
|
|
260
|
+
{
|
|
261
|
+
category: 'ai_mcp_tool_poisoning',
|
|
262
|
+
maxCount: 5,
|
|
263
|
+
reason: 'Some safe patterns may still flag at info level for hygiene',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
category: 'ai_mcp_confused_deputy',
|
|
267
|
+
maxCount: 5,
|
|
268
|
+
reason: 'User context patterns may still flag at info level',
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
category: 'ai_overpermissive_tool',
|
|
272
|
+
maxCount: 10,
|
|
273
|
+
reason: 'ai-agent-tools detector flags MCP tool registrations (separate detector)',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
category: 'high_entropy_string',
|
|
277
|
+
maxCount: 2,
|
|
278
|
+
reason: 'DOMPurify import may trigger entropy detection',
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
file: {
|
|
282
|
+
path: 'src/mcp/tools/safe-tools.ts',
|
|
283
|
+
content: `
|
|
284
|
+
import { McpServer } from '@modelcontextprotocol/sdk'
|
|
285
|
+
import { sanitize } from 'dompurify'
|
|
286
|
+
import { validateSchema } from 'zod'
|
|
287
|
+
|
|
288
|
+
const server = new McpServer({ name: 'safe-server' })
|
|
289
|
+
|
|
290
|
+
// Safe: Content sanitized before return
|
|
291
|
+
server.tool('fetch_safe', async ({ url }: { url: string }, context) => {
|
|
292
|
+
const userId = context.user.id // User context!
|
|
293
|
+
const response = await fetch(url)
|
|
294
|
+
const html = await response.text()
|
|
295
|
+
// Content is sanitized
|
|
296
|
+
const sanitized = sanitize(html, { ALLOWED_TAGS: ['p', 'a', 'b'] })
|
|
297
|
+
return { content: sanitized }
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
// Safe: User context properly validated
|
|
301
|
+
server.tool('get_my_files', async (_params, context) => {
|
|
302
|
+
const userId = context.user.id
|
|
303
|
+
// Only getting the current user's files
|
|
304
|
+
const files = await db.files.findByUser(userId)
|
|
305
|
+
return { files: files.map(f => ({ id: f.id, name: f.name })) }
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// Safe: Server-side auth, no credentials exposed
|
|
309
|
+
server.tool('call_external_api', async ({ endpoint }: { endpoint: string }) => {
|
|
310
|
+
// API key from environment, not passed as parameter
|
|
311
|
+
const response = await fetch(endpoint, {
|
|
312
|
+
headers: { 'Authorization': \`Bearer \${process.env.INTERNAL_API_KEY}\` }
|
|
313
|
+
})
|
|
314
|
+
// Only returning safe data, no credentials
|
|
315
|
+
return { data: await response.json() }
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
// Safe: Proper authorization check
|
|
319
|
+
server.tool('delete_my_record', async ({ recordId }: { recordId: string }, context) => {
|
|
320
|
+
const userId = context.user.id
|
|
321
|
+
const record = await db.records.findById(recordId)
|
|
322
|
+
|
|
323
|
+
// Authorization check
|
|
324
|
+
if (record.ownerId !== userId) {
|
|
325
|
+
throw new Error('Not authorized')
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
await db.records.delete(recordId)
|
|
329
|
+
return { success: true }
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// Safe: Content validation with schema
|
|
333
|
+
server.tool('search_public_data', async ({ query }: { query: string }, context) => {
|
|
334
|
+
const userId = context.user.id
|
|
335
|
+
const results = await db.publicData.search(query)
|
|
336
|
+
// Returning only safe fields, validated structure
|
|
337
|
+
return {
|
|
338
|
+
results: results.map(r => ({
|
|
339
|
+
id: r.id,
|
|
340
|
+
title: sanitize(r.title), // Sanitized
|
|
341
|
+
summary: sanitize(r.summary) // Sanitized
|
|
342
|
+
}))
|
|
343
|
+
}
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
// Safe: Tenant-scoped operations
|
|
347
|
+
server.tool('get_org_report', async ({ reportId }: { reportId: string }, context) => {
|
|
348
|
+
const tenantId = context.user.tenantId
|
|
349
|
+
const report = await db.reports.findById(reportId)
|
|
350
|
+
|
|
351
|
+
// Tenant check
|
|
352
|
+
if (report.tenantId !== tenantId) {
|
|
353
|
+
throw new Error('Not authorized')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return { report }
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// Safe: Pure computation, no external data
|
|
360
|
+
server.tool('calculate', async ({ expression }: { expression: string }) => {
|
|
361
|
+
// Safe math evaluation, no external content
|
|
362
|
+
const result = mathjs.evaluate(expression)
|
|
363
|
+
return { result }
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
// Safe: Static data only
|
|
367
|
+
server.tool('get_documentation', async ({ topic }: { topic: string }) => {
|
|
368
|
+
// Returns static documentation, not user content
|
|
369
|
+
const docs = await loadStaticDocs(topic)
|
|
370
|
+
return { content: docs }
|
|
371
|
+
})
|
|
372
|
+
`,
|
|
373
|
+
language: 'typescript',
|
|
374
|
+
size: 2500,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'MCP Security - Test File Patterns',
|
|
379
|
+
expectFindings: false,
|
|
380
|
+
description: 'Test files should not be flagged',
|
|
381
|
+
allowedInfoFindings: [
|
|
382
|
+
{
|
|
383
|
+
category: 'ai_mcp_tool_poisoning',
|
|
384
|
+
maxCount: 3,
|
|
385
|
+
reason: 'Test file may have info-level findings for review',
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
category: 'ai_mcp_credential_issue',
|
|
389
|
+
maxCount: 3,
|
|
390
|
+
reason: 'Test file may have info-level findings for review',
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
category: 'ai_mcp_confused_deputy',
|
|
394
|
+
maxCount: 3,
|
|
395
|
+
reason: 'Test file may have info-level findings for review',
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
category: 'ai_overpermissive_tool',
|
|
399
|
+
maxCount: 3,
|
|
400
|
+
reason: 'ai-agent-tools detector may flag MCP patterns',
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
file: {
|
|
404
|
+
path: 'src/mcp/__tests__/tools.test.ts',
|
|
405
|
+
content: `
|
|
406
|
+
import { McpServer } from '@modelcontextprotocol/sdk'
|
|
407
|
+
|
|
408
|
+
describe('MCP Tools', () => {
|
|
409
|
+
it('should handle raw content in tests', async () => {
|
|
410
|
+
const server = new McpServer({ name: 'test-server' })
|
|
411
|
+
|
|
412
|
+
server.tool('fetch_test', async ({ url }) => {
|
|
413
|
+
const response = await fetch(url)
|
|
414
|
+
return { content: await response.text() }
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// Test assertions
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it('should test credential handling', async () => {
|
|
421
|
+
const server = new McpServer({ name: 'test-server' })
|
|
422
|
+
|
|
423
|
+
server.tool('auth_test', async ({ apiKey }) => {
|
|
424
|
+
return { token: 'test-token', apiKey }
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should test user operations', async () => {
|
|
429
|
+
const server = new McpServer({ name: 'test-server' })
|
|
430
|
+
|
|
431
|
+
server.tool('delete_test', async ({ id }) => {
|
|
432
|
+
await mockDb.delete(id)
|
|
433
|
+
return { success: true }
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
`,
|
|
438
|
+
language: 'typescript',
|
|
439
|
+
size: 900,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: 'MCP Security - Example/Demo Files',
|
|
444
|
+
expectFindings: false,
|
|
445
|
+
description: 'Example files in documentation should be downgraded',
|
|
446
|
+
allowedInfoFindings: [
|
|
447
|
+
{
|
|
448
|
+
category: 'ai_mcp_tool_poisoning',
|
|
449
|
+
maxCount: 5,
|
|
450
|
+
reason: 'Example files may have info-level findings',
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
category: 'ai_mcp_credential_issue',
|
|
454
|
+
maxCount: 3,
|
|
455
|
+
reason: 'Example files may have info-level findings',
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
category: 'ai_mcp_confused_deputy',
|
|
459
|
+
maxCount: 3,
|
|
460
|
+
reason: 'Example files may have info-level findings',
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
category: 'ai_overpermissive_tool',
|
|
464
|
+
maxCount: 3,
|
|
465
|
+
reason: 'ai-agent-tools detector may flag MCP patterns',
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
file: {
|
|
469
|
+
path: 'examples/mcp-quickstart/server.ts',
|
|
470
|
+
content: `
|
|
471
|
+
// This is an example file for documentation
|
|
472
|
+
import { McpServer } from '@modelcontextprotocol/sdk'
|
|
473
|
+
|
|
474
|
+
const server = new McpServer({ name: 'example-server' })
|
|
475
|
+
|
|
476
|
+
// Example: Simple fetch tool
|
|
477
|
+
server.tool('example_fetch', async ({ url }) => {
|
|
478
|
+
const response = await fetch(url)
|
|
479
|
+
return { content: await response.text() }
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// Example: Data retrieval
|
|
483
|
+
server.tool('example_query', async ({ query }) => {
|
|
484
|
+
const results = await db.query(query)
|
|
485
|
+
return { data: results }
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
console.log('Example server started')
|
|
489
|
+
`,
|
|
490
|
+
language: 'typescript',
|
|
491
|
+
size: 500,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
],
|
|
495
|
+
}
|