fullcourtdefense-cli 1.36.2 → 1.36.4
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/actionPolicyEngine.d.ts +19 -0
- package/dist/actionPolicyEngine.js +157 -1
- package/dist/askHeader.d.ts +6 -0
- package/dist/askHeader.js +49 -10
- package/dist/commandNarrative.d.ts +5 -0
- package/dist/commandNarrative.js +114 -7
- package/dist/commands/daemon.js +23 -3
- package/dist/commands/demoActions.js +2 -2
- package/dist/commands/deterministicGuard.d.ts +16 -0
- package/dist/commands/deterministicGuard.js +490 -35
- package/dist/commands/doctor.js +1 -0
- package/dist/commands/login.js +4 -0
- package/dist/identityRecovery.d.ts +50 -0
- package/dist/identityRecovery.js +99 -0
- package/dist/machineIdentity.d.ts +90 -1
- package/dist/machineIdentity.js +457 -94
- package/dist/telemetry.d.ts +9 -0
- package/dist/telemetry.js +29 -7
- package/dist/version.json +1 -1
- package/package.json +2 -1
|
@@ -204,12 +204,28 @@ export declare function setShellAstMode(mode: ShellAstMode): void;
|
|
|
204
204
|
export declare function getShellAstMode(): ShellAstMode;
|
|
205
205
|
/** Shadow telemetry: the comparison recorded by the most recent shell scan (consumed once). */
|
|
206
206
|
export declare function takeShellAstComparison(): ShellAstComparison | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* PowerShell line, judged on its CODE only (`unquotedCode`): a cmdlet named
|
|
209
|
+
* inside a commit message or issue body does not change how the shell reads it.
|
|
210
|
+
*/
|
|
211
|
+
export declare function isPowerShellLine(text: string): boolean;
|
|
207
212
|
/**
|
|
208
213
|
* Code-only view of a line: each `'…'` / `"…"` / `` `…` `` span becomes a
|
|
209
214
|
* space. Detectors that ask "does this EXECUTE" must look here; the raw line
|
|
210
215
|
* still carries fixture strings and docs.
|
|
211
216
|
*/
|
|
212
217
|
export declare function unquotedCode(text: string): string;
|
|
218
|
+
/** POSIX heredocs of a command line, in order: body span, delimiter-line end, and receiver. */
|
|
219
|
+
export declare function posixHeredocs(text: string): Array<{
|
|
220
|
+
bodyStart: number;
|
|
221
|
+
bodyEnd: number;
|
|
222
|
+
end: number;
|
|
223
|
+
body: string;
|
|
224
|
+
receiver: {
|
|
225
|
+
kind: 'shell' | 'language' | 'data';
|
|
226
|
+
program: string;
|
|
227
|
+
};
|
|
228
|
+
}>;
|
|
213
229
|
export declare function stripInertDataSegments(text: string): string;
|
|
214
230
|
export declare function scanDeterministicToolCall(toolName: string, toolArgs: Record<string, unknown>, options?: LocalSafetyScanOptions): DeterministicFinding | undefined;
|
|
215
231
|
export declare function scanDeterministicTextResponse(text: string, options?: LocalSafetyScanOptions): DeterministicFinding | undefined;
|
|
@@ -46,7 +46,9 @@ exports.setShellAstProvider = setShellAstProvider;
|
|
|
46
46
|
exports.setShellAstMode = setShellAstMode;
|
|
47
47
|
exports.getShellAstMode = getShellAstMode;
|
|
48
48
|
exports.takeShellAstComparison = takeShellAstComparison;
|
|
49
|
+
exports.isPowerShellLine = isPowerShellLine;
|
|
49
50
|
exports.unquotedCode = unquotedCode;
|
|
51
|
+
exports.posixHeredocs = posixHeredocs;
|
|
50
52
|
exports.stripInertDataSegments = stripInertDataSegments;
|
|
51
53
|
exports.scanDeterministicToolCall = scanDeterministicToolCall;
|
|
52
54
|
exports.scanDeterministicTextResponse = scanDeterministicTextResponse;
|
|
@@ -1055,9 +1057,10 @@ function destructiveCommandDecision(value, commandContext) {
|
|
|
1055
1057
|
if (astHit)
|
|
1056
1058
|
return astHit;
|
|
1057
1059
|
// The AST parsed the literal command only. A hit that exists only in a
|
|
1058
|
-
// decoded or de-obfuscated surface (`echo <base64> | base64 -d | sh`)
|
|
1059
|
-
// outside what it saw, so it
|
|
1060
|
-
|
|
1060
|
+
// decoded or de-obfuscated surface (`echo <base64> | base64 -d | sh`) or in
|
|
1061
|
+
// a heredoc body a shell runs (`bash <<'EOF'`) is outside what it saw, so it
|
|
1062
|
+
// cannot clear it.
|
|
1063
|
+
if (regexRm && !RM_SCOPE_ITEMS.has(destructiveCommandReason(withoutActingHeredocBodies(value), { literalOnly: true })?.itemId || ''))
|
|
1061
1064
|
return regexHit;
|
|
1062
1065
|
if (regexRm) {
|
|
1063
1066
|
// The AST cleared the rm-scope hit; other destructive rules still apply.
|
|
@@ -1510,13 +1513,15 @@ function isSensitivePathContext(toolName, candidate) {
|
|
|
1510
1513
|
// (`wsl -e bash -c ""r""m"…`) that per-span reasoning cannot survive.
|
|
1511
1514
|
// 2. A double-quoted span containing $( … ) or real backtick command
|
|
1512
1515
|
// substitution is kept — substitution executes BEFORE the outer command.
|
|
1513
|
-
// A single-char backtick pair (`n, `t) is a PowerShell ESCAPE, not code
|
|
1516
|
+
// A single-char backtick pair (`n, `t) is a PowerShell ESCAPE, not code;
|
|
1517
|
+
// on a PowerShell line (`isPowerShellLine`) so is every `$ `" `` `n in
|
|
1518
|
+
// a body that uses the backtick only that way. `""` is an escaped quote.
|
|
1514
1519
|
// 3. Single-token quoted spans (rm -rf "/") are argv, not prose — kept.
|
|
1515
1520
|
// 4. Quoted spans in a CODE POSITION (interpreter -c/-Command/-e flags, or a
|
|
1516
1521
|
// remote executor earlier in the same statement) are kept.
|
|
1517
1522
|
// 5. Secret-literal rules and honeypot/custom rules never use this view —
|
|
1518
1523
|
// a token pasted into a commit message still leaks.
|
|
1519
|
-
const DATA_TO_INTERPRETER = /\b(?:iex|invoke-expression|invoke-command)\b|\beval\b|\bxargs\b|\bbase64\b[^\n]*(?:-d\b|--decode\b)|frombase64string|-encodedcommand\b|\s-enc\s|(?<!\|)\|(?!\|)\s*&?\s*(?:sh|bash|zsh|dash|ksh|csh|pwsh|powershell(?:\.exe)?|cmd(?:\.exe)?|node|python[0-9.]*|perl|ruby)\b|\b(?:sh|bash|zsh|dash|ksh|csh|pwsh|powershell(?:\.exe)?|cmd(?:\.exe)?)\s+(?:-c|\/c|-e|-command|-scriptblock)\b|\b(?:ssh|wsl(?:\.exe)?|chroot)\b|\b(?:docker|kubectl|podman|nerdctl)(?:\.exe)?\s+(?:exec|run)\b/i;
|
|
1524
|
+
const DATA_TO_INTERPRETER = /\b(?:iex|icm|invoke-expression|invoke-command)\b|\[scriptblock\]::create\b|\beval\b|\bxargs\b|\bbase64\b[^\n]*(?:-d\b|--decode\b)|frombase64string|-encodedcommand\b|\s-enc\s|(?<!\|)\|(?!\|)\s*&?\s*(?:sh|bash|zsh|dash|ksh|csh|pwsh|powershell(?:\.exe)?|cmd(?:\.exe)?|node|python[0-9.]*|perl|ruby)\b|\b(?:sh|bash|zsh|dash|ksh|csh|pwsh|powershell(?:\.exe)?|cmd(?:\.exe)?)\s+(?:-c|\/c|-e|-command|-scriptblock)\b|\b(?:ssh|wsl(?:\.exe)?|chroot)\b|\b(?:docker|kubectl|podman|nerdctl)(?:\.exe)?\s+(?:exec|run)\b/i;
|
|
1520
1525
|
// A LANGUAGE interpreter's `-c` / `-e` argument is CODE in another language, not
|
|
1521
1526
|
// a shell command line. `node -e "console.log('rm -rf /')"` prints a string;
|
|
1522
1527
|
// `python -c "print('DROP TABLE')"` prints a string. That code only reaches the
|
|
@@ -1541,36 +1546,70 @@ function interpreterFileArguments(text) {
|
|
|
1541
1546
|
if (text.length > 32_000)
|
|
1542
1547
|
return '';
|
|
1543
1548
|
const paths = [];
|
|
1544
|
-
|
|
1549
|
+
const budget = { calls: 0 };
|
|
1545
1550
|
text.replace(/"((?:[^"\\]|\\.)*)"|'((?:[^'\\]|\\.)*)'/g, (whole, dq, sq, offset) => {
|
|
1546
1551
|
if (!LANG_INTERPRETER_CODE_BEFORE.test(text.slice(Math.max(0, offset - 80), offset)))
|
|
1547
1552
|
return whole;
|
|
1548
|
-
|
|
1549
|
-
// Preserve offsets while masking string contents: a call printed inside a
|
|
1550
|
-
// string is not an invocation; a sensitive path in another argument is data.
|
|
1551
|
-
const instructions = code.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, literal => ' '.repeat(literal.length));
|
|
1552
|
-
for (const call of instructions.matchAll(/\b(?:open|fopen|readFile(?:Sync)?|createReadStream|ReadAllText|ReadAllBytes)\s*\(/g)) {
|
|
1553
|
-
if (++calls > 48)
|
|
1554
|
-
break;
|
|
1555
|
-
const start = (call.index || 0) + call[0].length;
|
|
1556
|
-
let depth = 1;
|
|
1557
|
-
for (let end = start; end < instructions.length; end++) {
|
|
1558
|
-
const ch = instructions[end];
|
|
1559
|
-
if ((ch === ')' && --depth === 0) || (ch === ',' && depth === 1)) {
|
|
1560
|
-
paths.push(code.slice(start, end));
|
|
1561
|
-
break;
|
|
1562
|
-
}
|
|
1563
|
-
if (ch === '(')
|
|
1564
|
-
depth++;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1553
|
+
collectFileCallArguments(dq !== undefined ? dq : (sq || ''), paths, budget);
|
|
1567
1554
|
return whole;
|
|
1568
1555
|
});
|
|
1556
|
+
// Heredoc bodies a language interpreter runs (`python3 - <<'EOF'`, or
|
|
1557
|
+
// `cat > x.js <<'EOF' … node x.js`) are the same inline code.
|
|
1558
|
+
for (const heredoc of heredocView(text)?.heredocs || []) {
|
|
1559
|
+
if (heredoc.receiver.kind === 'language')
|
|
1560
|
+
collectFileCallArguments(heredoc.body, paths, budget);
|
|
1561
|
+
}
|
|
1569
1562
|
return paths.join('\n');
|
|
1570
1563
|
}
|
|
1564
|
+
const FILE_CALL_RE = /\b(?:open|fopen|readFile(?:Sync)?|createReadStream|ReadAllText|ReadAllBytes|readTextFile|file_get_contents|copyFile(?:Sync)?|copyfile|copy2?|Path|File\.read)\s*\(/g;
|
|
1565
|
+
function collectFileCallArguments(code, paths, budget) {
|
|
1566
|
+
// Preserve offsets while masking string contents: a call printed inside a
|
|
1567
|
+
// string is not an invocation; a sensitive path in another argument is data.
|
|
1568
|
+
const instructions = code.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, literal => ' '.repeat(literal.length));
|
|
1569
|
+
for (const call of instructions.matchAll(FILE_CALL_RE)) {
|
|
1570
|
+
if (++budget.calls > 48)
|
|
1571
|
+
break;
|
|
1572
|
+
const start = (call.index || 0) + call[0].length;
|
|
1573
|
+
let depth = 1;
|
|
1574
|
+
for (let end = start; end < instructions.length; end++) {
|
|
1575
|
+
const ch = instructions[end];
|
|
1576
|
+
if ((ch === ')' && --depth === 0) || (ch === ',' && depth === 1)) {
|
|
1577
|
+
paths.push(code.slice(start, end));
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
if (ch === '(')
|
|
1581
|
+
depth++;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1571
1585
|
// $( … ) always executes; backtick pairs only with ≥2 chars of content
|
|
1572
1586
|
// (PowerShell escapes like `n / `t are single-char and inert).
|
|
1573
1587
|
const INTERPOLATES = /\$\(|`[^`]{2,}`/;
|
|
1588
|
+
// Same signals as shellDialectOf (shellQuoteMask.ts / shellAst.ts looksLikePowerShell).
|
|
1589
|
+
const POWERSHELL_LINE = /\$env:|-ErrorAction\b|\bWrite-(?:Output|Host)\b|\b(?:Get|Set|New|Remove|Start|Stop|Invoke|Select|Where|ForEach|Test|Add|Format|Out|ConvertTo|ConvertFrom|Import|Export|Measure|Sort|Group|Restart|Register|Unregister|Enable|Disable|Copy|Move|Rename|Resolve|Split|Join|Read|Wait|Clear)-[A-Z][A-Za-z]+\b|@\{|@'|@"|\[(?:System\.|Security\.|IO\.|math\])|\$_\.|\$\w+\s*=\s*(?:Get|New|Read)-|\|\s*%\s|\|\s*\?\s/;
|
|
1590
|
+
const POWERSHELL_ONLY_SYNTAX = /(?:^|[\s;{(|])\$[A-Za-z_][\w:]*\s*[+-]?=(?!=)|`"|(?:^|[\s;{])(?:if|elseif|foreach|while|switch)\s*\((?!\()|\}\s*(?:else|elseif|catch|finally)\b|\|\s*(?:where|foreach|%|\?)\s*\{/i;
|
|
1591
|
+
/**
|
|
1592
|
+
* PowerShell line, judged on its CODE only (`unquotedCode`): a cmdlet named
|
|
1593
|
+
* inside a commit message or issue body does not change how the shell reads it.
|
|
1594
|
+
*/
|
|
1595
|
+
function isPowerShellLine(text) {
|
|
1596
|
+
const code = unquotedCode(text);
|
|
1597
|
+
return POWERSHELL_LINE.test(code) || POWERSHELL_ONLY_SYNTAX.test(code);
|
|
1598
|
+
}
|
|
1599
|
+
// Every backtick is a canonical PowerShell escape (`$ `" `' `` `n `t `r `0 …).
|
|
1600
|
+
const POWERSHELL_ESCAPES_ONLY = /^(?:[^`]|`[$"'`0abefnrtuv])*$/;
|
|
1601
|
+
/**
|
|
1602
|
+
* Does a double-quoted body run code before its command? `$( … )` always does.
|
|
1603
|
+
* A backtick pair is POSIX command substitution, except on a PowerShell line
|
|
1604
|
+
* whose body uses the backtick only as PowerShell's escape character there.
|
|
1605
|
+
*/
|
|
1606
|
+
function doubleQuotedInterpolates(body, powershell) {
|
|
1607
|
+
if (body.includes('$('))
|
|
1608
|
+
return true;
|
|
1609
|
+
if (!/`[^`]{2,}`/.test(body))
|
|
1610
|
+
return false;
|
|
1611
|
+
return !(powershell && POWERSHELL_ESCAPES_ONLY.test(body));
|
|
1612
|
+
}
|
|
1574
1613
|
const INERT_DATA = ' fcd_inert_data ';
|
|
1575
1614
|
// Code position immediately before a quoted span: interpreter code flags or a
|
|
1576
1615
|
// remote/nested executor earlier in the same statement (no ; & | between).
|
|
@@ -1596,6 +1635,11 @@ function unquotedCode(text) {
|
|
|
1596
1635
|
i += 2;
|
|
1597
1636
|
continue;
|
|
1598
1637
|
}
|
|
1638
|
+
// `""` inside "…" is PowerShell's escaped quote; POSIX reads close+reopen, text either way.
|
|
1639
|
+
if (q === '"' && text[i] === '"' && text[i + 1] === '"') {
|
|
1640
|
+
i += 2;
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1599
1643
|
if (text[i] === q)
|
|
1600
1644
|
break;
|
|
1601
1645
|
i += 1;
|
|
@@ -1629,12 +1673,12 @@ function replaceDelimited(text, open, close, fn) {
|
|
|
1629
1673
|
}
|
|
1630
1674
|
const LINE_TERMINATOR = new Set(['\n', '\r', '\u2028', '\u2029']);
|
|
1631
1675
|
/**
|
|
1632
|
-
* `src.replace(/"((?:[^"\\`]|\\.|`[\s\S])*)"|'((?:[^'\\]|\\.)*)'/g, fn)` in one
|
|
1676
|
+
* `src.replace(/"((?:[^"\\`]|""|\\.|`[\s\S])*)"|'((?:[^'\\]|\\.)*)'/g, fn)` in one
|
|
1633
1677
|
* pass, same matches and callback arguments (a PowerShell backtick escapes any
|
|
1634
|
-
* character inside double quotes
|
|
1635
|
-
* every later quote of that kind; each one
|
|
1636
|
-
*
|
|
1637
|
-
* of `\"`).
|
|
1678
|
+
* character inside double quotes; `""` is an escaped quote there). After an
|
|
1679
|
+
* unclosed quote the regex retried every later quote of that kind; each one
|
|
1680
|
+
* before the dead end is the tail of an escape pair and walks the same path to
|
|
1681
|
+
* the same dead end (quadratic on a body of `\"`).
|
|
1638
1682
|
*/
|
|
1639
1683
|
function replaceQuotedSpans(src, fn) {
|
|
1640
1684
|
const deadUntil = { '"': -1, "'": -1 };
|
|
@@ -1651,6 +1695,10 @@ function replaceQuotedSpans(src, fn) {
|
|
|
1651
1695
|
let closed = -1;
|
|
1652
1696
|
while (j < src.length) {
|
|
1653
1697
|
const c = src[j];
|
|
1698
|
+
if (c === '"' && quote === '"' && src[j + 1] === '"') {
|
|
1699
|
+
j += 2;
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1654
1702
|
if (c === quote) {
|
|
1655
1703
|
closed = j;
|
|
1656
1704
|
break;
|
|
@@ -1680,30 +1728,437 @@ function replaceQuotedSpans(src, fn) {
|
|
|
1680
1728
|
}
|
|
1681
1729
|
return out + src.slice(from);
|
|
1682
1730
|
}
|
|
1731
|
+
// Stdin of these is executed as shell/SQL code; `done`/`fi`/`esac`/`}`/`)`
|
|
1732
|
+
// close a compound command whose body acts on what it reads (`while read c; do $c; done <<EOF`).
|
|
1733
|
+
const SHELL_STDIN_PROGRAMS = /^(?:sh|bash|zsh|dash|ksh|mksh|ash|csh|tcsh|fish|busybox|pwsh|powershell|cmd|ssh|wsl|chroot|su|eval|source|\.|xargs|parallel|at|batch|crontab|expect|tclsh|wish|osascript|script|psql|mysql|mariadb|sqlite3|sqlcmd|mongosh|mongo|redis-cli|cqlsh|clickhouse-client|duckdb|snowsql|done|fi|esac|\}|\))$/;
|
|
1734
|
+
const LANGUAGE_STDIN_PROGRAMS = /^(?:node|nodejs|deno|bun|python[0-9.]*|py|pypy[0-9.]*|perl|ruby|php|lua|luajit|tsx|ts-node|rscript|julia|groovy)$/;
|
|
1735
|
+
const CONTAINER_CLI_PROGRAMS = /^(?:docker|podman|nerdctl|kubectl|oc)$/;
|
|
1736
|
+
const RECEIVER_RANK = { data: 0, language: 1, shell: 2 };
|
|
1737
|
+
function programName(token) {
|
|
1738
|
+
return token.toLowerCase().replace(/^.*[\\/]/, '').replace(/\.(?:exe|cmd|bat)$/, '');
|
|
1739
|
+
}
|
|
1740
|
+
/** Words of one stage without assignments and redirections (`> f`, `2>&1`, `<<'EOF'`). */
|
|
1741
|
+
function stageWords(stage) {
|
|
1742
|
+
const words = [];
|
|
1743
|
+
const tokens = segmentTokens(stage.trim());
|
|
1744
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1745
|
+
const token = tokens[i];
|
|
1746
|
+
if (/^(?:\d*>{1,2}|&>{1,2}|<{1,3}-?|\d*<)$/.test(token)) {
|
|
1747
|
+
i++;
|
|
1748
|
+
continue;
|
|
1749
|
+
}
|
|
1750
|
+
if (/^(?:\d*>|&>|<)/.test(token))
|
|
1751
|
+
continue;
|
|
1752
|
+
if (!words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(token))
|
|
1753
|
+
continue;
|
|
1754
|
+
words.push(token);
|
|
1755
|
+
}
|
|
1756
|
+
return words;
|
|
1757
|
+
}
|
|
1758
|
+
/** How one pipeline stage treats the bytes on its stdin. */
|
|
1759
|
+
function stdinReceiver(stage) {
|
|
1760
|
+
const words = stageWords(stage);
|
|
1761
|
+
for (let i = 0; i < words.length; i++) {
|
|
1762
|
+
const program = programName(words[i]);
|
|
1763
|
+
if (SHELL_STDIN_PROGRAMS.test(program))
|
|
1764
|
+
return { kind: 'shell', program };
|
|
1765
|
+
if (LANGUAGE_STDIN_PROGRAMS.test(program))
|
|
1766
|
+
return { kind: 'language', program };
|
|
1767
|
+
if (CONTAINER_CLI_PROGRAMS.test(program)) {
|
|
1768
|
+
return words.slice(i + 1).some(word => /^(?:exec|run|attach|debug)$/i.test(word)) ? { kind: 'shell', program } : { kind: 'data', program };
|
|
1769
|
+
}
|
|
1770
|
+
if (AST_TRANSPARENT_WRAPPERS.has(program)) {
|
|
1771
|
+
// `sudo -u root bash`, `env X=1 node`, `timeout 5 sh`: the reader is a later word, past the wrapper's own options.
|
|
1772
|
+
let reader = { kind: 'data', program };
|
|
1773
|
+
for (const word of words.slice(i + 1))
|
|
1774
|
+
reader = strongerReceiver(reader, stdinReceiver(word));
|
|
1775
|
+
return reader;
|
|
1776
|
+
}
|
|
1777
|
+
return { kind: 'data', program };
|
|
1778
|
+
}
|
|
1779
|
+
return { kind: 'data', program: '' };
|
|
1780
|
+
}
|
|
1781
|
+
function strongerReceiver(a, b) {
|
|
1782
|
+
return RECEIVER_RANK[b.kind] > RECEIVER_RANK[a.kind] ? b : a;
|
|
1783
|
+
}
|
|
1784
|
+
/** Files the opener stage writes its stdin to (`cat > f`, `tee f`). */
|
|
1785
|
+
function stageWriteTargets(stage) {
|
|
1786
|
+
const targets = redirectTargets(stage);
|
|
1787
|
+
const words = stageWords(stage);
|
|
1788
|
+
if (programName(words[0] || '') === 'tee')
|
|
1789
|
+
targets.push(...words.slice(1).filter(word => !word.startsWith('-')));
|
|
1790
|
+
return targets.map(target => programName(target)).filter(Boolean);
|
|
1791
|
+
}
|
|
1792
|
+
/** The strongest program among `statements` that runs one of `files` (`node x.js`, `bash x.sh`, `./x.sh`). */
|
|
1793
|
+
function laterRunnerOf(files, statements) {
|
|
1794
|
+
let runner = { kind: 'data', program: '' };
|
|
1795
|
+
if (!files.length)
|
|
1796
|
+
return runner;
|
|
1797
|
+
for (const stages of statements) {
|
|
1798
|
+
for (const stage of stages) {
|
|
1799
|
+
const words = stageWords(stage);
|
|
1800
|
+
if (!words.some(word => files.includes(programName(word))))
|
|
1801
|
+
continue;
|
|
1802
|
+
const direct = files.includes(programName(words[0] || ''));
|
|
1803
|
+
runner = strongerReceiver(runner, direct ? { kind: 'shell', program: programName(words[0]) } : stdinReceiver(stage));
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
return runner;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Every heredoc of a POSIX command line, with its exact body and its stages.
|
|
1810
|
+
* `undefined` when the line does not lex (an unclosed quote or substitution):
|
|
1811
|
+
* callers then keep every body as code.
|
|
1812
|
+
*/
|
|
1813
|
+
function lexHeredocs(text) {
|
|
1814
|
+
const spans = [];
|
|
1815
|
+
const pending = [];
|
|
1816
|
+
const frame = (closer, stageStart) => ({ closer, quote: '', depth: 0, afterPipe: false, stageStart, stages: [], open: [] });
|
|
1817
|
+
const stack = [frame('', 0)];
|
|
1818
|
+
/** `i` at the first `(` of `((`: index of the closing `))` (arithmetic holds no heredoc; `<<` there is a shift). */
|
|
1819
|
+
const arithmeticEnd = (i) => {
|
|
1820
|
+
let depth = 0;
|
|
1821
|
+
for (let k = i; k < text.length; k++) {
|
|
1822
|
+
if (text[k] === '(')
|
|
1823
|
+
depth++;
|
|
1824
|
+
else if (text[k] === ')' && --depth === 0)
|
|
1825
|
+
return k;
|
|
1826
|
+
}
|
|
1827
|
+
return -1;
|
|
1828
|
+
};
|
|
1829
|
+
const codeSlice = (from, to) => {
|
|
1830
|
+
let out = '';
|
|
1831
|
+
let at = from;
|
|
1832
|
+
for (const span of spans) {
|
|
1833
|
+
if (span.end <= at || span.bodyStart >= to)
|
|
1834
|
+
continue;
|
|
1835
|
+
out += text.slice(at, Math.max(at, span.bodyStart));
|
|
1836
|
+
at = Math.max(at, span.end);
|
|
1837
|
+
}
|
|
1838
|
+
return out + text.slice(at, Math.max(at, to));
|
|
1839
|
+
};
|
|
1840
|
+
const endStage = (ctx, at) => { ctx.stages.push(codeSlice(ctx.stageStart, at)); ctx.stageStart = at + 1; };
|
|
1841
|
+
const endStatement = (ctx, at) => {
|
|
1842
|
+
endStage(ctx, at);
|
|
1843
|
+
for (const { span, stage } of ctx.open)
|
|
1844
|
+
span.stages = ctx.stages.slice(stage);
|
|
1845
|
+
ctx.stages = [];
|
|
1846
|
+
ctx.open = [];
|
|
1847
|
+
};
|
|
1848
|
+
const readBodies = (newline) => {
|
|
1849
|
+
let pos = newline + 1;
|
|
1850
|
+
for (const { span, delimiter, stripTabs } of pending.splice(0)) {
|
|
1851
|
+
span.bodyStart = pos;
|
|
1852
|
+
span.bodyEnd = span.end = text.length;
|
|
1853
|
+
while (pos < text.length) {
|
|
1854
|
+
const lineEnd = text.indexOf('\n', pos);
|
|
1855
|
+
const stop = lineEnd < 0 ? text.length : lineEnd;
|
|
1856
|
+
let line = text.slice(pos, stop).replace(/\r$/, '');
|
|
1857
|
+
if (stripTabs)
|
|
1858
|
+
line = line.replace(/^\t+/, '');
|
|
1859
|
+
if (line === delimiter) {
|
|
1860
|
+
span.bodyEnd = pos;
|
|
1861
|
+
span.end = stop;
|
|
1862
|
+
pos = stop + 1;
|
|
1863
|
+
break;
|
|
1864
|
+
}
|
|
1865
|
+
pos = stop + 1;
|
|
1866
|
+
}
|
|
1867
|
+
span.body = text.slice(span.bodyStart, span.bodyEnd < text.length ? Math.max(span.bodyStart, span.bodyEnd - 1) : text.length);
|
|
1868
|
+
spans.push(span);
|
|
1869
|
+
}
|
|
1870
|
+
return pos;
|
|
1871
|
+
};
|
|
1872
|
+
for (let i = 0; i < text.length; i++) {
|
|
1873
|
+
const ctx = stack[stack.length - 1];
|
|
1874
|
+
const c = text[i];
|
|
1875
|
+
const next = text[i + 1];
|
|
1876
|
+
if (ctx.quote === "'") {
|
|
1877
|
+
if (c === "'")
|
|
1878
|
+
ctx.quote = '';
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (c === '\\') {
|
|
1882
|
+
i++;
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
if (ctx.quote === '"') {
|
|
1886
|
+
if (c === '"')
|
|
1887
|
+
ctx.quote = '';
|
|
1888
|
+
else if (c === '$' && next === '(' && text[i + 2] === '(') {
|
|
1889
|
+
const e = arithmeticEnd(i + 1);
|
|
1890
|
+
if (e < 0)
|
|
1891
|
+
return undefined;
|
|
1892
|
+
i = e;
|
|
1893
|
+
}
|
|
1894
|
+
else if (c === '$' && next === '(') {
|
|
1895
|
+
stack.push(frame(')', i + 2));
|
|
1896
|
+
i++;
|
|
1897
|
+
}
|
|
1898
|
+
else if (c === '`')
|
|
1899
|
+
stack.push(frame('`', i + 1));
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
if (c !== '|' && c !== '\n' && !/\s/.test(c))
|
|
1903
|
+
ctx.afterPipe = false;
|
|
1904
|
+
if (c === '#' && (i === 0 || /[\s;&|(]/.test(text[i - 1]))) {
|
|
1905
|
+
const nl = text.indexOf('\n', i);
|
|
1906
|
+
i = (nl < 0 ? text.length : nl) - 1;
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
if (c === "'" || c === '"') {
|
|
1910
|
+
ctx.quote = c;
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
if ((c === '$' && next === '(' && text[i + 2] === '(') || (c === '(' && next === '(')) {
|
|
1914
|
+
const e = arithmeticEnd(c === '$' ? i + 1 : i);
|
|
1915
|
+
if (e < 0)
|
|
1916
|
+
return undefined;
|
|
1917
|
+
i = e;
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
if (c === '$' && next === '(') {
|
|
1921
|
+
stack.push(frame(')', i + 2));
|
|
1922
|
+
i++;
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
if (c === '`') {
|
|
1926
|
+
if (ctx.closer === '`') {
|
|
1927
|
+
endStatement(ctx, i);
|
|
1928
|
+
stack.pop();
|
|
1929
|
+
}
|
|
1930
|
+
else
|
|
1931
|
+
stack.push(frame('`', i + 1));
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (c === '(') {
|
|
1935
|
+
endStatement(ctx, i);
|
|
1936
|
+
ctx.depth++;
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
if (c === ')') {
|
|
1940
|
+
endStatement(ctx, i);
|
|
1941
|
+
if (ctx.depth > 0)
|
|
1942
|
+
ctx.depth--;
|
|
1943
|
+
else if (ctx.closer === ')' && stack.length > 1)
|
|
1944
|
+
stack.pop();
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (c === '{' && (next === undefined || /\s/.test(next))) {
|
|
1948
|
+
endStatement(ctx, i);
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
if (c === ';' || (c === '&' && next !== '>' && text[i - 1] !== '>' && text[i - 1] !== '<')) {
|
|
1952
|
+
endStatement(ctx, i);
|
|
1953
|
+
if ((c === '&' && next === '&') || (c === ';' && next === ';'))
|
|
1954
|
+
i++;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
if (c === '|') {
|
|
1958
|
+
if (next === '|') {
|
|
1959
|
+
endStatement(ctx, i);
|
|
1960
|
+
i++;
|
|
1961
|
+
}
|
|
1962
|
+
else {
|
|
1963
|
+
endStage(ctx, i);
|
|
1964
|
+
ctx.afterPipe = true;
|
|
1965
|
+
if (next === '&')
|
|
1966
|
+
i++;
|
|
1967
|
+
}
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (c === '\n') {
|
|
1971
|
+
// `cat <<'EOF' |` continues the pipeline on the line after the body.
|
|
1972
|
+
if (!ctx.afterPipe)
|
|
1973
|
+
endStatement(ctx, i);
|
|
1974
|
+
if (pending.length) {
|
|
1975
|
+
const after = readBodies(i);
|
|
1976
|
+
if (ctx.stageStart <= after)
|
|
1977
|
+
ctx.stageStart = after;
|
|
1978
|
+
i = after - 1;
|
|
1979
|
+
}
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
if (c === '<' && next === '<' && text[i - 1] !== '<') {
|
|
1983
|
+
if (text[i + 2] === '<') {
|
|
1984
|
+
i += 2;
|
|
1985
|
+
continue;
|
|
1986
|
+
} // `<<<` here-string: quoted-span pass
|
|
1987
|
+
let j = i + 2;
|
|
1988
|
+
const stripTabs = text[j] === '-';
|
|
1989
|
+
if (stripTabs)
|
|
1990
|
+
j++;
|
|
1991
|
+
while (text[j] === ' ' || text[j] === '\t')
|
|
1992
|
+
j++;
|
|
1993
|
+
let delimiter = '';
|
|
1994
|
+
let literal = false;
|
|
1995
|
+
while (j < text.length && !/[\s;&|<>()]/.test(text[j])) {
|
|
1996
|
+
const q = text[j];
|
|
1997
|
+
if (q === "'" || q === '"') {
|
|
1998
|
+
const close = text.indexOf(q, j + 1);
|
|
1999
|
+
if (close < 0)
|
|
2000
|
+
return undefined;
|
|
2001
|
+
delimiter += text.slice(j + 1, close);
|
|
2002
|
+
literal = true;
|
|
2003
|
+
j = close + 1;
|
|
2004
|
+
}
|
|
2005
|
+
else if (q === '\\') {
|
|
2006
|
+
delimiter += text[j + 1] || '';
|
|
2007
|
+
literal = true;
|
|
2008
|
+
j += 2;
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
delimiter += q;
|
|
2012
|
+
j++;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
if (!delimiter) {
|
|
2016
|
+
i = j - 1;
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
const span = { bodyStart: text.length, bodyEnd: text.length, end: text.length, body: '', literal, stages: [] };
|
|
2020
|
+
pending.push({ span, delimiter, stripTabs });
|
|
2021
|
+
ctx.open.push({ span, stage: ctx.stages.length });
|
|
2022
|
+
i = j - 1;
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (stack.length > 1 || stack[0].quote)
|
|
2027
|
+
return undefined;
|
|
2028
|
+
if (pending.length)
|
|
2029
|
+
readBodies(text.length - 1);
|
|
2030
|
+
endStatement(stack[0], text.length);
|
|
2031
|
+
return spans;
|
|
2032
|
+
}
|
|
2033
|
+
/** Heredocs of a POSIX line, each with the receiver that decides what its body is. */
|
|
2034
|
+
function heredocView(text) {
|
|
2035
|
+
if (!text.includes('<<') || text.length > 256_000)
|
|
2036
|
+
return undefined;
|
|
2037
|
+
const spans = lexHeredocs(text);
|
|
2038
|
+
if (!spans || !spans.length)
|
|
2039
|
+
return undefined;
|
|
2040
|
+
let skeleton = '';
|
|
2041
|
+
let at = 0;
|
|
2042
|
+
for (const span of spans) {
|
|
2043
|
+
skeleton += text.slice(at, span.bodyStart);
|
|
2044
|
+
at = span.end;
|
|
2045
|
+
}
|
|
2046
|
+
skeleton += text.slice(at);
|
|
2047
|
+
const statements = splitUnquotedPipelines(skeleton);
|
|
2048
|
+
const heredocs = spans.map(span => {
|
|
2049
|
+
let receiver = { kind: 'data', program: '' };
|
|
2050
|
+
for (const stage of span.stages)
|
|
2051
|
+
receiver = strongerReceiver(receiver, stdinReceiver(stage));
|
|
2052
|
+
receiver = strongerReceiver(receiver, laterRunnerOf(stageWriteTargets(span.stages[0] || ''), statements));
|
|
2053
|
+
return { ...span, receiver };
|
|
2054
|
+
});
|
|
2055
|
+
return { heredocs, skeleton };
|
|
2056
|
+
}
|
|
2057
|
+
/** POSIX heredocs of a command line, in order: body span, delimiter-line end, and receiver. */
|
|
2058
|
+
function posixHeredocs(text) {
|
|
2059
|
+
return (heredocView(text)?.heredocs || []).map(({ bodyStart, bodyEnd, end, body, receiver }) => ({ bodyStart, bodyEnd, end, body, receiver }));
|
|
2060
|
+
}
|
|
2061
|
+
/** Does this heredoc body run as code where it lands? */
|
|
2062
|
+
function heredocBodyActs(heredoc) {
|
|
2063
|
+
// Unquoted delimiter: `$( … )` / backticks in the body run before any receiver sees it.
|
|
2064
|
+
if (!heredoc.literal && INTERPOLATES.test(heredoc.body))
|
|
2065
|
+
return true;
|
|
2066
|
+
if (heredoc.receiver.kind === 'shell')
|
|
2067
|
+
return true;
|
|
2068
|
+
if (heredoc.receiver.kind === 'language') {
|
|
2069
|
+
return CODE_SPAWNS_PROCESS.test(heredoc.body)
|
|
2070
|
+
|| (/^(?:perl|ruby)$/.test(heredoc.receiver.program) && /`[^`]*`|%x[\[{(]/.test(heredoc.body))
|
|
2071
|
+
|| interpreterCodeRequestsMetadata(heredoc.body);
|
|
2072
|
+
}
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Each body becomes `inert` when it is data, or `acting(index)` when it runs
|
|
2077
|
+
* (left as is without `acting`). Delimiter lines stay, so the result still
|
|
2078
|
+
* lexes. Returns the bodies that run so a caller can put them back verbatim.
|
|
2079
|
+
*/
|
|
2080
|
+
function replaceHeredocBodies(text, view, inert, acting) {
|
|
2081
|
+
let out = '';
|
|
2082
|
+
let at = 0;
|
|
2083
|
+
const kept = [];
|
|
2084
|
+
for (const heredoc of view.heredocs) {
|
|
2085
|
+
const acts = heredocBodyActs(heredoc);
|
|
2086
|
+
if (acts && !acting)
|
|
2087
|
+
continue;
|
|
2088
|
+
out += text.slice(at, heredoc.bodyStart) + (acts ? acting(kept.push(heredoc.body) - 1) : inert);
|
|
2089
|
+
at = heredoc.bodyStart + heredoc.body.length;
|
|
2090
|
+
}
|
|
2091
|
+
return { text: out + text.slice(at), acting: kept };
|
|
2092
|
+
}
|
|
2093
|
+
/** The line as a bash parser sees it: bodies that RUN are opaque text there, so blank them. */
|
|
2094
|
+
function withoutActingHeredocBodies(text) {
|
|
2095
|
+
const view = heredocView(text);
|
|
2096
|
+
if (!view)
|
|
2097
|
+
return text;
|
|
2098
|
+
let out = '';
|
|
2099
|
+
let at = 0;
|
|
2100
|
+
for (const heredoc of view.heredocs) {
|
|
2101
|
+
if (!heredocBodyActs(heredoc))
|
|
2102
|
+
continue;
|
|
2103
|
+
out += text.slice(at, heredoc.bodyStart);
|
|
2104
|
+
at = heredoc.bodyStart + heredoc.body.length;
|
|
2105
|
+
}
|
|
2106
|
+
return out + text.slice(at);
|
|
2107
|
+
}
|
|
2108
|
+
const ACTING_BODY_MARK = (index) => `fcd_acting_heredoc_${index}_`;
|
|
2109
|
+
/** Receiver of a `<<<` here-string whose quoted word starts at `offset`. */
|
|
2110
|
+
function hereStringReceiver(src, offset) {
|
|
2111
|
+
const head = src.slice(src.lastIndexOf('\n', offset - 1) + 1, offset).replace(/<<<\s*$/, '');
|
|
2112
|
+
const statements = splitUnquotedPipelines(head);
|
|
2113
|
+
const stages = statements[statements.length - 1] || [];
|
|
2114
|
+
return stdinReceiver(stages[stages.length - 1] || '');
|
|
2115
|
+
}
|
|
1683
2116
|
function stripInertDataSegments(text) {
|
|
1684
|
-
|
|
2117
|
+
const heredocs = heredocView(text);
|
|
2118
|
+
// A data body (`cat > notes.md <<'EOF' … ssh … EOF`) cannot make the line an
|
|
2119
|
+
// interpreter feed; only the code around it, and bodies that run, can.
|
|
2120
|
+
const executable = heredocs ? replaceHeredocBodies(text, heredocs, '').text : text;
|
|
2121
|
+
if (DATA_TO_INTERPRETER.test(unquotedCode(executable)))
|
|
1685
2122
|
return text;
|
|
2123
|
+
// A body that runs is code line for line: park it so the quoted-span pass
|
|
2124
|
+
// below cannot read `os.system('rm -rf ~')` as prose, then put it back.
|
|
2125
|
+
const parked = heredocs ? replaceHeredocBodies(text, heredocs, INERT_DATA, ACTING_BODY_MARK) : { text, acting: [] };
|
|
2126
|
+
let out = stripQuotedData(parked.text);
|
|
2127
|
+
parked.acting.forEach((body, index) => {
|
|
2128
|
+
const mark = ACTING_BODY_MARK(index);
|
|
2129
|
+
out = out.includes(mark) ? out.replace(mark, () => body) : `${out}\n${body}`;
|
|
2130
|
+
});
|
|
2131
|
+
return out;
|
|
2132
|
+
}
|
|
2133
|
+
/** Here-strings and quoted prose of a line whose heredoc bodies are already resolved. */
|
|
2134
|
+
function stripQuotedData(text) {
|
|
1686
2135
|
let out = text;
|
|
1687
2136
|
// PowerShell here-strings: @'…'@ is fully literal; @"…"@ interpolates $( ).
|
|
1688
2137
|
out = replaceDelimited(out, "@'", "'@", () => INERT_DATA);
|
|
1689
2138
|
out = replaceDelimited(out, '@"', '"@', m => (m.includes('$(') ? m : INERT_DATA));
|
|
1690
|
-
// POSIX heredocs: quoted delimiter (<<'EOF') is literal; unquoted interpolates.
|
|
1691
|
-
out = out.replace(/<<-?\s*'(\w+)'[\s\S]*?(?:\n\1\b|$)/g, INERT_DATA);
|
|
1692
|
-
out = out.replace(/<<-?\s*"?(\w+)"?[\s\S]*?(?:\n\1\b|$)/g, m => (INTERPOLATES.test(m) ? m : INERT_DATA));
|
|
1693
2139
|
// Generic quoted-span pass: whitespace inside quotes ⇒ free text ⇒ inert,
|
|
1694
2140
|
// unless the span sits in a code position (invariants 2–4 above).
|
|
1695
2141
|
const src = out;
|
|
2142
|
+
const powershell = isPowerShellLine(src);
|
|
1696
2143
|
out = replaceQuotedSpans(src, (whole, dq, sq, offset) => {
|
|
1697
2144
|
const body = dq !== undefined ? dq : (sq || '');
|
|
1698
2145
|
if (!/\s/.test(body))
|
|
1699
2146
|
return whole; // single token: argv, not prose
|
|
1700
2147
|
const before = src.slice(Math.max(0, offset - 80), offset);
|
|
2148
|
+
// `bash <<< "…"` feeds the quoted word to stdin: it is what its reader makes of it.
|
|
2149
|
+
if (/<<<\s*$/.test(before)) {
|
|
2150
|
+
const receiver = hereStringReceiver(src, offset);
|
|
2151
|
+
if (receiver.kind === 'shell')
|
|
2152
|
+
return whole;
|
|
2153
|
+
if (receiver.kind === 'language')
|
|
2154
|
+
return CODE_SPAWNS_PROCESS.test(body) || interpreterCodeRequestsMetadata(body) ? whole : INERT_DATA;
|
|
2155
|
+
}
|
|
1701
2156
|
// Language-interpreter code: data unless it spawns a process (see above).
|
|
1702
2157
|
if (LANG_INTERPRETER_CODE_BEFORE.test(before))
|
|
1703
2158
|
return interpreterCodeSpawnsProcess(before, body) || interpreterCodeRequestsMetadata(body) ? whole : INERT_DATA;
|
|
1704
2159
|
if (CODE_CONTEXT_BEFORE.test(before))
|
|
1705
2160
|
return whole; // code position: executes
|
|
1706
|
-
if (dq !== undefined &&
|
|
2161
|
+
if (dq !== undefined && doubleQuotedInterpolates(body, powershell))
|
|
1707
2162
|
return whole; // $( )/`cmd` executes first
|
|
1708
2163
|
return INERT_DATA;
|
|
1709
2164
|
});
|
package/dist/commands/doctor.js
CHANGED
|
@@ -165,6 +165,7 @@ async function healthCheck(apiUrl, config) {
|
|
|
165
165
|
}
|
|
166
166
|
console.log('');
|
|
167
167
|
const identity = getMachineIdentity();
|
|
168
|
+
console.log(`machine id: ${identity.machineId.slice(0, 8)} (source: ${identity.idSource || 'unknown'}${identity.idConfirmed ? ', backend-confirmed' : ', not yet confirmed by the backend'})`);
|
|
168
169
|
const report = await runDeepSelfTest({
|
|
169
170
|
creds,
|
|
170
171
|
developerName: identity.developerName,
|
package/dist/commands/login.js
CHANGED
|
@@ -20,6 +20,9 @@ async function loginCommand(args, config) {
|
|
|
20
20
|
throw new Error('A fleet enrollment token is required. Pass --token <token> or set FCD_ENROLL_TOKEN.\n'
|
|
21
21
|
+ 'An org admin issues one from the AI Fleet console (Settings → Fleet enrollment token).');
|
|
22
22
|
}
|
|
23
|
+
// Enrollment binds the fleet record to this id for good: an unconfirmed
|
|
24
|
+
// fallback pin gets one native re-check first, throttle or not.
|
|
25
|
+
(0, machineIdentity_1.reconcileMachineIdentity)('force');
|
|
23
26
|
const identity = (0, machineIdentity_1.getMachineIdentity)();
|
|
24
27
|
const shieldName = (0, machineIdentity_1.suggestedShieldName)(identity);
|
|
25
28
|
const nickname = (args.nickname || process.env.FCD_MACHINE_NICKNAME || '').trim().slice(0, 80) || undefined;
|
|
@@ -51,6 +54,7 @@ async function loginCommand(args, config) {
|
|
|
51
54
|
throw new Error(data.error || `Enrollment failed (HTTP ${resp.status})`);
|
|
52
55
|
}
|
|
53
56
|
const result = data.data;
|
|
57
|
+
(0, machineIdentity_1.confirmMachineIdentity)(identity.machineId, 'enrollment');
|
|
54
58
|
// Clear any leftover runtime cache (previous enrollment, failed/partial
|
|
55
59
|
// install) so a stale enforcing bundle can never keep blocking on a freshly
|
|
56
60
|
// enrolled machine. The next hook/gateway call fetches the real bundle.
|