fullcourtdefense-cli 1.34.16 → 1.34.17

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.
@@ -5739,10 +5739,11 @@ function parse3(input, options) {
5739
5739
  }
5740
5740
 
5741
5741
  // src/actionPolicyEngine.ts
5742
+ var import_detectionData = require("./detectionData");
5742
5743
  function isFirestoreQueryUrl(target) {
5743
5744
  try {
5744
5745
  const url = new URL(target);
5745
- return url.origin === "https://firestore.googleapis.com" && !url.username && !url.password && !url.search && !url.hash && /^\/v1\/projects\/[^/]+\/databases\/[^/]+\/documents(?:\/[^/]+)*:runQuery$/.test(url.pathname);
5746
+ return (0, import_detectionData.getDetectionData)().firestoreQueryOrigins.includes(url.origin) && !url.username && !url.password && !url.search && !url.hash && /^\/v1\/projects\/[^/]+\/databases\/[^/]+\/documents(?:\/[^/]+)*:runQuery$/.test(url.pathname);
5746
5747
  } catch {
5747
5748
  return false;
5748
5749
  }
@@ -5871,7 +5872,7 @@ function provenJavaScriptReadUrls(code) {
5871
5872
  const options = ev(node.arguments[0]);
5872
5873
  if (options.kind !== "object" || options.fields.size !== 1) return fail();
5873
5874
  const scopes = options.fields.get("scopes");
5874
- if (scopes?.kind !== "array" || scopes.items.length !== 1 || string(scopes.items[0]) !== "https://www.googleapis.com/auth/datastore") return fail();
5875
+ if (scopes?.kind !== "array" || scopes.items.length !== 1 || !(0, import_detectionData.getDetectionData)().googleAuthScopes.includes(string(scopes.items[0]))) return fail();
5875
5876
  return object([["getClient", callable("google.getClient")]]);
5876
5877
  }
5877
5878
  case "ReturnStatement":
@@ -7007,7 +7008,45 @@ function extractUrl(args, _argsText) {
7007
7008
  return "";
7008
7009
  }
7009
7010
  function shellUrlActionText(command) {
7010
- const segments = splitShellSegments(command);
7011
+ if (command.length > 65536) return command;
7012
+ const originalSegments = splitShellSegments(command);
7013
+ const localPaths = /* @__PURE__ */ new Map();
7014
+ const pathAssignments = /* @__PURE__ */ new Set();
7015
+ let expandedSize = command.length;
7016
+ let expansionOverflow = false;
7017
+ const expand = (original, value) => {
7018
+ expandedSize += Math.max(0, value.length - original.length);
7019
+ if (expandedSize > 65536) {
7020
+ expansionOverflow = true;
7021
+ return original;
7022
+ }
7023
+ return value;
7024
+ };
7025
+ const segments = originalSegments.map((segment, index) => {
7026
+ const assignment = /^\$([A-Za-z_][\w]*)\s*=\s*(?:'([^']*)'|"([^"$`]*)")$/.exec(segment);
7027
+ if (assignment) {
7028
+ const name = assignment[1].toLowerCase();
7029
+ const value = assignment[2] ?? assignment[3];
7030
+ if (localPaths.has(name) || localPaths.size >= 32 || value.length > 4096 || !/^(?:[A-Za-z]:[\\/]|\/(?!\/))[\w ./\\-]+$/.test(value)) return segment;
7031
+ localPaths.set(name, value);
7032
+ pathAssignments.add(index);
7033
+ return segment;
7034
+ }
7035
+ const literal2 = /^(?:@'\r?\n[\s\S]*?\r?\n'@|@"\r?\n[^$`]*?\r?\n"@|'(?:[^']|'')*'|"[^"$`]*")/.exec(segment);
7036
+ const prefix = literal2?.[0] || "";
7037
+ const tail = segment.slice(prefix.length).replace(
7038
+ /"\$([A-Za-z_][\w]*)([\\/][\w ./\\-]*)?"/g,
7039
+ (match, name, suffix) => {
7040
+ const value = localPaths.get(name.toLowerCase());
7041
+ return value ? expand(match, `"${value}${suffix || ""}"`) : match;
7042
+ }
7043
+ ).replace(/(?<=\s)\$([A-Za-z_][\w]*)(?=\s|$)/g, (match, name) => {
7044
+ const value = localPaths.get(name.toLowerCase());
7045
+ return value ? expand(match, `"${value}"`) : match;
7046
+ });
7047
+ return prefix + tail;
7048
+ });
7049
+ if (expansionOverflow) return command;
7011
7050
  const filtered = segments.map((segment) => {
7012
7051
  const literal2 = /^(?:@'\r?\n[\s\S]*?\r?\n'@|@"\r?\n[^$`]*?\r?\n"@|'(?:[^']|'')*'|"[^"$`]*")/;
7013
7052
  const piped = segment.match(literal2);
@@ -7040,7 +7079,10 @@ function shellUrlActionText(command) {
7040
7079
  return tail;
7041
7080
  });
