dsh-dlp 0.2.0 → 0.4.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/README.md +282 -12
- package/SECURITY.md +14 -6
- package/cordis.patch.yml +2 -0
- package/lib/cli.js +8 -4
- package/lib/config-writes.js +215 -0
- package/lib/detectors.js +136 -22
- package/lib/images.js +183 -0
- package/lib/index.js +140 -2
- package/lib/mutation.js +102 -0
- package/lib/paths.js +52 -0
- package/lib/policy.js +25 -10
- package/lib/sink.js +25 -1
- package/lib/telemetry.js +29 -0
- package/lib/types/config-writes.d.ts +89 -0
- package/lib/types/detectors.d.ts +41 -5
- package/lib/types/images.d.ts +81 -0
- package/lib/types/index.d.ts +14 -1
- package/lib/types/mutation.d.ts +83 -0
- package/lib/types/paths.d.ts +25 -0
- package/lib/types/policy.d.ts +11 -1
- package/lib/types/sink.d.ts +18 -1
- package/lib/types/telemetry.d.ts +16 -1
- package/package.json +1 -1
package/lib/types/detectors.d.ts
CHANGED
|
@@ -64,6 +64,11 @@ export interface SyncRule {
|
|
|
64
64
|
* delimiters make a match structurally unambiguous, plus PEM blocks and
|
|
65
65
|
* credential-bearing URLs. Anything requiring entropy heuristics is left to
|
|
66
66
|
* tier 2, where a false positive costs a redaction rather than a denial.
|
|
67
|
+
*
|
|
68
|
+
* Prefix-anchored is the whole membership criterion, and the reason this table
|
|
69
|
+
* keeps growing rather than deferring to tier 2: the `session-telemetry/record`
|
|
70
|
+
* waterfall is synchronous and cannot reach tier 2 at all, so a format missing
|
|
71
|
+
* here is exported in the clear when telemetry is on.
|
|
67
72
|
*/
|
|
68
73
|
export declare const SYNC_RULES: readonly SyncRule[];
|
|
69
74
|
/**
|
|
@@ -72,8 +77,14 @@ export declare const SYNC_RULES: readonly SyncRule[];
|
|
|
72
77
|
*
|
|
73
78
|
* `strip` classes have no legitimate use in tool output, so they are replaced
|
|
74
79
|
* like any other detection. `report` classes do: `U+200D` joins an emoji
|
|
75
|
-
* sequence
|
|
76
|
-
*
|
|
80
|
+
* sequence, a variation selector chooses a glyph, and a CSI sequence colours
|
|
81
|
+
* the output of `git diff`, so replacing them would corrupt ordinary text.
|
|
82
|
+
* They are counted and never rewritten.
|
|
83
|
+
*
|
|
84
|
+
* The split is per class and per lane both: {@link stripControlSequences}
|
|
85
|
+
* applies the `strip` treatment to a `report` class on the audit and
|
|
86
|
+
* approval-facing lanes, where colour buys nothing and a forged record costs
|
|
87
|
+
* everything.
|
|
77
88
|
*/
|
|
78
89
|
export type UnicodeAction = 'strip' | 'report';
|
|
79
90
|
/** One class of invisible or direction-changing characters. */
|
|
@@ -84,12 +95,37 @@ export interface UnicodeRule {
|
|
|
84
95
|
readonly action: UnicodeAction;
|
|
85
96
|
/** Global, unicode-flagged, matching one run of this class. */
|
|
86
97
|
readonly pattern: RegExp;
|
|
87
|
-
/**
|
|
88
|
-
|
|
98
|
+
/**
|
|
99
|
+
* The class's ranges as a character-class body, for the combined run pattern.
|
|
100
|
+
*
|
|
101
|
+
* Absent for a class whose matches are not a run of one character class —
|
|
102
|
+
* a terminal control sequence has an ASCII body — which is scanned over the
|
|
103
|
+
* whole input instead of within a combined run.
|
|
104
|
+
*/
|
|
105
|
+
readonly ranges?: string;
|
|
89
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Text substituted for a stripped control sequence. Visible on purpose: the
|
|
109
|
+
* lanes that strip are the ones an operator reads as evidence, and silently
|
|
110
|
+
* deleting the bytes would hide that a forgery was attempted.
|
|
111
|
+
*/
|
|
112
|
+
export declare const CONTROL_SEQUENCE_PLACEHOLDER = "[REDACTED:dsh-dlp:control-sequence]";
|
|
113
|
+
/**
|
|
114
|
+
* Remove every terminal control sequence from one string.
|
|
115
|
+
*
|
|
116
|
+
* This is the `strip` half of {@link CONTROL_SEQUENCE_RULE}, applied on the
|
|
117
|
+
* lanes that must never carry forgeable bytes: an audit record and the strings
|
|
118
|
+
* an operator or an approval prompt reads back. Ordinary tool-result text takes
|
|
119
|
+
* the `report` half instead, because `git diff`, `rg` and `pytest` legitimately
|
|
120
|
+
* colourise their output.
|
|
121
|
+
* @param text - the string to clean.
|
|
122
|
+
* @returns the string with each control sequence replaced by a visible marker.
|
|
123
|
+
*/
|
|
124
|
+
export declare function stripControlSequences(text: string): string;
|
|
90
125
|
/**
|
|
91
126
|
* Character classes that hide text from the reader while the model still reads
|
|
92
|
-
* it, verified against the Unicode character database
|
|
127
|
+
* it, verified against the Unicode character database, plus the terminal
|
|
128
|
+
* control sequences that show the reader something other than what is there.
|
|
93
129
|
*
|
|
94
130
|
* Every class is `medium`. These are injection *indicators*, not credentials:
|
|
95
131
|
* the guard floor denies at `high` and above, so an argument carrying one is
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neutralising remote markdown images in assistant output, on the `llm/stream`
|
|
3
|
+
* waterfall.
|
|
4
|
+
*
|
|
5
|
+
* The web UI renders any absolute `http:`/`https:` markdown image a model
|
|
6
|
+
* emits as a real `<img src>`, and the harness sets no Content-Security-Policy,
|
|
7
|
+
* so the fetch happens in the user's browser where no host-side listener can
|
|
8
|
+
* see it. This module rewrites the destination out of the assistant's text
|
|
9
|
+
* before it becomes an `assistant/chunk` or `assistant/message` session event,
|
|
10
|
+
* so the log and the rendered answer stay in agreement.
|
|
11
|
+
*
|
|
12
|
+
* Two properties this module exists to hold:
|
|
13
|
+
*
|
|
14
|
+
* - **A destination split across chunks is still caught.** The mock and real
|
|
15
|
+
* adapters both emit text in small deltas, so `` is
|
|
16
|
+
* routinely spread over several of them and the browser renders the
|
|
17
|
+
* accumulation. Text that could still be the start of an image is held back
|
|
18
|
+
* until it either completes or exceeds {@link MAX_HELD_CHARACTERS}.
|
|
19
|
+
* - **Only the destination is replaced.** The alt text survives, so the
|
|
20
|
+
* sentence the model wrote still reads, and the renderer's own
|
|
21
|
+
* non-absolute-URL arm shows that alt text instead of fetching anything.
|
|
22
|
+
*
|
|
23
|
+
* This does not close the channel. It matches inline image syntax only:
|
|
24
|
+
* reference-style images, an alt text carrying a `]`, and any destination form
|
|
25
|
+
* the pattern does not model still reach the renderer. Raw HTML needs no
|
|
26
|
+
* handling — the renderer keeps it as literal text and no HTML enters the DOM
|
|
27
|
+
* (`packages/client/ui-primitives/src/markdown/render.tsx:261-263`). The
|
|
28
|
+
* upstream fix is one `img-src` directive.
|
|
29
|
+
* @module dsh-dlp/images
|
|
30
|
+
*/
|
|
31
|
+
import type { StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
32
|
+
/**
|
|
33
|
+
* Destination substituted for a remote image URL.
|
|
34
|
+
*
|
|
35
|
+
* It is deliberately not a URL: `new URL()` throws on it, which is the
|
|
36
|
+
* renderer's own "not an absolute destination" arm, and that arm renders the
|
|
37
|
+
* alt text as a `<span>` instead of emitting an `<img>`.
|
|
38
|
+
*/
|
|
39
|
+
export declare const BLOCKED_IMAGE_DESTINATION = "dsh-dlp-blocked-remote-image";
|
|
40
|
+
/**
|
|
41
|
+
* Longest suffix held back waiting for an image to complete.
|
|
42
|
+
*
|
|
43
|
+
* Held text is text the user cannot see yet, so the wait is bounded: past this
|
|
44
|
+
* many characters the suffix is emitted as it stands and a destination that
|
|
45
|
+
* completes later is caught only by the assembled block. A protocol bound on
|
|
46
|
+
* this module's own buffering, not a deployment choice.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MAX_HELD_CHARACTERS = 4096;
|
|
49
|
+
/** One string after its remote image destinations were replaced. */
|
|
50
|
+
export interface NeutralizedText {
|
|
51
|
+
readonly text: string;
|
|
52
|
+
/** Hostnames of the replaced destinations, in match order; never the full URL. */
|
|
53
|
+
readonly hosts: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Replace every absolute HTTP(S) inline image destination in one string.
|
|
57
|
+
* @param text - assistant text, whole or partial.
|
|
58
|
+
* @returns the rewritten text and the hosts whose destinations were replaced.
|
|
59
|
+
*/
|
|
60
|
+
export declare function neutralizeRemoteImages(text: string): NeutralizedText;
|
|
61
|
+
/**
|
|
62
|
+
* Where the held suffix of a partially streamed string starts.
|
|
63
|
+
* @param text - everything accumulated for one block and not yet emitted.
|
|
64
|
+
* @returns the offset to emit up to; the string's length when nothing is held.
|
|
65
|
+
*/
|
|
66
|
+
export declare function heldSuffixStart(text: string): number;
|
|
67
|
+
/**
|
|
68
|
+
* Wrap one model stream, replacing remote image destinations in its text.
|
|
69
|
+
*
|
|
70
|
+
* Text deltas are rewritten as they pass, with a possible image start held
|
|
71
|
+
* back until it resolves, and the assembled block on `block-end` — which is
|
|
72
|
+
* what the agent loop turns into the assistant message — is rewritten too. A
|
|
73
|
+
* held suffix is always flushed as a delta before the block closes and before
|
|
74
|
+
* the terminal finish, so no text is lost and the emitted chunks still satisfy
|
|
75
|
+
* the stream grammar.
|
|
76
|
+
* @param source - the stream from the rest of the waterfall.
|
|
77
|
+
* @param onNeutralized - notified once per host per text block.
|
|
78
|
+
* @returns the rewritten stream.
|
|
79
|
+
*/
|
|
80
|
+
export declare function neutralizeImageStream(source: AsyncIterable<StreamChunk>, onNeutralized: (host: string) => void): AsyncIterable<StreamChunk>;
|
|
81
|
+
//# sourceMappingURL=images.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `dsh-dlp` — data-loss prevention for DeepSeek Harness.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Six registrations, in descending order of how much they can be trusted:
|
|
5
5
|
*
|
|
6
6
|
* 1. `ctx.tools.guard()` — an unconditional, non-configurable deny floor for
|
|
7
7
|
* credential paths named in a path-typed argument and for secrets heading
|
|
@@ -9,11 +9,24 @@
|
|
|
9
9
|
* has no allow arm.
|
|
10
10
|
* 2. `tools/pre-execute` — the async breadth tier, which can await
|
|
11
11
|
* `@secretlint/core`. Neutralizable by any listener registered ahead of it.
|
|
12
|
+
* 2b. `tools/pre-execute` — the `ask` tier for writes to behaviour-changing
|
|
13
|
+
* config paths. Deliberately here rather than on the floor: its rules have a
|
|
14
|
+
* real false-positive rate and the floor cannot ask. Neutralizable, and it
|
|
15
|
+
* abstains entirely when no approval service is mounted.
|
|
12
16
|
* 3. `tools/post-execute` — result redaction, applied before the `tool/result`
|
|
13
17
|
* session event is appended, so the durable log records the redacted copy;
|
|
14
18
|
* a result that cannot be cleaned is withheld rather than accepted.
|
|
15
19
|
* 4. `session-telemetry/record` — fail-closed redaction of exported telemetry,
|
|
16
20
|
* reaching tier 1 only because the waterfall is synchronous.
|
|
21
|
+
* 5. `llm/stream` — neutralising remote markdown image destinations in
|
|
22
|
+
* assistant output, before the text becomes a session event.
|
|
23
|
+
*
|
|
24
|
+
* Three of those registrations mitigate defects in the harness rather than in
|
|
25
|
+
* a deployment's own configuration: the missing Content-Security-Policy behind
|
|
26
|
+
* (5), the mutable execution object behind the guard's mutation check, and the
|
|
27
|
+
* silently inert telemetry seam behind the notice reported at mount. Each one
|
|
28
|
+
* is partial, none closes its channel, and README.md says so beside the
|
|
29
|
+
* feature.
|
|
17
30
|
*
|
|
18
31
|
* This plugin is not a containment boundary. It runs in-process at the agent's
|
|
19
32
|
* own uid; anything the agent can execute can read the same files the guard
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detecting a tool call that was rewritten after it was logged.
|
|
3
|
+
*
|
|
4
|
+
* The registry deep-freezes `exec.arguments` but does not freeze the execution
|
|
5
|
+
* object until results are notified, so a `tools/pre-execute` listener can
|
|
6
|
+
* reassign `exec.arguments` or `exec.name` — and reassigning `exec.name`
|
|
7
|
+
* changes which tool body runs. The agent loop appended `tool/call` from the
|
|
8
|
+
* model's own response block before the waterfall ran, so nothing in the
|
|
9
|
+
* session log records the change: the durable record then describes a
|
|
10
|
+
* different call than the one about to execute.
|
|
11
|
+
*
|
|
12
|
+
* This module snapshots the call as early in the waterfall as it can and
|
|
13
|
+
* compares in the guard, which runs after the whole waterfall and cannot be
|
|
14
|
+
* out-ordered. It is detection, not prevention: preventing the rewrite would
|
|
15
|
+
* mean freezing an object this plugin does not own, and the snapshot itself is
|
|
16
|
+
* best-effort — a later `{ prepend: true }` registration runs ahead of ours and
|
|
17
|
+
* would be snapshotted after its own rewrite.
|
|
18
|
+
* @module dsh-dlp/mutation
|
|
19
|
+
*/
|
|
20
|
+
import type { ToolExecution } from '@deepseek-ai/dsh-tools';
|
|
21
|
+
import type { SpanHasher } from './redaction.ts';
|
|
22
|
+
/** The parts of an execution this module compares. */
|
|
23
|
+
type Comparable = Pick<ToolExecution, 'name' | 'arguments'>;
|
|
24
|
+
/** One rewritten field. */
|
|
25
|
+
export type MutatedField = 'name' | 'arguments';
|
|
26
|
+
/** A call whose identity changed between the snapshot and the guard. */
|
|
27
|
+
export interface ExecutionMutation {
|
|
28
|
+
/** Which fields differ, in a stable order. */
|
|
29
|
+
readonly fields: readonly MutatedField[];
|
|
30
|
+
/** The tool name the session log recorded. */
|
|
31
|
+
readonly originalTool: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render a JSON value with object keys in a fixed order, so two equal argument
|
|
35
|
+
* sets hash equally whatever order a listener rebuilt them in.
|
|
36
|
+
* @param value - the argument value; JSON-serializable by the registry's own snapshot step.
|
|
37
|
+
* @returns a canonical string for hashing.
|
|
38
|
+
*/
|
|
39
|
+
export declare function canonicalJson(value: unknown): string;
|
|
40
|
+
/**
|
|
41
|
+
* Remembers what each pending call looked like before the rest of the
|
|
42
|
+
* `tools/pre-execute` waterfall ran.
|
|
43
|
+
*
|
|
44
|
+
* Keyed by the execution object's identity in a `WeakMap`, the way the
|
|
45
|
+
* registry keys its own per-execution state, so the entry is found again in
|
|
46
|
+
* the guard and released with the execution.
|
|
47
|
+
*/
|
|
48
|
+
export declare class ExecutionSnapshots {
|
|
49
|
+
#private;
|
|
50
|
+
/**
|
|
51
|
+
* @param hasher - mints the keyed digest of the arguments; the values themselves are never stored.
|
|
52
|
+
*/
|
|
53
|
+
constructor(hasher: SpanHasher);
|
|
54
|
+
/**
|
|
55
|
+
* Snapshot one pending call.
|
|
56
|
+
* @param exec - the execution as the earliest listener sees it.
|
|
57
|
+
*/
|
|
58
|
+
record(exec: Comparable): void;
|
|
59
|
+
/**
|
|
60
|
+
* Compare one call against its snapshot.
|
|
61
|
+
*
|
|
62
|
+
* A call with no snapshot is not a finding: the listener may never have run
|
|
63
|
+
* for it, and reporting absence as mutation would deny calls this plugin
|
|
64
|
+
* simply did not observe.
|
|
65
|
+
* @param exec - the execution as the guard stage sees it.
|
|
66
|
+
* @returns what changed, or `undefined` when nothing did.
|
|
67
|
+
*/
|
|
68
|
+
detect(exec: Comparable): ExecutionMutation | undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Denial text for a rewritten call.
|
|
72
|
+
*
|
|
73
|
+
* Both tool names are named: a tool name is already in the session log and in
|
|
74
|
+
* every other denial this plugin writes, and naming them is the whole point —
|
|
75
|
+
* the operator needs to know which call the log describes and which one was
|
|
76
|
+
* about to run. No argument value appears.
|
|
77
|
+
* @param exec - the call as the guard sees it, after the rewrite.
|
|
78
|
+
* @param mutation - the fields that changed and the recorded tool name.
|
|
79
|
+
* @returns the model-facing reason.
|
|
80
|
+
*/
|
|
81
|
+
export declare function mutationReason(exec: Comparable, mutation: ExecutionMutation): string;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=mutation.d.ts.map
|
package/lib/types/paths.d.ts
CHANGED
|
@@ -40,6 +40,31 @@ export interface CredentialPathRule {
|
|
|
40
40
|
* authenticates with is agent-readable. That is the gap this table closes.
|
|
41
41
|
*/
|
|
42
42
|
export declare const CREDENTIAL_PATH_RULES: readonly CredentialPathRule[];
|
|
43
|
+
/**
|
|
44
|
+
* Escape one literal path so it can anchor a regular expression.
|
|
45
|
+
* @param literal - the path to quote.
|
|
46
|
+
* @returns the same text with every metacharacter escaped.
|
|
47
|
+
*/
|
|
48
|
+
export declare function escapePathPattern(literal: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* Credential-path rules anchored at the user's home directory, resolved at
|
|
51
|
+
* mount because the directory is not known until then.
|
|
52
|
+
*
|
|
53
|
+
* A coding agent's *home* configuration decides how every future session in
|
|
54
|
+
* every repository behaves: the Miasma worm's `SessionStart` hooks went into
|
|
55
|
+
* exactly these files. Writing one is never ordinary repository work, so it is
|
|
56
|
+
* on the floor. Reading one is — a user asking the agent why its own
|
|
57
|
+
* configuration behaves a certain way is a normal request — so the rule is
|
|
58
|
+
* `writes-only` rather than `every-call`, unlike the `auth.json` and `mcp.json`
|
|
59
|
+
* stores in {@link CREDENTIAL_PATH_RULES}, which hold nothing but credentials.
|
|
60
|
+
*
|
|
61
|
+
* The *repository-local* copies of these same file names are a different
|
|
62
|
+
* question with a different answer: they are edited legitimately and often, so
|
|
63
|
+
* they sit in the neutralizable `ask` tier rather than on the floor.
|
|
64
|
+
* @param home - the user's home directory.
|
|
65
|
+
* @returns rules appended after the built-in table.
|
|
66
|
+
*/
|
|
67
|
+
export declare function homeCredentialPathRules(home: string): readonly CredentialPathRule[];
|
|
43
68
|
/**
|
|
44
69
|
* Normalize one candidate path for matching: Windows separators become
|
|
45
70
|
* forward slashes, surrounding quotes come off, `~` expands to a
|
package/lib/types/policy.d.ts
CHANGED
|
@@ -33,12 +33,20 @@ export interface Config {
|
|
|
33
33
|
resultRedaction: boolean;
|
|
34
34
|
/** Whether `session-telemetry/record` redaction runs. */
|
|
35
35
|
telemetryRedaction: boolean;
|
|
36
|
+
/** Whether remote markdown image destinations are neutralised in assistant output. */
|
|
37
|
+
remoteImageNeutralization: boolean;
|
|
36
38
|
/** Whether telemetry's `session.cwd` attribute is replaced with a keyed hash. */
|
|
37
39
|
redactTelemetryWorkspacePaths: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a write to a behaviour-changing config path asks the user first.
|
|
42
|
+
* Needs an approval service; without one the tier abstains rather than
|
|
43
|
+
* letting an `ask` degrade into a denial.
|
|
44
|
+
*/
|
|
45
|
+
configWriteAsk: boolean;
|
|
38
46
|
}
|
|
39
47
|
export declare const Config: z<Config>;
|
|
40
48
|
/** Config toggles a repo-local policy may switch on, and never off. */
|
|
41
|
-
declare const ENABLEABLE: readonly ["breadthTier", "resultRedaction", "telemetryRedaction", "redactTelemetryWorkspacePaths"];
|
|
49
|
+
declare const ENABLEABLE: readonly ["breadthTier", "resultRedaction", "telemetryRedaction", "remoteImageNeutralization", "redactTelemetryWorkspacePaths", "configWriteAsk"];
|
|
42
50
|
/** One toggle name a repo-local policy may name in `enable`. */
|
|
43
51
|
export type EnableableToggle = typeof ENABLEABLE[number];
|
|
44
52
|
/** Payload version this package writes and accepts for repo-local policy files. */
|
|
@@ -59,7 +67,9 @@ export interface ResolvedPolicy {
|
|
|
59
67
|
readonly breadthTier: boolean;
|
|
60
68
|
readonly resultRedaction: boolean;
|
|
61
69
|
readonly telemetryRedaction: boolean;
|
|
70
|
+
readonly remoteImageNeutralization: boolean;
|
|
62
71
|
readonly redactTelemetryWorkspacePaths: boolean;
|
|
72
|
+
readonly configWriteAsk: boolean;
|
|
63
73
|
}
|
|
64
74
|
/** Thrown when a policy file is malformed or attempts to loosen the policy. */
|
|
65
75
|
export declare class PolicyError extends Error {
|
package/lib/types/sink.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare function newDecisionId(): DecisionId;
|
|
|
28
28
|
/** Payload version carried inside every record this plugin writes. */
|
|
29
29
|
export declare const RECORD_VERSION = 1;
|
|
30
30
|
/** What produced one audit record. */
|
|
31
|
-
export type AuditKind = 'guard-deny' | 'pre-execute-deny' | 'result-redaction' | 'telemetry-redaction' | 'audit-failure';
|
|
31
|
+
export type AuditKind = 'guard-deny' | 'pre-execute-deny' | 'pre-execute-ask' | 'execution-mutation' | 'result-redaction' | 'telemetry-redaction' | 'assistant-image-neutralized' | 'audit-failure';
|
|
32
32
|
/** One durable record. Never carries matched secret text. */
|
|
33
33
|
export interface AuditRecord {
|
|
34
34
|
readonly v: number;
|
|
@@ -60,8 +60,25 @@ export interface AuditRecord {
|
|
|
60
60
|
* instruction is exactly the content this file must not repeat.
|
|
61
61
|
*/
|
|
62
62
|
readonly unicode?: Readonly<Record<string, number>>;
|
|
63
|
+
/**
|
|
64
|
+
* The single rule behind a decision that has no matched region to describe,
|
|
65
|
+
* which is every `pre-execute-ask`: the finding is that a path names a
|
|
66
|
+
* behaviour-changing file, not that any part of it matched a secret.
|
|
67
|
+
*/
|
|
68
|
+
readonly ruleId?: string;
|
|
63
69
|
/** Telemetry record channel, for `telemetry-redaction`. */
|
|
64
70
|
readonly channel?: string;
|
|
71
|
+
/** Fields another plugin rewrote after the call was logged, for `execution-mutation`. */
|
|
72
|
+
readonly mutatedFields?: readonly string[];
|
|
73
|
+
/** Tool name the session log recorded, when a rewrite changed it. */
|
|
74
|
+
readonly originalTool?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Hostname of a neutralised remote image destination, for
|
|
77
|
+
* `assistant-image-neutralized`. The hostname only: a path and a query
|
|
78
|
+
* string are where an exfiltration payload rides, and this file must not
|
|
79
|
+
* carry it.
|
|
80
|
+
*/
|
|
81
|
+
readonly host?: string;
|
|
65
82
|
}
|
|
66
83
|
/** Append-only JSONL sink for this plugin's decisions. */
|
|
67
84
|
export declare class AuditSink {
|
package/lib/types/telemetry.d.ts
CHANGED
|
@@ -25,9 +25,24 @@
|
|
|
25
25
|
* workspace path.
|
|
26
26
|
* @module dsh-dlp/telemetry
|
|
27
27
|
*/
|
|
28
|
-
import type { SessionTelemetryRecord } from '@deepseek-ai/dsh-session-telemetry';
|
|
28
|
+
import type { SessionTelemetryRecord, SessionTelemetrySharingStatus } from '@deepseek-ai/dsh-session-telemetry';
|
|
29
29
|
import type { ResolvedPolicy } from './policy.ts';
|
|
30
30
|
import { type RedactedSpan, type SpanHasher } from './redaction.ts';
|
|
31
|
+
/**
|
|
32
|
+
* What to tell the operator when the redaction seam will never dispatch.
|
|
33
|
+
*
|
|
34
|
+
* A `session-telemetry/record` listener mounts successfully and never runs
|
|
35
|
+
* unless a backend built a coordinator, and the shipped default builds none:
|
|
36
|
+
* the mode is `DISABLED`, so nothing is exported and nothing is dispatched.
|
|
37
|
+
* That is the safe posture, not a leak — but an operator who mounts a redactor
|
|
38
|
+
* under it sees every signal of success and has verified nothing. The
|
|
39
|
+
* backend's own `sharing` disclosure is the resolved answer, so this never
|
|
40
|
+
* guesses at `DSH_TELEMETRY_MODE`, which is only the base patch's default
|
|
41
|
+
* expression for a `mode` a deployment can also set directly.
|
|
42
|
+
* @param sharing - the mounted backend's disclosure, or `undefined` when no backend is mounted.
|
|
43
|
+
* @returns the line to report, or `undefined` when the seam does dispatch.
|
|
44
|
+
*/
|
|
45
|
+
export declare function telemetrySeamNotice(sharing: SessionTelemetrySharingStatus | undefined): string | undefined;
|
|
31
46
|
/** Rule identity recorded when a workspace path is replaced. */
|
|
32
47
|
export declare const WORKSPACE_PATH_RULE = "dsh-dlp/telemetry-workspace-path";
|
|
33
48
|
/** One redacted telemetry record and what was replaced in it. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dlp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Data-loss-prevention plugin for DeepSeek Harness: a non-configurable tool guard floor, tool-result redaction, and fail-closed telemetry redaction",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ivan Tyshchenko <nsof@protonmail.com>",
|