@theoremjs/agents 0.3.4-next.464.g5fc5eed → 0.3.4-next.468.ge2da06f
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/esm/src/guardrails/detect-at.js +3 -3
- package/esm/src/guardrails/egress.d.ts +1 -1
- package/esm/src/guardrails/egress.js +12 -9
- package/esm/src/guardrails/hints.js +5 -2
- package/esm/src/guardrails/hits.d.ts +3 -3
- package/esm/src/guardrails/hits.js +5 -3
- package/esm/src/guardrails/thought-guard.js +2 -1
- package/esm/src/kernel/engine/runner/gates.js +2 -0
- package/esm/src/kernel/types.d.ts +1 -1
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { actionAt, DETECTORS, detects, } from './detectors.js';
|
|
|
7
7
|
import { boundaryNote } from './egress.js';
|
|
8
8
|
import { notePattern, SYSTEM_BOUNDARY } from './egress-patterns.js';
|
|
9
9
|
import { imageLeakSpans, linkLeakSpans } from './egress-urls.js';
|
|
10
|
-
import { CANARY_HIT, hitFromSpan,
|
|
10
|
+
import { CANARY_HIT, hitFromSpan, promptEchoHit } from './hits.js';
|
|
11
11
|
import { injectionSpans } from './injection.js';
|
|
12
12
|
import { promptEchoRanges } from './prompt-echo.js';
|
|
13
13
|
import { DETECT_RULES, detectRule } from './rules.js';
|
|
@@ -122,13 +122,13 @@ function theoremSpans(detector, text, scope, skipImages = false) {
|
|
|
122
122
|
}
|
|
123
123
|
return sensitiveSpans(text, ONLY[detector]);
|
|
124
124
|
}
|
|
125
|
-
/** The hit for a match of `detector`.
|
|
125
|
+
/** The hit for a match of `detector`. A canary leak never carries the token. */
|
|
126
126
|
function hitOf(detector, text, span) {
|
|
127
127
|
const { start, end } = span;
|
|
128
128
|
if (detector === 'canary_leak')
|
|
129
129
|
return { ...CANARY_HIT, span: { start, end } };
|
|
130
130
|
if (detector === 'prompt_leak')
|
|
131
|
-
return
|
|
131
|
+
return promptEchoHit(text, start, end);
|
|
132
132
|
if (detector === 'tool_instructions' && span.signal) {
|
|
133
133
|
const severity = span.signal === 'order' || span.signal === 'authority' ? 'medium' : 'high';
|
|
134
134
|
return { ...hitFromSpan(text, span, DETECT_RULES[detector], severity), signal: span.signal };
|
|
@@ -8,7 +8,7 @@ declare const WITHHELD_REASON: {
|
|
|
8
8
|
readonly providerToolLeak: "a provider-side tool already sent the canary or system prompt";
|
|
9
9
|
readonly egress: "Turn withheld: egress disclosure violation";
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
11
|
+
/** A hit for each stretch of `text` that repeats the guarded system prompt's own words. */
|
|
12
12
|
declare function promptEchoHits(text: string, privateSystem?: readonly string[], canary?: string): GuardrailHit[];
|
|
13
13
|
/** What a prompt-leak check reads of the turn. */
|
|
14
14
|
type LeakScope = Pick<GuardrailContext, 'canary' | 'canaryGiven' | 'privateSystem'>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { canaryNoteMarker, guardedEventTexts, scanTextForCanaryLeak } from './canary.js';
|
|
2
2
|
import { SYSTEM_BOUNDARY } from './egress-patterns.js';
|
|
3
|
-
import { CANARY_HIT,
|
|
4
|
-
import {
|
|
3
|
+
import { CANARY_HIT, promptEchoHit } from './hits.js';
|
|
4
|
+
import { promptEchoRanges } from './prompt-echo.js';
|
|
5
5
|
import { DETECT_RULES, EGRESS_RULES } from './rules.js';
|
|
6
6
|
/** Why a reply was withheld, for the builder (`errorInternal`); the user reads `error.safety`. */
|
|
7
7
|
const WITHHELD_REASON = {
|
|
@@ -13,11 +13,11 @@ const WITHHELD_REASON = {
|
|
|
13
13
|
function canaryHits(text, canary) {
|
|
14
14
|
return canary && scanTextForCanaryLeak(text, canary) ? [CANARY_HIT] : [];
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
16
|
+
/** A hit for each stretch of `text` that repeats the guarded system prompt's own words. */
|
|
17
17
|
function promptEchoHits(text, privateSystem, canary) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!privateSystem)
|
|
19
|
+
return [];
|
|
20
|
+
return promptEchoRanges(text, privateSystem, canary).map(([start, end]) => promptEchoHit(text, start, end));
|
|
21
21
|
}
|
|
22
22
|
/** The system-prompt leak hits: the canary unless it was given, and the prompt's own words when guarded. */
|
|
23
23
|
function promptLeakHits(text, scope) {
|
|
@@ -78,9 +78,12 @@ function eventLeak(event, scope, detect, boundary) {
|
|
|
78
78
|
found.push([canaryAction, CANARY_HIT]);
|
|
79
79
|
}
|
|
80
80
|
const promptAction = detect.prompt_leak[boundary];
|
|
81
|
-
if (promptAction !== 'ignore'
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
if (promptAction !== 'ignore') {
|
|
82
|
+
for (const text of texts) {
|
|
83
|
+
for (const hit of promptEchoHits(text, scope.privateSystem, scope.canary)) {
|
|
84
|
+
found.push([promptAction, hit]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
84
87
|
}
|
|
85
88
|
if (found.length === 0)
|
|
86
89
|
return undefined;
|
|
@@ -14,8 +14,11 @@ const OTHER_HINTS = {
|
|
|
14
14
|
[EGRESS_RULES.unscannable]: 'egress.hint_unscannable',
|
|
15
15
|
[EGRESS_RULES.providerToolLeak]: 'egress.hint_provider_tool_leak',
|
|
16
16
|
};
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The rules whose match is never repeated: the canary is the profile's own secret. A prompt leak is
|
|
19
|
+
* quoted, since the model already holds the instruction it repeated and must know which passage.
|
|
20
|
+
*/
|
|
21
|
+
const UNQUOTED = [DETECT_RULES.canary_leak];
|
|
19
22
|
/** How many matches a detector's line quotes, and how much of each. */
|
|
20
23
|
const MAX_QUOTED = 3;
|
|
21
24
|
const MAX_QUOTE = 120;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { GuardrailEvent, GuardrailHit, Severity } from './types.js';
|
|
2
2
|
/** A canary leak. Never carries the live token, only a placeholder. */
|
|
3
3
|
declare const CANARY_HIT: GuardrailHit;
|
|
4
|
-
/** A prompt leak. Never carries the words: they are the private system instruction's. */
|
|
5
|
-
declare const PROMPT_ECHO_HIT: GuardrailHit;
|
|
6
4
|
/** A guardrail hit for a span of the text, with the matched text sliced from it. */
|
|
7
5
|
declare function hitFromSpan(text: string, span: {
|
|
8
6
|
start: number;
|
|
9
7
|
end: number;
|
|
10
8
|
name?: string;
|
|
11
9
|
}, rule: string, severity: Severity): GuardrailHit;
|
|
10
|
+
/** A prompt leak: the stretch of `text` from `start` to `end` that repeats the private system instruction. */
|
|
11
|
+
declare function promptEchoHit(text: string, start: number, end: number): GuardrailHit;
|
|
12
12
|
/** `match` is the caught text itself, so hosts and traces get it only via `observability.include.guardrailMatchPreview`. */
|
|
13
13
|
declare function projectGuardrailEvent(event: GuardrailEvent, includeMatch: boolean): GuardrailEvent;
|
|
14
|
-
export { CANARY_HIT, hitFromSpan,
|
|
14
|
+
export { CANARY_HIT, hitFromSpan, projectGuardrailEvent, promptEchoHit };
|
|
@@ -5,8 +5,6 @@ const CANARY_HIT = {
|
|
|
5
5
|
severity: 'high',
|
|
6
6
|
match: '[canary]',
|
|
7
7
|
};
|
|
8
|
-
/** A prompt leak. Never carries the words: they are the private system instruction's. */
|
|
9
|
-
const PROMPT_ECHO_HIT = { rule: DETECT_RULES.prompt_leak, severity: 'high' };
|
|
10
8
|
/** A guardrail hit for a span of the text, with the matched text sliced from it. */
|
|
11
9
|
function hitFromSpan(text, span, rule, severity) {
|
|
12
10
|
return {
|
|
@@ -17,6 +15,10 @@ function hitFromSpan(text, span, rule, severity) {
|
|
|
17
15
|
...(span.name === undefined ? {} : { pattern: span.name }),
|
|
18
16
|
};
|
|
19
17
|
}
|
|
18
|
+
/** A prompt leak: the stretch of `text` from `start` to `end` that repeats the private system instruction. */
|
|
19
|
+
function promptEchoHit(text, start, end) {
|
|
20
|
+
return hitFromSpan(text, { start, end }, DETECT_RULES.prompt_leak, 'high');
|
|
21
|
+
}
|
|
20
22
|
/** `match` is the caught text itself, so hosts and traces get it only via `observability.include.guardrailMatchPreview`. */
|
|
21
23
|
function projectGuardrailEvent(event, includeMatch) {
|
|
22
24
|
if (includeMatch) {
|
|
@@ -27,4 +29,4 @@ function projectGuardrailEvent(event, includeMatch) {
|
|
|
27
29
|
hits: event.hits.map(({ match: _match, ...hit }) => hit),
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
|
-
export { CANARY_HIT, hitFromSpan,
|
|
32
|
+
export { CANARY_HIT, hitFromSpan, projectGuardrailEvent, promptEchoHit };
|
|
@@ -15,7 +15,7 @@ import { boundaryNote } from './egress.js';
|
|
|
15
15
|
import { notePattern, SYSTEM_BOUNDARY } from './egress-patterns.js';
|
|
16
16
|
import { createEgressStream, loadEgressAutomata } from './egress-stream.js';
|
|
17
17
|
import { imageLeakSpans, linkLeakSpans } from './egress-urls.js';
|
|
18
|
-
import { CANARY_HIT
|
|
18
|
+
import { CANARY_HIT } from './hits.js';
|
|
19
19
|
import { labelText } from './labels.js';
|
|
20
20
|
import { promptEchoHoldFrom, promptEchoRanges, promptEchoScanFrom } from './prompt-echo.js';
|
|
21
21
|
import { DETECT_RULES } from './rules.js';
|
|
@@ -27,6 +27,7 @@ const OMIT_KEYS = {
|
|
|
27
27
|
const IMAGE_HIT = { rule: DETECT_RULES.ungiven_images, severity: 'high' };
|
|
28
28
|
const LINK_HIT = { rule: DETECT_RULES.ungiven_links, severity: 'high' };
|
|
29
29
|
const MARKER_HIT = { rule: DETECT_RULES.marker_leak, severity: 'high' };
|
|
30
|
+
const PROMPT_ECHO_HIT = { rule: DETECT_RULES.prompt_leak, severity: 'high' };
|
|
30
31
|
/** Where `raw`, an offset into `view.text`, stands in the thought: a placeholder's start, or with `end` its end. */
|
|
31
32
|
function shownAt(view, raw, end = false) {
|
|
32
33
|
let piece = view.pieces[0];
|
|
@@ -412,6 +412,8 @@ async function* executeSingleAttemptCycle(args) {
|
|
|
412
412
|
// invariant: The attempt's reply spans its before_end re-entries; only a new attempt starts it again.
|
|
413
413
|
state.replyText = '';
|
|
414
414
|
state.replyMessages = [];
|
|
415
|
+
// invariant: The leak check's carry is the end of this attempt's reply; a retry's is read on its own.
|
|
416
|
+
state.canaryCarry = undefined;
|
|
415
417
|
let latestStructured;
|
|
416
418
|
for (;;) {
|
|
417
419
|
state.attemptEvents = [];
|
|
@@ -348,7 +348,7 @@ export interface ProfileOutputsSpec {
|
|
|
348
348
|
export interface PrivateSystemPart {
|
|
349
349
|
private: string;
|
|
350
350
|
}
|
|
351
|
-
/** One part of a system prompt:
|
|
351
|
+
/** One part of a system prompt: shareable text, or private text a reply may not repeat. */
|
|
352
352
|
export type SystemPart = string | PrivateSystemPart;
|
|
353
353
|
/**
|
|
354
354
|
* A system prompt: a string, or parts sent concatenated as written. A string,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/agents",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.468.ge2da06f",
|
|
4
4
|
"description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|