Auto Deployer commited on
Commit ·
a273844
1
Parent(s): e030f23
Add enterprise agents: log/code/reg endpoints
Browse files- .gitignore +6 -0
- package-lock.json +19 -0
- package.json +1 -0
- src/crawler/api.ts +1 -1
- src/crawler/extractor.ts +1 -1
- src/index.ts +318 -1
- src/llm.ts +68 -9
- src/middlewares.ts +8 -1
- src/models/index.ts +85 -0
- src/schemas/CodeSecurityResult.schema.json +45 -0
- src/schemas/LogAnalysisResult.schema.json +75 -0
- src/schemas/RegulatoryPenaltySummary.schema.json +63 -0
- tsconfig.json +1 -1
.gitignore
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
node_modules/
|
| 2 |
+
dist/
|
| 3 |
+
.DS_Store
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
package-lock.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
"better-sqlite3": "^12.9.0",
|
| 14 |
"cheerio": "^1.2.0",
|
| 15 |
"express": "^5.2.1",
|
|
|
|
| 16 |
"node-cron": "^4.2.1",
|
| 17 |
"openai": "^6.34.0",
|
| 18 |
"uuid": "^14.0.0"
|
|
@@ -279,6 +280,12 @@
|
|
| 279 |
"dev": true,
|
| 280 |
"license": "MIT"
|
| 281 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
"node_modules/asynckit": {
|
| 283 |
"version": "0.4.0",
|
| 284 |
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
@@ -1400,6 +1407,18 @@
|
|
| 1400 |
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
| 1401 |
"license": "MIT"
|
| 1402 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1403 |
"node_modules/make-error": {
|
| 1404 |
"version": "1.3.6",
|
| 1405 |
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
|
|
|
| 13 |
"better-sqlite3": "^12.9.0",
|
| 14 |
"cheerio": "^1.2.0",
|
| 15 |
"express": "^5.2.1",
|
| 16 |
+
"js-yaml": "^4.1.1",
|
| 17 |
"node-cron": "^4.2.1",
|
| 18 |
"openai": "^6.34.0",
|
| 19 |
"uuid": "^14.0.0"
|
|
|
|
| 280 |
"dev": true,
|
| 281 |
"license": "MIT"
|
| 282 |
},
|
| 283 |
+
"node_modules/argparse": {
|
| 284 |
+
"version": "2.0.1",
|
| 285 |
+
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
| 286 |
+
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
| 287 |
+
"license": "Python-2.0"
|
| 288 |
+
},
|
| 289 |
"node_modules/asynckit": {
|
| 290 |
"version": "0.4.0",
|
| 291 |
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
|
|
| 1407 |
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
| 1408 |
"license": "MIT"
|
| 1409 |
},
|
| 1410 |
+
"node_modules/js-yaml": {
|
| 1411 |
+
"version": "4.1.1",
|
| 1412 |
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
| 1413 |
+
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
| 1414 |
+
"license": "MIT",
|
| 1415 |
+
"dependencies": {
|
| 1416 |
+
"argparse": "^2.0.1"
|
| 1417 |
+
},
|
| 1418 |
+
"bin": {
|
| 1419 |
+
"js-yaml": "bin/js-yaml.js"
|
| 1420 |
+
}
|
| 1421 |
+
},
|
| 1422 |
"node_modules/make-error": {
|
| 1423 |
"version": "1.3.6",
|
| 1424 |
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
package.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
| 17 |
"better-sqlite3": "^12.9.0",
|
| 18 |
"cheerio": "^1.2.0",
|
| 19 |
"express": "^5.2.1",
|
|
|
|
| 20 |
"node-cron": "^4.2.1",
|
| 21 |
"openai": "^6.34.0",
|
| 22 |
"uuid": "^14.0.0"
|
|
|
|
| 17 |
"better-sqlite3": "^12.9.0",
|
| 18 |
"cheerio": "^1.2.0",
|
| 19 |
"express": "^5.2.1",
|
| 20 |
+
"js-yaml": "^4.1.1",
|
| 21 |
"node-cron": "^4.2.1",
|
| 22 |
"openai": "^6.34.0",
|
| 23 |
"uuid": "^14.0.0"
|
src/crawler/api.ts
CHANGED
|
@@ -250,7 +250,7 @@ router.get('/updates', (req: Request<{}, {}, {}, { app_name?: string, business_l
|
|
| 250 |
|
| 251 |
let topicFilters: string[] = [];
|
| 252 |
if (topics) {
|
| 253 |
-
topicFilters = topics.split(',').map(t => t.trim());
|
| 254 |
}
|
| 255 |
|
| 256 |
const updatesMap = {
|
|
|
|
| 250 |
|
| 251 |
let topicFilters: string[] = [];
|
| 252 |
if (topics) {
|
| 253 |
+
topicFilters = topics.split(',').map((t: string) => t.trim());
|
| 254 |
}
|
| 255 |
|
| 256 |
const updatesMap = {
|
src/crawler/extractor.ts
CHANGED
|
@@ -30,7 +30,7 @@ export function extractAndNormalize(snapshot: RawSnapshot): NormalizedDocument |
|
|
| 30 |
// 2. Full-width to half-width (simplified)
|
| 31 |
let normalizedText = rawText
|
| 32 |
.replace(/\s+/g, ' ')
|
| 33 |
-
.replace(/[\uFF01-\uFF5E]/g, (char) => String.fromCharCode(char.charCodeAt(0) - 0xFEE0))
|
| 34 |
.trim();
|
| 35 |
|
| 36 |
const normalizedHash = crypto.createHash('sha256').update(normalizedText).digest('hex');
|
|
|
|
| 30 |
// 2. Full-width to half-width (simplified)
|
| 31 |
let normalizedText = rawText
|
| 32 |
.replace(/\s+/g, ' ')
|
| 33 |
+
.replace(/[\uFF01-\uFF5E]/g, (char: string) => String.fromCharCode(char.charCodeAt(0) - 0xFEE0))
|
| 34 |
.trim();
|
| 35 |
|
| 36 |
const normalizedHash = crypto.createHash('sha256').update(normalizedText).digest('hex');
|
src/index.ts
CHANGED
|
@@ -7,7 +7,10 @@ import {
|
|
| 7 |
GapAnalysis,
|
| 8 |
ComplianceCheck,
|
| 9 |
LegalReviewPack,
|
| 10 |
-
MissingMaterialItem
|
|
|
|
|
|
|
|
|
|
| 11 |
} from './models';
|
| 12 |
import crawlerRouter from './crawler/api';
|
| 13 |
import { initDB } from './crawler/db';
|
|
@@ -152,6 +155,320 @@ app.post('/api/agents/legal-pack', async (req: Request, res: Response, next: Nex
|
|
| 152 |
}
|
| 153 |
});
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
// 挂载全局错误处理中间件
|
| 156 |
app.use(errorHandler);
|
| 157 |
|
|
|
|
| 7 |
GapAnalysis,
|
| 8 |
ComplianceCheck,
|
| 9 |
LegalReviewPack,
|
| 10 |
+
MissingMaterialItem,
|
| 11 |
+
LogAnalysisResult,
|
| 12 |
+
CodeSecurityResult,
|
| 13 |
+
RegulatoryPenaltySummary
|
| 14 |
} from './models';
|
| 15 |
import crawlerRouter from './crawler/api';
|
| 16 |
import { initDB } from './crawler/db';
|
|
|
|
| 155 |
}
|
| 156 |
});
|
| 157 |
|
| 158 |
+
function normString(value: unknown): string {
|
| 159 |
+
if (value === null || value === undefined) return '';
|
| 160 |
+
if (Array.isArray(value)) return value.map(v => String(v)).join('\n').trim();
|
| 161 |
+
if (typeof value !== 'string') return String(value).trim();
|
| 162 |
+
return value.trim();
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
function maskSecrets(text: string): string {
|
| 166 |
+
let s = text;
|
| 167 |
+
s = s.replace(/sk-[A-Za-z0-9_-]{8,}/g, 'sk-***');
|
| 168 |
+
s = s.replace(/(?<![A-Za-z0-9])[A-Fa-f0-9]{32,}(?![A-Za-z0-9])/g, '***');
|
| 169 |
+
s = s.replace(/(?<![A-Za-z0-9])[A-Za-z0-9_\\-]{24,}(?![A-Za-z0-9])/g, '***');
|
| 170 |
+
return s;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
function parseLogPatterns(logText: string) {
|
| 174 |
+
const lines = logText.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
|
| 175 |
+
const map = new Map<string, { count: number; first?: string; last?: string; samples: string[]; level: 'high' | 'medium' | 'low' }>();
|
| 176 |
+
|
| 177 |
+
const timeRe = /(\d{2}:\d{2}:\d{2}|\d{2}:\d{2})/;
|
| 178 |
+
for (const line of lines) {
|
| 179 |
+
const timeMatch = line.match(timeRe);
|
| 180 |
+
const time = timeMatch ? timeMatch[1] : '';
|
| 181 |
+
const upper = line.toUpperCase();
|
| 182 |
+
const level: 'high' | 'medium' | 'low' =
|
| 183 |
+
upper.includes('ERROR') || upper.includes('EXCEPTION') || upper.includes('FATAL') || upper.includes('TIMEOUT') ? 'high' :
|
| 184 |
+
upper.includes('WARN') ? 'medium' : 'low';
|
| 185 |
+
|
| 186 |
+
const normalized = line
|
| 187 |
+
.replace(/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\s*/g, '')
|
| 188 |
+
.replace(/^\[?\d{2}:\d{2}:\d{2}\]?\s*/g, '')
|
| 189 |
+
.replace(/\b0x[0-9a-fA-F]+\b/g, '0x#')
|
| 190 |
+
.replace(/\b\d+\b/g, '#')
|
| 191 |
+
.replace(/\s+/g, ' ')
|
| 192 |
+
.slice(0, 220);
|
| 193 |
+
|
| 194 |
+
const key = normalized || line.slice(0, 220);
|
| 195 |
+
const existing = map.get(key);
|
| 196 |
+
if (!existing) {
|
| 197 |
+
const record: { count: number; first?: string; last?: string; samples: string[]; level: 'high' | 'medium' | 'low' } = {
|
| 198 |
+
count: 1,
|
| 199 |
+
samples: [maskSecrets(line).slice(0, 320)],
|
| 200 |
+
level
|
| 201 |
+
};
|
| 202 |
+
if (time) {
|
| 203 |
+
record.first = time;
|
| 204 |
+
record.last = time;
|
| 205 |
+
}
|
| 206 |
+
map.set(key, record);
|
| 207 |
+
} else {
|
| 208 |
+
existing.count += 1;
|
| 209 |
+
if (time) {
|
| 210 |
+
if (!existing.first) existing.first = time;
|
| 211 |
+
existing.last = time;
|
| 212 |
+
}
|
| 213 |
+
if (existing.samples.length < 3) existing.samples.push(maskSecrets(line).slice(0, 320));
|
| 214 |
+
if (level === 'high' || (level === 'medium' && existing.level === 'low')) existing.level = level;
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
const patterns = Array.from(map.entries())
|
| 219 |
+
.sort((a, b) => b[1].count - a[1].count)
|
| 220 |
+
.slice(0, 12)
|
| 221 |
+
.map(([pattern, v]) => ({
|
| 222 |
+
pattern,
|
| 223 |
+
count: v.count,
|
| 224 |
+
first_seen: v.first || '',
|
| 225 |
+
last_seen: v.last || '',
|
| 226 |
+
level: v.level,
|
| 227 |
+
sample_logs: v.samples
|
| 228 |
+
}));
|
| 229 |
+
|
| 230 |
+
const timeline = patterns
|
| 231 |
+
.filter(p => p.first_seen)
|
| 232 |
+
.slice(0, 8)
|
| 233 |
+
.map(p => ({ time: p.first_seen, event: `首次出现:${p.pattern}` }));
|
| 234 |
+
|
| 235 |
+
return { patterns, timeline, line_count: lines.length };
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function parseScanFindings(scanText: string) {
|
| 239 |
+
const s = scanText.trim();
|
| 240 |
+
if (!s) return [];
|
| 241 |
+
let obj: any;
|
| 242 |
+
try {
|
| 243 |
+
obj = JSON.parse(s);
|
| 244 |
+
} catch {
|
| 245 |
+
return [];
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
const findings: any[] = [];
|
| 249 |
+
|
| 250 |
+
const semgrepResults = Array.isArray(obj?.results) ? obj.results : [];
|
| 251 |
+
for (const r of semgrepResults) {
|
| 252 |
+
const file = r?.path || r?.extra?.path || '';
|
| 253 |
+
const line = Number(r?.start?.line || r?.extra?.line || 0);
|
| 254 |
+
const ruleId = r?.check_id || r?.rule_id || r?.extra?.metadata?.id || '';
|
| 255 |
+
const severityRaw = String(r?.extra?.severity || r?.extra?.metadata?.severity || r?.severity || 'medium').toLowerCase();
|
| 256 |
+
const severity =
|
| 257 |
+
severityRaw.includes('critical') ? 'critical' :
|
| 258 |
+
severityRaw.includes('high') || severityRaw.includes('error') ? 'high' :
|
| 259 |
+
severityRaw.includes('low') ? 'low' :
|
| 260 |
+
severityRaw.includes('info') ? 'info' :
|
| 261 |
+
'medium';
|
| 262 |
+
const message = r?.extra?.message || r?.extra?.metadata?.message || r?.message || '';
|
| 263 |
+
const evidence = r?.extra?.lines || r?.extra?.metavars ? JSON.stringify({ lines: r?.extra?.lines, metavars: r?.extra?.metavars }) : '';
|
| 264 |
+
findings.push({
|
| 265 |
+
tool: 'semgrep',
|
| 266 |
+
type: 'static_analysis',
|
| 267 |
+
severity,
|
| 268 |
+
status: 'confirmed',
|
| 269 |
+
file: String(file),
|
| 270 |
+
line,
|
| 271 |
+
rule_id: String(ruleId),
|
| 272 |
+
description: String(message),
|
| 273 |
+
evidence: maskSecrets(String(evidence || message || ''))
|
| 274 |
+
});
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
const gitleaksResults = Array.isArray(obj) ? obj : Array.isArray(obj?.leaks) ? obj.leaks : [];
|
| 278 |
+
for (const r of gitleaksResults) {
|
| 279 |
+
const ruleId = r?.RuleID || r?.rule_id || '';
|
| 280 |
+
const file = r?.File || r?.file || '';
|
| 281 |
+
const line = Number(r?.StartLine || r?.line || 0);
|
| 282 |
+
const desc = r?.Description || r?.description || r?.Message || r?.message || 'Possible secret';
|
| 283 |
+
const secret = r?.Secret || r?.secret || '';
|
| 284 |
+
const evidence = secret ? maskSecrets(String(secret)) : maskSecrets(JSON.stringify(r).slice(0, 240));
|
| 285 |
+
if (ruleId || file) {
|
| 286 |
+
findings.push({
|
| 287 |
+
tool: 'gitleaks',
|
| 288 |
+
type: 'secret',
|
| 289 |
+
severity: 'high',
|
| 290 |
+
status: 'confirmed',
|
| 291 |
+
file: String(file),
|
| 292 |
+
line,
|
| 293 |
+
rule_id: String(ruleId || 'gitleaks'),
|
| 294 |
+
description: String(desc),
|
| 295 |
+
evidence
|
| 296 |
+
});
|
| 297 |
+
}
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
return findings.slice(0, 80);
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
const AGENT_VERSION = 'v1.0.0';
|
| 304 |
+
const PROMPT_VERSION = 'v1.0.0';
|
| 305 |
+
|
| 306 |
+
app.post('/api/agents/log-analysis/run', async (req: Request, res: Response, next: NextFunction) => {
|
| 307 |
+
try {
|
| 308 |
+
const system_name = normString(req.body?.system_name);
|
| 309 |
+
const environment = normString(req.body?.environment);
|
| 310 |
+
const time_range = normString(req.body?.time_range);
|
| 311 |
+
const log_text = normString(req.body?.log_text || req.body?.log_content || req.body?.logs);
|
| 312 |
+
const known_changes = req.body?.known_changes ?? req.body?.change_notes ?? '';
|
| 313 |
+
const business_impact = normString(req.body?.business_impact);
|
| 314 |
+
const log_format = normString(req.body?.log_format);
|
| 315 |
+
const knowledge_context = normString(req.body?.knowledge_context || req.body?.kb || req.body?.knowledge_base);
|
| 316 |
+
|
| 317 |
+
const missingItems: MissingMaterialItem[] = [];
|
| 318 |
+
if (!system_name) missingItems.push({ material_type: 'system_name', reason: '缺少系统名称', requirement_level: 'required' });
|
| 319 |
+
if (!environment) missingItems.push({ material_type: 'environment', reason: '缺少环境信息', requirement_level: 'required' });
|
| 320 |
+
if (!time_range) missingItems.push({ material_type: 'time_range', reason: '缺少时间范围', requirement_level: 'required' });
|
| 321 |
+
if (!log_text) missingItems.push({ material_type: 'log_text', reason: '缺少日志文本', requirement_level: 'required' });
|
| 322 |
+
|
| 323 |
+
if (missingItems.length > 0) {
|
| 324 |
+
throw new MissingMaterialError(missingItems);
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
const parsed = parseLogPatterns(log_text);
|
| 328 |
+
const payload = {
|
| 329 |
+
system_name,
|
| 330 |
+
environment,
|
| 331 |
+
time_range,
|
| 332 |
+
business_impact,
|
| 333 |
+
log_format,
|
| 334 |
+
parsed,
|
| 335 |
+
known_changes,
|
| 336 |
+
knowledge_context
|
| 337 |
+
};
|
| 338 |
+
|
| 339 |
+
const startLLM = Date.now();
|
| 340 |
+
const response = await callLLM<LogAnalysisResult>(JSON.stringify(payload), 'LogAnalysisResult');
|
| 341 |
+
res.locals.llm_latency_ms = Date.now() - startLLM;
|
| 342 |
+
|
| 343 |
+
res.json({
|
| 344 |
+
...response,
|
| 345 |
+
schema_version: 'v1.0',
|
| 346 |
+
agent_version: AGENT_VERSION,
|
| 347 |
+
prompt_version: PROMPT_VERSION,
|
| 348 |
+
model_version: process.env.LLM_MODEL_NAME || 'deepseek-chat'
|
| 349 |
+
});
|
| 350 |
+
} catch (error) {
|
| 351 |
+
next(error);
|
| 352 |
+
}
|
| 353 |
+
});
|
| 354 |
+
|
| 355 |
+
app.post('/api/agents/code-security/run', async (req: Request, res: Response, next: NextFunction) => {
|
| 356 |
+
try {
|
| 357 |
+
const repo_name = normString(req.body?.repo_name);
|
| 358 |
+
const language = normString(req.body?.language);
|
| 359 |
+
const branch = normString(req.body?.branch);
|
| 360 |
+
const security_standard = normString(req.body?.security_standard);
|
| 361 |
+
const review_mode = normString(req.body?.review_mode);
|
| 362 |
+
const scan_result_text = normString(req.body?.scan_result_text || req.body?.scan_results || req.body?.scan_result);
|
| 363 |
+
const code_text = normString(req.body?.code_text || req.body?.code_snippet || req.body?.code);
|
| 364 |
+
const knowledge_context = normString(req.body?.knowledge_context || req.body?.kb || req.body?.knowledge_base);
|
| 365 |
+
|
| 366 |
+
const missingItems: MissingMaterialItem[] = [];
|
| 367 |
+
if (!repo_name) missingItems.push({ material_type: 'repo_name', reason: '缺少仓库名称', requirement_level: 'required' });
|
| 368 |
+
if (!language) missingItems.push({ material_type: 'language', reason: '缺少代码语言', requirement_level: 'required' });
|
| 369 |
+
if (!security_standard) missingItems.push({ material_type: 'security_standard', reason: '缺少安全标准', requirement_level: 'required' });
|
| 370 |
+
if (!review_mode) missingItems.push({ material_type: 'review_mode', reason: '缺少审查模式', requirement_level: 'required' });
|
| 371 |
+
|
| 372 |
+
if (missingItems.length > 0) {
|
| 373 |
+
throw new MissingMaterialError(missingItems);
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
const findings = parseScanFindings(scan_result_text);
|
| 377 |
+
const hasScan = findings.length > 0;
|
| 378 |
+
|
| 379 |
+
if (!hasScan && !code_text) {
|
| 380 |
+
throw new MissingMaterialError([{ material_type: 'scan_result_text/code_text', reason: '缺少扫描结果或代码内容', requirement_level: 'required' }]);
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
const payload = {
|
| 384 |
+
repo_name,
|
| 385 |
+
language,
|
| 386 |
+
branch,
|
| 387 |
+
security_standard,
|
| 388 |
+
review_mode,
|
| 389 |
+
findings,
|
| 390 |
+
code_text: maskSecrets(code_text).slice(0, 8000),
|
| 391 |
+
knowledge_context,
|
| 392 |
+
has_scan_results: hasScan
|
| 393 |
+
};
|
| 394 |
+
|
| 395 |
+
const startLLM = Date.now();
|
| 396 |
+
const response = await callLLM<CodeSecurityResult>(JSON.stringify(payload), 'CodeSecurityResult');
|
| 397 |
+
res.locals.llm_latency_ms = Date.now() - startLLM;
|
| 398 |
+
|
| 399 |
+
res.json({
|
| 400 |
+
...response,
|
| 401 |
+
schema_version: 'v1.0',
|
| 402 |
+
agent_version: AGENT_VERSION,
|
| 403 |
+
prompt_version: PROMPT_VERSION,
|
| 404 |
+
model_version: process.env.LLM_MODEL_NAME || 'deepseek-chat'
|
| 405 |
+
});
|
| 406 |
+
} catch (error) {
|
| 407 |
+
next(error);
|
| 408 |
+
}
|
| 409 |
+
});
|
| 410 |
+
|
| 411 |
+
app.post('/api/agents/regulatory-penalty-summary/run', async (req: Request, res: Response, next: NextFunction) => {
|
| 412 |
+
try {
|
| 413 |
+
const topic = normString(req.body?.topic);
|
| 414 |
+
const industry = normString(req.body?.industry);
|
| 415 |
+
const time_range = normString(req.body?.time_range);
|
| 416 |
+
const regulators = req.body?.regulators;
|
| 417 |
+
const keywords = req.body?.keywords;
|
| 418 |
+
const report_type = normString(req.body?.report_type);
|
| 419 |
+
const cases = Array.isArray(req.body?.cases) ? req.body.cases : [];
|
| 420 |
+
const knowledge_context = normString(req.body?.knowledge_context || req.body?.kb || req.body?.knowledge_base);
|
| 421 |
+
|
| 422 |
+
const missingItems: MissingMaterialItem[] = [];
|
| 423 |
+
if (!topic) missingItems.push({ material_type: 'topic', reason: '缺少主题', requirement_level: 'required' });
|
| 424 |
+
if (!industry) missingItems.push({ material_type: 'industry', reason: '缺少行业', requirement_level: 'required' });
|
| 425 |
+
if (!time_range) missingItems.push({ material_type: 'time_range', reason: '缺少时间范围', requirement_level: 'required' });
|
| 426 |
+
if (!regulators || (Array.isArray(regulators) && regulators.length === 0)) {
|
| 427 |
+
missingItems.push({ material_type: 'regulators', reason: '缺少监管机构', requirement_level: 'required' });
|
| 428 |
+
}
|
| 429 |
+
if (!report_type) missingItems.push({ material_type: 'report_type', reason: '缺少报告类型', requirement_level: 'required' });
|
| 430 |
+
|
| 431 |
+
if (missingItems.length > 0) {
|
| 432 |
+
throw new MissingMaterialError(missingItems);
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
const normalizedCases = cases
|
| 436 |
+
.map((c: any) => ({
|
| 437 |
+
case_name: normString(c?.case_name || c?.title || ''),
|
| 438 |
+
regulator: normString(c?.regulator || ''),
|
| 439 |
+
date: normString(c?.date || c?.publish_date || ''),
|
| 440 |
+
source_url: normString(c?.source_url || c?.url || ''),
|
| 441 |
+
excerpt: normString(c?.excerpt || c?.summary || '')
|
| 442 |
+
}))
|
| 443 |
+
.filter((c: any) => c.case_name && c.regulator && c.date && c.source_url && c.excerpt);
|
| 444 |
+
|
| 445 |
+
const payload = {
|
| 446 |
+
topic,
|
| 447 |
+
industry,
|
| 448 |
+
time_range,
|
| 449 |
+
regulators,
|
| 450 |
+
keywords,
|
| 451 |
+
report_type,
|
| 452 |
+
cases: normalizedCases.slice(0, 80),
|
| 453 |
+
knowledge_context
|
| 454 |
+
};
|
| 455 |
+
|
| 456 |
+
const startLLM = Date.now();
|
| 457 |
+
const response = await callLLM<RegulatoryPenaltySummary>(JSON.stringify(payload), 'RegulatoryPenaltySummary');
|
| 458 |
+
res.locals.llm_latency_ms = Date.now() - startLLM;
|
| 459 |
+
|
| 460 |
+
res.json({
|
| 461 |
+
...response,
|
| 462 |
+
schema_version: 'v1.0',
|
| 463 |
+
agent_version: AGENT_VERSION,
|
| 464 |
+
prompt_version: PROMPT_VERSION,
|
| 465 |
+
model_version: process.env.LLM_MODEL_NAME || 'deepseek-chat'
|
| 466 |
+
});
|
| 467 |
+
} catch (error) {
|
| 468 |
+
next(error);
|
| 469 |
+
}
|
| 470 |
+
});
|
| 471 |
+
|
| 472 |
// 挂载全局错误处理中间件
|
| 473 |
app.use(errorHandler);
|
| 474 |
|
src/llm.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
| 1 |
import OpenAI from 'openai';
|
| 2 |
-
import { PeerRegSummary, GapAnalysis, ComplianceCheck, LegalReviewPack } from './models';
|
| 3 |
|
| 4 |
-
|
| 5 |
-
const
|
| 6 |
-
baseURL: 'https://api.deepseek.com/v1',
|
| 7 |
-
apiKey: process.env.DEEPSEEK_API_KEY || 'sk-a05d3ac4dcc440f689521151c718ead8',
|
| 8 |
-
});
|
| 9 |
|
| 10 |
-
|
| 11 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
const PROMPTS: Record<string, string> = {
|
| 14 |
PeerRegSummary: `你是银行APP隐私合规场景中的“同业/监管变化摘要智能体”。
|
|
@@ -91,6 +100,51 @@ const PROMPTS: Record<string, string> = {
|
|
| 91 |
- focus_items 只保留最关键的法务关注点,并为其打上 priority 标签 (P0/P1/P2);
|
| 92 |
- 不得出现最终审批语气。
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
输出语言:中文。
|
| 95 |
请严格输出JSON格式,不要包裹在 \`\`\`json 中,直接输出 { 开始。`
|
| 96 |
};
|
|
@@ -103,6 +157,7 @@ export async function callLLM<T>(prompt: string, schemaType: string): Promise<T>
|
|
| 103 |
}
|
| 104 |
|
| 105 |
try {
|
|
|
|
| 106 |
const response = await openai.chat.completions.create({
|
| 107 |
model: MODEL_NAME,
|
| 108 |
messages: [
|
|
@@ -120,7 +175,11 @@ export async function callLLM<T>(prompt: string, schemaType: string): Promise<T>
|
|
| 120 |
|
| 121 |
return JSON.parse(content) as T;
|
| 122 |
} catch (error) {
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
throw new Error('LLM_INFERENCE_FAILED');
|
| 125 |
}
|
| 126 |
}
|
|
|
|
| 1 |
import OpenAI from 'openai';
|
| 2 |
+
import { PeerRegSummary, GapAnalysis, ComplianceCheck, LegalReviewPack, LogAnalysisResult, CodeSecurityResult, RegulatoryPenaltySummary } from './models';
|
| 3 |
|
| 4 |
+
const MODEL_NAME = process.env.LLM_MODEL_NAME || 'deepseek-chat';
|
| 5 |
+
const BASE_URL = process.env.DEEPSEEK_BASE_URL || 'https://api.deepseek.com/v1';
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
+
function getApiKey(): string {
|
| 8 |
+
const key = process.env.DEEPSEEK_API_KEY;
|
| 9 |
+
if (!key) {
|
| 10 |
+
throw new Error('MISSING_LLM_API_KEY');
|
| 11 |
+
}
|
| 12 |
+
return key;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
function getClient(): OpenAI {
|
| 16 |
+
return new OpenAI({
|
| 17 |
+
baseURL: BASE_URL,
|
| 18 |
+
apiKey: getApiKey(),
|
| 19 |
+
});
|
| 20 |
+
}
|
| 21 |
|
| 22 |
const PROMPTS: Record<string, string> = {
|
| 23 |
PeerRegSummary: `你是银行APP隐私合规场景中的“同业/监管变化摘要智能体”。
|
|
|
|
| 100 |
- focus_items 只保留最关键的法务关注点,并为其打上 priority 标签 (P0/P1/P2);
|
| 101 |
- 不得出现最终审批语气。
|
| 102 |
|
| 103 |
+
输出语言:中文。
|
| 104 |
+
请严格输出JSON格式,不要包裹在 \`\`\`json 中,直接输出 { 开始。`
|
| 105 |
+
,
|
| 106 |
+
|
| 107 |
+
LogAnalysisResult: `你是国内中小银行生产系统日志分析专家。
|
| 108 |
+
|
| 109 |
+
你的任务:
|
| 110 |
+
基于日志解析结果、错误聚类、时间线、已知变更、业务影响与知识库内容,生成日志异常摘要、疑似原因、排查建议和风险提示,并生成可归档 Markdown 报告。
|
| 111 |
+
|
| 112 |
+
必须遵守:
|
| 113 |
+
1. 不得直接认定根因,除非证据充分;优先使用“疑似”“可能”“建议进一步核查”。
|
| 114 |
+
2. 不得建议用户直接重启生产服务、删除文件、回滚版本或修改生产配置;只给出检查/对比/验证类建议。
|
| 115 |
+
3. 所有判断必须给出 evidence。
|
| 116 |
+
4. 如果日志样本不足或信息缺失,必须写 insufficient_context=true,并说明缺失点。
|
| 117 |
+
5. 输出必须为 JSON。
|
| 118 |
+
|
| 119 |
+
输出语言:中文。
|
| 120 |
+
请严格输出JSON格式,不要包裹在 \`\`\`json 中,直接输出 { 开始。`,
|
| 121 |
+
|
| 122 |
+
CodeSecurityResult: `你是银行代码安全检测结果解释与修复建议专家。
|
| 123 |
+
|
| 124 |
+
你的任务:
|
| 125 |
+
基于扫描工具结果(优先)与代码片段(可选)解释漏洞、给出修复建议与安全边界提示,并生成可归档 Markdown 报告。
|
| 126 |
+
|
| 127 |
+
必须遵守:
|
| 128 |
+
1. 以扫描结果为主;没有扫描结果时,不得输出“confirmed”确定性漏洞结论,只能输出“suspicious”并提示补充扫描或进入人工复核。
|
| 129 |
+
2. 不得输出完整密钥、token、账号等敏感信息;若 evidence 中出现疑似 secret,必须掩码。
|
| 130 |
+
3. 每条漏洞必须包含 file/line/rule_id/evidence。
|
| 131 |
+
4. 输出必须为 JSON。
|
| 132 |
+
|
| 133 |
+
输出语言:中文。
|
| 134 |
+
请严格输出JSON格式,不要包裹在 \`\`\`json 中,直接输出 { 开始。`,
|
| 135 |
+
|
| 136 |
+
RegulatoryPenaltySummary: `你是国内中小银行监管处罚信息汇总专家。
|
| 137 |
+
|
| 138 |
+
你的任务:
|
| 139 |
+
基于白名单来源抓取/检索结果、监管案例材料与法规知识库,归纳监管处罚趋势、高频风险类型、典型案例和整改建议,并生成简报 Markdown。
|
| 140 |
+
|
| 141 |
+
必须遵守:
|
| 142 |
+
1. 不得编造监管处罚案例、日期、监管机构、来源链接。
|
| 143 |
+
2. 每个典型案例必须包含 regulator、date、source_url、excerpt。
|
| 144 |
+
3. 无来源信息时,必须写 insufficient_context=true,不得输出确定性结论。
|
| 145 |
+
4. 不得作出最终法律意见,只能输出风险提示与整改建议。
|
| 146 |
+
5. 输出必须为 JSON。
|
| 147 |
+
|
| 148 |
输出语言:中文。
|
| 149 |
请严格输出JSON格式,不要包裹在 \`\`\`json 中,直接输出 { 开始。`
|
| 150 |
};
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
try {
|
| 160 |
+
const openai = getClient();
|
| 161 |
const response = await openai.chat.completions.create({
|
| 162 |
model: MODEL_NAME,
|
| 163 |
messages: [
|
|
|
|
| 175 |
|
| 176 |
return JSON.parse(content) as T;
|
| 177 |
} catch (error) {
|
| 178 |
+
const msg = error instanceof Error ? error.message : String(error);
|
| 179 |
+
if (msg === 'MISSING_LLM_API_KEY') {
|
| 180 |
+
throw new Error('MISSING_LLM_API_KEY');
|
| 181 |
+
}
|
| 182 |
+
console.error(`LLM Call Failed for ${schemaType}: ${msg}`);
|
| 183 |
throw new Error('LLM_INFERENCE_FAILED');
|
| 184 |
}
|
| 185 |
}
|
src/middlewares.ts
CHANGED
|
@@ -29,7 +29,7 @@ export const requestLogger = (req: Request, res: Response, next: NextFunction) =
|
|
| 29 |
|
| 30 |
// 拦截 res.send 记录结束时间
|
| 31 |
const originalSend = res.send;
|
| 32 |
-
res.send = function (body) {
|
| 33 |
const ended_at = new Date();
|
| 34 |
|
| 35 |
// 提取 case_context
|
|
@@ -46,6 +46,9 @@ export const requestLogger = (req: Request, res: Response, next: NextFunction) =
|
|
| 46 |
else if (req.path.includes('policy-rewrite')) agent_name = 'AGENT-02';
|
| 47 |
else if (req.path.includes('compliance-check')) agent_name = 'AGENT-03';
|
| 48 |
else if (req.path.includes('legal-pack')) agent_name = 'AGENT-04';
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
const status = res.statusCode >= 400 ? 'failed' : 'success';
|
| 51 |
const error_code = res.locals.errorCode || 0;
|
|
@@ -105,6 +108,10 @@ export const errorHandler = (err: any, req: Request, res: Response, next: NextFu
|
|
| 105 |
statusCode = 500;
|
| 106 |
errorCode = 5002;
|
| 107 |
message = '外部上下文缺失';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
// 记录 errorCode 给 logger 使用
|
|
|
|
| 29 |
|
| 30 |
// 拦截 res.send 记录结束时间
|
| 31 |
const originalSend = res.send;
|
| 32 |
+
res.send = function (body: any) {
|
| 33 |
const ended_at = new Date();
|
| 34 |
|
| 35 |
// 提取 case_context
|
|
|
|
| 46 |
else if (req.path.includes('policy-rewrite')) agent_name = 'AGENT-02';
|
| 47 |
else if (req.path.includes('compliance-check')) agent_name = 'AGENT-03';
|
| 48 |
else if (req.path.includes('legal-pack')) agent_name = 'AGENT-04';
|
| 49 |
+
else if (req.path.includes('log-analysis')) agent_name = 'LOG_ANALYSIS';
|
| 50 |
+
else if (req.path.includes('code-security')) agent_name = 'CODE_SECURITY';
|
| 51 |
+
else if (req.path.includes('regulatory-penalty-summary')) agent_name = 'REG_PENALTY_SUMMARY';
|
| 52 |
|
| 53 |
const status = res.statusCode >= 400 ? 'failed' : 'success';
|
| 54 |
const error_code = res.locals.errorCode || 0;
|
|
|
|
| 108 |
statusCode = 500;
|
| 109 |
errorCode = 5002;
|
| 110 |
message = '外部上下文缺失';
|
| 111 |
+
} else if (err.message === 'MISSING_LLM_API_KEY') {
|
| 112 |
+
statusCode = 500;
|
| 113 |
+
errorCode = 5003;
|
| 114 |
+
message = 'LLM密钥未配置';
|
| 115 |
}
|
| 116 |
|
| 117 |
// 记录 errorCode 给 logger 使用
|
src/models/index.ts
CHANGED
|
@@ -168,3 +168,88 @@ export interface NeedMoreMaterialResponse extends BaseResponse {
|
|
| 168 |
message: string;
|
| 169 |
missing_materials: MissingMaterialItem[];
|
| 170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
message: string;
|
| 169 |
missing_materials: MissingMaterialItem[];
|
| 170 |
}
|
| 171 |
+
|
| 172 |
+
export interface LogAnalysisResult extends BaseResponse {
|
| 173 |
+
insufficient_context?: boolean;
|
| 174 |
+
summary: string;
|
| 175 |
+
error_patterns: {
|
| 176 |
+
pattern: string;
|
| 177 |
+
count: number;
|
| 178 |
+
first_seen?: string;
|
| 179 |
+
last_seen?: string;
|
| 180 |
+
level: 'high' | 'medium' | 'low';
|
| 181 |
+
sample_logs: string[];
|
| 182 |
+
}[];
|
| 183 |
+
timeline: {
|
| 184 |
+
time: string;
|
| 185 |
+
event: string;
|
| 186 |
+
}[];
|
| 187 |
+
possible_causes: {
|
| 188 |
+
cause: string;
|
| 189 |
+
confidence: 'high' | 'medium' | 'low';
|
| 190 |
+
evidence: string[];
|
| 191 |
+
}[];
|
| 192 |
+
suggested_actions: string[];
|
| 193 |
+
risks: {
|
| 194 |
+
risk: string;
|
| 195 |
+
level: 'high' | 'medium' | 'low';
|
| 196 |
+
suggestion: string;
|
| 197 |
+
}[];
|
| 198 |
+
need_human_review: boolean;
|
| 199 |
+
confidence?: 'high' | 'medium' | 'low';
|
| 200 |
+
evidence?: string[];
|
| 201 |
+
report_markdown: string;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
export interface CodeSecurityResult extends BaseResponse {
|
| 205 |
+
insufficient_context?: boolean;
|
| 206 |
+
risk_summary: string;
|
| 207 |
+
vulnerabilities: {
|
| 208 |
+
type: string;
|
| 209 |
+
severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
|
| 210 |
+
status: 'confirmed' | 'suspicious' | 'false_positive_candidate';
|
| 211 |
+
tool?: string;
|
| 212 |
+
file: string;
|
| 213 |
+
line: number;
|
| 214 |
+
rule_id: string;
|
| 215 |
+
description: string;
|
| 216 |
+
evidence: string;
|
| 217 |
+
impact: string;
|
| 218 |
+
fix_suggestion: string;
|
| 219 |
+
safe_example?: string;
|
| 220 |
+
}[];
|
| 221 |
+
fix_plan: string[];
|
| 222 |
+
compliance_notes: string[];
|
| 223 |
+
need_security_review: boolean;
|
| 224 |
+
confidence?: 'high' | 'medium' | 'low';
|
| 225 |
+
evidence?: string[];
|
| 226 |
+
report_markdown: string;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
export interface RegulatoryPenaltySummary extends BaseResponse {
|
| 230 |
+
insufficient_context?: boolean;
|
| 231 |
+
topic: string;
|
| 232 |
+
summary: string;
|
| 233 |
+
risk_categories: {
|
| 234 |
+
category: string;
|
| 235 |
+
frequency: 'high' | 'medium' | 'low';
|
| 236 |
+
severity: 'high' | 'medium' | 'low';
|
| 237 |
+
description: string;
|
| 238 |
+
typical_cases: {
|
| 239 |
+
case_name: string;
|
| 240 |
+
regulator: string;
|
| 241 |
+
date: string;
|
| 242 |
+
source_url: string;
|
| 243 |
+
excerpt: string;
|
| 244 |
+
}[];
|
| 245 |
+
suggestions: string[];
|
| 246 |
+
}[];
|
| 247 |
+
top_risks: string[];
|
| 248 |
+
action_suggestions: string[];
|
| 249 |
+
references: {
|
| 250 |
+
source: string;
|
| 251 |
+
url: string;
|
| 252 |
+
details: string;
|
| 253 |
+
}[];
|
| 254 |
+
briefing_markdown: string;
|
| 255 |
+
}
|
src/schemas/CodeSecurityResult.schema.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
| 3 |
+
"title": "CodeSecurityResult",
|
| 4 |
+
"type": "object",
|
| 5 |
+
"required": [
|
| 6 |
+
"risk_summary",
|
| 7 |
+
"vulnerabilities",
|
| 8 |
+
"fix_plan",
|
| 9 |
+
"compliance_notes",
|
| 10 |
+
"need_security_review",
|
| 11 |
+
"report_markdown"
|
| 12 |
+
],
|
| 13 |
+
"properties": {
|
| 14 |
+
"insufficient_context": { "type": "boolean" },
|
| 15 |
+
"risk_summary": { "type": "string" },
|
| 16 |
+
"vulnerabilities": {
|
| 17 |
+
"type": "array",
|
| 18 |
+
"items": {
|
| 19 |
+
"type": "object",
|
| 20 |
+
"required": ["type", "severity", "status", "file", "line", "rule_id", "description", "evidence", "impact", "fix_suggestion"],
|
| 21 |
+
"properties": {
|
| 22 |
+
"type": { "type": "string" },
|
| 23 |
+
"severity": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"] },
|
| 24 |
+
"status": { "type": "string", "enum": ["confirmed", "suspicious", "false_positive_candidate"] },
|
| 25 |
+
"tool": { "type": "string" },
|
| 26 |
+
"file": { "type": "string" },
|
| 27 |
+
"line": { "type": "number" },
|
| 28 |
+
"rule_id": { "type": "string" },
|
| 29 |
+
"description": { "type": "string" },
|
| 30 |
+
"evidence": { "type": "string" },
|
| 31 |
+
"impact": { "type": "string" },
|
| 32 |
+
"fix_suggestion": { "type": "string" },
|
| 33 |
+
"safe_example": { "type": "string" }
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"fix_plan": { "type": "array", "items": { "type": "string" } },
|
| 38 |
+
"compliance_notes": { "type": "array", "items": { "type": "string" } },
|
| 39 |
+
"need_security_review": { "type": "boolean" },
|
| 40 |
+
"confidence": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 41 |
+
"evidence": { "type": "array", "items": { "type": "string" } },
|
| 42 |
+
"report_markdown": { "type": "string" }
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
src/schemas/LogAnalysisResult.schema.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
| 3 |
+
"title": "LogAnalysisResult",
|
| 4 |
+
"type": "object",
|
| 5 |
+
"required": [
|
| 6 |
+
"summary",
|
| 7 |
+
"error_patterns",
|
| 8 |
+
"timeline",
|
| 9 |
+
"possible_causes",
|
| 10 |
+
"suggested_actions",
|
| 11 |
+
"risks",
|
| 12 |
+
"need_human_review",
|
| 13 |
+
"report_markdown"
|
| 14 |
+
],
|
| 15 |
+
"properties": {
|
| 16 |
+
"insufficient_context": { "type": "boolean" },
|
| 17 |
+
"summary": { "type": "string" },
|
| 18 |
+
"error_patterns": {
|
| 19 |
+
"type": "array",
|
| 20 |
+
"items": {
|
| 21 |
+
"type": "object",
|
| 22 |
+
"required": ["pattern", "count", "level", "sample_logs"],
|
| 23 |
+
"properties": {
|
| 24 |
+
"pattern": { "type": "string" },
|
| 25 |
+
"count": { "type": "number" },
|
| 26 |
+
"first_seen": { "type": "string" },
|
| 27 |
+
"last_seen": { "type": "string" },
|
| 28 |
+
"level": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 29 |
+
"sample_logs": { "type": "array", "items": { "type": "string" } }
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
},
|
| 33 |
+
"timeline": {
|
| 34 |
+
"type": "array",
|
| 35 |
+
"items": {
|
| 36 |
+
"type": "object",
|
| 37 |
+
"required": ["time", "event"],
|
| 38 |
+
"properties": {
|
| 39 |
+
"time": { "type": "string" },
|
| 40 |
+
"event": { "type": "string" }
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"possible_causes": {
|
| 45 |
+
"type": "array",
|
| 46 |
+
"items": {
|
| 47 |
+
"type": "object",
|
| 48 |
+
"required": ["cause", "confidence", "evidence"],
|
| 49 |
+
"properties": {
|
| 50 |
+
"cause": { "type": "string" },
|
| 51 |
+
"confidence": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 52 |
+
"evidence": { "type": "array", "items": { "type": "string" } }
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
},
|
| 56 |
+
"suggested_actions": { "type": "array", "items": { "type": "string" } },
|
| 57 |
+
"risks": {
|
| 58 |
+
"type": "array",
|
| 59 |
+
"items": {
|
| 60 |
+
"type": "object",
|
| 61 |
+
"required": ["risk", "level", "suggestion"],
|
| 62 |
+
"properties": {
|
| 63 |
+
"risk": { "type": "string" },
|
| 64 |
+
"level": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 65 |
+
"suggestion": { "type": "string" }
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
},
|
| 69 |
+
"need_human_review": { "type": "boolean" },
|
| 70 |
+
"confidence": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 71 |
+
"evidence": { "type": "array", "items": { "type": "string" } },
|
| 72 |
+
"report_markdown": { "type": "string" }
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
src/schemas/RegulatoryPenaltySummary.schema.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
| 3 |
+
"title": "RegulatoryPenaltySummary",
|
| 4 |
+
"type": "object",
|
| 5 |
+
"required": [
|
| 6 |
+
"topic",
|
| 7 |
+
"summary",
|
| 8 |
+
"risk_categories",
|
| 9 |
+
"top_risks",
|
| 10 |
+
"action_suggestions",
|
| 11 |
+
"references",
|
| 12 |
+
"briefing_markdown"
|
| 13 |
+
],
|
| 14 |
+
"properties": {
|
| 15 |
+
"insufficient_context": { "type": "boolean" },
|
| 16 |
+
"topic": { "type": "string" },
|
| 17 |
+
"summary": { "type": "string" },
|
| 18 |
+
"risk_categories": {
|
| 19 |
+
"type": "array",
|
| 20 |
+
"items": {
|
| 21 |
+
"type": "object",
|
| 22 |
+
"required": ["category", "frequency", "severity", "description", "typical_cases", "suggestions"],
|
| 23 |
+
"properties": {
|
| 24 |
+
"category": { "type": "string" },
|
| 25 |
+
"frequency": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 26 |
+
"severity": { "type": "string", "enum": ["high", "medium", "low"] },
|
| 27 |
+
"description": { "type": "string" },
|
| 28 |
+
"typical_cases": {
|
| 29 |
+
"type": "array",
|
| 30 |
+
"items": {
|
| 31 |
+
"type": "object",
|
| 32 |
+
"required": ["case_name", "regulator", "date", "source_url", "excerpt"],
|
| 33 |
+
"properties": {
|
| 34 |
+
"case_name": { "type": "string" },
|
| 35 |
+
"regulator": { "type": "string" },
|
| 36 |
+
"date": { "type": "string" },
|
| 37 |
+
"source_url": { "type": "string" },
|
| 38 |
+
"excerpt": { "type": "string" }
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"suggestions": { "type": "array", "items": { "type": "string" } }
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
},
|
| 46 |
+
"top_risks": { "type": "array", "items": { "type": "string" } },
|
| 47 |
+
"action_suggestions": { "type": "array", "items": { "type": "string" } },
|
| 48 |
+
"references": {
|
| 49 |
+
"type": "array",
|
| 50 |
+
"items": {
|
| 51 |
+
"type": "object",
|
| 52 |
+
"required": ["source", "url", "details"],
|
| 53 |
+
"properties": {
|
| 54 |
+
"source": { "type": "string" },
|
| 55 |
+
"url": { "type": "string" },
|
| 56 |
+
"details": { "type": "string" }
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"briefing_markdown": { "type": "string" }
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
tsconfig.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
| 9 |
// See also https://aka.ms/tsconfig/module
|
| 10 |
"module": "nodenext",
|
| 11 |
"target": "esnext",
|
| 12 |
-
"types": [],
|
| 13 |
// For nodejs:
|
| 14 |
// "lib": ["esnext"],
|
| 15 |
// "types": ["node"],
|
|
|
|
| 9 |
// See also https://aka.ms/tsconfig/module
|
| 10 |
"module": "nodenext",
|
| 11 |
"target": "esnext",
|
| 12 |
+
"types": ["node"],
|
| 13 |
// For nodejs:
|
| 14 |
// "lib": ["esnext"],
|
| 15 |
// "types": ["node"],
|