@intutic/gate 2.1.0 → 2.3.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 +29 -12
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +20 -6
- package/dist/actions.js.map +1 -1
- package/dist/client.d.ts +19 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +48 -4
- package/dist/client.js.map +1 -1
- package/dist/dsh.d.ts +1 -1
- package/dist/dsh.js +1 -1
- package/dist/errors.d.ts +25 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -1
- package/dist/eve.d.ts +11 -11
- package/dist/eve.d.ts.map +1 -1
- package/dist/eve.js +10 -10
- package/dist/eve.js.map +1 -1
- package/dist/gate.d.ts +34 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +135 -9
- package/dist/gate.js.map +1 -1
- package/dist/harness.d.ts +28 -20
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +165 -26
- package/dist/harness.js.map +1 -1
- package/dist/hold.d.ts +69 -0
- package/dist/hold.d.ts.map +1 -0
- package/dist/hold.js +127 -0
- package/dist/hold.js.map +1 -0
- package/dist/imagecheck.d.ts +14 -7
- package/dist/imagecheck.d.ts.map +1 -1
- package/dist/imagecheck.js +3 -3
- package/dist/imagecheck.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +26 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +45 -0
- package/dist/limits.js.map +1 -0
- package/dist/managedAgents.d.ts +3 -3
- package/dist/managedAgents.d.ts.map +1 -1
- package/dist/managedAgents.js +2 -2
- package/dist/mcpRegistryRecord.d.ts +143 -0
- package/dist/mcpRegistryRecord.d.ts.map +1 -0
- package/dist/mcpRegistryRecord.js +217 -0
- package/dist/mcpRegistryRecord.js.map +1 -0
- package/dist/openai.d.ts +3 -3
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +9 -8
- package/dist/openai.js.map +1 -1
- package/dist/phrases.d.ts +87 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +236 -0
- package/dist/phrases.js.map +1 -0
- package/dist/sequence.d.ts +52 -0
- package/dist/sequence.d.ts.map +1 -0
- package/dist/sequence.js +121 -0
- package/dist/sequence.js.map +1 -0
- package/dist/snapshot.d.ts +58 -36
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +106 -48
- package/dist/snapshot.js.map +1 -1
- package/dist/ssoGroups.d.ts +50 -0
- package/dist/ssoGroups.d.ts.map +1 -0
- package/dist/ssoGroups.js +101 -0
- package/dist/ssoGroups.js.map +1 -0
- package/dist/workflow.d.ts +5 -3
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +8 -4
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
package/dist/sequence.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence rules: a gate rule `A.*B` run as "find A, then find B after it" in
|
|
3
|
+
* linear time.
|
|
4
|
+
*
|
|
5
|
+
* A rule like ` git .*reset +--hard` is a regex. `grep -E` runs it as an
|
|
6
|
+
* automaton, in time linear in the command. JavaScript's and Python's engines
|
|
7
|
+
* backtrack: for every ` git ` they try every end of `.*`, so 200 KB of
|
|
8
|
+
* ` git git git …` took 9.5 s, which is past the point where a harness that
|
|
9
|
+
* treats a hook timeout as "allow" runs the call. A rule marked `sequence`
|
|
10
|
+
* (`s` in a `.rules` line's flags column) is the same regex, read differently:
|
|
11
|
+
*
|
|
12
|
+
* - its top-level `|` alternatives are tried in turn;
|
|
13
|
+
* - each alternative is cut at its top-level `.*` into steps;
|
|
14
|
+
* - each step is searched for once, starting where the previous one ended.
|
|
15
|
+
*
|
|
16
|
+
* Each search is one pass over the text, so a rule costs at most its number of
|
|
17
|
+
* steps times the length of the text. It means the same as the regex provided
|
|
18
|
+
* the first match of every step but the last also ends first, which holds for
|
|
19
|
+
* every step the rule tables use: a step that starts and ends at a space with
|
|
20
|
+
* no space inside, or a fixed word. `.` does not match a newline and neither
|
|
21
|
+
* does a step search cross one in the regex, but the gates normalise every
|
|
22
|
+
* command subject to single spaces first, so there is none. A test fuzzes each
|
|
23
|
+
* sequence rule against its own regex.
|
|
24
|
+
*
|
|
25
|
+
* Self-contained on purpose — no imports, no module state — because the
|
|
26
|
+
* JavaScript gates emit it as source ({@link SEQUENCE_JS_SOURCE}).
|
|
27
|
+
* `packages/gate-js/src/sequence.ts` is a byte-identical copy and
|
|
28
|
+
* `intutic_clawde/gate/sequence.py` a transliteration; all of them run
|
|
29
|
+
* `fixtures/gate-rule-vectors.json`.
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* A sequence rule's source as alternatives of steps: split at every top-level
|
|
35
|
+
* `|`, then each alternative at every top-level `.*`. ` a .*b| c .*d` is
|
|
36
|
+
* `[[' a ', 'b'], [' c ', 'd']]`. Outside a group or a class only, so `(x|y)`
|
|
37
|
+
* stays inside its step. Empty steps are dropped.
|
|
38
|
+
*/
|
|
39
|
+
export function sequenceAlternatives(source) {
|
|
40
|
+
const alternatives = [];
|
|
41
|
+
let steps = [];
|
|
42
|
+
let current = '';
|
|
43
|
+
let depth = 0;
|
|
44
|
+
let inClass = false;
|
|
45
|
+
for (let i = 0; i < source.length; i++) {
|
|
46
|
+
const c = source.charAt(i);
|
|
47
|
+
if (c === '\\') {
|
|
48
|
+
current += c + source.charAt(i + 1);
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (inClass) {
|
|
53
|
+
current += c;
|
|
54
|
+
if (c === ']')
|
|
55
|
+
inClass = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (c === '[') {
|
|
59
|
+
inClass = true;
|
|
60
|
+
current += c;
|
|
61
|
+
if (source.charAt(i + 1) === '^')
|
|
62
|
+
current += source.charAt(++i);
|
|
63
|
+
if (source.charAt(i + 1) === ']')
|
|
64
|
+
current += source.charAt(++i);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (c === '(')
|
|
68
|
+
depth++;
|
|
69
|
+
if (c === ')')
|
|
70
|
+
depth--;
|
|
71
|
+
if (depth === 0 && c === '|') {
|
|
72
|
+
steps.push(current);
|
|
73
|
+
alternatives.push(steps.filter((step) => step !== ''));
|
|
74
|
+
steps = [];
|
|
75
|
+
current = '';
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (depth === 0 && c === '.' && source.charAt(i + 1) === '*') {
|
|
79
|
+
steps.push(current);
|
|
80
|
+
current = '';
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
current += c;
|
|
85
|
+
}
|
|
86
|
+
steps.push(current);
|
|
87
|
+
alternatives.push(steps.filter((step) => step !== ''));
|
|
88
|
+
return alternatives;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compiles a sequence rule's source for {@link sequenceMatch}: each step with
|
|
92
|
+
* the `g` flag, which is what lets a search start at `lastIndex`.
|
|
93
|
+
*/
|
|
94
|
+
export function compileSequence(source, ignoreCase) {
|
|
95
|
+
return sequenceAlternatives(source).map((steps) => steps.map((step) => new RegExp(step, ignoreCase ? 'gi' : 'g')));
|
|
96
|
+
}
|
|
97
|
+
/** Whether `text` matches a compiled sequence rule: some alternative's steps occur in order. */
|
|
98
|
+
export function sequenceMatch(alternatives, text) {
|
|
99
|
+
for (const steps of alternatives) {
|
|
100
|
+
let pos = 0;
|
|
101
|
+
let ok = true;
|
|
102
|
+
for (const step of steps) {
|
|
103
|
+
step.lastIndex = pos;
|
|
104
|
+
const m = step.exec(text);
|
|
105
|
+
if (!m) {
|
|
106
|
+
ok = false;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
pos = m.index + m[0].length;
|
|
110
|
+
}
|
|
111
|
+
if (ok)
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The three functions as JavaScript source, for a gate to emit: the gates run
|
|
118
|
+
* this exact code, not a retyped copy.
|
|
119
|
+
*/
|
|
120
|
+
export const SEQUENCE_JS_SOURCE = `${sequenceAlternatives.toString()}\n${compileSequence.toString()}\n${sequenceMatch.toString()}`;
|
|
121
|
+
//# sourceMappingURL=sequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sequence.js","sourceRoot":"","sources":["../src/sequence.ts"],"names":[],"mappings":"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"}
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -29,41 +29,20 @@
|
|
|
29
29
|
* * Snapshot health is reported as an event — "snapshot missing on 400
|
|
30
30
|
* machines" must not look identical to "snapshot present and healthy".
|
|
31
31
|
*
|
|
32
|
-
* ##
|
|
32
|
+
* ## Padding and case
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `^` (`' rm( +-[a-zA-Z-]+)+ +/( |\\*)'` requires a literal space before
|
|
47
|
-
* `rm`) — POSIX ERE has no `\b` and no reliable `^`/`$` inside the emitted
|
|
48
|
-
* shell gates, so the whole pattern table is authored against the padded
|
|
49
|
-
* contract. It is also why `policySnapshot.ts`'s `toGuardPattern` strips
|
|
50
|
-
* the `^`/`$` off an SOP `toolPattern` and re-wraps it as `' (name) '`
|
|
51
|
-
* before shipping it as a `subject: 'tool'` snapshot rule — that
|
|
52
|
-
* transformation only makes sense against a padded reader. Without
|
|
53
|
-
* padding, a command that BEGINS with the dangerous verb (`"rm -rf /"`,
|
|
54
|
-
* no leading space) never matches `' rm...'` at all, and the block
|
|
55
|
-
* silently never fires.
|
|
56
|
-
* 2. **Forced lowercasing.** `_normalise()` lowercases unconditionally; the
|
|
57
|
-
* real `NORMALISE_CONTRACT.jsSource` does not — case sensitivity is
|
|
58
|
-
* governed entirely by each rule's own `ignoreCase`/`i`-flag column.
|
|
59
|
-
* `bypass.env_kill_switch` (` [A-Z][A-Z0-9_]*_HOOKS?=`) and
|
|
60
|
-
* `destructive.chmod_recursive_root`/`chown_recursive_root`
|
|
61
|
-
* (`[a-zA-Z]*R[a-zA-Z]*`, a literal uppercase `R` for the recursive flag)
|
|
62
|
-
* both key on case WITHOUT setting that flag; forcing the subject to
|
|
63
|
-
* lowercase first makes them never fire, on real production patterns.
|
|
64
|
-
*
|
|
65
|
-
* This reader pads and preserves case, matching the real shipped contract;
|
|
66
|
-
* see the fidelity suite for the evidence.
|
|
34
|
+
* Normalisation pads the subject with a space at each end and collapses
|
|
35
|
+
* whitespace, and does not lowercase, as the shipped gates do
|
|
36
|
+
* (`NORMALISE_CONTRACT` in `protectedPaths.ts`). Padding is what lets a
|
|
37
|
+
* floor/destructive pattern use a plain leading space or non-word character as
|
|
38
|
+
* a stand-in for `^` (POSIX ERE has no `\b` and no reliable `^`/`$` inside the
|
|
39
|
+
* emitted shell gates), so without it a command that BEGINS with the dangerous
|
|
40
|
+
* verb (`"rm -rf /"`, `"DROP TABLE users"`) never matches. Case sensitivity is
|
|
41
|
+
* each rule's own `ignoreCase` flag: `bypass.env_kill_switch` and the
|
|
42
|
+
* recursive chmod/chown rules key on an uppercase letter without one. The
|
|
43
|
+
* Python SDK's reader once lowercased and did not pad, and missed both; it now
|
|
44
|
+
* follows the same contract, and `destructive-sql-vectors.json` in
|
|
45
|
+
* shared-types runs both readers over the same commands.
|
|
67
46
|
*
|
|
68
47
|
* The regex-dialect divergence the Python module documents (`.rules` patterns
|
|
69
48
|
* are authored as JavaScript regexes; compiling them with a different engine
|
|
@@ -71,13 +50,26 @@
|
|
|
71
50
|
* here — this reader compiles them as native JS `RegExp`, the dialect they
|
|
72
51
|
* were authored in.
|
|
73
52
|
*/
|
|
53
|
+
import { type McpAllowlistRecord, type McpRegistryRecord } from './mcpRegistryRecord.js';
|
|
54
|
+
import { type SsoGroupRecord } from './ssoGroups.js';
|
|
74
55
|
export declare const SNAPSHOT_STALE_AFTER_DAYS = 7;
|
|
75
56
|
/** Severity tiers, in the order the shipped gate handles them. */
|
|
76
57
|
export declare const SEV_SHADOW: "shadow";
|
|
77
58
|
export declare const SEV_WARN: "warn";
|
|
78
59
|
export declare const SEV_BLOCK: "block";
|
|
79
|
-
|
|
80
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A hold rule (a `REQUIRE_APPROVAL:` SOP, or a local `review_before:` token):
|
|
62
|
+
* refuses like a block, but asks a person rather than denying — see hold.ts.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SEV_HOLD: "hold";
|
|
65
|
+
export type Severity = typeof SEV_SHADOW | typeof SEV_WARN | typeof SEV_BLOCK | typeof SEV_HOLD;
|
|
66
|
+
/**
|
|
67
|
+
* `phrase`: the source is `|`-separated phrases matched as words (phrases.ts),
|
|
68
|
+
* not a regex. `action`: the source is matched against the action tokens the
|
|
69
|
+
* command classifies to (`" action:deploy "`, actions.ts) — what a local
|
|
70
|
+
* `review_before: action:<name>` compiles to.
|
|
71
|
+
*/
|
|
72
|
+
export type RuleSubject = 'tool' | 'command' | 'target' | 'phrase' | 'action' | 'any';
|
|
81
73
|
export type SnapshotState = 'ok' | 'absent' | 'invalid' | 'empty' | 'stale';
|
|
82
74
|
export interface Rule {
|
|
83
75
|
id: string;
|
|
@@ -85,6 +77,14 @@ export interface Rule {
|
|
|
85
77
|
subject: RuleSubject;
|
|
86
78
|
reason: string;
|
|
87
79
|
pattern: RegExp;
|
|
80
|
+
/**
|
|
81
|
+
* A sequence rule's compiled steps (flags `s`, sequence.ts), or null. The
|
|
82
|
+
* rule is matched by searching for its steps in order, in linear time,
|
|
83
|
+
* rather than by {@link pattern}, which a backtracking engine runs in time
|
|
84
|
+
* growing with the square of the text. Absent on a rule built by hand,
|
|
85
|
+
* which is matched by {@link pattern}.
|
|
86
|
+
*/
|
|
87
|
+
steps?: RegExp[][] | null;
|
|
88
88
|
}
|
|
89
89
|
export declare class Snapshot {
|
|
90
90
|
rules: Rule[];
|
|
@@ -95,11 +95,33 @@ export declare class Snapshot {
|
|
|
95
95
|
ageDays: number;
|
|
96
96
|
/** Regexes that would not compile. */
|
|
97
97
|
droppedRules: number;
|
|
98
|
+
/**
|
|
99
|
+
* The workspace's SSO-group policy and the member this snapshot was issued
|
|
100
|
+
* to, or null when the workspace has no group policy. On a snapshot that
|
|
101
|
+
* fails its integrity check the member is null — groups unknown — so an
|
|
102
|
+
* edited group list in the file clears nothing.
|
|
103
|
+
*/
|
|
104
|
+
ssoGroups: SsoGroupRecord | null;
|
|
105
|
+
/**
|
|
106
|
+
* The workspace's MCP server registry decisions (the `@mcp_registry`
|
|
107
|
+
* record), or null when it made none. Null on a snapshot that fails its
|
|
108
|
+
* integrity check, on which the gate admits no MCP server at all
|
|
109
|
+
* (`mcpSnapshotUnverifiedRefusal`): a deleted record looks like one never
|
|
110
|
+
* set, so neither record can be vouched for.
|
|
111
|
+
*/
|
|
112
|
+
mcpRegistry: McpRegistryRecord | null;
|
|
113
|
+
/**
|
|
114
|
+
* The workspace's `mcpAllowedServers` list (the `@mcp_allowlist` record),
|
|
115
|
+
* or null when it set none. Null on a snapshot that fails its integrity
|
|
116
|
+
* check, as {@link mcpRegistry} is.
|
|
117
|
+
*/
|
|
118
|
+
mcpAllowlist: McpAllowlistRecord | null;
|
|
98
119
|
get healthMessage(): string;
|
|
99
120
|
}
|
|
100
121
|
export interface Decision {
|
|
101
122
|
/** `null` means allow. */
|
|
102
123
|
severity: Severity | null;
|
|
124
|
+
/** The rule's reason and `[id]`; for a hold, the reason alone, which the gate words as a hold. */
|
|
103
125
|
reason: string;
|
|
104
126
|
ruleId: string;
|
|
105
127
|
}
|
package/dist/snapshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAMH,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAA8C,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAKhG,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,kEAAkE;AAClE,eAAO,MAAM,UAAU,EAAG,QAAiB,CAAA;AAC3C,eAAO,MAAM,QAAQ,EAAG,MAAe,CAAA;AACvC,eAAO,MAAM,SAAS,EAAG,OAAgB,CAAA;AACzC;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAG,MAAe,CAAA;AAEvC,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,GAAG,OAAO,QAAQ,GAAG,OAAO,SAAS,GAAG,OAAO,QAAQ,CAAA;AAC/F;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAA;AACrF,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAA;AAE3E,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAAA;CAC1B;AAED,qBAAa,QAAQ;IACnB,KAAK,EAAE,IAAI,EAAE,CAAK;IAClB,MAAM,SAAS;IACf,KAAK,EAAE,aAAa,CAAW;IAC/B,WAAW,SAAK;IAChB,WAAW,SAAK;IAChB,OAAO,SAAI;IACX,sCAAsC;IACtC,YAAY,SAAI;IAChB;;;;;OAKG;IACH,SAAS,EAAE,cAAc,GAAG,IAAI,CAAO;IACvC;;;;;;OAMG;IACH,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAO;IAC5C;;;;OAIG;IACH,YAAY,EAAE,kBAAkB,GAAG,IAAI,CAAO;IAE9C,IAAI,aAAa,IAAI,MAAM,CAa1B;CACF;AAED,MAAM,WAAW,QAAQ;IACvB,0BAA0B;IAC1B,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,kGAAkG;IAClG,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAMD,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAYD,wBAAgB,YAAY,CAAC,WAAW,SAAK,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAuGtE;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,QAAQ,EACd,aAAa,UAAQ,GACpB,QAAQ,CAiEV;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
|
package/dist/snapshot.js
CHANGED
|
@@ -29,41 +29,20 @@
|
|
|
29
29
|
* * Snapshot health is reported as an event — "snapshot missing on 400
|
|
30
30
|
* machines" must not look identical to "snapshot present and healthy".
|
|
31
31
|
*
|
|
32
|
-
* ##
|
|
32
|
+
* ## Padding and case
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `^` (`' rm( +-[a-zA-Z-]+)+ +/( |\\*)'` requires a literal space before
|
|
47
|
-
* `rm`) — POSIX ERE has no `\b` and no reliable `^`/`$` inside the emitted
|
|
48
|
-
* shell gates, so the whole pattern table is authored against the padded
|
|
49
|
-
* contract. It is also why `policySnapshot.ts`'s `toGuardPattern` strips
|
|
50
|
-
* the `^`/`$` off an SOP `toolPattern` and re-wraps it as `' (name) '`
|
|
51
|
-
* before shipping it as a `subject: 'tool'` snapshot rule — that
|
|
52
|
-
* transformation only makes sense against a padded reader. Without
|
|
53
|
-
* padding, a command that BEGINS with the dangerous verb (`"rm -rf /"`,
|
|
54
|
-
* no leading space) never matches `' rm...'` at all, and the block
|
|
55
|
-
* silently never fires.
|
|
56
|
-
* 2. **Forced lowercasing.** `_normalise()` lowercases unconditionally; the
|
|
57
|
-
* real `NORMALISE_CONTRACT.jsSource` does not — case sensitivity is
|
|
58
|
-
* governed entirely by each rule's own `ignoreCase`/`i`-flag column.
|
|
59
|
-
* `bypass.env_kill_switch` (` [A-Z][A-Z0-9_]*_HOOKS?=`) and
|
|
60
|
-
* `destructive.chmod_recursive_root`/`chown_recursive_root`
|
|
61
|
-
* (`[a-zA-Z]*R[a-zA-Z]*`, a literal uppercase `R` for the recursive flag)
|
|
62
|
-
* both key on case WITHOUT setting that flag; forcing the subject to
|
|
63
|
-
* lowercase first makes them never fire, on real production patterns.
|
|
64
|
-
*
|
|
65
|
-
* This reader pads and preserves case, matching the real shipped contract;
|
|
66
|
-
* see the fidelity suite for the evidence.
|
|
34
|
+
* Normalisation pads the subject with a space at each end and collapses
|
|
35
|
+
* whitespace, and does not lowercase, as the shipped gates do
|
|
36
|
+
* (`NORMALISE_CONTRACT` in `protectedPaths.ts`). Padding is what lets a
|
|
37
|
+
* floor/destructive pattern use a plain leading space or non-word character as
|
|
38
|
+
* a stand-in for `^` (POSIX ERE has no `\b` and no reliable `^`/`$` inside the
|
|
39
|
+
* emitted shell gates), so without it a command that BEGINS with the dangerous
|
|
40
|
+
* verb (`"rm -rf /"`, `"DROP TABLE users"`) never matches. Case sensitivity is
|
|
41
|
+
* each rule's own `ignoreCase` flag: `bypass.env_kill_switch` and the
|
|
42
|
+
* recursive chmod/chown rules key on an uppercase letter without one. The
|
|
43
|
+
* Python SDK's reader once lowercased and did not pad, and missed both; it now
|
|
44
|
+
* follows the same contract, and `destructive-sql-vectors.json` in
|
|
45
|
+
* shared-types runs both readers over the same commands.
|
|
67
46
|
*
|
|
68
47
|
* The regex-dialect divergence the Python module documents (`.rules` patterns
|
|
69
48
|
* are authored as JavaScript regexes; compiling them with a different engine
|
|
@@ -75,11 +54,21 @@ import { createHash } from 'node:crypto';
|
|
|
75
54
|
import { readFileSync } from 'node:fs';
|
|
76
55
|
import { homedir } from 'node:os';
|
|
77
56
|
import { join } from 'node:path';
|
|
57
|
+
import { decodeMcpAllowlistRecord, decodeMcpRegistryRecord, MCP_ALLOWLIST_RECORD_TAG, MCP_REGISTRY_RECORD_TAG, } from './mcpRegistryRecord.js';
|
|
58
|
+
import { decodeSsoGroupRecord, SSO_GROUP_RECORD_TAG } from './ssoGroups.js';
|
|
59
|
+
import { hasPhrase, phraseText } from './phrases.js';
|
|
60
|
+
import { classify } from './actions.js';
|
|
61
|
+
import { compileSequence, sequenceMatch } from './sequence.js';
|
|
78
62
|
export const SNAPSHOT_STALE_AFTER_DAYS = 7;
|
|
79
63
|
/** Severity tiers, in the order the shipped gate handles them. */
|
|
80
64
|
export const SEV_SHADOW = 'shadow';
|
|
81
65
|
export const SEV_WARN = 'warn';
|
|
82
66
|
export const SEV_BLOCK = 'block';
|
|
67
|
+
/**
|
|
68
|
+
* A hold rule (a `REQUIRE_APPROVAL:` SOP, or a local `review_before:` token):
|
|
69
|
+
* refuses like a block, but asks a person rather than denying — see hold.ts.
|
|
70
|
+
*/
|
|
71
|
+
export const SEV_HOLD = 'hold';
|
|
83
72
|
export class Snapshot {
|
|
84
73
|
rules = [];
|
|
85
74
|
digest = 'none';
|
|
@@ -89,12 +78,33 @@ export class Snapshot {
|
|
|
89
78
|
ageDays = 0;
|
|
90
79
|
/** Regexes that would not compile. */
|
|
91
80
|
droppedRules = 0;
|
|
81
|
+
/**
|
|
82
|
+
* The workspace's SSO-group policy and the member this snapshot was issued
|
|
83
|
+
* to, or null when the workspace has no group policy. On a snapshot that
|
|
84
|
+
* fails its integrity check the member is null — groups unknown — so an
|
|
85
|
+
* edited group list in the file clears nothing.
|
|
86
|
+
*/
|
|
87
|
+
ssoGroups = null;
|
|
88
|
+
/**
|
|
89
|
+
* The workspace's MCP server registry decisions (the `@mcp_registry`
|
|
90
|
+
* record), or null when it made none. Null on a snapshot that fails its
|
|
91
|
+
* integrity check, on which the gate admits no MCP server at all
|
|
92
|
+
* (`mcpSnapshotUnverifiedRefusal`): a deleted record looks like one never
|
|
93
|
+
* set, so neither record can be vouched for.
|
|
94
|
+
*/
|
|
95
|
+
mcpRegistry = null;
|
|
96
|
+
/**
|
|
97
|
+
* The workspace's `mcpAllowedServers` list (the `@mcp_allowlist` record),
|
|
98
|
+
* or null when it set none. Null on a snapshot that fails its integrity
|
|
99
|
+
* check, as {@link mcpRegistry} is.
|
|
100
|
+
*/
|
|
101
|
+
mcpAllowlist = null;
|
|
92
102
|
get healthMessage() {
|
|
93
103
|
switch (this.state) {
|
|
94
104
|
case 'absent':
|
|
95
105
|
return 'No policy snapshot — built-in protections only';
|
|
96
106
|
case 'invalid':
|
|
97
|
-
return 'Policy snapshot failed its digest or workspace check — dynamic rules dropped';
|
|
107
|
+
return 'Policy snapshot failed its digest or workspace check — dynamic rules dropped except SSO-group refusals, and every MCP call refused';
|
|
98
108
|
case 'empty':
|
|
99
109
|
return 'Policy snapshot contains no rules — the compile produced nothing';
|
|
100
110
|
case 'stale':
|
|
@@ -114,8 +124,7 @@ export function snapshotPath() {
|
|
|
114
124
|
/**
|
|
115
125
|
* Collapse and pad whitespace, as the shipped gates do — case is
|
|
116
126
|
* DELIBERATELY left untouched; a rule that wants case-insensitivity sets its
|
|
117
|
-
* own `ignoreCase` flag. See the module doc comment
|
|
118
|
-
* SDK's reader diverges from this.
|
|
127
|
+
* own `ignoreCase` flag. See the module doc comment.
|
|
119
128
|
*/
|
|
120
129
|
function normalise(value) {
|
|
121
130
|
const s = value === null || value === undefined ? '' : String(value);
|
|
@@ -147,17 +156,32 @@ export function loadSnapshot(workspaceId = '', path) {
|
|
|
147
156
|
}
|
|
148
157
|
if (!line || line.startsWith('#'))
|
|
149
158
|
continue;
|
|
159
|
+
if (line.startsWith(`${SSO_GROUP_RECORD_TAG}\t`)) {
|
|
160
|
+
snap.ssoGroups = decodeSsoGroupRecord(line);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (line.startsWith(`${MCP_REGISTRY_RECORD_TAG}\t`)) {
|
|
164
|
+
snap.mcpRegistry = decodeMcpRegistryRecord(line);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (line.startsWith(`${MCP_ALLOWLIST_RECORD_TAG}\t`)) {
|
|
168
|
+
snap.mcpAllowlist = decodeMcpAllowlistRecord(line);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
150
171
|
const f = line.split('\t');
|
|
151
172
|
// Column order: id, severity, flags, subject, reason, source(regex), [argPatternB64].
|
|
173
|
+
// Flags: i = case-insensitive, s = a sequence rule.
|
|
152
174
|
if (f.length < 6 || !f[5])
|
|
153
175
|
continue;
|
|
176
|
+
const ic = f[2].includes('i');
|
|
154
177
|
try {
|
|
155
178
|
snap.rules.push({
|
|
156
179
|
id: f[0],
|
|
157
180
|
severity: f[1],
|
|
158
181
|
subject: f[3] || 'any',
|
|
159
182
|
reason: f[4],
|
|
160
|
-
pattern: new RegExp(f[5],
|
|
183
|
+
pattern: new RegExp(f[5], ic ? 'i' : ''),
|
|
184
|
+
steps: f[2].includes('s') ? compileSequence(f[5], ic) : null,
|
|
161
185
|
});
|
|
162
186
|
}
|
|
163
187
|
catch {
|
|
@@ -172,7 +196,9 @@ export function loadSnapshot(workspaceId = '', path) {
|
|
|
172
196
|
.filter((l) => l && !l.startsWith('#'))
|
|
173
197
|
.join('\n');
|
|
174
198
|
const actual = createHash('sha256').update(body, 'utf-8').digest('hex').slice(0, 32);
|
|
175
|
-
|
|
199
|
+
// No digest line is unverified too: the sync daemon always writes one, so
|
|
200
|
+
// its absence means the file was edited.
|
|
201
|
+
if (actual !== snap.digest) {
|
|
176
202
|
snap.state = 'invalid';
|
|
177
203
|
}
|
|
178
204
|
if (snap.state === 'ok' && snap.workspaceId && workspaceId && snap.workspaceId !== workspaceId) {
|
|
@@ -185,6 +211,13 @@ export function loadSnapshot(workspaceId = '', path) {
|
|
|
185
211
|
}
|
|
186
212
|
if (snap.state === 'invalid') {
|
|
187
213
|
snap.rules = []; // additive tier — dropping it returns to yesterday's behaviour
|
|
214
|
+
// Except the group policy, which only ever refuses: it still applies, to a
|
|
215
|
+
// member whose groups this gate can no longer vouch for.
|
|
216
|
+
if (snap.ssoGroups)
|
|
217
|
+
snap.ssoGroups = { ...snap.ssoGroups, member: null };
|
|
218
|
+
// Neither MCP record can be vouched for: the gate admits no MCP server.
|
|
219
|
+
snap.mcpRegistry = null;
|
|
220
|
+
snap.mcpAllowlist = null;
|
|
188
221
|
}
|
|
189
222
|
if (snap.state === 'ok' && snap.generatedAt) {
|
|
190
223
|
const t = Date.parse(snap.generatedAt);
|
|
@@ -209,16 +242,36 @@ export function evaluate(toolName, target, command, snap, guardDisabled = false)
|
|
|
209
242
|
const nTool = normalise(toolName);
|
|
210
243
|
const nCommand = normalise(command);
|
|
211
244
|
const nTarget = normalise(target);
|
|
245
|
+
let words = null;
|
|
246
|
+
// The command's action tokens, space-padded as the hook gates write them, so
|
|
247
|
+
// a hold on `action:deploy` matches whole tokens. Classified with the same
|
|
248
|
+
// needles and phrase matcher as the hook gates and the proxy's actions.rs.
|
|
249
|
+
let actions = null;
|
|
212
250
|
for (const rule of rules) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
251
|
+
if (rule.subject === 'phrase') {
|
|
252
|
+
// The source is `|`-separated phrases matched as words against the raw
|
|
253
|
+
// command (phrases.ts), not a regex: a backtracking regex for "whatever
|
|
254
|
+
// separates the words" is super-linear on crafted input.
|
|
255
|
+
words ??= phraseText(command);
|
|
256
|
+
const w = words;
|
|
257
|
+
if (!rule.pattern.source.split('|').some((p) => hasPhrase(w, p, true)))
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (rule.subject === 'action')
|
|
261
|
+
actions ??= ' ' + classify(toolName, { command }).join(' ') + ' ';
|
|
262
|
+
const subjects = rule.subject === 'phrase'
|
|
263
|
+
? [nCommand]
|
|
264
|
+
: rule.subject === 'action'
|
|
265
|
+
? [actions]
|
|
266
|
+
: rule.subject === 'tool'
|
|
267
|
+
? [nTool]
|
|
268
|
+
: rule.subject === 'command'
|
|
269
|
+
? [nCommand]
|
|
270
|
+
: rule.subject === 'target'
|
|
271
|
+
? [nTarget]
|
|
272
|
+
: [nCommand, nTarget];
|
|
220
273
|
for (const subject of subjects) {
|
|
221
|
-
if (!rule.pattern.test(subject))
|
|
274
|
+
if (rule.subject !== 'phrase' && !(rule.steps ? sequenceMatch(rule.steps, subject) : rule.pattern.test(subject)))
|
|
222
275
|
continue;
|
|
223
276
|
if (rule.severity === SEV_SHADOW) {
|
|
224
277
|
return { severity: SEV_SHADOW, reason: `${rule.reason} [${rule.id}]`, ruleId: rule.id };
|
|
@@ -231,7 +284,12 @@ export function evaluate(toolName, target, command, snap, guardDisabled = false)
|
|
|
231
284
|
ruleId: rule.id,
|
|
232
285
|
};
|
|
233
286
|
}
|
|
234
|
-
|
|
287
|
+
if (rule.severity === SEV_HOLD) {
|
|
288
|
+
return { severity: SEV_HOLD, reason: rule.reason, ruleId: rule.id };
|
|
289
|
+
}
|
|
290
|
+
// Any other severity blocks, as an unknown one does in the shipped
|
|
291
|
+
// gates: a severity this reader does not know must not allow. The
|
|
292
|
+
// rule's own reason, not a generic one — resolveSeverity reads it.
|
|
235
293
|
return { severity: SEV_BLOCK, reason: `${rule.reason} [${rule.id}]`, ruleId: rule.id };
|
|
236
294
|
}
|
|
237
295
|
}
|
package/dist/snapshot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSO-group tool clearance, read from the policy snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Port of `evaluateSsoGroupClearance` and the `@sso_groups` record in
|
|
5
|
+
* `packages/shared-types/src/ssoGroupClearance.ts` — the evaluator the control
|
|
6
|
+
* plane's hook gate, the MCP proxy and the sync daemon's snapshot compiler
|
|
7
|
+
* share. This package has no workspace dependencies, so it carries a copy, and
|
|
8
|
+
* `src/__tests__/ssoGroups.test.ts` runs the shared vectors
|
|
9
|
+
* (`packages/shared-types/fixtures/sso-group-clearance-vectors.json`) through
|
|
10
|
+
* it so the copy cannot drift. `intutic_clawde/gate/sso_groups.py` is the
|
|
11
|
+
* Python port of the same thing.
|
|
12
|
+
*
|
|
13
|
+
* The snapshot's record carries the workspace's policy and the member the
|
|
14
|
+
* snapshot was issued to, with their groups as the control plane resolved
|
|
15
|
+
* them. It sits inside the snapshot's digest; when the digest fails, the gate
|
|
16
|
+
* still applies the policy but treats the member's groups as unknown, so an
|
|
17
|
+
* edited group list clears nothing.
|
|
18
|
+
*/
|
|
19
|
+
export type SsoGroupClearance = 'GRANTED' | 'DENIED' | 'REQUIRES_OBO';
|
|
20
|
+
export interface SsoGroupPolicy {
|
|
21
|
+
highRiskTools: string[];
|
|
22
|
+
requiredGroups: string[];
|
|
23
|
+
requireOboFor: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface SsoGroupRecord {
|
|
26
|
+
policy: SsoGroupPolicy;
|
|
27
|
+
member: {
|
|
28
|
+
memberId: string;
|
|
29
|
+
ssoGroups: string[];
|
|
30
|
+
} | null;
|
|
31
|
+
issuedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SsoGroupDecision {
|
|
34
|
+
clearance: SsoGroupClearance;
|
|
35
|
+
ruleId: string | null;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const SSO_GROUP_RECORD_TAG = "@sso_groups";
|
|
39
|
+
export declare function parseSsoGroupPolicy(value: unknown): SsoGroupPolicy | null;
|
|
40
|
+
/**
|
|
41
|
+
* Whether a call named `name` is the tool a policy entry names: exactly, or,
|
|
42
|
+
* for an entry that is an MCP tool's own name (not starting with `mcp__`), as
|
|
43
|
+
* the name a harness gives it on any server, `mcp__<server>__<entry>`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ssoGroupToolMatches(entry: string, name: string): boolean;
|
|
46
|
+
/** `memberGroups` null means the groups are unknown — a high-risk tool is then refused. */
|
|
47
|
+
export declare function evaluateSsoGroupClearance(policy: SsoGroupPolicy | null, toolName: string, memberGroups: readonly string[] | null): SsoGroupDecision;
|
|
48
|
+
/** Reads the snapshot's record line; null for any other line or a damaged one. */
|
|
49
|
+
export declare function decodeSsoGroupRecord(line: string): SsoGroupRecord | null;
|
|
50
|
+
//# sourceMappingURL=ssoGroups.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssoGroups.d.ts","sourceRoot":"","sources":["../src/ssoGroups.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAA;AAErE,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,aAAa,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;IACxD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,iBAAiB,CAAA;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,eAAO,MAAM,oBAAoB,gBAAgB,CAAA;AAMjD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAQzE;AAMD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAKxE;AAOD,2FAA2F;AAC3F,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,cAAc,GAAG,IAAI,EAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,GACrC,gBAAgB,CAuBlB;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAgBxE"}
|