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
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.25.5"
2
+ "version": "1.25.7"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.25.5",
3
+ "version": "1.25.7",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {