@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,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Schema Validation Test Fixtures
|
|
3
|
+
* Tests for detecting missing/weak validation of AI-generated outputs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const aiSchemaValidationTests: TestGroup = {
|
|
9
|
+
name: 'AI Schema Validation',
|
|
10
|
+
tier: 'B',
|
|
11
|
+
layer: 2,
|
|
12
|
+
description: 'Detection of missing or weak schema validation on AI-generated outputs',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'Schema Validation - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['ai_schema_mismatch'],
|
|
19
|
+
description: 'Unvalidated AI output parsing that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/ai/chat-handler.ts',
|
|
22
|
+
content: `
|
|
23
|
+
import OpenAI from 'openai'
|
|
24
|
+
|
|
25
|
+
const openai = new OpenAI()
|
|
26
|
+
|
|
27
|
+
// Unvalidated JSON.parse of AI response - MEDIUM
|
|
28
|
+
export async function getStructuredResponse(prompt: string) {
|
|
29
|
+
const completion = await openai.chat.completions.create({
|
|
30
|
+
model: 'gpt-4',
|
|
31
|
+
messages: [{ role: 'user', content: prompt }],
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// UNSAFE: No schema validation
|
|
35
|
+
const data = JSON.parse(completion.choices[0].message.content)
|
|
36
|
+
return data
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Tool call arguments parsed without validation - MEDIUM
|
|
40
|
+
export async function handleToolCall(toolCall: any) {
|
|
41
|
+
const args = JSON.parse(toolCall.function.arguments)
|
|
42
|
+
// Using args directly without validation
|
|
43
|
+
return processArgs(args)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Anthropic response parsed directly - MEDIUM
|
|
47
|
+
export async function handleAnthropicResponse(response: any) {
|
|
48
|
+
const parsed = JSON.parse(response.content[0].text)
|
|
49
|
+
return parsed.action
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// AI response typed as any - LOW
|
|
53
|
+
export async function processAIOutput() {
|
|
54
|
+
const response: any = await getAICompletion()
|
|
55
|
+
// Using untyped response
|
|
56
|
+
return response.result
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
language: 'typescript',
|
|
60
|
+
size: 900,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Weak Schema Patterns',
|
|
65
|
+
expectFindings: true,
|
|
66
|
+
expectedCategories: ['ai_schema_mismatch'],
|
|
67
|
+
description: 'Permissive schema patterns that provide inadequate validation',
|
|
68
|
+
file: {
|
|
69
|
+
path: 'src/ai/weak-schemas.ts',
|
|
70
|
+
content: `
|
|
71
|
+
import { z } from 'zod'
|
|
72
|
+
import OpenAI from 'openai'
|
|
73
|
+
|
|
74
|
+
const openai = new OpenAI()
|
|
75
|
+
|
|
76
|
+
// Using z.any() defeats schema validation - LOW
|
|
77
|
+
const responseSchema = z.any()
|
|
78
|
+
const aiResponse = responseSchema.parse(completion.choices[0].message.content)
|
|
79
|
+
|
|
80
|
+
// Passthrough allowing extra properties - INFO
|
|
81
|
+
const dataSchema = z.object({
|
|
82
|
+
action: z.string(),
|
|
83
|
+
}).passthrough()
|
|
84
|
+
|
|
85
|
+
// Record<string, any> for AI data - LOW
|
|
86
|
+
interface AIData {
|
|
87
|
+
aiOutput: Record<string, any>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Generic object type - INFO
|
|
91
|
+
function processResponse(completion: object) {
|
|
92
|
+
return completion
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Any typed response at API boundary - MEDIUM (elevated)
|
|
96
|
+
export async function handler(req: Request) {
|
|
97
|
+
const response: any = await openai.chat.completions.create({
|
|
98
|
+
model: 'gpt-4',
|
|
99
|
+
messages: [{ role: 'user', content: 'test' }],
|
|
100
|
+
})
|
|
101
|
+
return Response.json(response)
|
|
102
|
+
}
|
|
103
|
+
`,
|
|
104
|
+
language: 'typescript',
|
|
105
|
+
size: 700,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'Tool Parameters in Security Sinks',
|
|
110
|
+
expectFindings: true,
|
|
111
|
+
expectedCategories: ['ai_schema_mismatch'],
|
|
112
|
+
description: 'AI-generated tool parameters used in dangerous operations',
|
|
113
|
+
file: {
|
|
114
|
+
path: 'src/ai/tool-execution.ts',
|
|
115
|
+
content: `
|
|
116
|
+
import { exec } from 'child_process'
|
|
117
|
+
import * as fs from 'fs'
|
|
118
|
+
|
|
119
|
+
// Tool parameter in shell command - CRITICAL
|
|
120
|
+
async function executeToolCommand(toolArgs: any) {
|
|
121
|
+
const command = toolArgs.command
|
|
122
|
+
exec(command, (error, stdout) => {
|
|
123
|
+
console.log(stdout)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Tool parameter in file path - HIGH
|
|
128
|
+
async function readToolFile(args: { path: string }) {
|
|
129
|
+
const filePath = args.path
|
|
130
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
131
|
+
return content
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Tool parameter in database query - HIGH
|
|
135
|
+
async function queryFromTool(parameters: any, db: any) {
|
|
136
|
+
const tableName = parameters.table
|
|
137
|
+
const results = await db.query(\`SELECT * FROM \${tableName}\`)
|
|
138
|
+
return results
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Tool parameter in URL - HIGH
|
|
142
|
+
async function fetchToolUrl(args: any) {
|
|
143
|
+
const endpoint = args.url
|
|
144
|
+
const response = await fetch(endpoint)
|
|
145
|
+
return response.json()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Function name routing without validation - MEDIUM
|
|
149
|
+
async function routeToolCall(tool_call: any) {
|
|
150
|
+
const name = tool_call.function.name
|
|
151
|
+
switch (name) {
|
|
152
|
+
case 'search': return search()
|
|
153
|
+
default: return functions[name]() // Dynamic access
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
`,
|
|
157
|
+
language: 'typescript',
|
|
158
|
+
size: 950,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
|
|
163
|
+
falseNegatives: [
|
|
164
|
+
{
|
|
165
|
+
name: 'Schema Validation - False Negatives',
|
|
166
|
+
expectFindings: false,
|
|
167
|
+
description: 'Properly validated AI outputs that should NOT be flagged',
|
|
168
|
+
allowedInfoFindings: [
|
|
169
|
+
{
|
|
170
|
+
category: 'ai_schema_mismatch',
|
|
171
|
+
maxCount: 2,
|
|
172
|
+
reason: 'Some patterns may generate info-level findings even when properly handled',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
category: 'dangerous_function',
|
|
176
|
+
maxCount: 2,
|
|
177
|
+
reason: 'JSON.parse with schema validation nearby may still trigger info-level findings',
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
file: {
|
|
181
|
+
path: 'src/ai/validated-handler.ts',
|
|
182
|
+
content: `
|
|
183
|
+
import OpenAI from 'openai'
|
|
184
|
+
import { z } from 'zod'
|
|
185
|
+
|
|
186
|
+
const openai = new OpenAI()
|
|
187
|
+
|
|
188
|
+
// Proper schema definition
|
|
189
|
+
const ResponseSchema = z.object({
|
|
190
|
+
action: z.enum(['search', 'create', 'delete']),
|
|
191
|
+
target: z.string().min(1),
|
|
192
|
+
params: z.record(z.string()).optional(),
|
|
193
|
+
}).strict()
|
|
194
|
+
|
|
195
|
+
// Validated AI response parsing - SAFE
|
|
196
|
+
export async function getValidatedResponse(prompt: string) {
|
|
197
|
+
const completion = await openai.chat.completions.create({
|
|
198
|
+
model: 'gpt-4',
|
|
199
|
+
messages: [{ role: 'user', content: prompt }],
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Schema validation with zod
|
|
203
|
+
const validated = ResponseSchema.parse(
|
|
204
|
+
JSON.parse(completion.choices[0].message.content!)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return validated
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// safeParse with error handling - SAFE
|
|
211
|
+
export async function getSafeResponse(prompt: string) {
|
|
212
|
+
const raw = await getAICompletion(prompt)
|
|
213
|
+
const result = ResponseSchema.safeParse(JSON.parse(raw))
|
|
214
|
+
|
|
215
|
+
if (!result.success) {
|
|
216
|
+
throw new Error('Invalid AI response: ' + result.error.message)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return result.data
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Tool arguments with schema validation - SAFE
|
|
223
|
+
const ToolArgsSchema = z.object({
|
|
224
|
+
query: z.string(),
|
|
225
|
+
limit: z.number().int().positive().max(100),
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
export async function handleToolWithValidation(toolCall: any) {
|
|
229
|
+
const args = ToolArgsSchema.parse(
|
|
230
|
+
JSON.parse(toolCall.function.arguments)
|
|
231
|
+
)
|
|
232
|
+
return search(args.query, args.limit)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// OpenAI Structured Outputs - SAFE
|
|
236
|
+
export async function getStructuredOutput(prompt: string) {
|
|
237
|
+
const completion = await openai.chat.completions.create({
|
|
238
|
+
model: 'gpt-4',
|
|
239
|
+
messages: [{ role: 'user', content: prompt }],
|
|
240
|
+
response_format: {
|
|
241
|
+
type: 'json_schema',
|
|
242
|
+
json_schema: {
|
|
243
|
+
name: 'response',
|
|
244
|
+
schema: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
action: { type: 'string' },
|
|
248
|
+
target: { type: 'string' },
|
|
249
|
+
},
|
|
250
|
+
required: ['action', 'target'],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return JSON.parse(completion.choices[0].message.content!)
|
|
257
|
+
}
|
|
258
|
+
`,
|
|
259
|
+
language: 'typescript',
|
|
260
|
+
size: 1700,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'Tool Parameters with Allowlist',
|
|
265
|
+
expectFindings: false,
|
|
266
|
+
description: 'Tool parameters validated against allowlists',
|
|
267
|
+
allowedInfoFindings: [
|
|
268
|
+
{
|
|
269
|
+
category: 'ai_schema_mismatch',
|
|
270
|
+
maxCount: 1,
|
|
271
|
+
reason: 'May flag routing pattern at info level',
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
category: 'dangerous_function',
|
|
275
|
+
maxCount: 2,
|
|
276
|
+
reason: 'fs operations may trigger info-level findings',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
category: 'ai_unsafe_execution',
|
|
280
|
+
maxCount: 2,
|
|
281
|
+
reason: 'File operations with proper validation may still flag at low severity',
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
file: {
|
|
285
|
+
path: 'src/ai/safe-tool-execution.ts',
|
|
286
|
+
content: `
|
|
287
|
+
import * as path from 'path'
|
|
288
|
+
import * as fs from 'fs'
|
|
289
|
+
import { z } from 'zod'
|
|
290
|
+
|
|
291
|
+
// Allowlisted commands - SAFE
|
|
292
|
+
const ALLOWED_COMMANDS = ['ls', 'cat', 'echo'] as const
|
|
293
|
+
const CommandSchema = z.enum(ALLOWED_COMMANDS)
|
|
294
|
+
|
|
295
|
+
async function executeAllowlistedCommand(args: { cmd: string }) {
|
|
296
|
+
const validated = CommandSchema.parse(args.cmd)
|
|
297
|
+
// Only allowed commands can execute
|
|
298
|
+
return runCommand(validated)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Path validation with allowlist - SAFE
|
|
302
|
+
const ALLOWED_PATHS = ['/app/data', '/app/uploads']
|
|
303
|
+
|
|
304
|
+
async function readAllowlistedFile(args: { filePath: string }) {
|
|
305
|
+
const resolved = path.resolve(args.filePath)
|
|
306
|
+
|
|
307
|
+
// Validate path against allowlist
|
|
308
|
+
if (!ALLOWED_PATHS.some(p => resolved.startsWith(p))) {
|
|
309
|
+
throw new Error('Path not allowed')
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return fs.readFileSync(resolved, 'utf-8')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// URL validation against allowed hosts - SAFE
|
|
316
|
+
const ALLOWED_HOSTS = ['api.example.com', 'data.example.com']
|
|
317
|
+
|
|
318
|
+
async function fetchAllowlistedUrl(args: { url: string }) {
|
|
319
|
+
const parsed = new URL(args.url)
|
|
320
|
+
|
|
321
|
+
if (!ALLOWED_HOSTS.includes(parsed.host)) {
|
|
322
|
+
throw new Error('Host not allowed')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return fetch(args.url)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Function routing with explicit allowlist - SAFE
|
|
329
|
+
const ALLOWED_FUNCTIONS = ['search', 'calculate', 'format'] as const
|
|
330
|
+
type AllowedFunction = typeof ALLOWED_FUNCTIONS[number]
|
|
331
|
+
|
|
332
|
+
const handlers: Record<AllowedFunction, () => void> = {
|
|
333
|
+
search: () => {},
|
|
334
|
+
calculate: () => {},
|
|
335
|
+
format: () => {},
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function routeWithAllowlist(toolCall: { name: string }) {
|
|
339
|
+
const name = toolCall.name as AllowedFunction
|
|
340
|
+
|
|
341
|
+
if (!ALLOWED_FUNCTIONS.includes(name)) {
|
|
342
|
+
throw new Error('Function not allowed: ' + name)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return handlers[name]()
|
|
346
|
+
}
|
|
347
|
+
`,
|
|
348
|
+
language: 'typescript',
|
|
349
|
+
size: 1400,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'Strongly Typed AI Interfaces',
|
|
354
|
+
expectFindings: false,
|
|
355
|
+
description: 'Properly typed AI responses with strict interfaces',
|
|
356
|
+
allowedInfoFindings: [
|
|
357
|
+
{
|
|
358
|
+
category: 'ai_schema_mismatch',
|
|
359
|
+
maxCount: 1,
|
|
360
|
+
reason: 'Type-only patterns may still flag for runtime validation',
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
category: 'dangerous_function',
|
|
364
|
+
maxCount: 2,
|
|
365
|
+
reason: 'JSON.parse with schema validation nearby may still trigger info-level findings',
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
file: {
|
|
369
|
+
path: 'src/ai/typed-responses.ts',
|
|
370
|
+
content: `
|
|
371
|
+
import OpenAI from 'openai'
|
|
372
|
+
import { z } from 'zod'
|
|
373
|
+
|
|
374
|
+
// Strict interface definition
|
|
375
|
+
interface AISearchResult {
|
|
376
|
+
query: string
|
|
377
|
+
results: Array<{
|
|
378
|
+
id: string
|
|
379
|
+
title: string
|
|
380
|
+
score: number
|
|
381
|
+
}>
|
|
382
|
+
metadata: {
|
|
383
|
+
processingTime: number
|
|
384
|
+
model: string
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Runtime validation with zod matching interface
|
|
389
|
+
const AISearchResultSchema = z.object({
|
|
390
|
+
query: z.string(),
|
|
391
|
+
results: z.array(z.object({
|
|
392
|
+
id: z.string(),
|
|
393
|
+
title: z.string(),
|
|
394
|
+
score: z.number().min(0).max(1),
|
|
395
|
+
})),
|
|
396
|
+
metadata: z.object({
|
|
397
|
+
processingTime: z.number(),
|
|
398
|
+
model: z.string(),
|
|
399
|
+
}),
|
|
400
|
+
}).strict()
|
|
401
|
+
|
|
402
|
+
// Type-safe handler - SAFE
|
|
403
|
+
export async function searchWithAI(
|
|
404
|
+
query: string,
|
|
405
|
+
openai: OpenAI
|
|
406
|
+
): Promise<AISearchResult> {
|
|
407
|
+
const completion = await openai.chat.completions.create({
|
|
408
|
+
model: 'gpt-4',
|
|
409
|
+
messages: [{ role: 'user', content: query }],
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// Runtime validation ensures type safety
|
|
413
|
+
const validated = AISearchResultSchema.parse(
|
|
414
|
+
JSON.parse(completion.choices[0].message.content!)
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
return validated
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Generic with constraint - SAFE
|
|
421
|
+
export async function getTypedResponse<T>(
|
|
422
|
+
schema: z.ZodSchema<T>,
|
|
423
|
+
prompt: string
|
|
424
|
+
): Promise<T> {
|
|
425
|
+
const raw = await getCompletion(prompt)
|
|
426
|
+
return schema.parse(JSON.parse(raw))
|
|
427
|
+
}
|
|
428
|
+
`,
|
|
429
|
+
language: 'typescript',
|
|
430
|
+
size: 1200,
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Anti-Patterns Test Fixtures
|
|
3
|
+
* Tests for detecting missing or weak authentication patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const authAntipatternsTests: TestGroup = {
|
|
9
|
+
name: 'Auth Anti-Patterns',
|
|
10
|
+
tier: 'B',
|
|
11
|
+
layer: 2,
|
|
12
|
+
description: 'Detection of missing or weak authentication patterns in API routes',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'Auth Anti-Patterns - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['missing_auth'],
|
|
19
|
+
description: 'Missing auth patterns that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/api/admin/route.ts',
|
|
22
|
+
content: `
|
|
23
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
24
|
+
import { db } from '@/lib/db'
|
|
25
|
+
|
|
26
|
+
// No authentication on sensitive endpoint - HIGH
|
|
27
|
+
export async function GET(request: NextRequest) {
|
|
28
|
+
// No auth check!
|
|
29
|
+
const users = await db.user.findMany({
|
|
30
|
+
include: { apiKeys: true } // Exposing sensitive data
|
|
31
|
+
})
|
|
32
|
+
return NextResponse.json(users)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// No authentication on data mutation - CRITICAL
|
|
36
|
+
export async function POST(request: NextRequest) {
|
|
37
|
+
// No auth check!
|
|
38
|
+
const body = await request.json()
|
|
39
|
+
await db.user.update({
|
|
40
|
+
where: { id: body.userId },
|
|
41
|
+
data: { role: 'admin' } // Anyone can make themselves admin!
|
|
42
|
+
})
|
|
43
|
+
return NextResponse.json({ success: true })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// No authentication on delete - CRITICAL
|
|
47
|
+
export async function DELETE(request: NextRequest) {
|
|
48
|
+
const { searchParams } = new URL(request.url)
|
|
49
|
+
const userId = searchParams.get('userId')
|
|
50
|
+
// No auth check!
|
|
51
|
+
await db.user.delete({ where: { id: userId } })
|
|
52
|
+
return NextResponse.json({ success: true })
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
language: 'typescript',
|
|
56
|
+
size: 900,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Auth Anti-Patterns - IDOR Vulnerabilities',
|
|
61
|
+
expectFindings: true,
|
|
62
|
+
expectedCategories: ['missing_auth'],
|
|
63
|
+
description: 'Insecure Direct Object Reference patterns',
|
|
64
|
+
file: {
|
|
65
|
+
path: 'src/api/users/[id]/route.ts',
|
|
66
|
+
content: `
|
|
67
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
68
|
+
import { db } from '@/lib/db'
|
|
69
|
+
|
|
70
|
+
// IDOR - accessing any user's data without ownership check
|
|
71
|
+
export async function GET(
|
|
72
|
+
request: NextRequest,
|
|
73
|
+
{ params }: { params: { id: string } }
|
|
74
|
+
) {
|
|
75
|
+
// No check that current user owns this resource!
|
|
76
|
+
const user = await db.user.findUnique({
|
|
77
|
+
where: { id: params.id },
|
|
78
|
+
include: {
|
|
79
|
+
apiKeys: true,
|
|
80
|
+
sessions: true,
|
|
81
|
+
billingInfo: true
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
return NextResponse.json(user)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// IDOR - updating any user's data
|
|
88
|
+
export async function PUT(
|
|
89
|
+
request: NextRequest,
|
|
90
|
+
{ params }: { params: { id: string } }
|
|
91
|
+
) {
|
|
92
|
+
const body = await request.json()
|
|
93
|
+
// No ownership verification!
|
|
94
|
+
await db.user.update({
|
|
95
|
+
where: { id: params.id },
|
|
96
|
+
data: body
|
|
97
|
+
})
|
|
98
|
+
return NextResponse.json({ success: true })
|
|
99
|
+
}
|
|
100
|
+
`,
|
|
101
|
+
language: 'typescript',
|
|
102
|
+
size: 800,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
|
|
107
|
+
falseNegatives: [
|
|
108
|
+
{
|
|
109
|
+
name: 'Auth Anti-Patterns - False Negatives',
|
|
110
|
+
expectFindings: false,
|
|
111
|
+
description: 'Properly authenticated patterns that should NOT be flagged',
|
|
112
|
+
file: {
|
|
113
|
+
path: 'src/api/protected/route.ts',
|
|
114
|
+
content: `
|
|
115
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
116
|
+
import { auth } from '@/lib/auth'
|
|
117
|
+
import { getCurrentUserId } from '@/lib/auth-helpers'
|
|
118
|
+
import { db } from '@/lib/db'
|
|
119
|
+
|
|
120
|
+
// Authenticated with session check - SAFE
|
|
121
|
+
export async function GET(request: NextRequest) {
|
|
122
|
+
const session = await auth()
|
|
123
|
+
if (!session?.user) {
|
|
124
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const userData = await db.user.findUnique({
|
|
128
|
+
where: { id: session.user.id }
|
|
129
|
+
})
|
|
130
|
+
return NextResponse.json(userData)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Using throwing auth helper - SAFE
|
|
134
|
+
export async function POST(request: NextRequest) {
|
|
135
|
+
// This throws if not authenticated
|
|
136
|
+
const userId = await getCurrentUserId()
|
|
137
|
+
|
|
138
|
+
// userId is guaranteed to exist here
|
|
139
|
+
const body = await request.json()
|
|
140
|
+
await db.post.create({
|
|
141
|
+
data: {
|
|
142
|
+
...body,
|
|
143
|
+
authorId: userId
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
return NextResponse.json({ success: true })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Public health check - SAFE (intentionally public)
|
|
150
|
+
export async function HEAD() {
|
|
151
|
+
return new Response(null, { status: 200 })
|
|
152
|
+
}
|
|
153
|
+
`,
|
|
154
|
+
language: 'typescript',
|
|
155
|
+
size: 1000,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BYOK (Bring Your Own Key) Test Fixtures
|
|
3
|
+
* Tests for detecting insecure handling of user-provided API keys
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { TestGroup } from '../../types'
|
|
7
|
+
|
|
8
|
+
export const byokTests: TestGroup = {
|
|
9
|
+
name: 'BYOK Patterns',
|
|
10
|
+
tier: 'A',
|
|
11
|
+
layer: 2,
|
|
12
|
+
description: 'Detection of insecure BYOK (Bring Your Own Key) patterns',
|
|
13
|
+
|
|
14
|
+
truePositives: [
|
|
15
|
+
{
|
|
16
|
+
name: 'BYOK - True Positives',
|
|
17
|
+
expectFindings: true,
|
|
18
|
+
expectedCategories: ['ai_pattern'],
|
|
19
|
+
description: 'Insecure BYOK patterns that MUST be detected',
|
|
20
|
+
file: {
|
|
21
|
+
path: 'src/api/ai/byok-storage.ts',
|
|
22
|
+
content: `
|
|
23
|
+
import { db } from '@/lib/db'
|
|
24
|
+
|
|
25
|
+
// BYOK key stored in database (Medium - should encrypt)
|
|
26
|
+
export async function saveUserApiKey(userId: string, apiKey: string) {
|
|
27
|
+
await db.user.update({
|
|
28
|
+
where: { id: userId },
|
|
29
|
+
data: { openaiApiKey: apiKey } // Stored without encryption!
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// BYOK key logged (Medium - never log secrets)
|
|
34
|
+
export async function useUserKey(userId: string) {
|
|
35
|
+
const user = await db.user.findUnique({ where: { id: userId } })
|
|
36
|
+
console.log('Using API key:', user.openaiApiKey) // Logged!
|
|
37
|
+
return callOpenAI(user.openaiApiKey)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// BYOK key stored in plain text file
|
|
41
|
+
export function saveKeyToFile(apiKey: string) {
|
|
42
|
+
fs.writeFileSync('/data/api-keys.txt', apiKey)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Cross-tenant key access (High - data isolation risk)
|
|
46
|
+
export async function getAnyUserKey(targetUserId: string) {
|
|
47
|
+
// No check that current user owns this key!
|
|
48
|
+
const user = await db.user.findUnique({ where: { id: targetUserId } })
|
|
49
|
+
return user.apiKey
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// BYOK on unauthenticated endpoint (Medium - abuse risk)
|
|
53
|
+
export async function POST(request: Request) {
|
|
54
|
+
// No auth check!
|
|
55
|
+
const { apiKey, prompt } = await request.json()
|
|
56
|
+
return callOpenAI(apiKey, prompt)
|
|
57
|
+
}
|
|
58
|
+
`,
|
|
59
|
+
language: 'typescript',
|
|
60
|
+
size: 1200,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
falseNegatives: [
|
|
66
|
+
{
|
|
67
|
+
name: 'BYOK - False Negatives',
|
|
68
|
+
expectFindings: false,
|
|
69
|
+
description: 'Secure BYOK patterns that should NOT be flagged',
|
|
70
|
+
file: {
|
|
71
|
+
path: 'src/api/ai/byok-transient.ts',
|
|
72
|
+
content: `
|
|
73
|
+
import { auth } from '@/lib/auth'
|
|
74
|
+
import OpenAI from 'openai'
|
|
75
|
+
|
|
76
|
+
// Transient BYOK usage (authenticated) - SAFE (info at most)
|
|
77
|
+
export async function POST(request: Request) {
|
|
78
|
+
// Auth check
|
|
79
|
+
const session = await auth()
|
|
80
|
+
if (!session?.user) {
|
|
81
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Key from request body, used transiently
|
|
85
|
+
const { apiKey, prompt } = await request.json()
|
|
86
|
+
|
|
87
|
+
// Key only exists in memory for this request
|
|
88
|
+
const openai = new OpenAI({ apiKey })
|
|
89
|
+
const response = await openai.chat.completions.create({
|
|
90
|
+
model: 'gpt-4',
|
|
91
|
+
messages: [{ role: 'user', content: prompt }]
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// Key is NOT stored or logged - this is the IDEAL BYOK pattern
|
|
95
|
+
return Response.json({ result: response.choices[0].message.content })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// User-scoped encrypted key storage - SAFE
|
|
99
|
+
export async function saveEncryptedKey(userId: string, apiKey: string) {
|
|
100
|
+
const encryptedKey = await encrypt(apiKey, process.env.ENCRYPTION_KEY!)
|
|
101
|
+
await db.user.update({
|
|
102
|
+
where: { id: userId },
|
|
103
|
+
data: { encryptedApiKey: encryptedKey }
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
`,
|
|
107
|
+
language: 'typescript',
|
|
108
|
+
size: 1100,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
}
|