7042
7081
  const onlyStoredData = segments.every((segment, index) => {
7082
+ if (pathAssignments.has(index)) return true;
7043
7083
  if (filtered[index] !== segment) return true;
7084
+ const directory = /^New-Item\s+-ItemType\s+Directory\s+(?:-Force\s+)?-(?:LiteralPath|Path)\s+(?:'([^']*)'|"([^"$`]*)")\s*(?:\|\s*Out-Null)?$/i.exec(segment);
7085
+ if (directory && /^(?:[A-Za-z]:[\\/]|\/(?!\/))[\w ./\\-]+$/.test(directory[1] ?? directory[2])) return true;
7044
7086
  const words = maskQuotedSpans(segment);
7045
7087
  if (!/[$`|;&(){}<>]/.test(segment) && /^(?:(?:cd|pushd|set-location)\s+[^\r\n]+|(?:pwd|popd|get-location)|git\s+status(?:\s+[^\r\n]+)?)$/i.test(segment)) return true;
7046
7088
  return !/[$`]/.test(segment) && /^\(Get-Content\s+(?:[A-Za-z]:[\\/])?[\w./\\-]+\s+-Raw\)\.Replace\("",\s*""\)\s*\|\s*Set-Content\s+(?:[A-Za-z]:[\\/])?[\w./\\-]+\s+-Encoding\s+utf8\s*$/i.test(words);
@@ -75,6 +75,7 @@ const telemetry_1 = require("../telemetry");
75
75
  const notify_1 = require("../notify");
76
76
  const integrity_1 = require("../integrity");
77
77
  const machineIdentity_1 = require("../machineIdentity");
78
+ const localDetectionUpdates_1 = require("../localDetectionUpdates");
78
79
  const discoveryMarker_1 = require("../discoveryMarker");
79
80
  const selfUpdate_1 = require("../selfUpdate");
80
81
  const cmdGuard_1 = require("./cmdGuard");
@@ -1204,6 +1205,7 @@ async function runDaemon(args, config) {
1204
1205
  const pollBundle = async () => {
1205
1206
  if (!creds.shieldId)
1206
1207
  return;
1208
+ void localDetectionUpdates_1.localDetectionUpdates.refresh();
1207
1209
  if (!creds.shieldKey) {
1208
1210
  // Credential-broken machine: stay reachable via the key-less signed-
1209
1211
  // action channel while credential recovery keeps retrying.
@@ -0,0 +1,21 @@
1
+ export declare const DETECTION_SCHEMA = 1;
2
+ export declare const MAX_DETECTION_BYTES = 16384;
3
+ export declare const DETECTION_SIGNATURE_DOMAIN = "fullcourtdefense/detection-data/v1\n";
4
+ export declare const DETECTION_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEALTPVDAPBFn0XNHo+y3GFHQ8Inr/OM7z1vqUixWL8R00=\n-----END PUBLIC KEY-----";
5
+ export interface DetectionData {
6
+ schema: 1;
7
+ revision: number;
8
+ /** These capabilities never execute data, and never contain a policy verdict. */
9
+ googleAuthScopes: string[];
10
+ firestoreQueryOrigins: string[];
11
+ }
12
+ export declare const BUNDLED_DETECTION_DATA: Readonly<DetectionData>;
13
+ export declare function getDetectionData(): Readonly<DetectionData>;
14
+ export declare function validateDetectionData(value: unknown): DetectionData;
15
+ export interface DetectionEnvelope {
16
+ payload: string;
17
+ signature: string;
18
+ }
19
+ export declare function verifyDetectionEnvelope(raw: string, now?: number, publicKey?: string): DetectionData;
20
+ /** Callers must authenticate envelopes before activation; always copy/freeze to avoid mutation. */
21
+ export declare function activateDetectionData(data: DetectionData): boolean;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUNDLED_DETECTION_DATA = exports.DETECTION_PUBLIC_KEY = exports.DETECTION_SIGNATURE_DOMAIN = exports.MAX_DETECTION_BYTES = exports.DETECTION_SCHEMA = void 0;
4
+ exports.getDetectionData = getDetectionData;
5
+ exports.validateDetectionData = validateDetectionData;
6
+ exports.verifyDetectionEnvelope = verifyDetectionEnvelope;
7
+ exports.activateDetectionData = activateDetectionData;
8
+ /** Pure, bounded detection vocabulary. Vendored with the policy engine into CLI/demo. */
9
+ const crypto_1 = require("crypto");
10
+ exports.DETECTION_SCHEMA = 1;
11
+ exports.MAX_DETECTION_BYTES = 16_384;
12
+ exports.DETECTION_SIGNATURE_DOMAIN = 'fullcourtdefense/detection-data/v1\n';
13
+ exports.DETECTION_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
14
+ MCowBQYDK2VwAyEALTPVDAPBFn0XNHo+y3GFHQ8Inr/OM7z1vqUixWL8R00=
15
+ -----END PUBLIC KEY-----`;
16
+ exports.BUNDLED_DETECTION_DATA = Object.freeze({
17
+ schema: 1, revision: 1,
18
+ googleAuthScopes: Object.freeze(['https://www.googleapis.com/auth/datastore']),
19
+ firestoreQueryOrigins: Object.freeze(['https://firestore.googleapis.com']),
20
+ });
21
+ let active = exports.BUNDLED_DETECTION_DATA;
22
+ function getDetectionData() { return active; }
23
+ function exactKeys(value, keys) {
24
+ return !!value && typeof value === 'object' && !Array.isArray(value)
25
+ && Object.keys(value).length === keys.length && keys.every(key => Object.prototype.hasOwnProperty.call(value, key));
26
+ }
27
+ function validateDetectionData(value) {
28
+ const data = value;
29
+ if (!exactKeys(data, ['schema', 'revision', 'googleAuthScopes', 'firestoreQueryOrigins'])
30
+ || data.schema !== exports.DETECTION_SCHEMA || !Number.isSafeInteger(data.revision) || data.revision < 1)
31
+ throw new Error('Unsupported detection schema/revision');
32
+ const list = (items, valid) => {
33
+ if (!Array.isArray(items) || items.length < 1 || items.length > 32
34
+ || items.some(item => typeof item !== 'string' || item.length > 200 || !valid(item))
35
+ || new Set(items).size !== items.length)
36
+ throw new Error('Invalid detection vocabulary');
37
+ return [...items];
38
+ };
39
+ // Scope URLs are authorization metadata, not destinations. Restrict to the provider namespace.
40
+ const scopes = list(data.googleAuthScopes, s => /^https:\/\/www\.googleapis\.com\/auth\/[a-z][a-z0-9._-]*$/.test(s));
41
+ // Only Firestore service origins can use the fixed structuredQuery body/path proof.
42
+ // No wildcard host, arbitrary URL, regex, flag, verdict, or shell pattern is supported.
43
+ const origins = list(data.firestoreQueryOrigins, s => /^https:\/\/firestore(?:\.[a-z][a-z0-9-]{0,40})?\.googleapis\.com$/.test(s));
44
+ return { schema: 1, revision: data.revision, googleAuthScopes: scopes, firestoreQueryOrigins: origins };
45
+ }
46
+ function verifyDetectionEnvelope(raw, now = Date.now(), publicKey = exports.DETECTION_PUBLIC_KEY) {
47
+ if (Buffer.byteLength(raw) > exports.MAX_DETECTION_BYTES)
48
+ throw new Error('Detection update too large');
49
+ const envelope = JSON.parse(raw);
50
+ if (!exactKeys(envelope, ['payload', 'signature']) || typeof envelope.payload !== 'string'
51
+ || typeof envelope.signature !== 'string' || !/^[A-Za-z0-9+/]{86}==$/.test(envelope.signature))
52
+ throw new Error('Invalid detection envelope');
53
+ if (!(0, crypto_1.verify)(null, Buffer.from(exports.DETECTION_SIGNATURE_DOMAIN + envelope.payload), (0, crypto_1.createPublicKey)(publicKey), Buffer.from(envelope.signature, 'base64')))
54
+ throw new Error('Invalid detection signature');
55
+ const payload = JSON.parse(envelope.payload);
56
+ if (!exactKeys(payload, ['issuedAt', 'expiresAt', 'data']) || !Number.isSafeInteger(payload.issuedAt)
57
+ || !Number.isSafeInteger(payload.expiresAt) || payload.issuedAt > now + 60_000
58
+ || payload.expiresAt <= now || payload.expiresAt <= payload.issuedAt
59
+ || payload.expiresAt - payload.issuedAt > 90 * 86400_000)
60
+ throw new Error('Detection update outside validity window');
61
+ return validateDetectionData(payload.data);
62
+ }
63
+ /** Callers must authenticate envelopes before activation; always copy/freeze to avoid mutation. */
64
+ function activateDetectionData(data) {
65
+ const valid = validateDetectionData(data);
66
+ if (valid.revision <= active.revision)
67
+ return false;
68
+ active = Object.freeze({ ...valid, googleAuthScopes: Object.freeze(valid.googleAuthScopes),
69
+ firestoreQueryOrigins: Object.freeze(valid.firestoreQueryOrigins) });
70
+ return true;
71
+ }
@@ -0,0 +1,25 @@
1
+ export declare const DETECTION_UPDATE_URL = "https://storage.googleapis.com/fullcourtdefense-cli-releases/detection/stable.json";
2
+ export interface DetectionUpdateStatus {
3
+ revision: number;
4
+ bundledRevision: number;
5
+ source: 'bundled' | 'update';
6
+ lastCheckedAt?: string;
7
+ state: 'bundled' | 'current' | 'unavailable' | 'rejected';
8
+ }
9
+ export declare class DetectionUpdates {
10
+ private readonly cachePath;
11
+ private readonly publicKey;
12
+ private readonly channel;
13
+ private nextCheck;
14
+ private nextLocalRead;
15
+ private pending?;
16
+ private status;
17
+ constructor(cachePath: string, publicKey?: string, channel?: 'stable' | 'staging');
18
+ getStatus(): DetectionUpdateStatus;
19
+ private readCache;
20
+ private cachedData;
21
+ loadLocal(): void;
22
+ /** Call only from a background worker. Coalesces concurrent calls and backs off on failure. */
23
+ refresh(): Promise<void>;
24
+ private download;
25
+ }
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DetectionUpdates = exports.DETECTION_UPDATE_URL = void 0;
37
+ /** Background-only transport. Commands use authenticated local data; never fetch here on a verdict path. */
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const crypto_1 = require("crypto");
41
+ const detectionData_1 = require("./detectionData");
42
+ exports.DETECTION_UPDATE_URL = 'https://storage.googleapis.com/fullcourtdefense-cli-releases/detection/stable.json';
43
+ class DetectionUpdates {
44
+ cachePath;
45
+ publicKey;
46
+ channel;
47
+ nextCheck = 0;
48
+ nextLocalRead = 0;
49
+ pending;
50
+ status = { revision: detectionData_1.BUNDLED_DETECTION_DATA.revision,
51
+ bundledRevision: detectionData_1.BUNDLED_DETECTION_DATA.revision, source: 'bundled', state: 'bundled' };
52
+ constructor(cachePath, publicKey = detectionData_1.DETECTION_PUBLIC_KEY, channel = 'stable') {
53
+ this.cachePath = cachePath;
54
+ this.publicKey = publicKey;
55
+ this.channel = channel;
56
+ }
57
+ getStatus() {
58
+ const revision = (0, detectionData_1.getDetectionData)().revision;
59
+ return { ...this.status, revision, source: revision > detectionData_1.BUNDLED_DETECTION_DATA.revision ? 'update' : 'bundled' };
60
+ }
61
+ readCache() {
62
+ try {
63
+ if (fs.statSync(this.cachePath).size > detectionData_1.MAX_DETECTION_BYTES)
64
+ return undefined;
65
+ return fs.readFileSync(this.cachePath, 'utf8');
66
+ }
67
+ catch {
68
+ return undefined;
69
+ }
70
+ }
71
+ cachedData(raw) {
72
+ // Expiration gates first acceptance, not continued offline use. Authenticate
73
+ // the signed validity period before using the last verified cache after restart.
74
+ const payload = JSON.parse(JSON.parse(raw).payload);
75
+ return (0, detectionData_1.verifyDetectionEnvelope)(raw, Math.min(Date.now(), payload.issuedAt), this.publicKey);
76
+ }
77
+ loadLocal() {
78
+ if (Date.now() < this.nextLocalRead)
79
+ return;
80
+ this.nextLocalRead = Date.now() + 30_000;
81
+ const raw = this.readCache();
82
+ if (!raw)
83
+ return;
84
+ try {
85
+ (0, detectionData_1.activateDetectionData)(this.cachedData(raw));
86
+ }
87
+ catch {
88
+ this.status.state = 'rejected';
89
+ }
90
+ }
91
+ /** Call only from a background worker. Coalesces concurrent calls and backs off on failure. */
92
+ refresh() {
93
+ if (this.pending)
94
+ return this.pending;
95
+ if (Date.now() < this.nextCheck)
96
+ return Promise.resolve();
97
+ this.nextCheck = Date.now() + 60_000 + Math.floor(Math.random() * 15_000);
98
+ this.pending = this.download().finally(() => { this.pending = undefined; });
99
+ return this.pending;
100
+ }
101
+ async download() {
102
+ this.status.lastCheckedAt = new Date().toISOString();
103
+ let raw;
104
+ try {
105
+ const response = await fetch(this.channel === 'staging' ? exports.DETECTION_UPDATE_URL.replace('/stable.json', '/staging.json') : exports.DETECTION_UPDATE_URL, { redirect: 'error', signal: AbortSignal.timeout(3000) });
106
+ if (!response.ok || !response.body)
107
+ throw new Error('Unavailable');
108
+ const reader = response.body.getReader();
109
+ const chunks = [];
110
+ let size = 0;
111
+ try {
112
+ for (;;) {
113
+ const { done, value } = await reader.read();
114
+ if (done)
115
+ break;
116
+ size += value.byteLength;
117
+ if (size > detectionData_1.MAX_DETECTION_BYTES)
118
+ throw new Error('Oversized');
119
+ chunks.push(value);
120
+ }
121
+ }
122
+ finally {
123
+ await reader.cancel().catch(() => { });
124
+ }
125
+ raw = Buffer.concat(chunks).toString('utf8');
126
+ }
127
+ catch {
128
+ this.status.state = 'unavailable';
129
+ this.nextCheck = Date.now() + 5 * 60_000;
130
+ return;
131
+ }
132
+ let lock;
133
+ let temporary;
134
+ try {
135
+ const data = (0, detectionData_1.verifyDetectionEnvelope)(raw, Date.now(), this.publicKey);
136
+ fs.mkdirSync(path.dirname(this.cachePath), { recursive: true });
137
+ const lockPath = this.cachePath + '.lock';
138
+ try {
139
+ const stat = fs.statSync(lockPath);
140
+ const pid = stat.size <= 30 ? Number(fs.readFileSync(lockPath, 'utf8')) : NaN;
141
+ if (Number.isSafeInteger(pid) && pid > 0) {
142
+ try {
143
+ process.kill(pid, 0);
144
+ }
145
+ catch (error) {
146
+ if (error.code === 'ESRCH')
147
+ fs.unlinkSync(lockPath);
148
+ }
149
+ }
150
+ else if (Date.now() - stat.mtimeMs > 10 * 60_000)
151
+ fs.unlinkSync(lockPath);
152
+ }
153
+ catch { /* absent lock or another writer; exclusive creation decides */ }
154
+ lock = fs.openSync(lockPath, 'wx', 0o600);
155
+ fs.writeFileSync(lock, String(process.pid));
156
+ const currentRaw = this.readCache();
157
+ let cached;
158
+ try {
159
+ cached = currentRaw ? this.cachedData(currentRaw) : undefined;
160
+ }
161
+ catch { /* recover corrupt cache */ }
162
+ const cachedRevision = cached?.revision ?? 0;
163
+ const currentRevision = Math.max(cachedRevision, (0, detectionData_1.getDetectionData)().revision);
164
+ if (data.revision < currentRevision)
165
+ throw new Error('Revision downgrade');
166
+ if (cached && data.revision === cached.revision && JSON.stringify(data) !== JSON.stringify(cached))
167
+ throw new Error('Cached revision reused for different content');
168
+ if (data.revision === (0, detectionData_1.getDetectionData)().revision && JSON.stringify(data) !== JSON.stringify((0, detectionData_1.getDetectionData)()))
169
+ throw new Error('Revision reused for different content');
170
+ if (data.revision > currentRevision) {
171
+ temporary = this.cachePath + '.' + (0, crypto_1.randomUUID)() + '.tmp';
172
+ const fd = fs.openSync(temporary, 'wx', 0o600);
173
+ try {
174
+ fs.writeFileSync(fd, raw);
175
+ fs.fsyncSync(fd);
176
+ }
177
+ finally {
178
+ fs.closeSync(fd);
179
+ }
180
+ fs.renameSync(temporary, this.cachePath);
181
+ temporary = undefined;
182
+ (0, detectionData_1.activateDetectionData)(data);
183
+ }
184
+ else if (cached)
185
+ (0, detectionData_1.activateDetectionData)(cached);
186
+ this.status.state = 'current';
187
+ }
188
+ catch {
189
+ this.status.state = 'rejected';
190
+ this.nextCheck = Date.now() + 5 * 60_000;
191
+ }
192
+ finally {
193
+ if (temporary) {
194
+ try {
195
+ fs.unlinkSync(temporary);
196
+ }
197
+ catch { }
198
+ }
199
+ if (lock !== undefined) {
200
+ fs.closeSync(lock);
201
+ try {
202
+ fs.unlinkSync(this.cachePath + '.lock');
203
+ }
204
+ catch { }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ exports.DetectionUpdates = DetectionUpdates;
@@ -0,0 +1,2 @@
1
+ import { DetectionUpdates } from './detectionUpdates';
2
+ export declare const localDetectionUpdates: DetectionUpdates;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.localDetectionUpdates = void 0;
37
+ const os = __importStar(require("os"));
38
+ const path = __importStar(require("path"));
39
+ const detectionUpdates_1 = require("./detectionUpdates");
40
+ exports.localDetectionUpdates = new detectionUpdates_1.DetectionUpdates(path.join(os.homedir(), '.fullcourtdefense', 'detection-update.json'));
41
+ exports.localDetectionUpdates.loadLocal();
@@ -46,6 +46,7 @@ const os = __importStar(require("os"));
46
46
  const path = __importStar(require("path"));
47
47
  const distress_1 = require("./distress");
48
48
  const sessionLimits_1 = require("./sessionLimits");
49
+ const localDetectionUpdates_1 = require("./localDetectionUpdates");
49
50
  const CACHE_PATH = path.join(os.homedir(), '.fullcourtdefense-runtime.json');
50
51
  const DEFAULT_TTL_MS = 60_000;
51
52
  const REFRESH_TIMEOUT_MS = 1_500; // tight: the hook must stay fast
@@ -216,6 +217,7 @@ function sanitizeBundlePolicies(value) {
216
217
  * or a 'default' marker so the caller can apply its local fallback.
217
218
  */
218
219
  async function getRuntimeBundle(input) {
220
+ localDetectionUpdates_1.localDetectionUpdates.loadLocal();
219
221
  const ttl = input.ttlMs ?? DEFAULT_TTL_MS;
220
222
  const cache = readCacheFile();
221
223
  const cached = cache[input.shieldId];
package/dist/telemetry.js CHANGED
@@ -49,6 +49,7 @@ const fs = __importStar(require("fs"));
49
49
  const os = __importStar(require("os"));
50
50
  const path = __importStar(require("path"));
51
51
  const machineIdentity_1 = require("./machineIdentity");
52
+ const localDetectionUpdates_1 = require("./localDetectionUpdates");
52
53
  /**
53
54
  * Local-first telemetry: every enforcement decision is appended to an on-disk
54
55
  * spool (instant, offline-safe) and flushed to the backend in batches. Critical
@@ -355,6 +356,7 @@ async function flushSpoolLocked(input) {
355
356
  heartbeat: input.heartbeat
356
357
  ? {
357
358
  agentVersion: input.agentVersion,
359
+ detectionUpdates: localDetectionUpdates_1.localDetectionUpdates.getStatus(),
358
360
  integrityOk: input.integrityOk,
359
361
  integrityReasons: input.integrityReasons,
360
362
  integrityCheckedAt: input.integrityCheckedAt,
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.34.16"
2
+ "version": "1.34.17"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.34.16",
3
+ "version": "1.34.17",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -96,7 +96,7 @@
96
96
  "test:ide-fp-corpus:hook": "npm run build && node scripts/test-ide-fp-corpus.js --hook",
97
97
  "test:msi-payload-deps": "npm run build && node scripts/test-msi-payload-deps.js",
98
98
  "build:msi": "powershell -NoProfile -ExecutionPolicy Bypass -File installer/windows/Build-Msi.ps1",
99
- "prepublishOnly": "npm run build && node scripts/test-msi-payload-deps.js && node scripts/test-shell-parity.js && node scripts/test-terminal-mcp-ask.js"
99
+ "prepublishOnly": "npm run build && node scripts/check-detection-baseline.js && node scripts/test-detection-updates.js && node scripts/test-detection-cache.js && node scripts/test-msi-payload-deps.js && node scripts/test-shell-parity.js && node scripts/test-terminal-mcp-ask.js"
100
100
  },
101
101
  "keywords": [
102
102
  "llm",