fullcourtdefense-cli 1.25.5 → 1.25.7
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.
|
@@ -112,6 +112,7 @@ function batQuote(value) {
|
|
|
112
112
|
function buildGuardJs(nodePath, cliEntry) {
|
|
113
113
|
return [
|
|
114
114
|
`'use strict';`,
|
|
115
|
+
`// ${(0, shellGuard_1.guardBuildStamp)()}`,
|
|
115
116
|
`const fs=require('fs');const os=require('os');const path=require('path');const{spawn,spawnSync}=require('child_process');const crypto=require('crypto');`,
|
|
116
117
|
`const RULES_PATH=${JSON.stringify(GUARD_RULES_PATH)};`,
|
|
117
118
|
`const SPOOL_PATH=${JSON.stringify(path.join(os.homedir(), '.fullcourtdefense-spool.jsonl'))};`,
|
|
@@ -146,6 +147,7 @@ function buildAutorunBat(nodePath) {
|
|
|
146
147
|
return [
|
|
147
148
|
'@echo off',
|
|
148
149
|
'REM FullCourtDefense cmd.exe guard (installed by fullcourtdefense-cli).',
|
|
150
|
+
`REM ${(0, shellGuard_1.guardBuildStamp)()}`,
|
|
149
151
|
'REM Disable for one session: set FCD_CMD_GUARD=off',
|
|
150
152
|
'REM Remove permanently: fullcourtdefense uninstall-cmd-guard',
|
|
151
153
|
'if defined FCD_CMD_AUTORUN_DONE goto :eof',
|
|
@@ -275,6 +277,13 @@ function refreshCmdGuardRules() {
|
|
|
275
277
|
if (!isCmdGuardInstalled())
|
|
276
278
|
return;
|
|
277
279
|
(0, shellGuard_1.writeShellGuardRules)();
|
|
280
|
+
// Self-heal after CLI updates: the planted checker/autorun carry the
|
|
281
|
+
// detection logic of the build that wrote them — rewrite when stale.
|
|
282
|
+
if ((0, shellGuard_1.guardArtifactStale)(GUARD_JS_PATH)) {
|
|
283
|
+
const nodePath = process.execPath;
|
|
284
|
+
(0, shellGuard_1.writeGuardArtifact)(GUARD_JS_PATH, buildGuardJs(nodePath, process.argv[1] || ''));
|
|
285
|
+
(0, shellGuard_1.writeGuardArtifact)(AUTORUN_BAT_PATH, buildAutorunBat(nodePath));
|
|
286
|
+
}
|
|
278
287
|
}
|
|
279
288
|
catch { /* best-effort */ }
|
|
280
289
|
}
|
|
@@ -97,6 +97,7 @@ function shSingleQuote(value) {
|
|
|
97
97
|
function buildGuardJs(nodePath, cliEntry) {
|
|
98
98
|
return [
|
|
99
99
|
`'use strict';`,
|
|
100
|
+
`// ${(0, shellGuard_1.guardBuildStamp)()}`,
|
|
100
101
|
`const fs=require('fs');const crypto=require('crypto');const{spawn}=require('child_process');`,
|
|
101
102
|
`const RULES_PATH=${JSON.stringify(GUARD_RULES_PATH)};`,
|
|
102
103
|
`const SPOOL_PATH=${JSON.stringify(SPOOL_PATH)};`,
|
|
@@ -128,6 +129,7 @@ function buildGuardJs(nodePath, cliEntry) {
|
|
|
128
129
|
function buildGuardZsh(nodePath) {
|
|
129
130
|
return [
|
|
130
131
|
`# FullCourtDefense interactive shell guard for zsh (installed by fullcourtdefense-cli).`,
|
|
132
|
+
`# ${(0, shellGuard_1.guardBuildStamp)()}`,
|
|
131
133
|
`# Disable for one session: export FCD_SHELL_GUARD=off`,
|
|
132
134
|
`# Remove permanently: fullcourtdefense uninstall-shell-guard`,
|
|
133
135
|
``,
|
|
@@ -174,6 +176,7 @@ function buildGuardZsh(nodePath) {
|
|
|
174
176
|
function buildGuardBash(nodePath) {
|
|
175
177
|
return [
|
|
176
178
|
`# FullCourtDefense interactive shell guard for bash (installed by fullcourtdefense-cli).`,
|
|
179
|
+
`# ${(0, shellGuard_1.guardBuildStamp)()}`,
|
|
177
180
|
`# Disable for one session: export FCD_SHELL_GUARD=off`,
|
|
178
181
|
`# Remove permanently: fullcourtdefense uninstall-shell-guard`,
|
|
179
182
|
``,
|
|
@@ -393,6 +396,15 @@ function refreshPosixShellGuardRules() {
|
|
|
393
396
|
if (!isPosixShellGuardInstalled())
|
|
394
397
|
return;
|
|
395
398
|
(0, shellGuard_1.writeShellGuardRules)();
|
|
399
|
+
// Self-heal after CLI updates: planted guard scripts carry the detection
|
|
400
|
+
// logic of the build that wrote them — rewrite when the stamp is stale.
|
|
401
|
+
if ((0, shellGuard_1.guardArtifactStale)(GUARD_JS_PATH)) {
|
|
402
|
+
const nodePath = process.execPath;
|
|
403
|
+
const cliEntry = process.argv[1] || '';
|
|
404
|
+
(0, shellGuard_1.writeGuardArtifact)(GUARD_JS_PATH, buildGuardJs(nodePath, cliEntry), 0o600);
|
|
405
|
+
(0, shellGuard_1.writeGuardArtifact)(GUARD_ZSH_PATH, buildGuardZsh(nodePath), 0o600);
|
|
406
|
+
(0, shellGuard_1.writeGuardArtifact)(GUARD_BASH_PATH, buildGuardBash(nodePath), 0o600);
|
|
407
|
+
}
|
|
396
408
|
}
|
|
397
409
|
catch { /* best-effort */ }
|
|
398
410
|
}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build stamp — the CLI version that generated a planted guard artifact.
|
|
3
|
+
*
|
|
4
|
+
* The profile/rc/autorun guard scripts are COPIES of CLI detection logic
|
|
5
|
+
* written to disk at install time. Without a stamp, a fleet CLI self-update
|
|
6
|
+
* ships new detection logic but every machine keeps running the old planted
|
|
7
|
+
* script forever (rules JSON refreshes carry rules, never logic). Each
|
|
8
|
+
* builder embeds this stamp in a comment; the telemetry-cycle refresh
|
|
9
|
+
* rewrites any artifact whose stamp differs from the running build.
|
|
10
|
+
*/
|
|
11
|
+
export declare const GUARD_BUILD_MARKER = "fcd-guard-build:";
|
|
12
|
+
export declare function guardBuildStamp(): string;
|
|
13
|
+
/** True when a planted guard artifact was generated by a DIFFERENT CLI build. */
|
|
14
|
+
export declare function guardArtifactStale(filePath: string): boolean;
|
|
15
|
+
/** Atomic write — a torn guard script would break every new shell that sources it. */
|
|
16
|
+
export declare function writeGuardArtifact(filePath: string, content: string, mode?: number): void;
|
|
1
17
|
/** Blast-severity levels (shellfirm-style). Drives per-severity console policy. */
|
|
2
18
|
export type ShellGuardSeverity = 'critical' | 'high' | 'medium' | 'low';
|
|
3
19
|
export interface ShellGuardRule {
|
|
@@ -33,6 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GUARD_BUILD_MARKER = void 0;
|
|
37
|
+
exports.guardBuildStamp = guardBuildStamp;
|
|
38
|
+
exports.guardArtifactStale = guardArtifactStale;
|
|
39
|
+
exports.writeGuardArtifact = writeGuardArtifact;
|
|
36
40
|
exports.writeShellGuardRules = writeShellGuardRules;
|
|
37
41
|
exports.activeShellGuardRules = activeShellGuardRules;
|
|
38
42
|
exports.evaluateShellCommand = evaluateShellCommand;
|
|
@@ -81,6 +85,48 @@ const RUNTIME_CACHE_PATH = path.join(os.homedir(), '.fullcourtdefense-runtime.js
|
|
|
81
85
|
const SNAPSHOT_CACHE_DIR = path.join(os.homedir(), '.fullcourtdefense');
|
|
82
86
|
const MARKER_START = '# >>> fullcourtdefense shell guard >>>';
|
|
83
87
|
const MARKER_END = '# <<< fullcourtdefense shell guard <<<';
|
|
88
|
+
/**
|
|
89
|
+
* Build stamp — the CLI version that generated a planted guard artifact.
|
|
90
|
+
*
|
|
91
|
+
* The profile/rc/autorun guard scripts are COPIES of CLI detection logic
|
|
92
|
+
* written to disk at install time. Without a stamp, a fleet CLI self-update
|
|
93
|
+
* ships new detection logic but every machine keeps running the old planted
|
|
94
|
+
* script forever (rules JSON refreshes carry rules, never logic). Each
|
|
95
|
+
* builder embeds this stamp in a comment; the telemetry-cycle refresh
|
|
96
|
+
* rewrites any artifact whose stamp differs from the running build.
|
|
97
|
+
*/
|
|
98
|
+
exports.GUARD_BUILD_MARKER = 'fcd-guard-build:';
|
|
99
|
+
function guardBuildVersion() {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'version.json'), 'utf8'));
|
|
102
|
+
return parsed.version || 'unknown';
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return 'unknown';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function guardBuildStamp() {
|
|
109
|
+
return `${exports.GUARD_BUILD_MARKER} ${guardBuildVersion()}`;
|
|
110
|
+
}
|
|
111
|
+
/** True when a planted guard artifact was generated by a DIFFERENT CLI build. */
|
|
112
|
+
function guardArtifactStale(filePath) {
|
|
113
|
+
const version = guardBuildVersion();
|
|
114
|
+
// Dev builds without version.json must not churn artifacts every 30s.
|
|
115
|
+
if (version === 'unknown')
|
|
116
|
+
return false;
|
|
117
|
+
try {
|
|
118
|
+
return !fs.readFileSync(filePath, 'utf8').includes(`${exports.GUARD_BUILD_MARKER} ${version}`);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Atomic write — a torn guard script would break every new shell that sources it. */
|
|
125
|
+
function writeGuardArtifact(filePath, content, mode) {
|
|
126
|
+
const tmp = `${filePath}.tmp`;
|
|
127
|
+
fs.writeFileSync(tmp, content, mode !== undefined ? { encoding: 'utf8', mode } : 'utf8');
|
|
128
|
+
fs.renameSync(tmp, filePath);
|
|
129
|
+
}
|
|
84
130
|
/** Confine every lookahead to a single command segment (never span `;` `&` `|`). */
|
|
85
131
|
const SEG = String.raw `[^;&|]*`;
|
|
86
132
|
function compileSpec(spec) {
|
|
@@ -444,6 +490,12 @@ function refreshShellGuardRules() {
|
|
|
444
490
|
if (!fs.existsSync(GUARD_PS1_PATH))
|
|
445
491
|
return;
|
|
446
492
|
writeShellGuardRules();
|
|
493
|
+
// Self-heal after CLI updates: the planted profile script carries the
|
|
494
|
+
// detection logic of the build that WROTE it. Rewrite when stale so new
|
|
495
|
+
// shells load the current guard (rules refreshes alone never carry logic).
|
|
496
|
+
if (guardArtifactStale(GUARD_PS1_PATH)) {
|
|
497
|
+
writeGuardArtifact(GUARD_PS1_PATH, buildGuardPs1(process.execPath, process.argv[1] || ''));
|
|
498
|
+
}
|
|
447
499
|
}
|
|
448
500
|
catch { /* best-effort */ }
|
|
449
501
|
}
|
|
@@ -454,6 +506,7 @@ function psQuote(value) {
|
|
|
454
506
|
function buildGuardPs1(nodePath, cliEntry) {
|
|
455
507
|
const lines = [
|
|
456
508
|
`# FullCourtDefense interactive shell guard (installed by fullcourtdefense-cli).`,
|
|
509
|
+
`# ${guardBuildStamp()}`,
|
|
457
510
|
`# Blocks dangerous commands typed in PowerShell using the org's cached rules.`,
|
|
458
511
|
`# Disable for one session: $env:FCD_SHELL_GUARD = 'off'`,
|
|
459
512
|
`# Remove permanently: fullcourtdefense uninstall-shell-guard`,
|
|
@@ -577,9 +630,15 @@ function buildGuardPs1(nodePath, cliEntry) {
|
|
|
577
630
|
` try { $hit = Test-FcdShellGuard -CommandLine $line } catch { $hit = $null }`,
|
|
578
631
|
` }`,
|
|
579
632
|
` if ($null -ne $hit) {`,
|
|
633
|
+
` # Writing from inside a PSReadLine key handler races the line redraw:`,
|
|
634
|
+
` # on AcceptLine PSReadLine repaints the buffer at its saved coordinates,`,
|
|
635
|
+
` # which can overwrite the message (it flashes and disappears — seen in`,
|
|
636
|
+
` # Cursor/VS Code terminals). InvokePrompt() re-renders the prompt+buffer`,
|
|
637
|
+
` # BELOW the message so the warning stays in the scrollback permanently.`,
|
|
580
638
|
` if ($global:FcdGuardMode -eq 'monitor') {`,
|
|
581
639
|
` Write-Host ''`,
|
|
582
640
|
` Write-Host ('[FullCourtDefense] monitor: would block [' + $hit.Severity + '] ' + $hit.Reason + ' (rule ' + $hit.Id + ')') -ForegroundColor Yellow`,
|
|
641
|
+
` try { [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() } catch { }`,
|
|
583
642
|
` Write-FcdGuardEvent -Rule $hit -CommandLine $line -Decision 'warn'`,
|
|
584
643
|
` [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()`,
|
|
585
644
|
` return`,
|
|
@@ -588,6 +647,7 @@ function buildGuardPs1(nodePath, cliEntry) {
|
|
|
588
647
|
` if ($hit.Action -eq 'warn') {`,
|
|
589
648
|
` Write-Host ''`,
|
|
590
649
|
` Write-Host ('[FullCourtDefense] warning [' + $hit.Severity + ']: ' + $hit.Reason + ' (rule ' + $hit.Id + ') — allowed by org policy, reported to your security dashboard.') -ForegroundColor Yellow`,
|
|
650
|
+
` try { [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() } catch { }`,
|
|
591
651
|
` Write-FcdGuardEvent -Rule $hit -CommandLine $line -Decision 'warn'`,
|
|
592
652
|
` [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()`,
|
|
593
653
|
` return`,
|
|
@@ -596,6 +656,7 @@ function buildGuardPs1(nodePath, cliEntry) {
|
|
|
596
656
|
` Write-Host ''`,
|
|
597
657
|
` Write-Host ('[FullCourtDefense] BLOCKED [' + $hit.Severity + ']: ' + $hit.Reason + ' (rule ' + $hit.Id + ')') -ForegroundColor Red`,
|
|
598
658
|
` Write-Host 'This command was not executed. Reported to your security dashboard.' -ForegroundColor DarkGray`,
|
|
659
|
+
` try { [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() } catch { }`,
|
|
599
660
|
` Write-FcdGuardEvent -Rule $hit -CommandLine $line -Decision 'block'`,
|
|
600
661
|
` [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()`,
|
|
601
662
|
` return`,
|
package/dist/version.json
CHANGED