@yanlinglabs/winter-agent-runtime 0.0.41 → 0.0.44
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/commands/resolver.d.ts +8 -13
- package/dist/context/agent-listing.d.ts +7 -11
- package/dist/context/attachments.d.ts +29 -23
- package/dist/context/dynamic-sections.d.ts +5 -5
- package/dist/context/git-branch-fixture.d.ts +22 -0
- package/dist/context/git-status.d.ts +2 -2
- package/dist/context/memory.d.ts +1 -1
- package/dist/context/output-styles.d.ts +2 -2
- package/dist/context/request-layout.d.ts +16 -17
- package/dist/context/seam.d.ts +7 -6
- package/dist/embedded-host.js +1 -1
- package/dist/embedded-worker.js +4 -4
- package/dist/embedded.js +3 -3
- package/dist/engine.d.ts +3 -3
- package/dist/{index-97t2rmtf.js → index-dne3dw18.js} +11 -0
- package/dist/{index-z9yngxj4.js → index-hbkdq3xs.js} +1072 -827
- package/dist/{index-hdvvezf8.js → index-r1pter2v.js} +239 -155
- package/dist/{index-ey5c52j3.js → index-x7hdare6.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp/lifecycle.d.ts +2 -1
- package/dist/permissions/edit-recognition.d.ts +1 -1
- package/dist/permissions/evaluator.d.ts +19 -30
- package/dist/permissions/file-rules.d.ts +177 -277
- package/dist/permissions/grammar.d.ts +58 -0
- package/dist/permissions/policy-state.d.ts +1 -1
- package/dist/permissions/ruleset.d.ts +1 -1
- package/dist/permissions/shell-structure.d.ts +1 -1
- package/dist/plugins/bundle.d.ts +4 -6
- package/dist/plugins/loader.corpus-fixture.d.ts +26 -0
- package/dist/plugins/loader.d.ts +3 -4
- package/dist/plugins/manifest.d.ts +23 -46
- package/dist/production-wiring.d.ts +1 -1
- package/dist/protocol/channel.d.ts +6 -0
- package/dist/provider/lean-prompt.d.ts +1 -6
- package/dist/sandbox/profile.d.ts +39 -79
- package/dist/sandbox/spawn.d.ts +5 -6
- package/dist/skills/listing.d.ts +8 -26
- package/dist/skills/store.d.ts +2 -3
- package/dist/store/dialect.d.ts +1 -1
- package/dist/subagents/availability.d.ts +10 -8
- package/dist/subagents/child-handle.d.ts +5 -5
- package/dist/subagents/definitions.d.ts +7 -6
- package/dist/subagents/fork.d.ts +4 -8
- package/dist/subagents/notification-queue.d.ts +30 -38
- package/dist/testing.js +3 -3
- package/dist/tools/descriptors/web-fetch.d.ts +1 -1
- package/dist/tools/descriptors/web-search.d.ts +1 -1
- package/dist/tools/impl/_search-budget.d.ts +1 -1
- package/dist/tools/impl/_web-fetch-net.d.ts +6 -6
- package/dist/tools/impl/_web-search-assembler.d.ts +7 -20
- package/dist/tools/impl/background-task-runtime.d.ts +13 -13
- package/dist/tools/impl/bash.d.ts +3 -3
- package/dist/tools/impl/monitor.d.ts +2 -2
- package/dist/toolsearch/exposure.d.ts +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/web/fetchable-url.d.ts +1 -1
- package/dist/web/preapproved-hosts.d.ts +9 -31
- package/dist/workflows/store.d.ts +23 -31
- package/package.json +4 -4
|
@@ -1,84 +1,56 @@
|
|
|
1
1
|
export type FileRuleKind = "edit" | "read";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* data model. Claude's own WRITE decision function (`zC`) ALWAYS consults `"edit"`-kind rules,
|
|
8
|
-
* regardless of which literal write-shaped tool called it -- this is what makes an `Edit(...)` ask
|
|
9
|
-
* rule fire before a **Write** on claude (the router's own SV-7 measurement), and what makes a
|
|
10
|
-
* `Write(...)`-toolName rule a Winter-only spelling with no claude analogue at all: `ub` filtering
|
|
11
|
-
* on the literal string "Write" never runs, because nothing ever calls it with that string.
|
|
12
|
-
*
|
|
13
|
-
* Every `FILE_RULE_TOOLS` member (grammar.ts) routes to exactly one kind, on every direction (allow,
|
|
14
|
-
* ask, deny alike -- the ruling's own "for both ALLOW and DENY" instruction, extended to ask since
|
|
15
|
-
* ask shares deny's conservative "cross tools" posture throughout this codebase already).
|
|
3
|
+
* File rules come in exactly two kinds, `edit` and `read`. Every write-shaped tool (Edit, Write,
|
|
4
|
+
* NotebookEdit) is checked against the `edit` kind and every read-shaped one (Read, Glob, Grep)
|
|
5
|
+
* against the `read` kind, on every direction (allow, ask, deny). So an `Edit(...)` ask rule fires
|
|
6
|
+
* before a Write, as it does in Claude Code.
|
|
16
7
|
*/
|
|
17
8
|
export declare function fileRuleKindFor(toolName: string): FileRuleKind | undefined;
|
|
18
9
|
/**
|
|
19
|
-
* The ONE
|
|
20
|
-
*
|
|
21
|
-
* (`
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* `Grep(...)` is dead code claude never reads for ANY call -- not even a call from that SAME literal
|
|
25
|
-
* tool -- because `ub` was never invoked with that string. `Write`/`NotebookEdit`/`Glob`/`Grep`
|
|
26
|
-
* remain valid rule-authoring tool names SYNTACTICALLY (grammar.ts's `FILE_RULE_TOOLS` still parses
|
|
27
|
-
* them -- Winter does not forbid authoring one), but this function is what `findMatchingFileRuleEntry`
|
|
28
|
-
* (evaluator.ts) filters CANDIDATES with, so only `Edit(...)`/`Read(...)`-authored rules ever reach
|
|
29
|
-
* a group.
|
|
10
|
+
* The ONE tool name a rule must be written under to be consulted for `kind`: `Edit` for `edit`,
|
|
11
|
+
* `Read` for `read`. A rule written as `Write(...)`, `NotebookEdit(...)`, `Glob(...)` or `Grep(...)`
|
|
12
|
+
* still parses (grammar.ts's `FILE_RULE_TOOLS`), but is never consulted for any call -- not even one
|
|
13
|
+
* from that same tool -- matching Claude Code's rule grammar, where only `Edit(...)` and `Read(...)`
|
|
14
|
+
* rules are file rules. `findMatchingFileRuleEntry` (evaluator.ts) filters candidates with this.
|
|
30
15
|
*/
|
|
31
16
|
export declare function canonicalFileRuleAuthoringToolName(kind: FileRuleKind): "Edit" | "Read";
|
|
32
|
-
/** A sentinel distinct from `null` ("resolve against cwd"): a `/`-anchored rule with no
|
|
17
|
+
/** A sentinel distinct from `null` ("resolve against cwd"): a `/`-anchored rule with no settings-source directory is INERT, and never falls back to cwd. */
|
|
33
18
|
declare const INERT_ANCHOR: unique symbol;
|
|
34
19
|
export interface FileRuleAnchor {
|
|
35
20
|
/** The pattern text, relative to `root`, in `ignore`-package (gitignore) grammar. */
|
|
36
21
|
relativePattern: string;
|
|
37
|
-
/** `null` means "resolve against cwd"
|
|
22
|
+
/** `null` means "resolve against cwd"; the sentinel means the rule can never match anything. */
|
|
38
23
|
root: string | null | typeof INERT_ANCHOR;
|
|
39
24
|
}
|
|
40
25
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* `sourceDir` is claude's `bl(source)` -- the settings-source-derived root for a `/`-anchored rule.
|
|
53
|
-
* Since fix round 11 a settings-tier rule carries it (`SourcedRuleEntry.sourceDir`, set by
|
|
54
|
-
* production-wiring.ts's `buildSettingsRuleSeed` per claude's `Wyt`), and a `/`-anchored rule resolves
|
|
55
|
-
* against it. When it is absent -- a rule from Options, canUseTool or a plugin -- the rule is inert
|
|
56
|
-
* (the `INERT_ANCHOR` root below, treated as "no group to match against" by `matchFileRulesGrouped`).
|
|
26
|
+
* Resolves a pattern's anchor spelling to the root it is relative to and the pattern text relative
|
|
27
|
+
* to that root:
|
|
28
|
+
* - `//x` -> root `/`, pattern `/x` (one slash dropped, the leading `/` KEPT);
|
|
29
|
+
* - `~/x` -> root `home`, pattern `/x` (the `~` dropped, the leading `/` KEPT);
|
|
30
|
+
* - `/x` -> root `sourceDir` (the directory of the settings file the rule came from), pattern
|
|
31
|
+
* `/x` unchanged; with no `sourceDir` -- a rule from Options, canUseTool or a plugin --
|
|
32
|
+
* the rule is INERT (`INERT_ANCHOR`) and matches nothing;
|
|
33
|
+
* - `./x` -> root cwd (`null`), pattern `x` (the `./` dropped, NO leading `/`);
|
|
34
|
+
* - else -> root cwd (`null`), pattern unchanged (a bare `~` included: it is a file named `~`).
|
|
35
|
+
* The kept leading `/` is what anchors a pattern to its root in gitignore terms; its absence is what
|
|
36
|
+
* lets `deny ./.env` also reach `pkg/.env`.
|
|
57
37
|
*/
|
|
58
38
|
export declare function resolveFileRuleAnchor(pattern: string, opts: {
|
|
59
39
|
home: string;
|
|
60
40
|
sourceDir?: string | undefined;
|
|
61
41
|
}): FileRuleAnchor;
|
|
62
42
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* to; a real Seatbelt `subpath` already means "this directory and everything under it," so it needs
|
|
67
|
-
* ONE real path, never a pattern.
|
|
43
|
+
* A Read/Edit rule's pattern resolved to ONE absolute filesystem path, for the sandbox's `subpath`
|
|
44
|
+
* rules (sandbox/profile.ts's `denyWritePaths`/`denyReadPaths`/`writableRoots`), which take a real
|
|
45
|
+
* path, not a pattern; `subpath` already means "this directory and everything under it".
|
|
68
46
|
*
|
|
69
|
-
* `undefined`
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* - the pattern is genuinely GLOB-SHAPED once a single TRAILING `/**` is stripped (redundant with
|
|
77
|
-
* `subpath`'s own "and everything under it" semantics, so it is not itself disqualifying --
|
|
78
|
-
* `Edit(//repo/secrets/**)` becomes the plain path `/repo/secrets`) -- a glob ANYWHERE else
|
|
79
|
-
* (`src/*.ts`, `[wip]`, `a?b`) cannot become one exact path at all.
|
|
80
|
-
* A caller that gets `undefined` back simply does not add this rule to the sandbox's own filesystem
|
|
81
|
-
* lists; the permission-rule layer (`evaluate()`) still enforces it in full, exactly as it always has.
|
|
47
|
+
* `undefined` when there is no such path:
|
|
48
|
+
* - the pattern is INERT (a `/`-anchored rule with no settings-source directory);
|
|
49
|
+
* - the pattern is still glob-shaped once ONE trailing `/**` is stripped (`Edit(//repo/secrets/**)`
|
|
50
|
+
* becomes the plain path `/repo/secrets`; `src/*.ts`, `[wip]`, `a?b` cannot become one path).
|
|
51
|
+
* A glob-shaped write-ALLOW entry is simply left out of the sandbox, which then does not try to
|
|
52
|
+
* restrict writes through it; the permission-rule layer (`evaluate()`) still enforces the rule in
|
|
53
|
+
* full.
|
|
82
54
|
*/
|
|
83
55
|
export declare function resolveFileRuleAbsolutePath(pattern: string, opts: {
|
|
84
56
|
cwd: string;
|
|
@@ -86,155 +58,132 @@ export declare function resolveFileRuleAbsolutePath(pattern: string, opts: {
|
|
|
86
58
|
sourceDir?: string;
|
|
87
59
|
}): string | undefined;
|
|
88
60
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* still stripped first, identically, since `subpath`'s/the recursive-regex-suffix's own "and
|
|
93
|
-
* everything under it" semantics already cover it). `undefined` only for the one case that has no
|
|
94
|
-
* absolute form at all -- an INERT `/`-anchored pattern with no resolvable settings-source root,
|
|
95
|
-
* unchanged from `resolveFileRuleAbsolutePath`'s own posture.
|
|
61
|
+
* The sibling of `resolveFileRuleAbsolutePath` that KEEPS a glob-shaped pattern: the same anchor and
|
|
62
|
+
* root, the same stripping of one redundant trailing `/**`, but the absolute text comes back with any
|
|
63
|
+
* remaining glob characters intact. `undefined` only for an INERT pattern.
|
|
96
64
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
100
|
-
*
|
|
101
|
-
* unenforced by the sandbox layer. Callers check `isGlobShapedFileRulePattern` on the result to
|
|
102
|
-
* decide `subpath` vs a `globToSbplRegexSource`/`recursiveGlobToSbplRegexSource` conversion; ALLOW
|
|
103
|
-
* entries keep using `resolveFileRuleAbsolutePath` (glob-shaped dropped), per the controller's own
|
|
104
|
-
* explicit ruling: "Dropping glob-shaped ALLOW rules stays as it is, because that's stricter."
|
|
65
|
+
* Used for DENY entries: a glob-shaped deny is not dropped from the sandbox but rendered as an SBPL
|
|
66
|
+
* `(regex ...)` clause. Callers test the result with `isGlobShapedFileRulePattern` to choose between
|
|
67
|
+
* `subpath` and `globToSbplRegexSource`/`recursiveGlobToSbplRegexSource`. ALLOW entries keep using
|
|
68
|
+
* `resolveFileRuleAbsolutePath` (glob-shaped ones dropped), which is the stricter choice.
|
|
105
69
|
*/
|
|
106
70
|
export declare function resolveFileRuleAbsoluteGlobText(pattern: string, opts: {
|
|
107
71
|
cwd: string;
|
|
108
72
|
home: string;
|
|
109
73
|
sourceDir?: string;
|
|
110
74
|
}): string | undefined;
|
|
75
|
+
/** True when `text` holds any of `* ? [ ]`. The sandbox deny split classifies with `scanDenyPathGlob` instead (below). */
|
|
76
|
+
export declare function isGlobShapedFileRulePattern(text: string): boolean;
|
|
111
77
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
78
|
+
* Whether a realpath result for a glob's fixed prefix should be REJECTED (and the prefix kept as
|
|
79
|
+
* written). Both sides are first tidied with POSIX `normalize` (which keeps a trailing slash).
|
|
80
|
+
*
|
|
81
|
+
* The written path stands for one or two acceptable spellings: itself, and, when it lies under
|
|
82
|
+
* `/tmp/` or `/var/`, the same path under `/private` (macOS's real location for both). A resolution
|
|
83
|
+
* is trusted only when it is one of those spellings, or sits strictly BELOW one of them, and is not
|
|
84
|
+
* also shallow (the root, `.`, or a single top-level name) or an ancestor of one of them. Anything
|
|
85
|
+
* else -- climbing up, moving sideways, landing somewhere unrelated -- is rejected. Plain string
|
|
86
|
+
* comparisons only; no case folding, no filesystem access.
|
|
116
87
|
*/
|
|
117
|
-
export declare function
|
|
88
|
+
export declare function isSuspiciousRealpathResolution(original: string, resolved: string): boolean;
|
|
118
89
|
/**
|
|
119
|
-
*
|
|
120
|
-
* `
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
90
|
+
* Every ANCESTOR directory of `path`, nearest first: `path.dirname` applied repeatedly, starting from
|
|
91
|
+
* `path`'s parent and stopping before `/` or `.` (neither is included, nor is `path` itself), or when
|
|
92
|
+
* `dirname` stops changing the value.
|
|
93
|
+
*
|
|
94
|
+
* Feeds the sandbox's ancestor-rename fence: the profile also denies unlinking/creating every
|
|
95
|
+
* ancestor of a denied path (and of a glob deny's fixed prefix), so `mv <ancestor> <elsewhere>`,
|
|
96
|
+
* a write inside, and a rename back cannot slip a write past the deny.
|
|
126
97
|
*/
|
|
127
98
|
export declare function ancestorDirectoriesOf(path: string): string[];
|
|
99
|
+
/**
|
|
100
|
+
* An absolute glob as a whole-path SBPL regex. The fixed prefix is canonicalised first
|
|
101
|
+
* (`canonicalizedGlobFixedPrefix`) and spliced back as ESCAPED LITERAL text (`escapeRegexLiteralPath`);
|
|
102
|
+
* only the rest goes through `globToAnchoredRegex`. The prefix may hold `[`/`]` (it runs through
|
|
103
|
+
* one-character classes, `scanDenyPathGlob`), which must not become a class again. A glob-free text
|
|
104
|
+
* (reachable only by a direct call; the deny split sends it to `(subpath …)`) renders as the whole
|
|
105
|
+
* escaped, canonicalised literal.
|
|
106
|
+
*/
|
|
128
107
|
export declare function globToSbplRegexSource(absoluteGlob: string): string;
|
|
129
108
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* (a plain, non-glob deny already renders as `subpath`, which covers a directory AND everything under
|
|
134
|
-
* it with no extra syntax). claude's own `dR` (the deny-clause builder, same dump region) always uses
|
|
135
|
-
* this recursive form for a glob-shaped deny's OWN base clause -- never the bare `Li`/
|
|
136
|
-
* `globToSbplRegexSource` form, which claude reserves for an ALLOW-carve-out entry nested inside a
|
|
137
|
-
* deny (a feature this port does not carry -- see `resolveFileRuleAbsoluteGlobText`'s own header).
|
|
109
|
+
* `globToSbplRegexSource`, widened to also match everything BELOW a match: its final `$` is replaced
|
|
110
|
+
* by `(/.*)?$`. The regex counterpart of `subpath`'s "and everything under it"; a glob-shaped deny's
|
|
111
|
+
* own clause always uses this form.
|
|
138
112
|
*/
|
|
139
113
|
export declare function recursiveGlobToSbplRegexSource(absoluteGlob: string): string;
|
|
140
114
|
/**
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* "and everything under it" semantics and claude's own `dR`, which always uses the recursive form for
|
|
148
|
-
* a deny's own base clause). Called once per caller (tools/impl/bash.ts, tools/impl/monitor.ts) --
|
|
149
|
-
* kept as one shared, tested primitive rather than two hand-copies, per this codebase's own
|
|
150
|
-
* "a second copy would be exactly the kind of drift risk this whole phase's review lens exists to
|
|
151
|
-
* catch" precedent (evaluator.ts's `extractCandidateWritePaths`, verbatim).
|
|
115
|
+
* The ONE place a plain `sandbox.filesystem.denyWrite`/`denyRead` list (settings.json's own,
|
|
116
|
+
* user-typed, and `deriveSandboxPathsFromRules`' rule-derived denies, which may be glob-shaped -- see
|
|
117
|
+
* `resolveFileRuleAbsoluteGlobText`) is split by glob shape before reaching `SeatbeltProfileInput`/
|
|
118
|
+
* `RunCommandOptions`: a non-glob entry stays a plain path (`subpath`); a glob-shaped one becomes a
|
|
119
|
+
* `recursiveGlobToSbplRegexSource` regex. Shared by tools/impl/bash.ts and tools/impl/monitor.ts
|
|
120
|
+
* rather than copied into each.
|
|
152
121
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* `
|
|
156
|
-
* (`
|
|
157
|
-
*
|
|
158
|
-
* above) to close the bypass; a glob-shaped deny ALSO needs its fixed prefix walked, and the prefix
|
|
159
|
-
* itself added as a literal deny target (claude's own `Ch` adds both).
|
|
122
|
+
* Also returns `globFixedPrefixes`: for each glob-shaped entry, its canonicalised fixed-prefix
|
|
123
|
+
* directory, dropped when it is `/`. The ancestor-rename fence
|
|
124
|
+
* (`SeatbeltProfileInput.denyWriteGlobFixedPrefixes`/`denyReadGlobFixedPrefixes`) walks a plain
|
|
125
|
+
* entry's own ancestors (`ancestorDirectoriesOf`); a glob-shaped deny also needs its fixed prefix
|
|
126
|
+
* walked, and the prefix itself denied as a literal target.
|
|
160
127
|
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* path spelled for the glob grammar) lands in `paths` UNESCAPED (`/x/[wip] app/.winter/skills`), and a
|
|
164
|
-
* glob entry's fixed prefix runs through such classes. Winter-only hardening (R.3 C-1); claude's
|
|
165
|
-
* `Rt`/`Li` stop at the first `[` -- see `scanDenyPathGlob`'s own header.
|
|
128
|
+
* Classified by `scanDenyPathGlob`: an entry whose only glob syntax is one-character bracket classes
|
|
129
|
+
* (`/x/[[]wip] app/.winter/skills`) lands in `paths` UNESCAPED (`/x/[wip] app/.winter/skills`).
|
|
166
130
|
*/
|
|
167
131
|
export declare function splitDenyPathsByGlobShape(paths: readonly string[]): {
|
|
168
132
|
paths: string[];
|
|
169
133
|
regexes: string[];
|
|
170
134
|
globFixedPrefixes: string[];
|
|
171
135
|
};
|
|
172
|
-
/** One glob-shaped deny entry
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* always the literal string `"/"` in that case (claude's own `Rh` returns `"/"` too, and `fR`'s own
|
|
177
|
-
* skip/ancestor logic reads that value directly rather than treating "no prefix" as a distinct case). */
|
|
136
|
+
/** One glob-shaped deny entry: its recursive SBPL regex PAIRED with its own fixed-prefix directory.
|
|
137
|
+
* `splitDenyPathsByGlobShape`'s `regexes`/`globFixedPrefixes` are two independently filtered arrays
|
|
138
|
+
* (the latter drops a `/` prefix) with no positional correspondence; `fixedPrefix` here is never
|
|
139
|
+
* dropped -- it is the string `"/"` when there is no deeper prefix. */
|
|
178
140
|
export interface GlobDenyEntry {
|
|
179
141
|
regex: string;
|
|
180
142
|
fixedPrefix: string;
|
|
181
143
|
}
|
|
182
144
|
/**
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* entries only (a plain entry needs no pairing at all -- its own path IS both its recursive-clause
|
|
187
|
-
* anchor and its ancestor-walk root, `buildReadDenyKeepInPlaceBlock` uses `paths` directly for that).
|
|
145
|
+
* The PAIRED form `buildReadDenyKeepInPlaceBlock` (sandbox/profile.ts) needs -- see `GlobDenyEntry`.
|
|
146
|
+
* Glob-shaped entries only (a plain entry's own path is both its clause anchor and its ancestor-walk
|
|
147
|
+
* root), classified by the same `scanDenyPathGlob` as `splitDenyPathsByGlobShape`.
|
|
188
148
|
*/
|
|
189
149
|
export declare function globDenyEntriesOf(paths: readonly string[]): GlobDenyEntry[];
|
|
190
150
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
151
|
+
* Normalises a root-relative pattern before it reaches `ignore()`.
|
|
152
|
+
*
|
|
153
|
+
* Every run of slashes collapses to one. Then a leading byte-order mark (U+FEFF) is dealt with, one
|
|
154
|
+
* level deep, so it can neither vanish in a way that promotes the next character to a gitignore
|
|
155
|
+
* directive nor linger as an invisible first character:
|
|
156
|
+
* - a pattern that is only whitespace (JS `\s`, which includes the BOM), optionally ending in
|
|
157
|
+
* exactly `/**`, is left as it is;
|
|
158
|
+
* - a BOM followed by `!` or `#` becomes a backslash, so the directive character stays literal;
|
|
159
|
+
* - two BOMs become a one-character class holding a BOM (`[<BOM>]`), keeping the second literal;
|
|
160
|
+
* - a lone BOM before anything else is dropped.
|
|
161
|
+
* A pattern with no leading BOM is returned after the slash collapse alone.
|
|
201
162
|
*/
|
|
202
163
|
export declare function normalizeFileRulePattern(relativePattern: string): string;
|
|
203
164
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* `!`/`#`: the result is instead explicitly re-anchored (`x/**` -> `/x`), so `allow x/**` stays
|
|
213
|
-
* scoped to the anchor root rather than becoming an anywhere-match the way the deny/ask case does.
|
|
214
|
-
* This asymmetry is real and claude's own (not the allow-exact-only asymmetry the controller
|
|
215
|
-
* retired) -- it survives because it is measured, not invented.
|
|
216
|
-
* - A pattern whose stripped form is empty or all-slashes (`/**`, `//**`) is left as `/**`.
|
|
217
|
-
* - Any pattern that does NOT end in `/**` passes through unchanged.
|
|
165
|
+
* The trailing-`/**` rewrite, which differs by direction:
|
|
166
|
+
* - a pattern NOT ending in `/**` is returned unchanged;
|
|
167
|
+
* - if what precedes the `/**` is empty or only slashes, the result is `/**`;
|
|
168
|
+
* - otherwise the `/**` is dropped. For DENY/ASK (`isAllow: false`) the rest is returned as it is:
|
|
169
|
+
* a single segment `x` then matches a directory `x` at any depth and everything under it. For
|
|
170
|
+
* ALLOW, a rest that is a single segment (no `/`) not starting with `!` or `#` gets a leading `/`
|
|
171
|
+
* (`x/**` -> `/x`), so the allow stays scoped to the anchor root; a rest with a `/` in it, or
|
|
172
|
+
* starting with `!`/`#`, is returned as it is.
|
|
218
173
|
*/
|
|
219
174
|
export declare function unanchorTrailingDoubleStar(pattern: string, isAllow: boolean): string;
|
|
220
175
|
export interface FileRuleCandidate<TEntry> {
|
|
221
176
|
entry: TEntry;
|
|
222
|
-
/** The rule's own
|
|
177
|
+
/** The rule's own pattern text, exactly as written (anchor resolution, normalisation and the trailing-`/**` rewrite run on this). */
|
|
223
178
|
pattern: string;
|
|
224
|
-
/**
|
|
179
|
+
/** The settings-source directory for a `/`-anchored rule -- see `resolveFileRuleAnchor`. Absent = every `/`-anchored candidate is inert. */
|
|
225
180
|
sourceDir?: string | undefined;
|
|
226
181
|
}
|
|
227
182
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* `Ma` has no per-group catch at all (only the per-TOOL-CALL one far above it, at `d8t`/`ome`'s own
|
|
233
|
-
* boundary), so ONE throwing group aborts the WHOLE permission check for that call, exactly the same
|
|
234
|
-
* way regardless of which direction (deny/ask/allow) was being evaluated when it happened. This
|
|
235
|
-
* class is what makes that propagation typed rather than "any thrown Error" -- caught exactly once,
|
|
236
|
-
* at `evaluator.ts`'s own `evaluate()` (the "decide this one call" boundary), and turned into the
|
|
237
|
-
* generic fail-closed deny `d8t`'s own hardcoded fallback produces.
|
|
183
|
+
* A malformed pattern's compile failure is a THROW out of `matchFileRulesGrouped`, the same on every
|
|
184
|
+
* direction: one broken group aborts the whole permission check for that call. This class makes that
|
|
185
|
+
* propagation typed; it is caught exactly once, at `evaluator.ts`'s `evaluate()` (the "decide this one
|
|
186
|
+
* call" boundary), and turned into a generic fail-closed deny.
|
|
238
187
|
*/
|
|
239
188
|
export declare class FileRuleCompileError extends Error {
|
|
240
189
|
constructor(message: string, options?: {
|
|
@@ -242,140 +191,91 @@ export declare class FileRuleCompileError extends Error {
|
|
|
242
191
|
});
|
|
243
192
|
}
|
|
244
193
|
/**
|
|
245
|
-
* `
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
194
|
+
* Matches `path` against `candidates`, grouped by anchor root:
|
|
195
|
+
* - each candidate's anchor is resolved (`resolveFileRuleAnchor`, with its own `sourceDir`); an
|
|
196
|
+
* INERT candidate is skipped; a `null` root means `opts.cwd`;
|
|
197
|
+
* - its pattern is normalised (`normalizeFileRulePattern`) and then rewritten
|
|
198
|
+
* (`unanchorTrailingDoubleStar`, `isAllow` = `behavior === "allow"`);
|
|
199
|
+
* - all candidates with the same root go into one case-insensitive `ignore()` instance, in candidate
|
|
200
|
+
* order; the roots are kept in the order they first appear;
|
|
201
|
+
* - for each root in that order, `path` is made relative to the root (`path.relative`). A relative
|
|
202
|
+
* path that is empty, starts with `/`, `./` or `../`, or is exactly `.` or `..` is outside the
|
|
203
|
+
* root, and that group is skipped -- the `ignore` package's own rule for an invalid path, so a
|
|
204
|
+
* name that merely starts with `..` (`..x/evil.sh`) is inside;
|
|
205
|
+
* - the first group that reports the path ignored decides: the result is the entry of the candidate
|
|
206
|
+
* whose compiled pattern text the package names as the matching rule (when two candidates in a
|
|
207
|
+
* group compiled to the same text, the LATER one); if that text maps to no entry, the next group
|
|
208
|
+
* is tried;
|
|
209
|
+
* - `null` when no group matches.
|
|
210
|
+
* THROWS `FileRuleCompileError` when the package fails while testing a group (a malformed pattern
|
|
211
|
+
* only fails then, on first use, never when it is added). The message names the group's root:
|
|
212
|
+
* `a file-rule pattern under <JSON-quoted root> failed to compile`, with the package's error as `cause`.
|
|
257
213
|
*/
|
|
258
214
|
export declare function matchFileRulesGrouped<TEntry>(candidates: readonly FileRuleCandidate<TEntry>[], path: string, opts: {
|
|
259
215
|
cwd: string;
|
|
260
216
|
home: string;
|
|
261
217
|
}, behavior: "allow" | "denyAsk"): TEntry | null;
|
|
262
218
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
219
|
+
* Rewrites a path that starts with one of macOS's real system directories back to the short symlink
|
|
220
|
+
* spelling users type: `/private/tmp` -> `/tmp`, `/private/var` -> `/var`, `/private/etc` -> `/etc`,
|
|
221
|
+
* `/usr/bin` -> `/bin`, `/usr/lib` -> `/lib`, `/usr/sbin` -> `/sbin`, checked in that order. A path
|
|
222
|
+
* matches a pair when it equals the real directory or starts with it followed by `/`. Each pair is
|
|
223
|
+
* VERIFIED once per process (`realpathSync(alias) === real`) rather than assumed, and a pair that
|
|
224
|
+
* does not resolve that way on this machine is never applied. Any other path is returned unchanged.
|
|
267
225
|
*/
|
|
268
226
|
export declare function canonicalizeTrustedSymlinkPath(path: string): string;
|
|
269
227
|
/**
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* to reuse the general file-rule matcher with a `"**"` sentinel pattern -- harmless before SV-6/C-1,
|
|
274
|
-
* but once the general matcher started interpreting `[`, `]`, `*` and `\` as glob metacharacters, a
|
|
275
|
-
* cwd or additional-directory root containing any of them (e.g. `[wip] app`) made `"**"` fail to
|
|
276
|
-
* compile the way the caller intended, and acceptEdits asked for every write inside that cwd instead
|
|
277
|
-
* of auto-approving them.
|
|
278
|
-
*
|
|
279
|
-
* This function sidesteps the escaping question SV-8 raises entirely, the same way claude's own `sm`
|
|
280
|
-
* does: a plain path-prefix test never interprets EITHER path as glob syntax, so a root containing a
|
|
281
|
-
* glob-special character needs no escaping here at all -- unlike a real RULE pattern (I-G's own
|
|
282
|
-
* concern), which does.
|
|
228
|
+
* The acceptEdits working-directory boundary: is `childPath` inside `rootPath`? A plain path-prefix
|
|
229
|
+
* test, never a glob, so a root containing `[`, `]`, `*` or `\` (a cwd named `[wip] app`) needs no
|
|
230
|
+
* escaping.
|
|
283
231
|
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* ported: `sm`'s own `uncShapeParity` and `skipPrivateAlias` options (Windows-only concerns) and its
|
|
292
|
-
* `Gn`/`Ha` UNC-path checks -- this codebase supports macOS only (CLAUDE.md's own "latest-OS
|
|
293
|
-
* floors" rule).
|
|
294
|
-
*
|
|
295
|
-
* Fix round 6 (R5-2, the re-review against the pinned 2.1.250 dump): ONLY these TWO pairs -- round
|
|
296
|
-
* 5 widened this to the full six-pair `ni()`/`Sl()` map (`/private/etc`, `/usr/bin`, `/usr/lib`,
|
|
297
|
-
* `/usr/sbin` included), which was WRONG for `sm` specifically: content search against the pinned
|
|
298
|
-
* 2.1.250 dump (not the 2.1.280 build round 5 was cited against) found `sm`'s own alias regexes
|
|
299
|
-
* verbatim -- `g=r?/^\/private\/var\//i:/^\/private\/var\//,w=r?/^\/private\/tmp(\/|$)/i:/^\/private\/
|
|
300
|
-
* tmp(\/|$)/` -- exactly these two, unconditionally, never the wider six-pair set. Reverted to match;
|
|
301
|
-
* the six-pair map (`trustedSymlinkEquivalences`/`canonicalizeTrustedSymlinkPath`) stays, but is now
|
|
302
|
-
* used ONLY by the allow-rule retry in evaluator.ts (`cqe`'s own scope, confirmed at the same dump
|
|
303
|
-
* site), never by this function.
|
|
232
|
+
* 1. On each path (independently), a leading `/private/var/` becomes `/var/`, and a leading
|
|
233
|
+
* `/private/tmp` followed by `/` or the end of the path becomes `/tmp` (case-sensitive tests);
|
|
234
|
+
* macOS keeps both short spellings as symlinks, and `os.tmpdir()` resolves through
|
|
235
|
+
* `/private/var/folders/...`.
|
|
236
|
+
* 2. With `caseFold` (default `true`), both are then lower-cased.
|
|
237
|
+
* 3. The answer comes from `path.relative(root, child)`: `""` is inside; `..` or anything starting
|
|
238
|
+
* `../` is outside; otherwise inside unless the relative path is absolute.
|
|
304
239
|
*/
|
|
305
240
|
export declare function isPathWithinRoot(childPath: string, rootPath: string, opts?: {
|
|
306
241
|
caseFold?: boolean;
|
|
307
242
|
}): boolean;
|
|
308
243
|
/**
|
|
309
|
-
*
|
|
310
|
-
* `
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* `
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* it in full), unlike `sm`'s own `r?/.../i:/.../ ` case-fold branching. Fix round 5's own
|
|
321
|
-
* `resolvesWithinPluginRoot` wrongly delegated to `isPathWithinRoot`'s DEFAULT `caseFold:true`,
|
|
322
|
-
* so on a case-sensitive volume a manifest entry like `../FOO/agents` under a root
|
|
323
|
-
* `.../plugins/foo` was admitted (folded, `FOO` read as `foo`) where claude's own `nV` (and
|
|
324
|
-
* this rewrite) refuses it.
|
|
325
|
-
* 2. NAIVE STRING-PREFIX, not segment-aware -- `u.startsWith("..")` is a bare string test, unlike
|
|
326
|
-
* `sm`'s own `uj` (`/(?:^|[\\/])\.\.(?:[\\/]|$)/`, confirmed by reading ITS full definition too),
|
|
327
|
-
* which requires a `..` SEGMENT bounded by a separator or a string edge. This means a component
|
|
328
|
-
* name that merely STARTS WITH the two characters `..` -- e.g. `"..x/agents"`, a real,
|
|
329
|
-
* non-escaping subdirectory name -- is REFUSED by claude too, not only a genuine `"../"` escape.
|
|
330
|
-
* Matched here rather than "fixed", per the ruling: claude's own inconsistency between its two
|
|
331
|
-
* path-safety mechanisms is not this codebase's to resolve by choosing the more correct one.
|
|
332
|
-
* 3. NO trusted-symlink alias mapping at all -- `nV`'s own body never calls anything resembling
|
|
333
|
-
* `Smt`/`canonicalizeTrustedSymlinkPath`. Moot in practice here regardless, since both operands
|
|
334
|
-
* below are ALREADY realpath'd before this comparison runs (a real, resolved path from
|
|
335
|
-
* `/tmp`/`/var` already comes back in its long `/private/...` form either way).
|
|
336
|
-
*
|
|
337
|
-
* DISCLOSED DIVERGENCE FROM THE PINNED 2.1.250, kept as DELIBERATE HARDENING (the controller's own
|
|
338
|
-
* explicit ruling): `nV` itself is PURELY LEXICAL -- 2.1.250 has no symlink-following/realpath step
|
|
339
|
-
* for a plugin component path at all. This function still realpaths both the candidate and the
|
|
340
|
-
* plugin root first (originally ported from the INSTALLED 2.1.280 binary's own `KGe`/`Aoe`, which DID
|
|
341
|
-
* add this in a build newer than the pin), refusing a symlinked override that points outside the
|
|
342
|
-
* plugin where 2.1.250 would load it -- the safe direction, and it matches claude's own newer
|
|
343
|
-
* behaviour. `nV`'s own comparison shape (case-sensitive, naive-prefix, no alias) is then applied to
|
|
344
|
-
* the REALPATH'D forms rather than to the raw ones `nV` itself compares. `resolveRealTarget` (paths.ts)
|
|
345
|
-
* has the graceful "walk up to the nearest existing ancestor" fallback for a candidate that does not
|
|
346
|
-
* exist YET, and rethrows a non-ENOENT failure (ELOOP on a symlink cycle, EACCES, ...), caught here
|
|
347
|
-
* and treated as a refusal rather than letting a malformed manifest entry crash the whole
|
|
348
|
-
* plugin-loading pass.
|
|
244
|
+
* Whether a path a plugin MANIFEST declares for a component (`commands`/`agents`/`skills`/
|
|
245
|
+
* `output-styles`/`workflows`/`hooks`) stays inside the plugin:
|
|
246
|
+
* 1. a candidate containing a backslash is refused;
|
|
247
|
+
* 2. both the candidate and the plugin root are resolved to their real targets
|
|
248
|
+
* (`resolveRealTarget`, which walks up to the nearest existing ancestor for a path that does not
|
|
249
|
+
* exist yet); any failure there (a symlink loop, a permission error) refuses;
|
|
250
|
+
* 3. `path.relative(realRoot, realCandidate)`: `""` is inside; ANY relative path starting with the
|
|
251
|
+
* two characters `..` is refused -- `..x/agents` included, a naive string test kept on purpose;
|
|
252
|
+
* otherwise inside unless the relative path is absolute.
|
|
253
|
+
* Case-sensitive, and with no `/tmp`-style alias mapping (both sides are already real paths).
|
|
254
|
+
* Resolving symlinks first refuses an override that points outside the plugin.
|
|
349
255
|
*/
|
|
350
256
|
export declare function resolvesWithinPluginRoot(candidatePath: string, pluginRoot: string): boolean;
|
|
351
257
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
258
|
+
* A real path (e.g. `resolve(winterHome)`) can contain `[`, `]`, `*` or `\`, all of which are glob
|
|
259
|
+
* syntax to `matchFileRulesGrouped`. A caller building a rule PATTERN from a real path
|
|
260
|
+
* (`buildBaselineDenyRules`, engine.ts) escapes it with this first, or a home named
|
|
261
|
+
* `/Users/name[wip]` would have its own floor's `[wip]` read back as a character class.
|
|
262
|
+
*
|
|
263
|
+
* 1. a backslash goes before every `[`, `]`, `*` and `\`;
|
|
264
|
+
* 2. then the trailing run of whitespace (`\s`), if any, gets a backslash before EACH of its
|
|
265
|
+
* characters -- the `ignore` package trims unescaped trailing whitespace off a pattern, so a path
|
|
266
|
+
* ending in a space would otherwise lose its own protection.
|
|
359
267
|
*
|
|
360
|
-
* `?` is
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
* as a single-character wildcard, which still MATCHES a real `?` in the path (a wildcard matches
|
|
365
|
-
* anything, including the literal character) -- over-matching by one character class is the safe
|
|
366
|
-
* direction for a DENY floor (WS-07 §3.1's own posture: a deny that reaches slightly too far is a
|
|
367
|
-
* false-negative-avoiding cost, never a hole), where an escape that matches NOTHING would be a hole.
|
|
268
|
+
* `?` is deliberately left raw: the grammar has no working escape for it (`\?` would demand a literal
|
|
269
|
+
* backslash the real path never has, so it would match nothing). A raw `?` is a one-character
|
|
270
|
+
* wildcard, which still matches the real `?` -- over-matching by one character is the safe direction
|
|
271
|
+
* for a DENY floor, where an escape that matched nothing would be a hole.
|
|
368
272
|
*/
|
|
369
273
|
export declare function escapeFileRulePathSegment(path: string): string;
|
|
370
274
|
/**
|
|
371
|
-
* A SINGLE pattern against a SINGLE path -- for a caller
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* this codebase's own extension, not a ported behaviour). Grouping (this module's own header)
|
|
376
|
-
* therefore does not apply across DIFFERENT callers' unrelated single-pattern checks the way it does
|
|
377
|
-
* within one `matchFileRulesGrouped` call -- this is a thin, no-negation-support convenience, not a
|
|
378
|
-
* second matching engine.
|
|
275
|
+
* A SINGLE pattern against a SINGLE path -- for a caller already iterating rule entries one at a time
|
|
276
|
+
* for its own reason (e.g. evaluator.ts's cross-tool `findFileDenyBlockingEdit`, a Winter-only safety
|
|
277
|
+
* net: a Read deny also blocks a Write). Grouping does not apply across such unrelated single checks;
|
|
278
|
+
* this is a thin convenience, not a second matching engine.
|
|
379
279
|
*/
|
|
380
280
|
export declare function matchesSingleFileRulePattern(pattern: string, path: string, opts: {
|
|
381
281
|
cwd: string;
|