@zanii/blackbox 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/faults.js +11 -0
- package/dist/analysis/grounding.d.ts +6 -0
- package/dist/analysis/grounding.js +26 -0
- package/dist/analysis/index.d.ts +6 -0
- package/dist/analysis/index.js +39 -0
- package/dist/analysis/injection.d.ts +22 -0
- package/dist/analysis/injection.js +173 -0
- package/dist/analysis/taxonomy.js +3 -0
- package/dist/fleet/insights.d.ts +5 -2
- package/dist/fleet/insights.js +27 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/reconcile/record.d.ts +2 -0
- package/dist/reconcile/record.js +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/analysis/faults.js
CHANGED
|
@@ -232,6 +232,17 @@ export const FAULTS = {
|
|
|
232
232
|
en: "A tool's definition changed mid-session",
|
|
233
233
|
ar: "تغيّر تعريف أداة أثناء الجلسة",
|
|
234
234
|
},
|
|
235
|
+
PROMPT_INJECTION: {
|
|
236
|
+
fault: "BBX-7403",
|
|
237
|
+
squawk: "HIJACK",
|
|
238
|
+
en: "Prompt injection reached the model",
|
|
239
|
+
ar: "وصلت محاولة حقن تعليمات إلى النموذج",
|
|
240
|
+
},
|
|
241
|
+
TOOL_DRIFTED: {
|
|
242
|
+
fault: "BBX-7404",
|
|
243
|
+
en: "A tool's definition differs from earlier sessions",
|
|
244
|
+
ar: "يختلف تعريف أداة عمّا رأته الجلسات السابقة",
|
|
245
|
+
},
|
|
235
246
|
REVOKED_MEMORY_READ: {
|
|
236
247
|
fault: "BBX-7101",
|
|
237
248
|
en: "A revoked memory was used",
|
|
@@ -45,6 +45,12 @@ export declare class Evidence {
|
|
|
45
45
|
private pairSums;
|
|
46
46
|
}
|
|
47
47
|
/** The final answers on a record, each with the evidence it should rest on. */
|
|
48
|
+
/**
|
|
49
|
+
* spec/findings.md §9: what the answer's own web search returned (Anthropic's server tool): each
|
|
50
|
+
* result's title, and each citation's quoted text and title. The provider supplies these from the
|
|
51
|
+
* pages, not the model, so a fact they hold is backed. Never the answer's own words.
|
|
52
|
+
*/
|
|
53
|
+
export declare function webEvidence(c: Call): string;
|
|
48
54
|
export declare function answersOf(lines: readonly string[], bodies: Bodies): Array<{
|
|
49
55
|
seq: number;
|
|
50
56
|
text: string;
|
|
@@ -460,6 +460,30 @@ export class Evidence {
|
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
/** The final answers on a record, each with the evidence it should rest on. */
|
|
463
|
+
/**
|
|
464
|
+
* spec/findings.md §9: what the answer's own web search returned (Anthropic's server tool): each
|
|
465
|
+
* result's title, and each citation's quoted text and title. The provider supplies these from the
|
|
466
|
+
* pages, not the model, so a fact they hold is backed. Never the answer's own words.
|
|
467
|
+
*/
|
|
468
|
+
export function webEvidence(c) {
|
|
469
|
+
const parts = [];
|
|
470
|
+
const add = (v) => {
|
|
471
|
+
if (typeof v === "string" && v)
|
|
472
|
+
parts.push(v);
|
|
473
|
+
};
|
|
474
|
+
for (const [, b] of [...c.blocks].sort(([x], [y]) => x - y)) {
|
|
475
|
+
const block = b;
|
|
476
|
+
if (block.type === "web_search_tool_result" && Array.isArray(block.content))
|
|
477
|
+
for (const r of block.content)
|
|
478
|
+
add(r?.title);
|
|
479
|
+
if (Array.isArray(block.citations))
|
|
480
|
+
for (const cit of block.citations) {
|
|
481
|
+
add(cit?.cited_text);
|
|
482
|
+
add(cit?.title);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return parts.join("\n");
|
|
486
|
+
}
|
|
463
487
|
export function answersOf(lines, bodies) {
|
|
464
488
|
const calls = callsOf(lines, bodies);
|
|
465
489
|
const ends = new Set(toolCallsOf(calls).map((t) => t.seq));
|
|
@@ -480,9 +504,11 @@ export function answersOf(lines, bodies) {
|
|
|
480
504
|
const text = answerText(c);
|
|
481
505
|
if (!text)
|
|
482
506
|
continue;
|
|
507
|
+
const web = webEvidence(c);
|
|
483
508
|
const pieces = [
|
|
484
509
|
{ seq: c.requestSeq, text: canonical(c.request) },
|
|
485
510
|
...results.filter((r) => r.seq < c.endSeq),
|
|
511
|
+
...(web ? [{ seq: c.endSeq, text: web }] : []),
|
|
486
512
|
];
|
|
487
513
|
out.push({
|
|
488
514
|
seq: c.endSeq,
|
package/dist/analysis/index.d.ts
CHANGED
|
@@ -82,6 +82,12 @@ export declare function exitDurability(events: Ev[]): Finding[];
|
|
|
82
82
|
* is the reference; a different one later is a finding, and becomes the new reference.
|
|
83
83
|
*/
|
|
84
84
|
export declare function toolChanged(events: Ev[]): Finding[];
|
|
85
|
+
/**
|
|
86
|
+
* spec/findings.md §1a: a tool's definition differs from the one this tenant's earlier sessions saw
|
|
87
|
+
* (a rug pull between sessions). The gateway puts the previous hashes next to a `tools/list` answer
|
|
88
|
+
* (`meta.tool_defs_prev`); a tool listed with another hash is a finding, once per server and tool.
|
|
89
|
+
*/
|
|
90
|
+
export declare function toolDrifted(events: Ev[]): Finding[];
|
|
85
91
|
/** N6 (idea S3): the gateway refused a request that carried the session's own token. */
|
|
86
92
|
export declare function tokenMisuse(events: Ev[]): Finding[];
|
|
87
93
|
/** Stage 2 R3: a `verify` check that passed, then failed later in the run. */
|
package/dist/analysis/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { wallFindings } from "../walls/index.js";
|
|
|
14
14
|
import { detectors } from "./detectors.js";
|
|
15
15
|
import { groundingFindings, judgeFindings } from "./grounding.js";
|
|
16
16
|
import { toolHallucinations } from "./hallucination.js";
|
|
17
|
+
import { injectionFindings } from "./injection.js";
|
|
17
18
|
import { flightPlan } from "./landing.js";
|
|
18
19
|
import { memoryFindings } from "./memory.js";
|
|
19
20
|
export { detectorAccuracy, wilsonLow } from "./accuracy.js";
|
|
@@ -56,6 +57,8 @@ export function analyze(lines, options) {
|
|
|
56
57
|
...orphanedToolCalls(events, now),
|
|
57
58
|
...tokenMisuse(events),
|
|
58
59
|
...toolChanged(events),
|
|
60
|
+
...toolDrifted(events),
|
|
61
|
+
...injectionFindings(lines, options.bodies),
|
|
59
62
|
...verifyRegression(events),
|
|
60
63
|
...falseSuccess(events, now),
|
|
61
64
|
...toolHallucinations(lines, options.bodies),
|
|
@@ -401,6 +404,42 @@ export function toolChanged(events) {
|
|
|
401
404
|
}
|
|
402
405
|
return out;
|
|
403
406
|
}
|
|
407
|
+
/**
|
|
408
|
+
* spec/findings.md §1a: a tool's definition differs from the one this tenant's earlier sessions saw
|
|
409
|
+
* (a rug pull between sessions). The gateway puts the previous hashes next to a `tools/list` answer
|
|
410
|
+
* (`meta.tool_defs_prev`); a tool listed with another hash is a finding, once per server and tool.
|
|
411
|
+
*/
|
|
412
|
+
export function toolDrifted(events) {
|
|
413
|
+
const out = [];
|
|
414
|
+
const done = new Set();
|
|
415
|
+
for (const e of events) {
|
|
416
|
+
const { server, tool_defs: defs, tool_defs_prev: prev } = e.meta;
|
|
417
|
+
if (e.kind !== "tool.result" || typeof server !== "string")
|
|
418
|
+
continue;
|
|
419
|
+
if (!defs || typeof defs !== "object" || Array.isArray(defs))
|
|
420
|
+
continue;
|
|
421
|
+
if (!prev || typeof prev !== "object" || Array.isArray(prev))
|
|
422
|
+
continue;
|
|
423
|
+
for (const tool of Object.keys(defs).sort()) {
|
|
424
|
+
const after = defs[tool];
|
|
425
|
+
const before = prev[tool];
|
|
426
|
+
const key = `${server}\u0000${tool}`;
|
|
427
|
+
if (typeof after !== "string" || typeof before !== "string" || before === after)
|
|
428
|
+
continue;
|
|
429
|
+
if (done.has(key))
|
|
430
|
+
continue;
|
|
431
|
+
done.add(key);
|
|
432
|
+
out.push({
|
|
433
|
+
code: "TOOL_DRIFTED",
|
|
434
|
+
source: "detectors",
|
|
435
|
+
severity: "warning",
|
|
436
|
+
ref: { seq: e.seq, server, tool, before, after },
|
|
437
|
+
detail: "A tool's description, schema or annotations differ from what this tenant's earlier sessions saw: the tool was changed between runs.",
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
404
443
|
/** N6 (idea S3): the gateway refused a request that carried the session's own token. */
|
|
405
444
|
export function tokenMisuse(events) {
|
|
406
445
|
return events
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** The rules, in the order they're tried. Patterns run on ASCII-lowercased, space-folded text. */
|
|
2
|
+
export declare const INJECTION_RULES: ReadonlyArray<{
|
|
3
|
+
id: string;
|
|
4
|
+
re: RegExp;
|
|
5
|
+
}>;
|
|
6
|
+
/** Text no reader sees: Unicode tag characters, bidi overrides, a run of 4+ zero-width characters. */
|
|
7
|
+
export declare function hiddenText(text: string): boolean;
|
|
8
|
+
/** The first rule a text breaks, or null. */
|
|
9
|
+
export declare function injectionRule(text: string): string | null;
|
|
10
|
+
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record. */
|
|
11
|
+
export declare function injectionFindings(lines: readonly string[], bodies: (hash: string) => Uint8Array | undefined): {
|
|
12
|
+
code: string;
|
|
13
|
+
source: string;
|
|
14
|
+
severity: "warning";
|
|
15
|
+
ref: {
|
|
16
|
+
seq: number;
|
|
17
|
+
source: string;
|
|
18
|
+
rule: string;
|
|
19
|
+
tool?: string;
|
|
20
|
+
};
|
|
21
|
+
detail: string;
|
|
22
|
+
}[];
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// spec/findings.md §1b: prompt injection (PROMPT_INJECTION, squawk HIJACK). What reaches the model
|
|
2
|
+
// from outside the agent's instructions is scanned: each MCP tool result, and the newest turn of
|
|
3
|
+
// each model request (the tool results and the person's words it carries). Instruction overrides,
|
|
4
|
+
// role hijacks, exfiltration asks and hidden text, in English and Arabic. One finding per event and
|
|
5
|
+
// source; the finding names the rule, never the text. Mirrors analysis/injection.py; pinned by
|
|
6
|
+
// spec/vectors/injection.json.
|
|
7
|
+
const decoder = new TextDecoder();
|
|
8
|
+
const MAX_TEXT = 200_000; // characters scanned per event: an injection is near the start or not worth the cost
|
|
9
|
+
/** The rules, in the order they're tried. Patterns run on ASCII-lowercased, space-folded text. */
|
|
10
|
+
export const INJECTION_RULES = [
|
|
11
|
+
{
|
|
12
|
+
id: "override",
|
|
13
|
+
re: /\b(ignore|disregard|forget|override|bypass)( all| any| the| your| these| those| my)* (previous|prior|above|earlier|preceding|system|original|safety)( \w+)? (instructions|prompts?|rules|directions|guidelines|messages|policies)\b/,
|
|
14
|
+
},
|
|
15
|
+
{ id: "override", re: /(تجاهل|انس|انسى|تخط) (جميع |كل )?(التعليمات|الأوامر|القواعد|ما سبق)/ },
|
|
16
|
+
{
|
|
17
|
+
id: "role",
|
|
18
|
+
re: /(\byou are now\b|\bfrom now on,? you are\b|\bact as\b) (dan|an? unrestricted|a jailbroken|in developer mode)\b|<\|im_start\|>|<\|system\|>|\[system\]|### (system|new instructions)|\bnew instructions:/,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "exfiltrate",
|
|
22
|
+
re: /\b(send|post|upload|forward|email|exfiltrate|leak|paste)\b.{0,40}\b(system prompt|api keys?|passwords?|credentials|secrets?|conversation history|chat history|access tokens?|private keys?)\b/,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "exfiltrate",
|
|
26
|
+
re: /\b(reveal|print|repeat|show|output) (me )?(your|the) (system prompt|hidden instructions|initial instructions|original instructions)\b/,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "exfiltrate",
|
|
30
|
+
re: /(أرسل|ارسل|أفصح عن|اكشف) .{0,40}(كلمة المرور|كلمات المرور|مفتاح|بيانات الدخول|التعليمات المخفية)/,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
/** Text no reader sees: Unicode tag characters, bidi overrides, a run of 4+ zero-width characters. */
|
|
34
|
+
export function hiddenText(text) {
|
|
35
|
+
let zw = 0;
|
|
36
|
+
for (const ch of text) {
|
|
37
|
+
const c = ch.codePointAt(0);
|
|
38
|
+
if (c >= 0xe0000 && c <= 0xe007f)
|
|
39
|
+
return true;
|
|
40
|
+
if ((c >= 0x202a && c <= 0x202e) || (c >= 0x2066 && c <= 0x2069))
|
|
41
|
+
return true;
|
|
42
|
+
if (c === 0x200b || c === 0x200c || c === 0x200d || c === 0x2060 || c === 0xfeff) {
|
|
43
|
+
zw += 1;
|
|
44
|
+
if (zw >= 4)
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
zw = 0;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/** ASCII lowercase; spaces, tabs, newlines and line separators to one space: the same in every language. */
|
|
53
|
+
const fold = (s) => s.replace(/[A-Z]/g, (c) => c.toLowerCase()).replace(/[ \t\r\n\u2028\u2029]+/g, " ");
|
|
54
|
+
/** The first rule a text breaks, or null. */
|
|
55
|
+
export function injectionRule(text) {
|
|
56
|
+
const t = Array.from(text).slice(0, MAX_TEXT).join("");
|
|
57
|
+
if (hiddenText(t))
|
|
58
|
+
return "hidden";
|
|
59
|
+
const f = fold(t);
|
|
60
|
+
for (const r of INJECTION_RULES)
|
|
61
|
+
if (r.re.test(f))
|
|
62
|
+
return r.id;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/** Every string in a JSON value, depth first, in key order. */
|
|
66
|
+
function strings(v, out) {
|
|
67
|
+
if (typeof v === "string")
|
|
68
|
+
out.push(v);
|
|
69
|
+
else if (Array.isArray(v))
|
|
70
|
+
for (const x of v)
|
|
71
|
+
strings(x, out);
|
|
72
|
+
else if (v && typeof v === "object")
|
|
73
|
+
for (const x of Object.values(v))
|
|
74
|
+
strings(x, out);
|
|
75
|
+
}
|
|
76
|
+
const textOf = (v) => {
|
|
77
|
+
const out = [];
|
|
78
|
+
strings(v, out);
|
|
79
|
+
return out.join("\n");
|
|
80
|
+
};
|
|
81
|
+
/** The newest turn of a model request: what it adds to the conversation. */
|
|
82
|
+
function newestTurn(body) {
|
|
83
|
+
const out = [];
|
|
84
|
+
const messages = body.messages;
|
|
85
|
+
if (Array.isArray(messages) && messages.length) {
|
|
86
|
+
// Chat Completions: the trailing tool messages, and the last message itself
|
|
87
|
+
let i = messages.length - 1;
|
|
88
|
+
while (i > 0 && messages[i].role === "tool")
|
|
89
|
+
i -= 1;
|
|
90
|
+
for (const m of messages.slice(i)) {
|
|
91
|
+
if (m.role === "tool")
|
|
92
|
+
out.push({ source: "tool_result", text: textOf(m.content) });
|
|
93
|
+
else if (m.role === "user") {
|
|
94
|
+
// Anthropic: a user turn holds the tool results and the person's words
|
|
95
|
+
const blocks = Array.isArray(m.content) ? m.content : [m.content];
|
|
96
|
+
const results = blocks.filter((b) => b?.type === "tool_result");
|
|
97
|
+
const words = blocks.filter((b) => b?.type !== "tool_result");
|
|
98
|
+
if (results.length)
|
|
99
|
+
out.push({ source: "tool_result", text: textOf(results) });
|
|
100
|
+
if (words.length)
|
|
101
|
+
out.push({ source: "user", text: textOf(words) });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
const input = body.input;
|
|
107
|
+
if (typeof input === "string")
|
|
108
|
+
return [{ source: "user", text: input }];
|
|
109
|
+
if (Array.isArray(input) && input.length) {
|
|
110
|
+
// Responses: the trailing function_call_output items, then the last item
|
|
111
|
+
let i = input.length - 1;
|
|
112
|
+
while (i > 0 && input[i].type === "function_call_output")
|
|
113
|
+
i -= 1;
|
|
114
|
+
for (const item of input.slice(i)) {
|
|
115
|
+
if (item.type === "function_call_output")
|
|
116
|
+
out.push({ source: "tool_result", text: textOf(item.output) });
|
|
117
|
+
else if (item.role === "user")
|
|
118
|
+
out.push({ source: "user", text: textOf(item.content) });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
/** spec/findings.md §1b: PROMPT_INJECTION findings over a record. */
|
|
124
|
+
export function injectionFindings(lines, bodies) {
|
|
125
|
+
const out = [];
|
|
126
|
+
const add = (seq, source, rule, tool) => out.push({
|
|
127
|
+
code: "PROMPT_INJECTION",
|
|
128
|
+
source: "detectors",
|
|
129
|
+
severity: "warning",
|
|
130
|
+
ref: { seq, source, rule, ...(tool !== undefined ? { tool } : {}) },
|
|
131
|
+
detail: rule === "hidden"
|
|
132
|
+
? "Text the reader can't see (hidden characters) reached the model: a classic way to smuggle instructions."
|
|
133
|
+
: "Text that reached the model tries to give it new instructions: an override, a role change, or a request to send out secrets.",
|
|
134
|
+
});
|
|
135
|
+
for (const l of lines) {
|
|
136
|
+
const e = JSON.parse(l);
|
|
137
|
+
if (e.kind !== "tool.result" && e.kind !== "llm.request")
|
|
138
|
+
continue;
|
|
139
|
+
const raw = bodies(e.body_hash);
|
|
140
|
+
if (!raw || raw.length === 0)
|
|
141
|
+
continue;
|
|
142
|
+
let doc;
|
|
143
|
+
try {
|
|
144
|
+
doc = JSON.parse(decoder.decode(raw));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (e.kind === "tool.result") {
|
|
150
|
+
const result = doc?.result;
|
|
151
|
+
if (result === undefined)
|
|
152
|
+
continue;
|
|
153
|
+
const rule = injectionRule(textOf(result));
|
|
154
|
+
const tool = typeof e.meta.tool === "string" ? e.meta.tool : undefined;
|
|
155
|
+
if (rule)
|
|
156
|
+
add(e.seq, "tool_result", rule, tool);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc))
|
|
160
|
+
continue;
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
for (const part of newestTurn(doc)) {
|
|
163
|
+
if (seen.has(part.source))
|
|
164
|
+
continue;
|
|
165
|
+
const rule = injectionRule(part.text);
|
|
166
|
+
if (rule) {
|
|
167
|
+
seen.add(part.source);
|
|
168
|
+
add(e.seq, part.source, rule);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
@@ -18,6 +18,7 @@ export const MITRE_ATLAS = {
|
|
|
18
18
|
"AML.T0034.000": "Cost Harvesting: Excessive Queries",
|
|
19
19
|
"AML.T0034.001": "Cost Harvesting: Resource-Intensive Queries",
|
|
20
20
|
"AML.T0034.002": "Cost Harvesting: Agentic Resource Consumption",
|
|
21
|
+
"AML.T0051": "LLM Prompt Injection",
|
|
21
22
|
"AML.T0053": "AI Agent Tool Invocation",
|
|
22
23
|
"AML.T0057": "LLM Data Leakage",
|
|
23
24
|
"AML.T0080.000": "AI Agent Context Poisoning: Memory",
|
|
@@ -53,6 +54,8 @@ export const TAXONOMY = {
|
|
|
53
54
|
STERILE_VIOLATION: { owasp: ["ASI02", "ASI10"], atlas: ["AML.T0053"] },
|
|
54
55
|
TOKEN_MISUSE: { owasp: ["ASI03"], atlas: ["AML.T0091.000", "AML.T0086"] },
|
|
55
56
|
TOOL_CHANGED: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
|
|
57
|
+
TOOL_DRIFTED: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
|
|
58
|
+
PROMPT_INJECTION: { owasp: ["ASI01"], atlas: ["AML.T0051"] },
|
|
56
59
|
TOOL_OUTPUT_MISMATCH: { owasp: ["ASI04", "ASI06"], atlas: ["AML.T0110.002", "AML.T0099"] },
|
|
57
60
|
TOOL_WITNESS_INVALID: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
|
|
58
61
|
};
|
package/dist/fleet/insights.d.ts
CHANGED
|
@@ -64,8 +64,11 @@ export declare function insights(summaries: readonly SummaryIn[], window: {
|
|
|
64
64
|
from: string;
|
|
65
65
|
to: string;
|
|
66
66
|
}): Insights;
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* spec/insights.md §3: the record as one line per event, metadata only, at most 600 events. With
|
|
69
|
+
* `body` (a body by its hash), a verify event also says `ok=true` or `ok=false`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function copilotContext(lines: readonly string[], body?: (hash: string) => Uint8Array | null | undefined): string;
|
|
69
72
|
/** spec/insights.md §3: the system prompt and the user turn for the investigator's model. */
|
|
70
73
|
export declare function copilotPrompt(question: string, context: string, lang?: string): {
|
|
71
74
|
system: string;
|
package/dist/fleet/insights.js
CHANGED
|
@@ -195,8 +195,28 @@ function evenly(items, n) {
|
|
|
195
195
|
return [...items];
|
|
196
196
|
return Array.from({ length: n }, (_, i) => items[Math.floor((i * items.length) / n)]);
|
|
197
197
|
}
|
|
198
|
-
/**
|
|
199
|
-
|
|
198
|
+
/** A verify event's pass or fail, from its body: the one value read from a body. Never `detail`. */
|
|
199
|
+
function verifyOk(e, body) {
|
|
200
|
+
const meta = (e.meta ?? {});
|
|
201
|
+
if (!body || e.kind !== "sdk.event" || meta.type !== "verify")
|
|
202
|
+
return null;
|
|
203
|
+
const raw = body(String(e.body_hash));
|
|
204
|
+
if (!raw || raw.length === 0)
|
|
205
|
+
return null; // erased, or never sent
|
|
206
|
+
try {
|
|
207
|
+
const doc = JSON.parse(new TextDecoder().decode(raw));
|
|
208
|
+
const ok = doc && typeof doc === "object" && !Array.isArray(doc) ? doc.ok : null;
|
|
209
|
+
return typeof ok === "boolean" ? ok : null;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* spec/insights.md §3: the record as one line per event, metadata only, at most 600 events. With
|
|
217
|
+
* `body` (a body by its hash), a verify event also says `ok=true` or `ok=false`.
|
|
218
|
+
*/
|
|
219
|
+
export function copilotContext(lines, body) {
|
|
200
220
|
const events = lines.map((l) => JSON.parse(l));
|
|
201
221
|
const n = events.length;
|
|
202
222
|
let shown;
|
|
@@ -241,12 +261,16 @@ export function copilotContext(lines) {
|
|
|
241
261
|
if (v !== null)
|
|
242
262
|
parts.push(`${k}=${v}`);
|
|
243
263
|
}
|
|
264
|
+
const ok = verifyOk(e, body);
|
|
265
|
+
if (ok !== null)
|
|
266
|
+
parts.push(`ok=${ok}`);
|
|
244
267
|
out.push(parts.join(" "));
|
|
245
268
|
}
|
|
246
269
|
return out.join("\n");
|
|
247
270
|
}
|
|
248
271
|
const SYSTEM = "You are an investigator reading one AI agent's flight record from Zanii Blackbox. The record " +
|
|
249
|
-
"below is one line per event: #seq, time, kind, then its metadata
|
|
272
|
+
"below is one line per event: #seq, time, kind, then its metadata; ok=true or ok=false on a " +
|
|
273
|
+
"verify event is the result of the agent's own check. Answer the question only " +
|
|
250
274
|
"from the record. Cite the event numbers your answer rests on. If the record doesn't say, say " +
|
|
251
275
|
"so. Be brief and concrete. Answer with JSON only: " +
|
|
252
276
|
'{"answer": "<your answer>", "cites": [<seq>, ...]}';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { A2A_STREAMING, type AgentCardReport, a2aMethod, a2aRequestMeta, a2aResp
|
|
|
2
2
|
export { causality, memoryXray } from "./agents/index.ts";
|
|
3
3
|
export { type DetectorOptions, detectors, fingerprint } from "./analysis/detectors.ts";
|
|
4
4
|
export { FAULTS, type Fault, faultOf } from "./analysis/faults.ts";
|
|
5
|
-
export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, toolHallucinations, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
|
|
5
|
+
export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, toolDrifted, toolHallucinations, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
|
|
6
|
+
export { injectionFindings, injectionRule } from "./analysis/injection.ts";
|
|
6
7
|
export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, type Taxonomy, taxonomyOf, } from "./analysis/taxonomy.ts";
|
|
7
8
|
export { type ApprovalFinding, approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.ts";
|
|
8
9
|
export { textWarnings } from "./approvals/warnings.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@ export { A2A_STREAMING, a2aMethod, a2aRequestMeta, a2aResponseMeta, a2aRestMetho
|
|
|
4
4
|
export { causality, memoryXray } from "./agents/index.js";
|
|
5
5
|
export { detectors, fingerprint } from "./analysis/detectors.js";
|
|
6
6
|
export { FAULTS, faultOf } from "./analysis/faults.js";
|
|
7
|
-
export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, referenceContext, rollup, schemaProblem, toolHallucinations, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
|
|
7
|
+
export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, referenceContext, rollup, schemaProblem, toolDrifted, toolHallucinations, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
|
|
8
|
+
export { injectionFindings, injectionRule } from "./analysis/injection.js";
|
|
8
9
|
export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, taxonomyOf, } from "./analysis/taxonomy.js";
|
|
9
10
|
export { approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.js";
|
|
10
11
|
export { textWarnings } from "./approvals/warnings.js";
|
package/dist/reconcile/record.js
CHANGED
|
@@ -182,6 +182,10 @@ function rebuild(call, body) {
|
|
|
182
182
|
block.thinking = (block.thinking ?? "") + d.thinking;
|
|
183
183
|
else if (d.type === "input_json_delta" && typeof d.partial_json === "string")
|
|
184
184
|
partial.set(event.index, (partial.get(event.index) ?? "") + d.partial_json);
|
|
185
|
+
else if (d.type === "citations_delta" && isObj(d.citation)) {
|
|
186
|
+
block.citations ??= [];
|
|
187
|
+
block.citations.push(d.citation);
|
|
188
|
+
}
|
|
185
189
|
}
|
|
186
190
|
else if (type === "content_block_stop" && typeof event.index === "number") {
|
|
187
191
|
const block = call.blocks.get(event.index);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.17.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// The SDK version, on its own so the CLI can print it without loading the whole SDK.
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.17.0";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanii/blackbox",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0",
|
|
5
5
|
"description": "The flight recorder for AI agents: sessions, a zero-loss spool, framework hooks, approvals, and offline verification of the gateway's hash-chained record.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai-agents",
|