@pedrofariasx/qwenproxy 1.2.0 → 1.2.2
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/README.md +3 -13
- package/package.json +1 -1
- package/src/api/server.ts +0 -2
- package/src/cache/memory-cache.ts +52 -18
- package/src/routes/chat.ts +132 -79
- package/src/routes/upload.ts +4 -4
- package/src/services/playwright.ts +1 -0
- package/src/services/qwen.ts +36 -15
- package/src/tools/parser.ts +10 -13
- package/src/utils/context-truncation.ts +36 -10
- package/src/linter/extraction-engine.ts +0 -165
- package/src/linter/index.ts +0 -258
- package/src/linter/repair-normalize.ts +0 -245
- package/src/linter/safety-gate.ts +0 -219
- package/src/linter/streaming-state-machine.ts +0 -252
- package/src/linter/structural-parser.ts +0 -352
- package/src/linter/types.ts +0 -74
- package/src/tests/linter.test.ts +0 -151
- package/src/tests/parallel.test.ts +0 -42
- package/src/tests/structureVerification.test.ts +0 -176
- package/src/tools/ast.ts +0 -15
- package/src/tools/coercion.ts +0 -67
- package/src/tools/confidence.ts +0 -48
- package/src/tools/detector.ts +0 -40
- package/src/tools/executor.ts +0 -236
- package/src/tools/pipeline.ts +0 -122
- package/src/tools/registry-runtime.ts +0 -34
- package/src/tools/repair.ts +0 -42
- package/src/tools/validator.ts +0 -33
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Layer 4: Normalization + Repair Engine
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { CanonicalToolCall, ToolCallSource, RawToolCandidate } from './types'
|
|
6
|
-
import { StructuralParser } from './structural-parser'
|
|
7
|
-
|
|
8
|
-
export interface RepairResult {
|
|
9
|
-
repaired: boolean
|
|
10
|
-
value: unknown
|
|
11
|
-
confidence: number
|
|
12
|
-
strategy: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface NormalizationResult {
|
|
16
|
-
toolCall: CanonicalToolCall
|
|
17
|
-
warnings: string[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class GrammarRepairEngine {
|
|
21
|
-
private parser: StructuralParser = new StructuralParser()
|
|
22
|
-
|
|
23
|
-
repair(input: string): RepairResult {
|
|
24
|
-
const strategies: Array<{ name: string; attempt: () => unknown | undefined }> = [
|
|
25
|
-
{ name: 'json_strip_repair', attempt: () => this.tryStripRepair(input) },
|
|
26
|
-
{ name: 'quote_fix', attempt: () => this.tryQuoteFix(input) },
|
|
27
|
-
{ name: 'trailing_comma_fix', attempt: () => this.tryTrailingCommaFix(input) },
|
|
28
|
-
{ name: 'braces_balance', attempt: () => this.tryBraceBalance(input) },
|
|
29
|
-
{ name: 'key_unquote', attempt: () => this.tryKeyUnquote(input) },
|
|
30
|
-
{ name: 'parser_resync', attempt: () => this.tryParserResync(input) },
|
|
31
|
-
{ name: 'synthetic_construction', attempt: () => this.trySyntheticConstruction(input) },
|
|
32
|
-
{ name: 'json_parse_last_resort', attempt: () => this.tryJsonParseLastResort(input) },
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
for (const strategy of strategies) {
|
|
36
|
-
try {
|
|
37
|
-
const result = strategy.attempt()
|
|
38
|
-
if (result !== undefined && result !== null) {
|
|
39
|
-
return { repaired: true, value: result, confidence: this.inferConfidence(strategy.name), strategy: strategy.name }
|
|
40
|
-
}
|
|
41
|
-
} catch { continue }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return { repaired: false, value: null, confidence: 0, strategy: 'failed' }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private tryStripRepair(input: string): Record<string, unknown> | undefined {
|
|
48
|
-
const stripped = input
|
|
49
|
-
.replace(/```json\s*/gi, '')
|
|
50
|
-
.replace(/```\s*$/gm, '')
|
|
51
|
-
.replace(/```\s*/gi, '')
|
|
52
|
-
.replace(/^\s*[\[\{]\s*$/, '')
|
|
53
|
-
.replace(/[\u0000-\u001F]+/g, ' ')
|
|
54
|
-
.trim()
|
|
55
|
-
|
|
56
|
-
if (stripped === input) return undefined
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const parsed = JSON.parse(stripped)
|
|
60
|
-
if (typeof parsed === 'object' && parsed !== null) return parsed
|
|
61
|
-
} catch { /* not parseable */ }
|
|
62
|
-
|
|
63
|
-
return undefined
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private tryQuoteFix(input: string): Record<string, unknown> | undefined {
|
|
67
|
-
let fixed = input
|
|
68
|
-
const quoteCount = (fixed.match(/"/g) ?? []).length
|
|
69
|
-
if (quoteCount % 2 !== 0) fixed += '"'
|
|
70
|
-
|
|
71
|
-
if (/['"]/.test(fixed) && /(^|[,:{\s])'[a-z]/i.test(fixed)) {
|
|
72
|
-
fixed = fixed.replace(/'/g, '"')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const parsed = JSON.parse(fixed)
|
|
77
|
-
if (typeof parsed === 'object' && parsed !== null) return parsed
|
|
78
|
-
} catch { /* not parseable */ }
|
|
79
|
-
|
|
80
|
-
return undefined
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private tryTrailingCommaFix(input: string): Record<string, unknown> | undefined {
|
|
84
|
-
let fixed = input.replace(/,(\s*[}\]])/g, '$1')
|
|
85
|
-
if (fixed === input) return undefined
|
|
86
|
-
try {
|
|
87
|
-
const parsed = JSON.parse(fixed)
|
|
88
|
-
if (typeof parsed === 'object' && parsed !== null) return parsed
|
|
89
|
-
} catch { /* not parseable */ }
|
|
90
|
-
return undefined
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private tryBraceBalance(input: string): Record<string, unknown> | undefined {
|
|
94
|
-
let depth = 0, start = -1
|
|
95
|
-
for (let i = 0; i < input.length; i++) {
|
|
96
|
-
if (input[i] === '{') { if (depth === 0) start = i; depth++ }
|
|
97
|
-
else if (input[i] === '}') {
|
|
98
|
-
depth--
|
|
99
|
-
if (depth === 0 && start !== -1) {
|
|
100
|
-
try { const parsed = JSON.parse(input.slice(start, i + 1)); if (typeof parsed === 'object' && parsed !== null) return parsed }
|
|
101
|
-
catch { /* not parseable */ }
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return undefined
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private tryKeyUnquote(input: string): Record<string, unknown> | undefined {
|
|
109
|
-
if (!/^\{[\s]*[a-zA-Z_]/m.test(input)) return undefined
|
|
110
|
-
let fixed = input.replace(/([,{]\s*)([a-zA-Z_][a-zA-Z0-9_\-]*)(\s*:)/g, '$1"$2"$3')
|
|
111
|
-
try {
|
|
112
|
-
const parsed = JSON.parse(fixed)
|
|
113
|
-
if (typeof parsed === 'object' && parsed !== null) return parsed
|
|
114
|
-
} catch { /* not parseable */ }
|
|
115
|
-
return undefined
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private tryParserResync(input: string): Record<string, unknown> | undefined {
|
|
119
|
-
const result = this.parser.parse(input, 'unknown')
|
|
120
|
-
if (result.ast && result.confidence >= 0.5 && result.ast.type === 'object') {
|
|
121
|
-
return result.ast.value as Record<string, unknown>
|
|
122
|
-
}
|
|
123
|
-
return undefined
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private trySyntheticConstruction(input: string): Record<string, unknown> | undefined {
|
|
127
|
-
const nameMatch = input.match(/"name"\s*:\s*"([^"]+)"|name\s*[:=]\s*['"]?(\w+)['"]?/i)
|
|
128
|
-
const toolMatch = input.match(/"tool"\s*:\s*"([^"]+)"|tool\s*[:=]\s*['"]?(\w+)['"]?/i)
|
|
129
|
-
const funcNameMatch = input.match(/"functionCall"\s*:\s*\{\s*"name"\s*:\s*"([^"]+)"/)
|
|
130
|
-
const toolUseMatch = input.match(/"type"\s*:\s*"tool_use"[\s\S]*?"name"\s*:\s*"([^"]+)"/)
|
|
131
|
-
|
|
132
|
-
if (nameMatch || funcNameMatch || toolUseMatch || toolMatch) {
|
|
133
|
-
const toolName = nameMatch?.[1] ?? nameMatch?.[2] ?? funcNameMatch?.[1] ?? toolUseMatch?.[1] ?? toolMatch?.[1] ?? toolMatch?.[2] ?? 'unknown'
|
|
134
|
-
const inputBlock = this.extractInputBlock(input)
|
|
135
|
-
return { tool: toolName, input: inputBlock ?? {} }
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return undefined
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private tryJsonParseLastResort(input: string): Record<string, unknown> | undefined {
|
|
142
|
-
try {
|
|
143
|
-
const parsed = JSON.parse(input)
|
|
144
|
-
if (typeof parsed === 'object' && parsed !== null) return parsed
|
|
145
|
-
} catch { /* last resort failed */ }
|
|
146
|
-
return undefined
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private extractInputBlock(input: string): Record<string, unknown> | undefined {
|
|
150
|
-
const argsMatch = input.match(new RegExp('"arguments"\\s*:\\s*(\\{[\\s\\S]*\\})', 'i'))
|
|
151
|
-
const inputMatch = input.match(new RegExp('"input"\\s*:\\s*(\\{[\\s\\S]*\\})', 'i'))
|
|
152
|
-
const argsMatch2 = input.match(new RegExp('"args"\\s*:\\s*(\\{[\\s\\S]*\\})', 'i'))
|
|
153
|
-
const funcArgsMatch = input.match(/"functionCall"\s*:\s*\{\s*"name"\s*:\s*"[^"]+"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})\s*\}/)
|
|
154
|
-
const toolInputMatch = input.match(/"type"\s*:\s*"tool_use"[\s\S]*?"input"\s*:\s*(\{[\s\S]*?\})\s*\}/)
|
|
155
|
-
|
|
156
|
-
const target = argsMatch?.[1] ?? inputMatch?.[1] ?? argsMatch2?.[1] ?? funcArgsMatch?.[1] ?? toolInputMatch?.[1]
|
|
157
|
-
if (target) {
|
|
158
|
-
try { return JSON.parse(target) } catch { return undefined }
|
|
159
|
-
}
|
|
160
|
-
return undefined
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private inferConfidence(strategy: string): number {
|
|
164
|
-
const map: Record<string, number> = {
|
|
165
|
-
json_strip_repair: 0.95, json_parse_last_resort: 0.9, quote_fix: 0.85,
|
|
166
|
-
trailing_comma_fix: 0.85, braces_balance: 0.8, key_unquote: 0.8,
|
|
167
|
-
parser_resync: 0.7, synthetic_construction: 0.6,
|
|
168
|
-
}
|
|
169
|
-
return map[strategy] ?? 0.5
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export class NormalizationEngine {
|
|
174
|
-
normalize(candidate: RawToolCandidate, repaired = false): NormalizationResult {
|
|
175
|
-
const raw = candidate.raw
|
|
176
|
-
const warnings: string[] = []
|
|
177
|
-
let toolName = 'unknown'
|
|
178
|
-
let inputData: Record<string, unknown> = {}
|
|
179
|
-
|
|
180
|
-
const asRecord = raw as Record<string, unknown>
|
|
181
|
-
|
|
182
|
-
if (asRecord.tool) {
|
|
183
|
-
toolName = String(asRecord.tool)
|
|
184
|
-
inputData = this.normalizeInput(asRecord.arguments ?? asRecord.input ?? asRecord.args ?? {}, warnings)
|
|
185
|
-
} else if (asRecord.name) {
|
|
186
|
-
toolName = String(asRecord.name)
|
|
187
|
-
if (asRecord.functionCall && typeof asRecord.functionCall === 'object') {
|
|
188
|
-
const fc = asRecord.functionCall as Record<string, unknown>
|
|
189
|
-
toolName = String(fc.name ?? toolName)
|
|
190
|
-
inputData = this.normalizeInput(fc.args ?? fc.input ?? {}, warnings)
|
|
191
|
-
} else if (asRecord.type === 'tool_use') {
|
|
192
|
-
inputData = this.normalizeInput(asRecord.input ?? asRecord.args ?? {}, warnings)
|
|
193
|
-
} else if (asRecord.arguments !== undefined) {
|
|
194
|
-
inputData = this.normalizeInput(asRecord.arguments, warnings)
|
|
195
|
-
} else if (asRecord.input !== undefined) {
|
|
196
|
-
inputData = this.normalizeInput(asRecord.input, warnings)
|
|
197
|
-
} else if (asRecord.args !== undefined) {
|
|
198
|
-
inputData = this.normalizeInput(asRecord.args, warnings)
|
|
199
|
-
} else if (asRecord.action) {
|
|
200
|
-
toolName = String(asRecord.action)
|
|
201
|
-
inputData = this.normalizeInput(asRecord.input ?? asRecord.arguments ?? {}, warnings)
|
|
202
|
-
} else {
|
|
203
|
-
warnings.push('No tool name found in candidate')
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
const keys = Object.keys(asRecord)
|
|
207
|
-
warnings.push('No tool name found in candidate')
|
|
208
|
-
if (keys.length === 1 && typeof asRecord[keys[0]] === 'object' && asRecord[keys[0]] !== null && !Array.isArray(asRecord[keys[0]])) {
|
|
209
|
-
toolName = String(keys[0])
|
|
210
|
-
inputData = this.normalizeInput(asRecord[keys[0]], warnings)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const toolCall: CanonicalToolCall = {
|
|
215
|
-
tool: toolName,
|
|
216
|
-
input: inputData,
|
|
217
|
-
meta: { source: candidate.source, confidence: candidate.confidence, repaired },
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return { toolCall, warnings }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
private normalizeInput(input: unknown, warnings: string[]): Record<string, unknown> {
|
|
224
|
-
if (typeof input === 'string') {
|
|
225
|
-
return this.parseInputString(input, warnings)
|
|
226
|
-
}
|
|
227
|
-
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
|
|
228
|
-
return input as Record<string, unknown>
|
|
229
|
-
}
|
|
230
|
-
warnings.push(`Input was not a valid object: ${typeof input}`)
|
|
231
|
-
return {}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private parseInputString(input: string, warnings: string[]): Record<string, unknown> {
|
|
235
|
-
if (!input || input.trim().length === 0) { warnings.push('Empty input string'); return {} }
|
|
236
|
-
try {
|
|
237
|
-
const parsed = JSON.parse(input)
|
|
238
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
239
|
-
return parsed as Record<string, unknown>
|
|
240
|
-
}
|
|
241
|
-
} catch { /* not parseable */ }
|
|
242
|
-
warnings.push('Input string was not valid JSON, wrapping')
|
|
243
|
-
return { raw_input: input }
|
|
244
|
-
}
|
|
245
|
-
}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Layer 5: Validation + Safety Gate
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { CanonicalToolCall, ToolCallSource, SecurityViolation, ToolDefinition } from './types'
|
|
6
|
-
|
|
7
|
-
export interface ValidationReport {
|
|
8
|
-
isValid: boolean
|
|
9
|
-
violations: SecurityViolation[]
|
|
10
|
-
confidence: number
|
|
11
|
-
warnings: string[]
|
|
12
|
-
canExecute: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class SafetyGate {
|
|
16
|
-
private registry: Record<string, ToolDefinition> = {}
|
|
17
|
-
|
|
18
|
-
private readonly dangerousPatterns = [
|
|
19
|
-
/\$\(/g, /\$\{/g, /`/g,
|
|
20
|
-
/;\s*(rm|cat|ls|whoami|id|curl|wget|nc|ncat)\b/gi,
|
|
21
|
-
/\|\s*(sh|bash|zsh|csh|python|node|perl)\b/gi,
|
|
22
|
-
/&&\s*(rm|cat|whoami)/gi,
|
|
23
|
-
/exec\s*\(/gi, /eval\s*\(/gi, /\beval\s*\(/gi,
|
|
24
|
-
/system\s*\(/gi, /passthru\s*\(/gi, /shell_exec\s*\(/gi,
|
|
25
|
-
/proc\s*\(/gi, /subprocess\s*\./gi, /spawn\s*\(/gi,
|
|
26
|
-
/execSync\s*\(/gi, /child_process/gi,
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
private readonly ssrfPatterns = [
|
|
30
|
-
/http[s]?:\/\/(?:127\.0\.0\.1|0\.0\.0\.0|\[::1\]|localhost|169\.254\.\d+\.\d+|metadata\.google)/i,
|
|
31
|
-
/gopher:\/\//i, /file:\/\//i, /ftp:\/\//i,
|
|
32
|
-
/\b7777\b|\b9090\b|\b3389\b|\b22\b/,
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
private readonly promptInjectionPatterns = [
|
|
36
|
-
/ignore\s+(all\s+)?previous\s+instructions/i, /disregard\s+(all\s+)?previous/i,
|
|
37
|
-
/jailbreak/i, /you\s+are\s+now\s+DAN/i, /pretend\s+you\s+are/i,
|
|
38
|
-
/act\s+as\s+if/i, /your\s+new\s+instructions/i, /developer\s+mode/i,
|
|
39
|
-
/list\s+all\s+files/i, /cat\s+\/etc/i,
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
private readonly encodedPatterns = [
|
|
43
|
-
/%3Cscript/i, /javascript:/i, /\\x[0-9a-fA-F]{2}/, /\\u[0-9a-fA-F]{4}/,
|
|
44
|
-
/&#[0-9]+;/i, /data:text\/html/i,
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
registerTool(name: string, definition: Partial<ToolDefinition>): void {
|
|
48
|
-
this.registry[name] = {
|
|
49
|
-
name,
|
|
50
|
-
description: definition.description ?? '',
|
|
51
|
-
parameters: definition.parameters ?? {},
|
|
52
|
-
required: definition.required ?? [],
|
|
53
|
-
strict: definition.strict ?? false,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
registerRegistry(registry: Record<string, ToolDefinition>): void {
|
|
58
|
-
this.registry = registry
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
validate(call: CanonicalToolCall, sourceHint: ToolCallSource): ValidationReport {
|
|
62
|
-
const violations: SecurityViolation[] = []
|
|
63
|
-
const warnings: string[] = []
|
|
64
|
-
let confidence = call.meta?.confidence ?? 0.5
|
|
65
|
-
|
|
66
|
-
const semanticResult = this.validateSemantics(call)
|
|
67
|
-
violations.push(...semanticResult.violations)
|
|
68
|
-
warnings.push(...semanticResult.warnings)
|
|
69
|
-
confidence = Math.min(confidence, semanticResult.confidence)
|
|
70
|
-
|
|
71
|
-
const securityResult = this.validateSecurity(call)
|
|
72
|
-
violations.push(...securityResult.violations)
|
|
73
|
-
warnings.push(...securityResult.warnings)
|
|
74
|
-
|
|
75
|
-
warnings.push(...this.validateTypes(call).warnings)
|
|
76
|
-
|
|
77
|
-
const registryResult = this.validateRegistry(call)
|
|
78
|
-
warnings.push(...registryResult.warnings)
|
|
79
|
-
confidence = Math.min(confidence, registryResult.confidence)
|
|
80
|
-
|
|
81
|
-
const hasBlockingViolations = violations.some(v =>
|
|
82
|
-
v.type === 'shell_injection' || v.type === 'filesystem_access' || v.type === 'prompt_injection'
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
confidence = Math.max(0.1, Math.min(1.0, confidence))
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
isValid: !hasBlockingViolations && violations.length === 0,
|
|
89
|
-
violations,
|
|
90
|
-
confidence,
|
|
91
|
-
warnings,
|
|
92
|
-
canExecute: !hasBlockingViolations,
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private validateSemantics(call: CanonicalToolCall): { violations: SecurityViolation[]; warnings: string[]; confidence: number } {
|
|
97
|
-
const violations: SecurityViolation[] = []
|
|
98
|
-
const warnings: string[] = []
|
|
99
|
-
let confidence = 1.0
|
|
100
|
-
|
|
101
|
-
if (!call.tool || call.tool.trim().length === 0) {
|
|
102
|
-
violations.push({ type: 'shell_injection', field: 'tool', value: String(call.tool), detail: 'Tool name is empty' })
|
|
103
|
-
confidence -= 0.5
|
|
104
|
-
}
|
|
105
|
-
if (typeof call.tool !== 'string') {
|
|
106
|
-
violations.push({ type: 'shell_injection', field: 'tool', value: String(call.tool), detail: 'Tool name is not a string' })
|
|
107
|
-
confidence -= 0.4
|
|
108
|
-
}
|
|
109
|
-
if (call.tool && /[ /\\]/.test(call.tool)) {
|
|
110
|
-
warnings.push(`Suspicious tool name: "${call.tool}"`)
|
|
111
|
-
confidence -= 0.1
|
|
112
|
-
}
|
|
113
|
-
if (typeof call.input !== 'object' || call.input === null || Array.isArray(call.input)) {
|
|
114
|
-
violations.push({ type: 'shell_injection', field: 'input', value: String(call.input), detail: 'Input is not a valid object' })
|
|
115
|
-
confidence -= 0.4
|
|
116
|
-
}
|
|
117
|
-
for (const [key, value] of Object.entries(call.input)) {
|
|
118
|
-
if (typeof value === 'string' && value.trim().length === 0) {
|
|
119
|
-
warnings.push(`Empty string parameter: "${key}"`)
|
|
120
|
-
confidence -= 0.05
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return { violations, warnings, confidence: Math.max(0, confidence) }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private validateSecurity(call: CanonicalToolCall): { violations: SecurityViolation[]; warnings: string[] } {
|
|
128
|
-
const violations: SecurityViolation[] = []
|
|
129
|
-
const warnings: string[] = []
|
|
130
|
-
const inputString = JSON.stringify(call.input)
|
|
131
|
-
|
|
132
|
-
for (const pattern of this.dangerousPatterns) {
|
|
133
|
-
pattern.lastIndex = 0
|
|
134
|
-
if (pattern.test(inputString)) {
|
|
135
|
-
violations.push({ type: 'shell_injection', field: 'input', value: inputString.slice(0, 100), detail: `Potential shell injection pattern detected` })
|
|
136
|
-
break
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
for (const field of Object.values(call.input)) {
|
|
141
|
-
const str = String(field ?? '')
|
|
142
|
-
for (const pattern of this.ssrfPatterns) {
|
|
143
|
-
pattern.lastIndex = 0
|
|
144
|
-
if (pattern.test(str)) {
|
|
145
|
-
violations.push({ type: 'ssrf', field: 'input', value: str.slice(0, 200), detail: `SSRF pattern detected` })
|
|
146
|
-
break
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const combinedInput = Object.values(call.input)
|
|
152
|
-
.map(v => typeof v === 'string' ? v : JSON.stringify(v))
|
|
153
|
-
.join(' ')
|
|
154
|
-
|
|
155
|
-
for (const pattern of this.promptInjectionPatterns) {
|
|
156
|
-
pattern.lastIndex = 0
|
|
157
|
-
if (pattern.test(combinedInput)) {
|
|
158
|
-
violations.push({ type: 'prompt_injection', field: 'input', value: combinedInput.slice(0, 200), detail: `Prompt injection pattern detected` })
|
|
159
|
-
break
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
for (const pattern of this.encodedPatterns) {
|
|
164
|
-
pattern.lastIndex = 0
|
|
165
|
-
if (pattern.test(combinedInput)) {
|
|
166
|
-
violations.push({ type: 'encoded_payload', field: 'input', value: combinedInput.slice(0, 200), detail: `Encoded payload detected` })
|
|
167
|
-
break
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return { violations, warnings }
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private validateTypes(call: CanonicalToolCall): { warnings: string[] } {
|
|
175
|
-
const warnings: string[] = []
|
|
176
|
-
for (const [key, value] of Object.entries(call.input)) {
|
|
177
|
-
const type = typeof value
|
|
178
|
-
if (value !== null && typeof value !== 'undefined' && typeof value !== 'function' && typeof value !== 'symbol' && typeof value !== 'bigint' && typeof value !== 'object') {
|
|
179
|
-
warnings.push(`Unexpected type for parameter "${key}": ${type}`)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return { warnings }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private validateRegistry(call: CanonicalToolCall): { warnings: string[]; confidence: number } {
|
|
186
|
-
const warnings: string[] = []
|
|
187
|
-
let confidence = 1.0
|
|
188
|
-
|
|
189
|
-
const toolDef = this.registry[call.tool]
|
|
190
|
-
if (!toolDef) {
|
|
191
|
-
warnings.push(`Tool "${call.tool}" not found in registry`)
|
|
192
|
-
confidence -= 0.3
|
|
193
|
-
return { warnings, confidence: Math.max(0.1, confidence) }
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (toolDef.required && Array.isArray(toolDef.required)) {
|
|
197
|
-
for (const requiredField of toolDef.required) {
|
|
198
|
-
if (!(requiredField in call.input) || call.input[requiredField] === undefined) {
|
|
199
|
-
warnings.push(`Missing required field: "${requiredField}" for tool "${call.tool}"`)
|
|
200
|
-
confidence -= 0.2
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (toolDef.strict && toolDef.parameters) {
|
|
206
|
-
for (const [param, expectedType] of Object.entries(toolDef.parameters)) {
|
|
207
|
-
if (param in call.input && call.input[param] !== undefined) {
|
|
208
|
-
const actualType = typeof call.input[param]
|
|
209
|
-
if (actualType !== String(expectedType).toLowerCase()) {
|
|
210
|
-
warnings.push(`Type mismatch for "${param}": expected ${String(expectedType)}, got ${actualType}`)
|
|
211
|
-
confidence -= 0.1
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return { warnings, confidence: Math.max(0.1, confidence) }
|
|
218
|
-
}
|
|
219
|
-
}
|