@goodandready/dsh-moa 0.2.74 → 0.2.76
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 +8 -0
- package/README.ru.md +8 -0
- package/README.zh.md +8 -0
- package/lib/client.js +103 -2
- package/lib/events.js +4 -1
- package/lib/moa-schema.js +12 -1
- package/lib/pipeline/context.js +31 -0
- package/lib/pipeline/cove-verifier.js +401 -0
- package/lib/pipeline/developer-preferences.js +196 -0
- package/lib/pipeline/manifest-stack.js +287 -0
- package/lib/pipeline/runner.js +37 -2
- package/lib/pipeline/security-fuzzer.js +290 -0
- package/lib/routes/preferences.js +108 -0
- package/lib/routes.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stack-Aware Dynamic Persona Evolution from Project Manifests (#308)
|
|
3
|
+
*
|
|
4
|
+
* Scans project manifests (package.json, Cargo.toml, pyproject.toml, requirements.txt, go.mod)
|
|
5
|
+
* in the workspace root to infer primary programming languages, frameworks, ORMs,
|
|
6
|
+
* and test suites. Evolves candidate proposers into specialized domain experts
|
|
7
|
+
* (e.g. React Optimization, TypeScript Strict Safety, Accessibility, FastAPI Async)
|
|
8
|
+
* to maximize architectural hypothesis diversity.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'node:fs'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scans workspace root manifests to detect project languages and dependencies.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} cwd - Workspace root directory
|
|
18
|
+
* @returns {object} Detected stack information
|
|
19
|
+
*/
|
|
20
|
+
export function detectProjectStack(cwd = process.cwd()) {
|
|
21
|
+
const stack = {
|
|
22
|
+
primaryLanguage: 'unknown',
|
|
23
|
+
frameworks: [],
|
|
24
|
+
libraries: [],
|
|
25
|
+
manifestFound: false,
|
|
26
|
+
stackSummary: '',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!cwd || typeof cwd !== 'string') return stack
|
|
30
|
+
|
|
31
|
+
// 1. Node / JavaScript / TypeScript (package.json)
|
|
32
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
33
|
+
if (fs.existsSync(pkgPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const raw = fs.readFileSync(pkgPath, 'utf8')
|
|
36
|
+
const pkg = JSON.parse(raw)
|
|
37
|
+
stack.manifestFound = true
|
|
38
|
+
stack.primaryLanguage = 'javascript'
|
|
39
|
+
|
|
40
|
+
const allDeps = {
|
|
41
|
+
...(pkg.dependencies || {}),
|
|
42
|
+
...(pkg.devDependencies || {}),
|
|
43
|
+
...(pkg.peerDependencies || {}),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (allDeps.typescript || fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
|
|
47
|
+
stack.primaryLanguage = 'typescript'
|
|
48
|
+
stack.libraries.push('typescript')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Frameworks
|
|
52
|
+
if (allDeps.next) stack.frameworks.push('next.js')
|
|
53
|
+
else if (allDeps.react) stack.frameworks.push('react')
|
|
54
|
+
|
|
55
|
+
if (allDeps.nuxt) stack.frameworks.push('nuxt')
|
|
56
|
+
else if (allDeps.vue) stack.frameworks.push('vue')
|
|
57
|
+
|
|
58
|
+
if (allDeps['@sveltejs/kit']) stack.frameworks.push('sveltekit')
|
|
59
|
+
else if (allDeps.svelte) stack.frameworks.push('svelte')
|
|
60
|
+
|
|
61
|
+
if (allDeps['@angular/core']) stack.frameworks.push('angular')
|
|
62
|
+
if (allDeps['@nestjs/core']) stack.frameworks.push('nestjs')
|
|
63
|
+
if (allDeps.fastify) stack.frameworks.push('fastify')
|
|
64
|
+
if (allDeps.express) stack.frameworks.push('express')
|
|
65
|
+
|
|
66
|
+
// Styling & UI
|
|
67
|
+
if (allDeps.tailwindcss) stack.libraries.push('tailwindcss')
|
|
68
|
+
|
|
69
|
+
// ORM & Data
|
|
70
|
+
if (allDeps.prisma || allDeps['@prisma/client']) stack.libraries.push('prisma')
|
|
71
|
+
if (allDeps['drizzle-orm']) stack.libraries.push('drizzle')
|
|
72
|
+
if (allDeps.typeorm) stack.libraries.push('typeorm')
|
|
73
|
+
if (allDeps.mongoose) stack.libraries.push('mongoose')
|
|
74
|
+
|
|
75
|
+
// Validation
|
|
76
|
+
if (allDeps.zod) stack.libraries.push('zod')
|
|
77
|
+
if (allDeps.valibot) stack.libraries.push('valibot')
|
|
78
|
+
|
|
79
|
+
// Testing
|
|
80
|
+
if (allDeps.vitest) stack.libraries.push('vitest')
|
|
81
|
+
if (allDeps.jest) stack.libraries.push('jest')
|
|
82
|
+
if (allDeps.playwright || allDeps['@playwright/test']) stack.libraries.push('playwright')
|
|
83
|
+
} catch {
|
|
84
|
+
// safe fallback on corrupted package.json
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Rust (Cargo.toml)
|
|
89
|
+
const cargoPath = path.join(cwd, 'Cargo.toml')
|
|
90
|
+
if (fs.existsSync(cargoPath)) {
|
|
91
|
+
try {
|
|
92
|
+
const content = fs.readFileSync(cargoPath, 'utf8')
|
|
93
|
+
stack.manifestFound = true
|
|
94
|
+
stack.primaryLanguage = 'rust'
|
|
95
|
+
|
|
96
|
+
if (content.includes('axum')) stack.frameworks.push('axum')
|
|
97
|
+
if (content.includes('actix-web')) stack.frameworks.push('actix-web')
|
|
98
|
+
if (content.includes('tokio')) stack.libraries.push('tokio')
|
|
99
|
+
if (content.includes('serde')) stack.libraries.push('serde')
|
|
100
|
+
if (content.includes('sqlx')) stack.libraries.push('sqlx')
|
|
101
|
+
if (content.includes('diesel')) stack.libraries.push('diesel')
|
|
102
|
+
} catch {
|
|
103
|
+
// safe fallback
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. Python (pyproject.toml, requirements.txt)
|
|
108
|
+
const pyprojectPath = path.join(cwd, 'pyproject.toml')
|
|
109
|
+
const reqPath = path.join(cwd, 'requirements.txt')
|
|
110
|
+
if (fs.existsSync(pyprojectPath) || fs.existsSync(reqPath)) {
|
|
111
|
+
try {
|
|
112
|
+
const pyContent = (fs.existsSync(pyprojectPath) ? fs.readFileSync(pyprojectPath, 'utf8') : '') +
|
|
113
|
+
'\n' + (fs.existsSync(reqPath) ? fs.readFileSync(reqPath, 'utf8') : '')
|
|
114
|
+
stack.manifestFound = true
|
|
115
|
+
if (stack.primaryLanguage === 'unknown') stack.primaryLanguage = 'python'
|
|
116
|
+
|
|
117
|
+
if (pyContent.includes('fastapi')) stack.frameworks.push('fastapi')
|
|
118
|
+
if (pyContent.includes('django')) stack.frameworks.push('django')
|
|
119
|
+
if (pyContent.includes('flask')) stack.frameworks.push('flask')
|
|
120
|
+
if (pyContent.includes('pydantic')) stack.libraries.push('pydantic')
|
|
121
|
+
if (pyContent.includes('sqlalchemy')) stack.libraries.push('sqlalchemy')
|
|
122
|
+
if (pyContent.includes('pytest')) stack.libraries.push('pytest')
|
|
123
|
+
if (pyContent.includes('torch')) stack.libraries.push('pytorch')
|
|
124
|
+
if (pyContent.includes('pandas')) stack.libraries.push('pandas')
|
|
125
|
+
} catch {
|
|
126
|
+
// safe fallback
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 4. Go (go.mod)
|
|
131
|
+
const goModPath = path.join(cwd, 'go.mod')
|
|
132
|
+
if (fs.existsSync(goModPath)) {
|
|
133
|
+
try {
|
|
134
|
+
const goContent = fs.readFileSync(goModPath, 'utf8')
|
|
135
|
+
stack.manifestFound = true
|
|
136
|
+
if (stack.primaryLanguage === 'unknown') stack.primaryLanguage = 'go'
|
|
137
|
+
|
|
138
|
+
if (goContent.includes('gin-gonic/gin')) stack.frameworks.push('gin')
|
|
139
|
+
if (goContent.includes('gofiber/fiber')) stack.frameworks.push('fiber')
|
|
140
|
+
if (goContent.includes('labstack/echo')) stack.frameworks.push('echo')
|
|
141
|
+
if (goContent.includes('gorm.io/gorm')) stack.libraries.push('gorm')
|
|
142
|
+
if (goContent.includes('stretchr/testify')) stack.libraries.push('testify')
|
|
143
|
+
} catch {
|
|
144
|
+
// safe fallback
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Build summary string
|
|
149
|
+
const parts = []
|
|
150
|
+
if (stack.primaryLanguage !== 'unknown') parts.push(stack.primaryLanguage)
|
|
151
|
+
if (stack.frameworks.length > 0) parts.push(`framework: ${stack.frameworks.join(', ')}`)
|
|
152
|
+
if (stack.libraries.length > 0) parts.push(`libs: ${stack.libraries.slice(0, 4).join(', ')}`)
|
|
153
|
+
stack.stackSummary = parts.join(' | ')
|
|
154
|
+
|
|
155
|
+
return stack
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Returns specialized domain personas based on detected stack.
|
|
160
|
+
*
|
|
161
|
+
* @param {object} stackInfo
|
|
162
|
+
* @returns {Array<string>} Array of specialized persona role descriptions
|
|
163
|
+
*/
|
|
164
|
+
export function getStackPersonaSpecialties(stackInfo = {}) {
|
|
165
|
+
const lang = stackInfo.primaryLanguage || 'unknown'
|
|
166
|
+
const frameworks = Array.isArray(stackInfo.frameworks) ? stackInfo.frameworks : []
|
|
167
|
+
const libs = Array.isArray(stackInfo.libraries) ? stackInfo.libraries : []
|
|
168
|
+
|
|
169
|
+
// React / Next.js / Frontend
|
|
170
|
+
if (frameworks.includes('react') || frameworks.includes('next.js') || frameworks.includes('vue') || frameworks.includes('svelte')) {
|
|
171
|
+
return [
|
|
172
|
+
'Component Architecture, Hooks & Re-render Optimization Specialist',
|
|
173
|
+
libs.includes('typescript') || lang === 'typescript'
|
|
174
|
+
? 'TypeScript Strict Typing, Generic Invariants & Defensive Schema Expert'
|
|
175
|
+
: 'State Management, Data Flow & Edge-Case Specialist',
|
|
176
|
+
'Accessibility (a11y), Semantic UI & Modern CSS Performance Reviewer',
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Python / FastAPI / Django
|
|
181
|
+
if (lang === 'python') {
|
|
182
|
+
return [
|
|
183
|
+
frameworks.includes('fastapi')
|
|
184
|
+
? 'FastAPI Async Concurrency & High-Throughput REST Architect'
|
|
185
|
+
: 'Pythonic Clean Architecture & Idiomatic Object Design Specialist',
|
|
186
|
+
libs.includes('pydantic')
|
|
187
|
+
? 'Pydantic V2 Schema Validation & Type-Safe Domain Modeler'
|
|
188
|
+
: 'Defensive Data Integrity & Error Boundary Engineer',
|
|
189
|
+
libs.includes('pytest')
|
|
190
|
+
? 'Pytest Fixtures, Mocking & Comprehensive Edge-Case Test Engineer'
|
|
191
|
+
: 'Performance Profiling & Memory Leak Auditor',
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Rust
|
|
196
|
+
if (lang === 'rust') {
|
|
197
|
+
return [
|
|
198
|
+
'Rust Zero-Cost Abstractions & Concurrency Architect',
|
|
199
|
+
'Rust Memory Safety, Error Handling & Lifetime Specialist',
|
|
200
|
+
'Rust Idiomatic API & Performance Specialist',
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Go
|
|
205
|
+
if (lang === 'go') {
|
|
206
|
+
return [
|
|
207
|
+
'Go Idiomatic Concurrency & Channel Synchronization Architect',
|
|
208
|
+
'Go Interface Design, Error Wrapping & Zero-Allocation Specialist',
|
|
209
|
+
'Go Production Reliability & Table-Driven Unit Testing Specialist',
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// General TypeScript / Node
|
|
214
|
+
if (lang === 'typescript') {
|
|
215
|
+
return [
|
|
216
|
+
'TypeScript Strict Type Safety & Clean Architecture Specialist',
|
|
217
|
+
'Async Flow, Micro-Optimization & Error Boundary Engineer',
|
|
218
|
+
'Maintainability, Modularity & Test Coverage Auditor',
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Universal Default
|
|
223
|
+
return [
|
|
224
|
+
'System Architecture & Core Correctness Engineer',
|
|
225
|
+
'Edge-Case Analysis & Defensive Error Handling Specialist',
|
|
226
|
+
'Clean Code, Maintainability & Documentation Specialist',
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Assigns stack-aware specialized personas to candidate proposer slots.
|
|
232
|
+
*
|
|
233
|
+
* @param {Array<object>} references - Proposer slots
|
|
234
|
+
* @param {object} stackInfo - Output of detectProjectStack
|
|
235
|
+
* @param {object} [options]
|
|
236
|
+
* @param {boolean} [options.enabled=true] - Whether stack-aware personas are enabled
|
|
237
|
+
* @returns {{ references: Array<object>, isSpecialized: boolean, stackSummary: string }}
|
|
238
|
+
*/
|
|
239
|
+
export function assignStackAwarePersonas(references = [], stackInfo = {}, options = {}) {
|
|
240
|
+
const { enabled = true } = options
|
|
241
|
+
|
|
242
|
+
if (!enabled || !Array.isArray(references) || references.length === 0) {
|
|
243
|
+
return {
|
|
244
|
+
references: Array.isArray(references) ? references : [],
|
|
245
|
+
isSpecialized: false,
|
|
246
|
+
stackSummary: stackInfo.stackSummary || '',
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const specialties = getStackPersonaSpecialties(stackInfo)
|
|
251
|
+
const evolved = references.map((slot, idx) => {
|
|
252
|
+
// If user already specified a custom non-default persona, preserve it
|
|
253
|
+
const hasCustom = slot.role_persona && slot.role_persona !== 'general' && slot.role_persona !== 'proposer'
|
|
254
|
+
if (hasCustom) {
|
|
255
|
+
return { ...slot }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const specialty = specialties[idx % specialties.length]
|
|
259
|
+
return {
|
|
260
|
+
...slot,
|
|
261
|
+
role_persona: specialty,
|
|
262
|
+
stackSpecialization: specialty,
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
references: evolved,
|
|
268
|
+
isSpecialized: Boolean(stackInfo.manifestFound),
|
|
269
|
+
stackSummary: stackInfo.stackSummary || '',
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Formats a directive summarizing the project stack for injection into judge or synthesis prompts.
|
|
275
|
+
*
|
|
276
|
+
* @param {object} stackInfo
|
|
277
|
+
* @returns {string|null}
|
|
278
|
+
*/
|
|
279
|
+
export function formatStackAwareDirective(stackInfo = {}) {
|
|
280
|
+
if (!stackInfo || !stackInfo.manifestFound || !stackInfo.stackSummary) {
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return `### Project Stack & Architecture Context
|
|
285
|
+
- Detected Environment: ${stackInfo.stackSummary}
|
|
286
|
+
- Verification Requirement: All proposals must adhere to the conventions and idiomatic idioms of this technology stack.`
|
|
287
|
+
}
|
package/lib/pipeline/runner.js
CHANGED
|
@@ -18,6 +18,8 @@ import { stageSynthesis } from './synthesis.js'
|
|
|
18
18
|
import { stagePromotion } from './promotion.js'
|
|
19
19
|
import { applySyntaxFilter, formatSyntaxDirective } from './syntax-filter.js'
|
|
20
20
|
import { stageEarlyConsensus } from './early-consensus.js'
|
|
21
|
+
import { runSecurityAuditGate } from './security-fuzzer.js'
|
|
22
|
+
import { runCoveVerificationGate } from './cove-verifier.js'
|
|
21
23
|
import { emitEvent, MOA_EVENTS } from '../events.js'
|
|
22
24
|
|
|
23
25
|
export async function runMoAPipeline({
|
|
@@ -199,6 +201,39 @@ export async function runMoAPipeline({
|
|
|
199
201
|
return { error: new Error('Turn aborted') }
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
// Pre-Stage 5.8: Chain-of-Verification (CoVe) Fact-Checking Gate (#314)
|
|
205
|
+
let coveDirective = null
|
|
206
|
+
let coveGateResult = null
|
|
207
|
+
if (preset?.cove_verification_enabled) {
|
|
208
|
+
coveGateResult = runCoveVerificationGate(
|
|
209
|
+
syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
210
|
+
ctxStage,
|
|
211
|
+
{
|
|
212
|
+
ctx,
|
|
213
|
+
runId,
|
|
214
|
+
onProgress,
|
|
215
|
+
maxQuestions: preset.cove_max_questions || 5,
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
coveDirective = coveGateResult.coveDirective
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Pre-Stage 5.9: Adversarial White-Hat Security Fuzzing Gate (#302)
|
|
222
|
+
let securityDirective = null
|
|
223
|
+
let securityGateResult = null
|
|
224
|
+
if (preset?.security_gate_enabled) {
|
|
225
|
+
securityGateResult = runSecurityAuditGate(
|
|
226
|
+
syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
227
|
+
{
|
|
228
|
+
ctx,
|
|
229
|
+
runId,
|
|
230
|
+
onProgress,
|
|
231
|
+
strictVeto: preset.security_gate_strict_veto !== false,
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
securityDirective = securityGateResult.securityDirective
|
|
235
|
+
}
|
|
236
|
+
|
|
202
237
|
// Pre-Stage 6: Dynamic Consensus Threshold & Multi-Judge Escalation (#300)
|
|
203
238
|
const escalationResult = evaluateJudgeEscalation({
|
|
204
239
|
candidateOutputs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
@@ -249,7 +284,7 @@ export async function runMoAPipeline({
|
|
|
249
284
|
preset: effectivePreset,
|
|
250
285
|
successfulRefs: syntaxResult.validCandidates.length > 0 ? syntaxResult.validCandidates : successfulRefs,
|
|
251
286
|
referenceOutputs, primaryJudge: effectivePrimaryJudge,
|
|
252
|
-
userPrompt, judgeCriteria: ctxStage.judgeCriteria, prices, maxTokens: ctxStage.maxTokens,
|
|
287
|
+
userPrompt, judgeCriteria: [ctxStage.judgeCriteria, coveDirective, securityDirective].filter(Boolean).join('\n\n'), prices, maxTokens: ctxStage.maxTokens,
|
|
253
288
|
callLlm, signal, onProgress, cwd, runId, isRefinement: ctxStage.isRefinement,
|
|
254
289
|
isFastMode: ctxStage.isFastMode, collectedCtx: ctxStage.collectedCtx, startTime,
|
|
255
290
|
})
|
|
@@ -307,7 +342,7 @@ export async function runMoAPipeline({
|
|
|
307
342
|
}
|
|
308
343
|
}
|
|
309
344
|
|
|
310
|
-
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, pruningDirective].filter(Boolean).join("\n\n") || null
|
|
345
|
+
const fileScoringDirective = [rawFileDirective, visualResult?.directive, adversarialTddResult?.directive, syntaxDirective, coveDirective, securityDirective, pruningDirective, ctxStage.stackAwareDirective, ctxStage.developerPreferencesDirective].filter(Boolean).join("\n\n") || null
|
|
311
346
|
|
|
312
347
|
// Stage 7: Synthesis Phase
|
|
313
348
|
const synthRes = await stageSynthesis({
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Harness Mixture of Agents (MoA) — Adversarial Security Fuzzing Gate (#302)
|
|
3
|
+
*
|
|
4
|
+
* White-Hat Red Team security gate that audits candidate code proposals for critical
|
|
5
|
+
* vulnerabilities, injection vectors, memory exhaustion, prototype pollution, and CWE patterns
|
|
6
|
+
* before final Judge arbitration.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { MOA_EVENTS, emitEvent } from '../events.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Known vulnerability rule signatures with CWE classifications.
|
|
13
|
+
*/
|
|
14
|
+
export const SECURITY_RULES = [
|
|
15
|
+
{
|
|
16
|
+
cwe: 'CWE-89',
|
|
17
|
+
title: 'SQL Injection Risk',
|
|
18
|
+
severity: 'critical',
|
|
19
|
+
regex: /(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|EXEC)\s+[^;`'"]*?(\$\{[^}]+\}|['"`]\s*\+\s*[a-zA-Z0-9_]+)/i,
|
|
20
|
+
detail: 'Unsanitized query construction using string interpolation or concatenation instead of parameterized bindings.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
cwe: 'CWE-78',
|
|
24
|
+
title: 'OS Command Injection',
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
regex: /(?:child_process\.)?(?:exec|execSync)\s*\(\s*(?:`[^`]*\$\{[^}]+\}[^`]*`|['"][^'"]*['"]\s*\+)/i,
|
|
27
|
+
detail: 'Executing shell commands with interpolated or concatenated untrusted input.',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
cwe: 'CWE-78',
|
|
31
|
+
title: 'Shell Invocation with Dynamic Arguments',
|
|
32
|
+
severity: 'critical',
|
|
33
|
+
regex: /spawn\s*\([^,]+,\s*\{[^}]*shell\s*:\s*true/i,
|
|
34
|
+
detail: 'Invoking child_process.spawn with shell: true exposes process to command injection.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
cwe: 'CWE-22',
|
|
38
|
+
title: 'Path Traversal Vulnerability',
|
|
39
|
+
severity: 'high',
|
|
40
|
+
regex: /(?:readFile|writeFile|createReadStream|createWriteStream|unlink|stat)\s*\([^)]*?(?:\.\.\/|\.\.\\)/i,
|
|
41
|
+
detail: 'File system operation contains unconstrained relative path traversal sequences (../).',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
cwe: 'CWE-79',
|
|
45
|
+
title: 'Cross-Site Scripting (DOM XSS)',
|
|
46
|
+
severity: 'high',
|
|
47
|
+
regex: /(?:\.innerHTML\s*=|document\.write\s*\(|dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!DOMPurify|sanitize))/i,
|
|
48
|
+
detail: 'Unescaped HTML or DOM insertion allows arbitrary script execution in browser contexts.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
cwe: 'CWE-1321',
|
|
52
|
+
title: 'Prototype Pollution',
|
|
53
|
+
severity: 'critical',
|
|
54
|
+
regex: /(?:__proto__|constructor\s*\[\s*['"`]prototype['"`]\s*\]|prototype\s*\[\s*['"`]\w+['"`]\s*\]\s*=)/i,
|
|
55
|
+
detail: 'Direct modification of object prototype chain may lead to remote code execution or privilege escalation.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
cwe: 'CWE-400',
|
|
59
|
+
title: 'Regular Expression Denial of Service (ReDoS)',
|
|
60
|
+
severity: 'high',
|
|
61
|
+
regex: /\/(?:[^\/\\]|\\.)*(?:\([^\)]*[\+\*]\)[^\)]*[\+\*]|\([^\)]*\|[^\)]*\)[\+\*])(?:[^\/\\]|\\.)*\//,
|
|
62
|
+
detail: 'Regular expression pattern contains nested quantifiers with catastrophic polynomial/exponential backtracking.',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
cwe: 'CWE-798',
|
|
66
|
+
title: 'Hardcoded Cryptographic Secret / Credential',
|
|
67
|
+
severity: 'high',
|
|
68
|
+
regex: /(?:api_?key|auth_?token|secret_?key|private_?key|bearer_?token)\s*[:=]\s*['"`](?:sk_live_[0-9a-zA-Z]{16,}|ghp_[0-9a-zA-Z]{20,}|ey[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,})['"`]/i,
|
|
69
|
+
detail: 'Production-like API token, private key, or credential embedded directly in source code.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
cwe: 'CWE-362',
|
|
73
|
+
title: 'TOCTOU Concurrency Race Condition',
|
|
74
|
+
severity: 'medium',
|
|
75
|
+
regex: /fs\.existsSync\s*\([^)]+\)[\s\S]{1,80}?fs\.(?:writeFileSync|appendFileSync)\s*\(/i,
|
|
76
|
+
detail: 'Non-atomic check-then-act pattern creates file race condition under concurrent operations.',
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Audits a code snippet or model response for security vulnerabilities.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} code - Candidate response text or extracted code
|
|
84
|
+
* @param {object} [options]
|
|
85
|
+
* @returns {object} Audit report with score, findings, and veto flag
|
|
86
|
+
*/
|
|
87
|
+
export function auditCodeSecurity(code, options = {}) {
|
|
88
|
+
if (!code || typeof code !== 'string') {
|
|
89
|
+
return {
|
|
90
|
+
veto: false,
|
|
91
|
+
score: 100,
|
|
92
|
+
findings: [],
|
|
93
|
+
summary: 'Clean: No executable code provided for security audit.',
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const findings = []
|
|
98
|
+
const lines = code.split('\n')
|
|
99
|
+
|
|
100
|
+
for (const rule of SECURITY_RULES) {
|
|
101
|
+
if (rule.regex.test(code)) {
|
|
102
|
+
// Pinpoint line number if possible
|
|
103
|
+
let matchedLine = null
|
|
104
|
+
let matchedSnippet = ''
|
|
105
|
+
for (let i = 0; i < lines.length; i++) {
|
|
106
|
+
if (rule.regex.test(lines[i])) {
|
|
107
|
+
matchedLine = i + 1
|
|
108
|
+
matchedSnippet = lines[i].trim().slice(0, 120)
|
|
109
|
+
break
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
findings.push({
|
|
114
|
+
cwe: rule.cwe,
|
|
115
|
+
title: rule.title,
|
|
116
|
+
severity: rule.severity,
|
|
117
|
+
detail: rule.detail,
|
|
118
|
+
line: matchedLine,
|
|
119
|
+
snippet: matchedSnippet || null,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let penalty = 0
|
|
125
|
+
let hasCriticalOrHigh = false
|
|
126
|
+
for (const f of findings) {
|
|
127
|
+
if (f.severity === 'critical') {
|
|
128
|
+
penalty += 45
|
|
129
|
+
hasCriticalOrHigh = true
|
|
130
|
+
} else if (f.severity === 'high') {
|
|
131
|
+
penalty += 25
|
|
132
|
+
hasCriticalOrHigh = true
|
|
133
|
+
} else if (f.severity === 'medium') {
|
|
134
|
+
penalty += 15
|
|
135
|
+
} else {
|
|
136
|
+
penalty += 5
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const score = Math.max(0, Math.min(100, 100 - penalty))
|
|
141
|
+
const strictVeto = options.strictVeto !== false
|
|
142
|
+
const veto = strictVeto ? hasCriticalOrHigh || score < 60 : score < 40
|
|
143
|
+
|
|
144
|
+
let summary = ''
|
|
145
|
+
if (findings.length === 0) {
|
|
146
|
+
summary = '🛡️ Red Team Security Audit: PASSED (Score 100/100). No known CWE vulnerabilities detected.'
|
|
147
|
+
} else if (veto) {
|
|
148
|
+
summary = `⛔ SECURITY_VETO (Score ${score}/100): Found ${findings.length} security violation(s) (${findings.map((f) => f.cwe).join(', ')}).`
|
|
149
|
+
} else {
|
|
150
|
+
summary = `⚠️ Security Warning (Score ${score}/100): ${findings.length} minor/medium issue(s) identified.`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
veto,
|
|
155
|
+
score,
|
|
156
|
+
findings,
|
|
157
|
+
summary,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Executes the Adversarial Security Fuzzing Gate across all candidate outputs.
|
|
163
|
+
*
|
|
164
|
+
* @param {Array<object>} candidates - List of candidate references with output/text
|
|
165
|
+
* @param {object} [options]
|
|
166
|
+
* @returns {object} Gate execution results with directive and veto counters
|
|
167
|
+
*/
|
|
168
|
+
export function runSecurityAuditGate(candidates, options = {}) {
|
|
169
|
+
const { ctx, runId, onProgress, strictVeto = true } = options
|
|
170
|
+
const results = []
|
|
171
|
+
let vetoCount = 0
|
|
172
|
+
|
|
173
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
174
|
+
return {
|
|
175
|
+
results: [],
|
|
176
|
+
vetoCount: 0,
|
|
177
|
+
cleanCount: 0,
|
|
178
|
+
allPassed: true,
|
|
179
|
+
securityDirective: null,
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
184
|
+
const cand = candidates[i]
|
|
185
|
+
const text = typeof cand === 'string' ? cand : cand.output || cand.text || cand.code || ''
|
|
186
|
+
const audit = auditCodeSecurity(text, { strictVeto })
|
|
187
|
+
|
|
188
|
+
if (typeof cand === 'object' && cand !== null) {
|
|
189
|
+
cand.securityAudit = audit
|
|
190
|
+
cand.securityVeto = audit.veto
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (audit.veto) {
|
|
194
|
+
vetoCount++
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
results.push({
|
|
198
|
+
candidateIndex: i,
|
|
199
|
+
model: cand?.model || `Candidate ${i + 1}`,
|
|
200
|
+
audit,
|
|
201
|
+
veto: audit.veto,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
if (ctx) {
|
|
205
|
+
emitEvent(ctx, MOA_EVENTS.SECURITY_AUDIT, {
|
|
206
|
+
runId,
|
|
207
|
+
candidateIndex: i,
|
|
208
|
+
score: audit.score,
|
|
209
|
+
veto: audit.veto,
|
|
210
|
+
findingsCount: audit.findings.length,
|
|
211
|
+
cwes: audit.findings.map((f) => f.cwe),
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const cleanCount = candidates.length - vetoCount
|
|
217
|
+
const allPassed = vetoCount === 0
|
|
218
|
+
|
|
219
|
+
if (typeof onProgress === 'function') {
|
|
220
|
+
if (allPassed) {
|
|
221
|
+
onProgress(`🛡️ *Security Gate Passed*: All ${candidates.length} candidate solutions cleared Red Team audit.\n`)
|
|
222
|
+
} else {
|
|
223
|
+
onProgress(
|
|
224
|
+
`🚨 *Security Gate Alert*: ${vetoCount}/${candidates.length} candidates triggered SECURITY_VETO (${results
|
|
225
|
+
.filter((r) => r.veto)
|
|
226
|
+
.map((r) => `Ref ${r.candidateIndex + 1}`)
|
|
227
|
+
.join(', ')}).\n`
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const securityDirective = formatSecurityAuditDirective(results, { strictVeto })
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
results,
|
|
236
|
+
vetoCount,
|
|
237
|
+
cleanCount,
|
|
238
|
+
allPassed,
|
|
239
|
+
securityDirective,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Formats Red Team audit findings into a high-priority prompt directive for Stage 6/7.
|
|
245
|
+
*
|
|
246
|
+
* @param {Array<object>} results
|
|
247
|
+
* @param {object} [options]
|
|
248
|
+
* @returns {string|null} Directive text
|
|
249
|
+
*/
|
|
250
|
+
export function formatSecurityAuditDirective(results, options = {}) {
|
|
251
|
+
if (!Array.isArray(results) || results.length === 0) return null
|
|
252
|
+
|
|
253
|
+
const hasAnyFindings = results.some((r) => r.audit && r.audit.findings.length > 0)
|
|
254
|
+
if (!hasAnyFindings) {
|
|
255
|
+
return '### 🛡️ RED TEAM SECURITY AUDIT: ALL CANDIDATES VERIFIED SECURE\nNo injection, XSS, ReDoS, path traversal, or credential exposure detected.'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const lines = [
|
|
259
|
+
'### 🛡️ ADVERSARIAL RED TEAM SECURITY AUDIT DIRECTIVE',
|
|
260
|
+
'Strict security evaluation of candidate proposals has been completed:',
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
for (const r of results) {
|
|
264
|
+
const label = `Candidate ${r.candidateIndex + 1}`
|
|
265
|
+
const a = r.audit || {}
|
|
266
|
+
if (a.veto) {
|
|
267
|
+
lines.push(
|
|
268
|
+
`- **${label}**: ⛔ **SECURITY_VETO** (Score ${a.score}/100) — DO NOT adopt this solution without removing critical flaws:`
|
|
269
|
+
)
|
|
270
|
+
for (const f of a.findings || []) {
|
|
271
|
+
lines.push(
|
|
272
|
+
` * [${f.cwe}] ${f.title} (${f.severity.toUpperCase()}): ${f.detail}${f.line ? ` (at line ${f.line})` : ''}`
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
} else if (a.findings && a.findings.length > 0) {
|
|
276
|
+
lines.push(`- **${label}**: ⚠️ Warning (Score ${a.score}/100):`)
|
|
277
|
+
for (const f of a.findings) {
|
|
278
|
+
lines.push(` * [${f.cwe}] ${f.title}: ${f.detail}`)
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
lines.push(`- **${label}**: ✅ PASSED (Score 100/100) — Free of detectable vulnerabilities.`)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
lines.push(
|
|
286
|
+
'\n**Judge Mandate**: Solutions marked with SECURITY_VETO must be heavily penalized or rejected. The final synthesized code MUST NOT contain any of the flagged CWE patterns.'
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return lines.join('\n')
|
|
290
|
+
}
|