@oculum/scanner 1.0.0
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/formatters/cli-terminal.d.ts +27 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -0
- package/dist/formatters/cli-terminal.js +412 -0
- package/dist/formatters/cli-terminal.js.map +1 -0
- package/dist/formatters/github-comment.d.ts +41 -0
- package/dist/formatters/github-comment.d.ts.map +1 -0
- package/dist/formatters/github-comment.js +306 -0
- package/dist/formatters/github-comment.js.map +1 -0
- package/dist/formatters/grouping.d.ts +52 -0
- package/dist/formatters/grouping.d.ts.map +1 -0
- package/dist/formatters/grouping.js +152 -0
- package/dist/formatters/grouping.js.map +1 -0
- package/dist/formatters/index.d.ts +9 -0
- package/dist/formatters/index.d.ts.map +1 -0
- package/dist/formatters/index.js +35 -0
- package/dist/formatters/index.js.map +1 -0
- package/dist/formatters/vscode-diagnostic.d.ts +103 -0
- package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
- package/dist/formatters/vscode-diagnostic.js +151 -0
- package/dist/formatters/vscode-diagnostic.js.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/layer1/comments.d.ts +8 -0
- package/dist/layer1/comments.d.ts.map +1 -0
- package/dist/layer1/comments.js +203 -0
- package/dist/layer1/comments.js.map +1 -0
- package/dist/layer1/config-audit.d.ts +8 -0
- package/dist/layer1/config-audit.d.ts.map +1 -0
- package/dist/layer1/config-audit.js +252 -0
- package/dist/layer1/config-audit.js.map +1 -0
- package/dist/layer1/entropy.d.ts +8 -0
- package/dist/layer1/entropy.d.ts.map +1 -0
- package/dist/layer1/entropy.js +500 -0
- package/dist/layer1/entropy.js.map +1 -0
- package/dist/layer1/file-flags.d.ts +7 -0
- package/dist/layer1/file-flags.d.ts.map +1 -0
- package/dist/layer1/file-flags.js +112 -0
- package/dist/layer1/file-flags.js.map +1 -0
- package/dist/layer1/index.d.ts +36 -0
- package/dist/layer1/index.d.ts.map +1 -0
- package/dist/layer1/index.js +132 -0
- package/dist/layer1/index.js.map +1 -0
- package/dist/layer1/patterns.d.ts +8 -0
- package/dist/layer1/patterns.d.ts.map +1 -0
- package/dist/layer1/patterns.js +482 -0
- package/dist/layer1/patterns.js.map +1 -0
- package/dist/layer1/urls.d.ts +8 -0
- package/dist/layer1/urls.d.ts.map +1 -0
- package/dist/layer1/urls.js +296 -0
- package/dist/layer1/urls.js.map +1 -0
- package/dist/layer1/weak-crypto.d.ts +7 -0
- package/dist/layer1/weak-crypto.d.ts.map +1 -0
- package/dist/layer1/weak-crypto.js +291 -0
- package/dist/layer1/weak-crypto.js.map +1 -0
- package/dist/layer2/ai-agent-tools.d.ts +19 -0
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
- package/dist/layer2/ai-agent-tools.js +528 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -0
- package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
- package/dist/layer2/ai-endpoint-protection.js +332 -0
- package/dist/layer2/ai-endpoint-protection.js.map +1 -0
- package/dist/layer2/ai-execution-sinks.d.ts +18 -0
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
- package/dist/layer2/ai-execution-sinks.js +496 -0
- package/dist/layer2/ai-execution-sinks.js.map +1 -0
- package/dist/layer2/ai-fingerprinting.d.ts +7 -0
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
- package/dist/layer2/ai-fingerprinting.js +654 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.js +356 -0
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
- package/dist/layer2/ai-rag-safety.d.ts +21 -0
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
- package/dist/layer2/ai-rag-safety.js +459 -0
- package/dist/layer2/ai-rag-safety.js.map +1 -0
- package/dist/layer2/ai-schema-validation.d.ts +25 -0
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
- package/dist/layer2/ai-schema-validation.js +375 -0
- package/dist/layer2/ai-schema-validation.js.map +1 -0
- package/dist/layer2/auth-antipatterns.d.ts +20 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
- package/dist/layer2/auth-antipatterns.js +333 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -0
- package/dist/layer2/byok-patterns.d.ts +12 -0
- package/dist/layer2/byok-patterns.d.ts.map +1 -0
- package/dist/layer2/byok-patterns.js +299 -0
- package/dist/layer2/byok-patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions.d.ts +7 -0
- package/dist/layer2/dangerous-functions.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions.js +1375 -0
- package/dist/layer2/dangerous-functions.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +16 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -0
- package/dist/layer2/data-exposure.js +279 -0
- package/dist/layer2/data-exposure.js.map +1 -0
- package/dist/layer2/framework-checks.d.ts +7 -0
- package/dist/layer2/framework-checks.d.ts.map +1 -0
- package/dist/layer2/framework-checks.js +388 -0
- package/dist/layer2/framework-checks.js.map +1 -0
- package/dist/layer2/index.d.ts +58 -0
- package/dist/layer2/index.d.ts.map +1 -0
- package/dist/layer2/index.js +380 -0
- package/dist/layer2/index.js.map +1 -0
- package/dist/layer2/logic-gates.d.ts +7 -0
- package/dist/layer2/logic-gates.d.ts.map +1 -0
- package/dist/layer2/logic-gates.js +182 -0
- package/dist/layer2/logic-gates.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +7 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -0
- package/dist/layer2/risky-imports.js +161 -0
- package/dist/layer2/risky-imports.js.map +1 -0
- package/dist/layer2/variables.d.ts +8 -0
- package/dist/layer2/variables.d.ts.map +1 -0
- package/dist/layer2/variables.js +152 -0
- package/dist/layer2/variables.js.map +1 -0
- package/dist/layer3/anthropic.d.ts +83 -0
- package/dist/layer3/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic.js +1745 -0
- package/dist/layer3/anthropic.js.map +1 -0
- package/dist/layer3/index.d.ts +24 -0
- package/dist/layer3/index.d.ts.map +1 -0
- package/dist/layer3/index.js +119 -0
- package/dist/layer3/index.js.map +1 -0
- package/dist/layer3/openai.d.ts +25 -0
- package/dist/layer3/openai.d.ts.map +1 -0
- package/dist/layer3/openai.js +238 -0
- package/dist/layer3/openai.js.map +1 -0
- package/dist/layer3/package-check.d.ts +63 -0
- package/dist/layer3/package-check.d.ts.map +1 -0
- package/dist/layer3/package-check.js +508 -0
- package/dist/layer3/package-check.js.map +1 -0
- package/dist/modes/incremental.d.ts +66 -0
- package/dist/modes/incremental.d.ts.map +1 -0
- package/dist/modes/incremental.js +200 -0
- package/dist/modes/incremental.js.map +1 -0
- package/dist/tiers.d.ts +125 -0
- package/dist/tiers.d.ts.map +1 -0
- package/dist/tiers.js +234 -0
- package/dist/tiers.js.map +1 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth-helper-detector.d.ts +56 -0
- package/dist/utils/auth-helper-detector.d.ts.map +1 -0
- package/dist/utils/auth-helper-detector.js +360 -0
- package/dist/utils/auth-helper-detector.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +96 -0
- package/dist/utils/context-helpers.d.ts.map +1 -0
- package/dist/utils/context-helpers.js +493 -0
- package/dist/utils/context-helpers.js.map +1 -0
- package/dist/utils/diff-detector.d.ts +53 -0
- package/dist/utils/diff-detector.d.ts.map +1 -0
- package/dist/utils/diff-detector.js +104 -0
- package/dist/utils/diff-detector.js.map +1 -0
- package/dist/utils/diff-parser.d.ts +80 -0
- package/dist/utils/diff-parser.d.ts.map +1 -0
- package/dist/utils/diff-parser.js +202 -0
- package/dist/utils/diff-parser.js.map +1 -0
- package/dist/utils/imported-auth-detector.d.ts +37 -0
- package/dist/utils/imported-auth-detector.d.ts.map +1 -0
- package/dist/utils/imported-auth-detector.js +251 -0
- package/dist/utils/imported-auth-detector.js.map +1 -0
- package/dist/utils/middleware-detector.d.ts +55 -0
- package/dist/utils/middleware-detector.d.ts.map +1 -0
- package/dist/utils/middleware-detector.js +260 -0
- package/dist/utils/middleware-detector.js.map +1 -0
- package/dist/utils/oauth-flow-detector.d.ts +41 -0
- package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
- package/dist/utils/oauth-flow-detector.js +202 -0
- package/dist/utils/oauth-flow-detector.js.map +1 -0
- package/dist/utils/path-exclusions.d.ts +55 -0
- package/dist/utils/path-exclusions.d.ts.map +1 -0
- package/dist/utils/path-exclusions.js +222 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/project-context-builder.d.ts +119 -0
- package/dist/utils/project-context-builder.d.ts.map +1 -0
- package/dist/utils/project-context-builder.js +534 -0
- package/dist/utils/project-context-builder.js.map +1 -0
- package/dist/utils/registry-clients.d.ts +93 -0
- package/dist/utils/registry-clients.d.ts.map +1 -0
- package/dist/utils/registry-clients.js +273 -0
- package/dist/utils/registry-clients.js.map +1 -0
- package/dist/utils/trpc-analyzer.d.ts +78 -0
- package/dist/utils/trpc-analyzer.d.ts.map +1 -0
- package/dist/utils/trpc-analyzer.js +297 -0
- package/dist/utils/trpc-analyzer.js.map +1 -0
- package/package.json +45 -0
- package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
- package/src/__tests__/benchmark/fixtures/index.ts +68 -0
- package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
- package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
- package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
- package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
- package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
- package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
- package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
- package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
- package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
- package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
- package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
- package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
- package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
- package/src/__tests__/benchmark/index.ts +29 -0
- package/src/__tests__/benchmark/run-benchmark.ts +144 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
- package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
- package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
- package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
- package/src/__tests__/benchmark/types.ts +144 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
- package/src/__tests__/regression/known-false-positives.test.ts +467 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
- package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
- package/src/__tests__/validation/analyze-results.ts +542 -0
- package/src/__tests__/validation/extract-for-triage.ts +146 -0
- package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
- package/src/__tests__/validation/run-validation.ts +364 -0
- package/src/__tests__/validation/triage-template.md +132 -0
- package/src/formatters/cli-terminal.ts +446 -0
- package/src/formatters/github-comment.ts +382 -0
- package/src/formatters/grouping.ts +190 -0
- package/src/formatters/index.ts +47 -0
- package/src/formatters/vscode-diagnostic.ts +243 -0
- package/src/index.ts +823 -0
- package/src/layer1/comments.ts +218 -0
- package/src/layer1/config-audit.ts +289 -0
- package/src/layer1/entropy.ts +583 -0
- package/src/layer1/file-flags.ts +127 -0
- package/src/layer1/index.ts +181 -0
- package/src/layer1/patterns.ts +516 -0
- package/src/layer1/urls.ts +334 -0
- package/src/layer1/weak-crypto.ts +328 -0
- package/src/layer2/ai-agent-tools.ts +601 -0
- package/src/layer2/ai-endpoint-protection.ts +387 -0
- package/src/layer2/ai-execution-sinks.ts +580 -0
- package/src/layer2/ai-fingerprinting.ts +758 -0
- package/src/layer2/ai-prompt-hygiene.ts +411 -0
- package/src/layer2/ai-rag-safety.ts +511 -0
- package/src/layer2/ai-schema-validation.ts +421 -0
- package/src/layer2/auth-antipatterns.ts +394 -0
- package/src/layer2/byok-patterns.ts +336 -0
- package/src/layer2/dangerous-functions.ts +1563 -0
- package/src/layer2/data-exposure.ts +315 -0
- package/src/layer2/framework-checks.ts +433 -0
- package/src/layer2/index.ts +473 -0
- package/src/layer2/logic-gates.ts +206 -0
- package/src/layer2/risky-imports.ts +186 -0
- package/src/layer2/variables.ts +166 -0
- package/src/layer3/anthropic.ts +2030 -0
- package/src/layer3/index.ts +130 -0
- package/src/layer3/package-check.ts +604 -0
- package/src/modes/incremental.ts +293 -0
- package/src/tiers.ts +318 -0
- package/src/types.ts +284 -0
- package/src/utils/auth-helper-detector.ts +443 -0
- package/src/utils/context-helpers.ts +535 -0
- package/src/utils/diff-detector.ts +135 -0
- package/src/utils/diff-parser.ts +272 -0
- package/src/utils/imported-auth-detector.ts +320 -0
- package/src/utils/middleware-detector.ts +333 -0
- package/src/utils/oauth-flow-detector.ts +246 -0
- package/src/utils/path-exclusions.ts +266 -0
- package/src/utils/project-context-builder.ts +707 -0
- package/src/utils/registry-clients.ts +351 -0
- package/src/utils/trpc-analyzer.ts +382 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Endpoint Protection Test Fixtures
|
|
3
|
+
* Tests for detecting unprotected AI/LLM endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const aiEndpointProtectionTests: TestGroup = {
|
|
9
|
+
name: 'AI Endpoint Protection',
|
|
10
|
+
tier: 'A',
|
|
11
|
+
layer: 2,
|
|
12
|
+
description: 'Detection of AI endpoints without proper authentication or rate limiting',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'AI Endpoints - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['ai_endpoint_unprotected'],
|
|
19
|
+
description: 'Unprotected AI endpoints that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/app/api/chat/route.ts',
|
|
22
|
+
content: `
|
|
23
|
+
import { OpenAI } from 'openai'
|
|
24
|
+
import { NextResponse } from 'next/server'
|
|
25
|
+
|
|
26
|
+
const openai = new OpenAI()
|
|
27
|
+
|
|
28
|
+
// Next.js App Router - NO AUTH, NO RATE LIMIT - HIGH
|
|
29
|
+
export async function POST(request: Request) {
|
|
30
|
+
const { messages } = await request.json()
|
|
31
|
+
|
|
32
|
+
const completion = await openai.chat.completions.create({
|
|
33
|
+
model: 'gpt-4',
|
|
34
|
+
messages,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return NextResponse.json({ result: completion.choices[0].message })
|
|
38
|
+
}
|
|
39
|
+
`,
|
|
40
|
+
language: 'typescript',
|
|
41
|
+
size: 400,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Express AI Route - No Protection',
|
|
46
|
+
expectFindings: true,
|
|
47
|
+
expectedCategories: ['ai_endpoint_unprotected'],
|
|
48
|
+
description: 'Express route with AI calls but no auth or rate limiting',
|
|
49
|
+
file: {
|
|
50
|
+
path: 'src/routes/api/generate.ts',
|
|
51
|
+
content: `
|
|
52
|
+
import express from 'express'
|
|
53
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
54
|
+
|
|
55
|
+
const router = express.Router()
|
|
56
|
+
const anthropic = new Anthropic()
|
|
57
|
+
|
|
58
|
+
// Express route - NO AUTH, NO RATE LIMIT - HIGH
|
|
59
|
+
router.post('/generate', async (req, res) => {
|
|
60
|
+
const { prompt } = req.body
|
|
61
|
+
|
|
62
|
+
const message = await anthropic.messages.create({
|
|
63
|
+
model: 'claude-3-opus-20240229',
|
|
64
|
+
max_tokens: 1024,
|
|
65
|
+
messages: [{ role: 'user', content: prompt }],
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
res.json({ response: message.content })
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
export default router
|
|
72
|
+
`,
|
|
73
|
+
language: 'typescript',
|
|
74
|
+
size: 450,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Next.js Pages API - No Protection',
|
|
79
|
+
expectFindings: true,
|
|
80
|
+
expectedCategories: ['ai_endpoint_unprotected'],
|
|
81
|
+
description: 'Next.js Pages API route with AI calls but no protection',
|
|
82
|
+
file: {
|
|
83
|
+
path: 'src/pages/api/completion.ts',
|
|
84
|
+
content: `
|
|
85
|
+
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
86
|
+
import OpenAI from 'openai'
|
|
87
|
+
|
|
88
|
+
const openai = new OpenAI()
|
|
89
|
+
|
|
90
|
+
// Next.js Pages API - NO AUTH - HIGH
|
|
91
|
+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
92
|
+
if (req.method !== 'POST') {
|
|
93
|
+
return res.status(405).json({ error: 'Method not allowed' })
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const { prompt } = req.body
|
|
97
|
+
|
|
98
|
+
const response = await openai.chat.completions.create({
|
|
99
|
+
model: 'gpt-3.5-turbo',
|
|
100
|
+
messages: [{ role: 'user', content: prompt }],
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
res.json({ result: response.choices[0].message.content })
|
|
104
|
+
}
|
|
105
|
+
`,
|
|
106
|
+
language: 'typescript',
|
|
107
|
+
size: 500,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Vercel AI SDK - No Protection',
|
|
112
|
+
expectFindings: true,
|
|
113
|
+
expectedCategories: ['ai_endpoint_unprotected'],
|
|
114
|
+
description: 'Vercel AI SDK endpoint without auth or rate limiting',
|
|
115
|
+
file: {
|
|
116
|
+
path: 'src/app/api/stream/route.ts',
|
|
117
|
+
content: `
|
|
118
|
+
import { streamText } from 'ai'
|
|
119
|
+
import { openai } from '@ai-sdk/openai'
|
|
120
|
+
|
|
121
|
+
// Vercel AI SDK - NO AUTH, NO RATE LIMIT - HIGH
|
|
122
|
+
export async function POST(req: Request) {
|
|
123
|
+
const { messages } = await req.json()
|
|
124
|
+
|
|
125
|
+
const result = await streamText({
|
|
126
|
+
model: openai('gpt-4'),
|
|
127
|
+
messages,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return result.toDataStreamResponse()
|
|
131
|
+
}
|
|
132
|
+
`,
|
|
133
|
+
language: 'typescript',
|
|
134
|
+
size: 300,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'Auth Only - Missing Rate Limit',
|
|
139
|
+
expectFindings: true,
|
|
140
|
+
expectedCategories: ['ai_endpoint_unprotected'],
|
|
141
|
+
description: 'AI endpoint with auth but missing rate limiting - LOW severity',
|
|
142
|
+
file: {
|
|
143
|
+
path: 'src/app/api/ask/route.ts',
|
|
144
|
+
content: `
|
|
145
|
+
import { OpenAI } from 'openai'
|
|
146
|
+
import { NextResponse } from 'next/server'
|
|
147
|
+
import { getServerSession } from 'next-auth'
|
|
148
|
+
|
|
149
|
+
const openai = new OpenAI()
|
|
150
|
+
|
|
151
|
+
// Has auth but no rate limiting - LOW
|
|
152
|
+
export async function POST(request: Request) {
|
|
153
|
+
const session = await getServerSession()
|
|
154
|
+
if (!session) {
|
|
155
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { prompt } = await request.json()
|
|
159
|
+
|
|
160
|
+
const completion = await openai.chat.completions.create({
|
|
161
|
+
model: 'gpt-4',
|
|
162
|
+
messages: [{ role: 'user', content: prompt }],
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return NextResponse.json({ result: completion.choices[0].message })
|
|
166
|
+
}
|
|
167
|
+
`,
|
|
168
|
+
language: 'typescript',
|
|
169
|
+
size: 500,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
|
|
174
|
+
falseNegatives: [
|
|
175
|
+
{
|
|
176
|
+
name: 'AI Endpoints - False Negatives',
|
|
177
|
+
expectFindings: false,
|
|
178
|
+
description: 'Properly protected AI endpoints that should NOT be flagged',
|
|
179
|
+
allowedInfoFindings: [
|
|
180
|
+
{
|
|
181
|
+
category: 'ai_endpoint_unprotected',
|
|
182
|
+
maxCount: 2,
|
|
183
|
+
reason: 'Protected endpoints may still generate info-level findings for documentation',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
category: 'dangerous_function',
|
|
187
|
+
maxCount: 1,
|
|
188
|
+
reason: 'Request body parsing may trigger info-level schema validation suggestions',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
file: {
|
|
192
|
+
path: 'src/app/api/protected-chat/route.ts',
|
|
193
|
+
content: `
|
|
194
|
+
import { OpenAI } from 'openai'
|
|
195
|
+
import { NextResponse } from 'next/server'
|
|
196
|
+
import { getServerSession } from 'next-auth'
|
|
197
|
+
import { rateLimit } from '@/lib/rate-limit'
|
|
198
|
+
|
|
199
|
+
const openai = new OpenAI()
|
|
200
|
+
const limiter = rateLimit({ limit: 10, window: 60 })
|
|
201
|
+
|
|
202
|
+
// Has both auth AND rate limiting - SAFE
|
|
203
|
+
export async function POST(request: Request) {
|
|
204
|
+
// Auth check
|
|
205
|
+
const session = await getServerSession()
|
|
206
|
+
if (!session) {
|
|
207
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Rate limit check
|
|
211
|
+
const { success } = await limiter.check(session.user.id)
|
|
212
|
+
if (!success) {
|
|
213
|
+
return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 })
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const { prompt } = await request.json()
|
|
217
|
+
|
|
218
|
+
const completion = await openai.chat.completions.create({
|
|
219
|
+
model: 'gpt-4',
|
|
220
|
+
messages: [{ role: 'user', content: prompt }],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
return NextResponse.json({ result: completion.choices[0].message })
|
|
224
|
+
}
|
|
225
|
+
`,
|
|
226
|
+
language: 'typescript',
|
|
227
|
+
size: 700,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'BYOK Endpoint - User API Key',
|
|
232
|
+
expectFindings: false,
|
|
233
|
+
description: 'Bring Your Own Key endpoint should be downgraded',
|
|
234
|
+
allowedInfoFindings: [
|
|
235
|
+
{
|
|
236
|
+
category: 'ai_endpoint_unprotected',
|
|
237
|
+
maxCount: 1,
|
|
238
|
+
reason: 'BYOK endpoints have lower risk but may still generate info findings',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
category: 'dangerous_function',
|
|
242
|
+
maxCount: 1,
|
|
243
|
+
reason: 'Request body parsing may trigger info-level schema validation suggestions',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
category: 'missing_auth',
|
|
247
|
+
maxCount: 1,
|
|
248
|
+
reason: 'BYOK pattern detected as form of implicit auth at low severity',
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
category: 'ai_pattern',
|
|
252
|
+
maxCount: 1,
|
|
253
|
+
reason: 'BYOK detection at low severity when transient usage',
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
file: {
|
|
257
|
+
path: 'src/app/api/byok-chat/route.ts',
|
|
258
|
+
content: `
|
|
259
|
+
import OpenAI from 'openai'
|
|
260
|
+
import { NextResponse } from 'next/server'
|
|
261
|
+
|
|
262
|
+
// BYOK endpoint - user provides their own key - LOWER RISK
|
|
263
|
+
export async function POST(request: Request) {
|
|
264
|
+
const { prompt, userApiKey } = await request.json()
|
|
265
|
+
|
|
266
|
+
if (!userApiKey) {
|
|
267
|
+
return NextResponse.json({ error: 'API key required' }, { status: 400 })
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// User's own key - they pay for their own usage
|
|
271
|
+
const openai = new OpenAI({ apiKey: userApiKey })
|
|
272
|
+
|
|
273
|
+
const completion = await openai.chat.completions.create({
|
|
274
|
+
model: 'gpt-4',
|
|
275
|
+
messages: [{ role: 'user', content: prompt }],
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
return NextResponse.json({ result: completion.choices[0].message })
|
|
279
|
+
}
|
|
280
|
+
`,
|
|
281
|
+
language: 'typescript',
|
|
282
|
+
size: 550,
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: 'Internal Admin Route',
|
|
287
|
+
expectFindings: false,
|
|
288
|
+
description: 'Internal/admin routes should be downgraded',
|
|
289
|
+
allowedInfoFindings: [
|
|
290
|
+
{
|
|
291
|
+
category: 'ai_endpoint_unprotected',
|
|
292
|
+
maxCount: 1,
|
|
293
|
+
reason: 'Internal routes are treated as lower risk',
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
category: 'dangerous_function',
|
|
297
|
+
maxCount: 1,
|
|
298
|
+
reason: 'Request body parsing may trigger info-level schema validation suggestions',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
category: 'missing_auth',
|
|
302
|
+
maxCount: 1,
|
|
303
|
+
reason: 'Internal secret check detected at low severity',
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
file: {
|
|
307
|
+
path: 'src/app/api/internal/ai-admin/route.ts',
|
|
308
|
+
content: `
|
|
309
|
+
import { OpenAI } from 'openai'
|
|
310
|
+
import { NextResponse } from 'next/server'
|
|
311
|
+
|
|
312
|
+
const openai = new OpenAI()
|
|
313
|
+
|
|
314
|
+
// Internal route - network-level protection assumed - INFO
|
|
315
|
+
export async function POST(request: Request) {
|
|
316
|
+
// Check internal secret
|
|
317
|
+
const internalSecret = request.headers.get('x-internal-secret')
|
|
318
|
+
if (internalSecret !== process.env.INTERNAL_SECRET) {
|
|
319
|
+
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { prompt } = await request.json()
|
|
323
|
+
|
|
324
|
+
const completion = await openai.chat.completions.create({
|
|
325
|
+
model: 'gpt-4',
|
|
326
|
+
messages: [{ role: 'user', content: prompt }],
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
return NextResponse.json({ result: completion.choices[0].message })
|
|
330
|
+
}
|
|
331
|
+
`,
|
|
332
|
+
language: 'typescript',
|
|
333
|
+
size: 550,
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: 'Express with Middleware',
|
|
338
|
+
expectFindings: false,
|
|
339
|
+
description: 'Express route with auth and rate limit middleware',
|
|
340
|
+
allowedInfoFindings: [
|
|
341
|
+
{
|
|
342
|
+
category: 'ai_endpoint_unprotected',
|
|
343
|
+
maxCount: 1,
|
|
344
|
+
reason: 'Protected routes may generate info findings',
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
file: {
|
|
348
|
+
path: 'src/routes/api/protected-generate.ts',
|
|
349
|
+
content: `
|
|
350
|
+
import express from 'express'
|
|
351
|
+
import { OpenAI } from 'openai'
|
|
352
|
+
import { authMiddleware } from '@/middleware/auth'
|
|
353
|
+
import { rateLimiter } from '@/middleware/rate-limit'
|
|
354
|
+
|
|
355
|
+
const router = express.Router()
|
|
356
|
+
const openai = new OpenAI()
|
|
357
|
+
|
|
358
|
+
// Express with proper middleware chain - SAFE
|
|
359
|
+
router.post('/generate', authMiddleware, rateLimiter, async (req, res) => {
|
|
360
|
+
const { prompt } = req.body
|
|
361
|
+
const userId = req.user.id
|
|
362
|
+
|
|
363
|
+
const completion = await openai.chat.completions.create({
|
|
364
|
+
model: 'gpt-4',
|
|
365
|
+
messages: [{ role: 'user', content: prompt }],
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
res.json({ result: completion.choices[0].message })
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
export default router
|
|
372
|
+
`,
|
|
373
|
+
language: 'typescript',
|
|
374
|
+
size: 550,
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'Fastify with Hooks',
|
|
379
|
+
expectFindings: false,
|
|
380
|
+
description: 'Fastify route with preHandler hooks for auth and rate limiting',
|
|
381
|
+
allowedInfoFindings: [
|
|
382
|
+
{
|
|
383
|
+
category: 'ai_endpoint_unprotected',
|
|
384
|
+
maxCount: 1,
|
|
385
|
+
reason: 'Protected routes may generate info findings',
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
file: {
|
|
389
|
+
path: 'src/routes/api/fastify-chat.ts',
|
|
390
|
+
content: `
|
|
391
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
392
|
+
import { authenticate, rateLimit } from '@/hooks'
|
|
393
|
+
|
|
394
|
+
const anthropic = new Anthropic()
|
|
395
|
+
|
|
396
|
+
// Fastify with preHandler hooks - SAFE
|
|
397
|
+
fastify.post('/chat', {
|
|
398
|
+
preHandler: [authenticate, rateLimit],
|
|
399
|
+
handler: async (request, reply) => {
|
|
400
|
+
const { prompt } = request.body
|
|
401
|
+
const userId = request.user.id
|
|
402
|
+
|
|
403
|
+
const message = await anthropic.messages.create({
|
|
404
|
+
model: 'claude-3-opus-20240229',
|
|
405
|
+
max_tokens: 1024,
|
|
406
|
+
messages: [{ role: 'user', content: prompt }],
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
return { response: message.content }
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
`,
|
|
413
|
+
language: 'typescript',
|
|
414
|
+
size: 500,
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Execution Sinks Test Fixtures
|
|
3
|
+
* Tests for detecting unsafe execution of LLM-generated code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const aiExecutionSinksTests: TestGroup = {
|
|
9
|
+
name: 'AI Execution Sinks',
|
|
10
|
+
tier: 'A',
|
|
11
|
+
layer: 2,
|
|
12
|
+
description: 'Detection of unsafe execution of LLM-generated code, SQL, and commands',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'AI Execution Sinks - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['ai_unsafe_execution', 'dangerous_function'],
|
|
19
|
+
description: 'AI execution sink patterns that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/api/ai/code-executor.ts',
|
|
22
|
+
content: `
|
|
23
|
+
import OpenAI from 'openai'
|
|
24
|
+
import { exec } from 'child_process'
|
|
25
|
+
|
|
26
|
+
const openai = new OpenAI()
|
|
27
|
+
|
|
28
|
+
// LLM output to eval() - CRITICAL
|
|
29
|
+
export async function executeAICode(prompt: string) {
|
|
30
|
+
const response = await openai.chat.completions.create({
|
|
31
|
+
model: 'gpt-4',
|
|
32
|
+
messages: [{ role: 'user', content: prompt }]
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const code = response.choices[0].message.content
|
|
36
|
+
return eval(code) // CRITICAL: AI output executed directly!
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// LLM output to Function() - CRITICAL
|
|
40
|
+
export async function createAIFunction(prompt: string) {
|
|
41
|
+
const response = await openai.chat.completions.create({
|
|
42
|
+
model: 'gpt-4',
|
|
43
|
+
messages: [{ role: 'user', content: \`Generate a JS function: \${prompt}\` }]
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return new Function(response.choices[0].message.content)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// LLM output to exec() - CRITICAL (Command Injection)
|
|
50
|
+
export async function executeAICommand(task: string) {
|
|
51
|
+
const response = await openai.chat.completions.create({
|
|
52
|
+
model: 'gpt-4',
|
|
53
|
+
messages: [{ role: 'user', content: \`Generate a shell command to: \${task}\` }]
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const command = response.choices[0].message.content
|
|
57
|
+
exec(command, (err, stdout) => console.log(stdout)) // CRITICAL!
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// LLM output to SQL query - CRITICAL (SQL Injection)
|
|
61
|
+
export async function executeAIQuery(userQuestion: string) {
|
|
62
|
+
const response = await openai.chat.completions.create({
|
|
63
|
+
model: 'gpt-4',
|
|
64
|
+
messages: [{
|
|
65
|
+
role: 'system',
|
|
66
|
+
content: 'Convert natural language to SQL'
|
|
67
|
+
}, {
|
|
68
|
+
role: 'user',
|
|
69
|
+
content: userQuestion
|
|
70
|
+
}]
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const sql = response.choices[0].message.content
|
|
74
|
+
return db.query(sql) // CRITICAL: AI-generated SQL executed directly!
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// LLM output to innerHTML - HIGH (XSS)
|
|
78
|
+
export async function renderAIContent(prompt: string) {
|
|
79
|
+
const response = await openai.chat.completions.create({
|
|
80
|
+
model: 'gpt-4',
|
|
81
|
+
messages: [{ role: 'user', content: prompt }]
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
document.getElementById('content').innerHTML = response.choices[0].message.content
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// LLM output to file system write - HIGH
|
|
88
|
+
export async function generateAndSaveFile(prompt: string) {
|
|
89
|
+
const response = await openai.chat.completions.create({
|
|
90
|
+
model: 'gpt-4',
|
|
91
|
+
messages: [{ role: 'user', content: prompt }]
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const filename = response.choices[0].message.content.split('\\n')[0]
|
|
95
|
+
const content = response.choices[0].message.content.split('\\n').slice(1).join('\\n')
|
|
96
|
+
fs.writeFileSync(filename, content) // AI controls filename!
|
|
97
|
+
}
|
|
98
|
+
`,
|
|
99
|
+
language: 'typescript',
|
|
100
|
+
size: 2200,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
|
|
105
|
+
falseNegatives: [
|
|
106
|
+
{
|
|
107
|
+
name: 'AI Execution Sinks - False Negatives',
|
|
108
|
+
expectFindings: false,
|
|
109
|
+
description: 'Safe AI output handling patterns that should NOT be flagged',
|
|
110
|
+
allowedInfoFindings: [
|
|
111
|
+
{
|
|
112
|
+
category: 'dangerous_function',
|
|
113
|
+
maxCount: 2,
|
|
114
|
+
reason: 'SQL query with whitelist validation and sandboxed VM execution generate info-level findings',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
category: 'suspicious_package',
|
|
118
|
+
maxCount: 1,
|
|
119
|
+
reason: 'vm2 is a security sandbox package, info-level only',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
category: 'ai_pattern',
|
|
123
|
+
maxCount: 2,
|
|
124
|
+
reason: 'AI console.log debugging is info-level operational finding',
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
file: {
|
|
128
|
+
path: 'src/api/ai/safe-executor.ts',
|
|
129
|
+
content: `
|
|
130
|
+
import OpenAI from 'openai'
|
|
131
|
+
import { VM } from 'vm2'
|
|
132
|
+
|
|
133
|
+
const openai = new OpenAI()
|
|
134
|
+
|
|
135
|
+
// LLM output for display only - SAFE
|
|
136
|
+
export async function getAIResponse(prompt: string) {
|
|
137
|
+
const response = await openai.chat.completions.create({
|
|
138
|
+
model: 'gpt-4',
|
|
139
|
+
messages: [{ role: 'user', content: prompt }]
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Only used for display, not execution
|
|
143
|
+
console.log(response.choices[0].message.content)
|
|
144
|
+
return { message: response.choices[0].message.content }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// LLM output with sandboxed execution - SAFE (Medium at most)
|
|
148
|
+
export async function executeSandboxed(prompt: string) {
|
|
149
|
+
const response = await openai.chat.completions.create({
|
|
150
|
+
model: 'gpt-4',
|
|
151
|
+
messages: [{ role: 'user', content: prompt }]
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const vm = new VM({
|
|
155
|
+
timeout: 1000,
|
|
156
|
+
sandbox: {}
|
|
157
|
+
})
|
|
158
|
+
return vm.run(response.choices[0].message.content)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// LLM output with parameterized SQL - SAFE
|
|
162
|
+
export async function safeAIQuery(userQuestion: string) {
|
|
163
|
+
const response = await openai.chat.completions.create({
|
|
164
|
+
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
|
+
}]
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const columns = response.choices[0].message.content
|
|
175
|
+
// Whitelist validation
|
|
176
|
+
const allowedColumns = ['id', 'name', 'email', 'created_at']
|
|
177
|
+
const requestedColumns = columns.split(',').map(c => c.trim())
|
|
178
|
+
const safeColumns = requestedColumns.filter(c => allowedColumns.includes(c))
|
|
179
|
+
|
|
180
|
+
// Parameterized query with validated columns
|
|
181
|
+
return db.query(\`SELECT \${safeColumns.join(', ')} FROM users WHERE id = $1\`, [userId])
|
|
182
|
+
}
|
|
183
|
+
`,
|
|
184
|
+
language: 'typescript',
|
|
185
|
+
size: 1500,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
}
|