@panguard-ai/threat-cloud 1.4.2 → 1.5.6
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/LICENSE +21 -0
- package/dist/audit-logger.d.ts +1 -1
- package/dist/audit-logger.d.ts.map +1 -1
- package/dist/audit-logger.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/database.d.ts +236 -2
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +603 -51
- package/dist/database.js.map +1 -1
- package/dist/llm-reviewer-tools.d.ts +110 -0
- package/dist/llm-reviewer-tools.d.ts.map +1 -0
- package/dist/llm-reviewer-tools.js +446 -0
- package/dist/llm-reviewer-tools.js.map +1 -0
- package/dist/llm-reviewer.d.ts +54 -0
- package/dist/llm-reviewer.d.ts.map +1 -1
- package/dist/llm-reviewer.js +708 -64
- package/dist/llm-reviewer.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +215 -0
- package/dist/migrations.js.map +1 -1
- package/dist/migrator-crystallization.d.ts +80 -0
- package/dist/migrator-crystallization.d.ts.map +1 -0
- package/dist/migrator-crystallization.js +108 -0
- package/dist/migrator-crystallization.js.map +1 -0
- package/dist/server.d.ts +69 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1093 -91
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +15 -12
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TC v2 — Tool-Use Primitives for the Drafter Loop
|
|
3
|
+
* ATR 威脅雲 v2 — 起草迴圈的工具原語
|
|
4
|
+
*
|
|
5
|
+
* Exposes three Anthropic-compatible tool definitions that Claude calls
|
|
6
|
+
* during rule drafting so it can make research-grounded, dedup-aware
|
|
7
|
+
* decisions instead of generating rules blind:
|
|
8
|
+
*
|
|
9
|
+
* grep_existing_rules(keywords)
|
|
10
|
+
* Search the public ATR repository for rules whose title,
|
|
11
|
+
* description, or conditions mention any of the given keywords.
|
|
12
|
+
* Used at the START of every draft to avoid duplicating existing
|
|
13
|
+
* coverage.
|
|
14
|
+
*
|
|
15
|
+
* read_rule(rule_id)
|
|
16
|
+
* Fetch the full YAML of a specific ATR rule by ID. Called when
|
|
17
|
+
* grep finds a potentially overlapping rule and Claude wants to
|
|
18
|
+
* inspect its exact patterns before deciding to extend vs. create.
|
|
19
|
+
*
|
|
20
|
+
* fetch_research(url)
|
|
21
|
+
* Retrieve the text content of a public research page (arxiv,
|
|
22
|
+
* Invariant Labs, Elastic Security Labs, Snyk, MITRE, etc.) so
|
|
23
|
+
* Claude can ground a new rule in a documented attack rather
|
|
24
|
+
* than guessing.
|
|
25
|
+
*
|
|
26
|
+
* All three tools:
|
|
27
|
+
* - Honor an in-memory LRU cache with a 10-minute TTL to avoid
|
|
28
|
+
* hammering GitHub or research sites during a burst of drafts.
|
|
29
|
+
* - Fall back gracefully (empty result + explanatory message)
|
|
30
|
+
* on network errors so Claude can still produce output.
|
|
31
|
+
* - Enforce a short domain allowlist for fetch_research so a poisoned
|
|
32
|
+
* prompt cannot coerce TC into making arbitrary outbound requests.
|
|
33
|
+
*
|
|
34
|
+
* @module @panguard-ai/threat-cloud/llm-reviewer-tools
|
|
35
|
+
*/
|
|
36
|
+
import * as https from 'node:https';
|
|
37
|
+
import { load as parseYaml } from 'js-yaml';
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Tool schema definitions (Anthropic tool use API)
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export const TC_DRAFTER_TOOLS = [
|
|
42
|
+
{
|
|
43
|
+
name: 'grep_existing_rules',
|
|
44
|
+
description: 'Search the existing ATR rule corpus for rules whose title, description, or detection conditions mention any of the given keywords. ALWAYS call this FIRST before drafting a new rule, to check whether the attack class is already covered. Returns a list of matching rule IDs with their titles and categories. If the keyword is already covered by an existing rule, you should call read_rule to inspect it and decide whether to extend the existing rule (not covered by this API — just emit NO_THREATS_FOUND and note the overlap) or propose a truly new rule for a variant.',
|
|
45
|
+
input_schema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
keywords: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: { type: 'string' },
|
|
51
|
+
description: 'One to five short keywords or phrases to search for. Examples: ["prompt injection", "IMPORTANT tag"], ["credential exfil", "ssh key"], ["tool poisoning", "cross-tool"]. Each keyword is case-insensitive and matched as a substring against rule metadata.',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ['keywords'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'read_rule',
|
|
59
|
+
description: 'Fetch the full YAML content of a specific ATR rule by its ID. Use this after grep_existing_rules identifies a potentially overlapping rule, so you can inspect its exact regex patterns and test cases before deciding whether to propose a new rule.',
|
|
60
|
+
input_schema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
rule_id: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'An ATR rule ID in the form ATR-2026-00XXX. Example: "ATR-2026-00100".',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ['rule_id'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'fetch_research',
|
|
73
|
+
description: 'Fetch the text content of a public research page about AI agent security. Use this to ground a new rule in a documented attack from a reputable source (Invariant Labs, Elastic Security Labs, Snyk, MITRE ATLAS, arxiv, Unit 42, etc.). The domain must be on the allowlist — generic URLs will be refused. Returns the first 5000 characters of the text content.',
|
|
74
|
+
input_schema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
url: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'The URL of the research page. Allowed domains: invariantlabs.ai, elastic.co, snyk.io, arxiv.org, atlas.mitre.org, mitre.org, unit42.paloaltonetworks.com, genai.owasp.org, owasp.org, github.com, raw.githubusercontent.com, developer.microsoft.com, trendmicro.com, helpnetsecurity.com, agentseal.org, keysight.com, zenity.io.',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
required: ['url'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Shared fetch helper (node:https, with size + time limits)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
const FETCH_TIMEOUT_MS = 12_000;
|
|
90
|
+
const MAX_RESPONSE_BYTES_DEFAULT = 500 * 1024; // 500 KB per fetch
|
|
91
|
+
const MAX_RESPONSE_BYTES_LARGE = 4 * 1024 * 1024; // 4 MB for GitHub trees API
|
|
92
|
+
async function httpsGet(url, maxBytes = MAX_RESPONSE_BYTES_DEFAULT) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const req = https.get(url, {
|
|
95
|
+
headers: {
|
|
96
|
+
'User-Agent': 'ATR-ThreatCloud/2.0 (+https://agent-threat-rules.com)',
|
|
97
|
+
Accept: 'text/plain, text/html, application/vnd.github.raw+json, */*',
|
|
98
|
+
},
|
|
99
|
+
}, (res) => {
|
|
100
|
+
const status = res.statusCode ?? 0;
|
|
101
|
+
if (status >= 300 && status < 400 && res.headers.location) {
|
|
102
|
+
// Follow one level of redirect
|
|
103
|
+
const next = new URL(res.headers.location, url).toString();
|
|
104
|
+
res.resume();
|
|
105
|
+
httpsGet(next, maxBytes).then(resolve, reject);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (status < 200 || status >= 300) {
|
|
109
|
+
res.resume();
|
|
110
|
+
reject(new Error(`HTTP ${status} for ${url}`));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const chunks = [];
|
|
114
|
+
let total = 0;
|
|
115
|
+
res.on('data', (chunk) => {
|
|
116
|
+
total += chunk.length;
|
|
117
|
+
if (total > maxBytes) {
|
|
118
|
+
res.destroy();
|
|
119
|
+
reject(new Error(`response exceeded ${maxBytes} bytes`));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
chunks.push(chunk);
|
|
123
|
+
});
|
|
124
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
125
|
+
res.on('error', reject);
|
|
126
|
+
});
|
|
127
|
+
req.on('error', reject);
|
|
128
|
+
req.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
129
|
+
req.destroy(new Error(`timeout after ${FETCH_TIMEOUT_MS}ms: ${url}`));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
134
|
+
const MAX_CACHE_ENTRIES = 256;
|
|
135
|
+
const cache = new Map();
|
|
136
|
+
function cacheGet(key) {
|
|
137
|
+
const entry = cache.get(key);
|
|
138
|
+
if (!entry)
|
|
139
|
+
return undefined;
|
|
140
|
+
if (entry.expiresAt < Date.now()) {
|
|
141
|
+
cache.delete(key);
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
return entry.value;
|
|
145
|
+
}
|
|
146
|
+
function cacheSet(key, value) {
|
|
147
|
+
if (cache.size >= MAX_CACHE_ENTRIES) {
|
|
148
|
+
// Evict oldest entry
|
|
149
|
+
const firstKey = cache.keys().next().value;
|
|
150
|
+
if (firstKey !== undefined)
|
|
151
|
+
cache.delete(firstKey);
|
|
152
|
+
}
|
|
153
|
+
cache.set(key, { value, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Tool 1: grep_existing_rules
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
const ATR_REPO_OWNER = 'Agent-Threat-Rule';
|
|
159
|
+
const ATR_REPO_NAME = 'agent-threat-rules';
|
|
160
|
+
const ATR_REPO_BRANCH = 'main';
|
|
161
|
+
/**
|
|
162
|
+
* Load all rule summaries from the ATR repository. Cached for 10 minutes
|
|
163
|
+
* so a burst of drafts doesn't re-fetch on every call.
|
|
164
|
+
*/
|
|
165
|
+
async function loadAllRuleSummaries() {
|
|
166
|
+
const cached = cacheGet('all_rule_summaries');
|
|
167
|
+
if (cached)
|
|
168
|
+
return cached;
|
|
169
|
+
// Use the GitHub trees API with recursive=1 to list all files in one call.
|
|
170
|
+
// The full tree for agent-threat-rules is ~1 MB (109 rule files + tests +
|
|
171
|
+
// docs), so we use the larger 4 MB cap for this specific fetch.
|
|
172
|
+
const treeUrl = `https://api.github.com/repos/${ATR_REPO_OWNER}/${ATR_REPO_NAME}/git/trees/${ATR_REPO_BRANCH}?recursive=1`;
|
|
173
|
+
let treeJson;
|
|
174
|
+
try {
|
|
175
|
+
treeJson = await httpsGet(treeUrl, MAX_RESPONSE_BYTES_LARGE);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error(`[tc-v2] grep_existing_rules: failed to list rule files via GitHub API: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
let parsed;
|
|
182
|
+
try {
|
|
183
|
+
parsed = JSON.parse(treeJson);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const ruleFiles = (parsed.tree ?? [])
|
|
189
|
+
.filter((t) => t.type === 'blob' && t.path && t.path.startsWith('rules/') && t.path.endsWith('.yaml'))
|
|
190
|
+
.map((t) => t.path);
|
|
191
|
+
const summaries = [];
|
|
192
|
+
// Fetch in small parallel batches to stay polite
|
|
193
|
+
const BATCH = 8;
|
|
194
|
+
for (let i = 0; i < ruleFiles.length; i += BATCH) {
|
|
195
|
+
const batch = ruleFiles.slice(i, i + BATCH);
|
|
196
|
+
const results = await Promise.all(batch.map(async (path) => {
|
|
197
|
+
try {
|
|
198
|
+
const raw = await httpsGet(`https://raw.githubusercontent.com/${ATR_REPO_OWNER}/${ATR_REPO_NAME}/${ATR_REPO_BRANCH}/${path}`);
|
|
199
|
+
const doc = parseYaml(raw);
|
|
200
|
+
if (!doc)
|
|
201
|
+
return null;
|
|
202
|
+
const id = typeof doc['id'] === 'string' ? doc['id'] : '';
|
|
203
|
+
const title = typeof doc['title'] === 'string' ? doc['title'] : '';
|
|
204
|
+
const severity = typeof doc['severity'] === 'string' ? doc['severity'] : '';
|
|
205
|
+
const tags = (doc['tags'] ?? {});
|
|
206
|
+
const category = typeof tags['category'] === 'string' ? tags['category'] : '';
|
|
207
|
+
return {
|
|
208
|
+
ruleId: id,
|
|
209
|
+
title,
|
|
210
|
+
category,
|
|
211
|
+
severity,
|
|
212
|
+
path,
|
|
213
|
+
previewMatch: '',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
for (const r of results) {
|
|
221
|
+
if (r && r.ruleId)
|
|
222
|
+
summaries.push(r);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
cacheSet('all_rule_summaries', summaries);
|
|
226
|
+
return summaries;
|
|
227
|
+
}
|
|
228
|
+
export async function grepExistingRules(keywords) {
|
|
229
|
+
const summaries = await loadAllRuleSummaries();
|
|
230
|
+
if (summaries.length === 0) {
|
|
231
|
+
return {
|
|
232
|
+
total: 0,
|
|
233
|
+
matches: [],
|
|
234
|
+
note: 'Could not load ATR rule list (GitHub API error). Draft defensively — assume similar rules may already exist.',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const normalized = keywords.map((k) => k.toLowerCase().trim()).filter((k) => k.length >= 2);
|
|
238
|
+
if (normalized.length === 0) {
|
|
239
|
+
return {
|
|
240
|
+
total: summaries.length,
|
|
241
|
+
matches: [],
|
|
242
|
+
note: 'No usable keywords provided. Pass 1-5 short keyword strings to search rule titles, descriptions, and conditions.',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Load each rule's raw YAML and check for keyword substring matches.
|
|
246
|
+
// We use the cached raw content from loadAllRuleSummaries' fetch where
|
|
247
|
+
// possible, but we need the full text for grep — so we do a second fetch
|
|
248
|
+
// layer here, cached by path.
|
|
249
|
+
const matches = [];
|
|
250
|
+
for (const s of summaries) {
|
|
251
|
+
const cacheKey = `raw:${s.path}`;
|
|
252
|
+
let raw = cacheGet(cacheKey);
|
|
253
|
+
if (!raw) {
|
|
254
|
+
try {
|
|
255
|
+
raw = await httpsGet(`https://raw.githubusercontent.com/${ATR_REPO_OWNER}/${ATR_REPO_NAME}/${ATR_REPO_BRANCH}/${s.path}`);
|
|
256
|
+
cacheSet(cacheKey, raw);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const lower = raw.toLowerCase();
|
|
263
|
+
const hit = normalized.find((kw) => lower.includes(kw));
|
|
264
|
+
if (hit) {
|
|
265
|
+
// Extract a 120-char preview around the match for Claude to see context
|
|
266
|
+
const idx = lower.indexOf(hit);
|
|
267
|
+
const start = Math.max(0, idx - 40);
|
|
268
|
+
const end = Math.min(raw.length, idx + hit.length + 80);
|
|
269
|
+
const preview = raw.slice(start, end).replace(/\s+/g, ' ').trim();
|
|
270
|
+
matches.push({ ...s, previewMatch: `...${preview}...` });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
total: summaries.length,
|
|
275
|
+
matches,
|
|
276
|
+
note: matches.length === 0
|
|
277
|
+
? `No existing rule matched the keywords. This attack class appears to be uncovered — proceed with drafting.`
|
|
278
|
+
: `Found ${matches.length} rules mentioning one or more keywords. Inspect them with read_rule before drafting to avoid duplication.`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Tool 2: read_rule
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
export async function readRule(ruleId) {
|
|
285
|
+
if (!/^ATR-\d{4}-\d{5}$/.test(ruleId)) {
|
|
286
|
+
return {
|
|
287
|
+
ruleId,
|
|
288
|
+
found: false,
|
|
289
|
+
content: '',
|
|
290
|
+
note: `rule_id must match the pattern ATR-YYYY-NNNNN (got "${ruleId}")`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const summaries = await loadAllRuleSummaries();
|
|
294
|
+
const match = summaries.find((s) => s.ruleId === ruleId);
|
|
295
|
+
if (!match) {
|
|
296
|
+
return {
|
|
297
|
+
ruleId,
|
|
298
|
+
found: false,
|
|
299
|
+
content: '',
|
|
300
|
+
note: `No rule with id ${ruleId} in the current ATR corpus`,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const cacheKey = `raw:${match.path}`;
|
|
304
|
+
let raw = cacheGet(cacheKey);
|
|
305
|
+
if (!raw) {
|
|
306
|
+
try {
|
|
307
|
+
raw = await httpsGet(`https://raw.githubusercontent.com/${ATR_REPO_OWNER}/${ATR_REPO_NAME}/${ATR_REPO_BRANCH}/${match.path}`);
|
|
308
|
+
cacheSet(cacheKey, raw);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
return {
|
|
312
|
+
ruleId,
|
|
313
|
+
found: false,
|
|
314
|
+
content: '',
|
|
315
|
+
note: `Fetch error: ${err instanceof Error ? err.message : String(err)}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// Truncate very long rules to keep the LLM context bounded
|
|
320
|
+
const MAX_CHARS = 6000;
|
|
321
|
+
const content = raw.length > MAX_CHARS ? raw.slice(0, MAX_CHARS) + '\n# ... (truncated)' : raw;
|
|
322
|
+
return {
|
|
323
|
+
ruleId,
|
|
324
|
+
found: true,
|
|
325
|
+
content,
|
|
326
|
+
note: `Full rule at https://github.com/${ATR_REPO_OWNER}/${ATR_REPO_NAME}/blob/${ATR_REPO_BRANCH}/${match.path}`,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Tool 3: fetch_research
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
const RESEARCH_ALLOWLIST = new Set([
|
|
333
|
+
'invariantlabs.ai',
|
|
334
|
+
'elastic.co',
|
|
335
|
+
'snyk.io',
|
|
336
|
+
'arxiv.org',
|
|
337
|
+
'atlas.mitre.org',
|
|
338
|
+
'mitre.org',
|
|
339
|
+
'unit42.paloaltonetworks.com',
|
|
340
|
+
'genai.owasp.org',
|
|
341
|
+
'owasp.org',
|
|
342
|
+
'github.com',
|
|
343
|
+
'raw.githubusercontent.com',
|
|
344
|
+
'developer.microsoft.com',
|
|
345
|
+
'trendmicro.com',
|
|
346
|
+
'helpnetsecurity.com',
|
|
347
|
+
'agentseal.org',
|
|
348
|
+
'keysight.com',
|
|
349
|
+
'zenity.io',
|
|
350
|
+
'docker.com',
|
|
351
|
+
'stytch.com',
|
|
352
|
+
'aembit.io',
|
|
353
|
+
'datasciencedojo.com',
|
|
354
|
+
'thehackernews.com',
|
|
355
|
+
]);
|
|
356
|
+
export async function fetchResearch(url) {
|
|
357
|
+
let parsedUrl;
|
|
358
|
+
try {
|
|
359
|
+
parsedUrl = new URL(url);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
return { url, ok: false, content: '', note: `Invalid URL: ${url}` };
|
|
363
|
+
}
|
|
364
|
+
if (parsedUrl.protocol !== 'https:') {
|
|
365
|
+
return { url, ok: false, content: '', note: 'Only https:// URLs are allowed' };
|
|
366
|
+
}
|
|
367
|
+
const host = parsedUrl.hostname.replace(/^www\./, '');
|
|
368
|
+
const domainOk = RESEARCH_ALLOWLIST.has(host) ||
|
|
369
|
+
Array.from(RESEARCH_ALLOWLIST).some((d) => host.endsWith('.' + d));
|
|
370
|
+
if (!domainOk) {
|
|
371
|
+
return {
|
|
372
|
+
url,
|
|
373
|
+
ok: false,
|
|
374
|
+
content: '',
|
|
375
|
+
note: `Domain ${host} is not on the research allowlist. Allowed: ${Array.from(RESEARCH_ALLOWLIST).slice(0, 10).join(', ')}, ...`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const cacheKey = `research:${url}`;
|
|
379
|
+
const cached = cacheGet(cacheKey);
|
|
380
|
+
if (cached) {
|
|
381
|
+
return { url, ok: true, content: cached, note: '(cached)' };
|
|
382
|
+
}
|
|
383
|
+
let raw;
|
|
384
|
+
try {
|
|
385
|
+
raw = await httpsGet(url);
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
return {
|
|
389
|
+
url,
|
|
390
|
+
ok: false,
|
|
391
|
+
content: '',
|
|
392
|
+
note: `Fetch error: ${err instanceof Error ? err.message : String(err)}`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
// Strip HTML tags, collapse whitespace, decode common entities
|
|
396
|
+
const text = raw
|
|
397
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
398
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
399
|
+
.replace(/<[^>]+>/g, ' ')
|
|
400
|
+
.replace(/ /gi, ' ')
|
|
401
|
+
.replace(/&/gi, '&')
|
|
402
|
+
.replace(/</gi, '<')
|
|
403
|
+
.replace(/>/gi, '>')
|
|
404
|
+
.replace(/"/gi, '"')
|
|
405
|
+
.replace(/'/gi, "'")
|
|
406
|
+
.replace(/\s+/g, ' ')
|
|
407
|
+
.trim();
|
|
408
|
+
const MAX_RESEARCH_CHARS = 5000;
|
|
409
|
+
const truncated = text.length > MAX_RESEARCH_CHARS
|
|
410
|
+
? text.slice(0, MAX_RESEARCH_CHARS) + ' ... (truncated)'
|
|
411
|
+
: text;
|
|
412
|
+
cacheSet(cacheKey, truncated);
|
|
413
|
+
return {
|
|
414
|
+
url,
|
|
415
|
+
ok: true,
|
|
416
|
+
content: truncated,
|
|
417
|
+
note: text.length > MAX_RESEARCH_CHARS ? 'truncated to 5000 chars' : 'full',
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
export async function executeToolCall(name, input) {
|
|
421
|
+
try {
|
|
422
|
+
if (name === 'grep_existing_rules') {
|
|
423
|
+
const keywords = Array.isArray(input['keywords']) ? input['keywords'] : [];
|
|
424
|
+
const result = await grepExistingRules(keywords);
|
|
425
|
+
return { content: JSON.stringify(result), isError: false };
|
|
426
|
+
}
|
|
427
|
+
if (name === 'read_rule') {
|
|
428
|
+
const ruleId = typeof input['rule_id'] === 'string' ? input['rule_id'] : '';
|
|
429
|
+
const result = await readRule(ruleId);
|
|
430
|
+
return { content: JSON.stringify(result), isError: false };
|
|
431
|
+
}
|
|
432
|
+
if (name === 'fetch_research') {
|
|
433
|
+
const url = typeof input['url'] === 'string' ? input['url'] : '';
|
|
434
|
+
const result = await fetchResearch(url);
|
|
435
|
+
return { content: JSON.stringify(result), isError: false };
|
|
436
|
+
}
|
|
437
|
+
return { content: `Unknown tool: ${name}`, isError: true };
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
return {
|
|
441
|
+
content: `Tool ${name} threw: ${err instanceof Error ? err.message : String(err)}`,
|
|
442
|
+
isError: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=llm-reviewer-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-reviewer-tools.js","sourceRoot":"","sources":["../src/llm-reviewer-tools.ts"],"names":[],"mappings":"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"}
|
package/dist/llm-reviewer.d.ts
CHANGED
|
@@ -34,7 +34,17 @@ interface SkillAnalysisResult {
|
|
|
34
34
|
export declare class LLMReviewer {
|
|
35
35
|
private readonly apiKey;
|
|
36
36
|
private readonly db;
|
|
37
|
+
/**
|
|
38
|
+
* Primary model used throughout this class. Historically one model served
|
|
39
|
+
* both drafting and reviewing; we now split to `drafterModel` (Haiku, cheap)
|
|
40
|
+
* and `reviewerModel` (Sonnet, quality-critical). `this.model` retains the
|
|
41
|
+
* reviewer value for backward compat with existing `reviewProposal` callers.
|
|
42
|
+
*/
|
|
37
43
|
private readonly model;
|
|
44
|
+
/** Model used for rule drafting (Haiku by default — 4x cheaper than Sonnet). */
|
|
45
|
+
private readonly drafterModel;
|
|
46
|
+
/** Model used for second-opinion review (Sonnet by default). */
|
|
47
|
+
private readonly reviewerModel;
|
|
38
48
|
constructor(apiKey: string, db: ThreatCloudDB, model?: string);
|
|
39
49
|
/** Check if the reviewer is available (API key is set) / 檢查審查器是否可用 */
|
|
40
50
|
isAvailable(): boolean;
|
|
@@ -56,6 +66,25 @@ export declare class LLMReviewer {
|
|
|
56
66
|
* 透過 node:https 呼叫 Anthropic API
|
|
57
67
|
*/
|
|
58
68
|
private callAnthropicAPI;
|
|
69
|
+
/**
|
|
70
|
+
* Low-level Anthropic messages call that accepts a prepared request body.
|
|
71
|
+
* Used by the tool-use loop so we can pass full message histories.
|
|
72
|
+
*/
|
|
73
|
+
private callAnthropicRaw;
|
|
74
|
+
/**
|
|
75
|
+
* Tool-use loop for TC v2 drafter. Runs a multi-turn conversation with
|
|
76
|
+
* Claude where it can call grep_existing_rules, read_rule, and
|
|
77
|
+
* fetch_research to ground its draft in existing ATR coverage and
|
|
78
|
+
* public threat research before emitting a rule YAML.
|
|
79
|
+
*
|
|
80
|
+
* Returns the concatenated text of Claude's final assistant turn (the
|
|
81
|
+
* message where stop_reason is "end_turn" and not "tool_use").
|
|
82
|
+
*
|
|
83
|
+
* Max 6 tool-use rounds per skill to bound latency and cost; if Claude
|
|
84
|
+
* still wants to use tools on round 7, we instruct it to finalize.
|
|
85
|
+
*/
|
|
86
|
+
private callAnthropicWithTools;
|
|
87
|
+
/** Prompt for skill/tool analysis (both MCP and SKILL.md) */
|
|
59
88
|
private static readonly ATR_DRAFTER_PROMPT;
|
|
60
89
|
/**
|
|
61
90
|
* Analyze skill scan results for semantic threats regex missed
|
|
@@ -65,6 +94,31 @@ export declare class LLMReviewer {
|
|
|
65
94
|
package: string;
|
|
66
95
|
tools: ToolDescription[];
|
|
67
96
|
}>): Promise<SkillAnalysisResult[]>;
|
|
97
|
+
/**
|
|
98
|
+
* Draft a full ATR YAML rule from a raw attack payload supplied by an
|
|
99
|
+
* external red-team source (e.g. NVIDIA garak). Same drafter pipeline as
|
|
100
|
+
* analyzeSkills but the input is the attack prompt itself rather than a
|
|
101
|
+
* skill's tool descriptions. Returns the drafted proposal or null when
|
|
102
|
+
* the drafter declined to write a rule (NO_THREATS_FOUND, duplicate of
|
|
103
|
+
* existing coverage, failed quality gate, failed self-test).
|
|
104
|
+
*
|
|
105
|
+
* Callers:
|
|
106
|
+
* - POST /api/atr-proposals/from-payload (partner / admin auth)
|
|
107
|
+
*
|
|
108
|
+
* Always inserts the resulting proposal into atr_proposals so the normal
|
|
109
|
+
* canary → auto-merge → npm publish pipeline can take over.
|
|
110
|
+
*/
|
|
111
|
+
draftRuleFromPayload(payload: string, meta: {
|
|
112
|
+
probe?: string;
|
|
113
|
+
detector?: string;
|
|
114
|
+
targetModel?: string;
|
|
115
|
+
partnerName?: string;
|
|
116
|
+
severity?: string;
|
|
117
|
+
}): Promise<{
|
|
118
|
+
patternHash: string;
|
|
119
|
+
ruleContent: string;
|
|
120
|
+
toolCalls: number;
|
|
121
|
+
} | null>;
|
|
68
122
|
/**
|
|
69
123
|
* Parse the LLM response into a structured verdict
|
|
70
124
|
* 解析 LLM 回應為結構化裁決
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"llm-reviewer.d.ts","sourceRoot":"","sources":["../src/llm-reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"llm-reviewer.d.ts","sourceRoot":"","sources":["../src/llm-reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AA8HnD,yCAAyC;AACzC,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,8CAA8C;AAC9C,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,yEAAyE;IACzE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsDD;;;GAGG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;IACnC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAE3B,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM;IAU7D,sEAAsE;IACtE,WAAW,IAAI,OAAO;IAItB;;;OAGG;IACG,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IAsDlD;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAsCzB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA6ExB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA+DxB;;;;;;;;;;;OAWG;YACW,sBAAsB;IAsFpC,6DAA6D;IAC7D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAgMkC;IAE5E;;;OAGG;IACG,aAAa,CACjB,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,eAAe,EAAE,CAAA;KAAE,CAAC,GAC3D,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAgMjC;;;;;;;;;;;;;OAaG;IACG,oBAAoB,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GACA,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAyNlF;;;OAGG;IACH,OAAO,CAAC,YAAY;CA0CrB"}
|