log-llm-config 1.3.6 → 1.3.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.
|
@@ -10,7 +10,7 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
10
10
|
import { pathToFileURL } from 'node:url';
|
|
11
11
|
import { resolve } from 'node:path';
|
|
12
12
|
import { getRemediationInstructionsPath } from './log_config_files/runtime/management_storage.js';
|
|
13
|
-
import { hookLogSessionBanner } from './log_config_files/runtime/hook_logger.js';
|
|
13
|
+
import { hookLogSessionBanner, hookRunLog } from './log_config_files/runtime/hook_logger.js';
|
|
14
14
|
const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
15
15
|
function parseIde() {
|
|
16
16
|
const eq = process.argv.find((a) => a.startsWith('--ide='));
|
|
@@ -111,7 +111,18 @@ export async function runCompliancePromptGate() {
|
|
|
111
111
|
}
|
|
112
112
|
const recheck = runLocalRemediationComplianceCheck();
|
|
113
113
|
const recheckOk = recheck.status === 'ok' || recheck.violations.length === 0;
|
|
114
|
-
|
|
114
|
+
// Cursor: tolerate a failing recheck only when SQLite updates are deferred (apply after restart).
|
|
115
|
+
// Claude Code: JSON remediations are written immediately; merge/verify timing can still leave the
|
|
116
|
+
// in-process recheck red for the same UUID — allow in that case only for --ide=claude.
|
|
117
|
+
const appliedUuids = new Set(appliedViolations.map((v) => v.uuid));
|
|
118
|
+
const claudeRecheckStaleAfterImmediateApply = ide === 'claude' &&
|
|
119
|
+
!recheckOk &&
|
|
120
|
+
recheck.violations.length > 0 &&
|
|
121
|
+
recheck.violations.every((v) => appliedUuids.has(v.uuid));
|
|
122
|
+
if (claudeRecheckStaleAfterImmediateApply) {
|
|
123
|
+
hookRunLog('compliance_prompt_gate: Claude — autofix wrote JSON; recheck still flags same UUID(s) — proceeding (immediate apply)');
|
|
124
|
+
}
|
|
125
|
+
if (deferredSqlitePending || recheckOk || claudeRecheckStaleAfterImmediateApply) {
|
|
115
126
|
const autofixMessage = `Optimus Security auto-fixed ${fixed} policy violation(s):\n${appliedViolations.map((v) => autofixDialogLine(v)).join('\n')}`;
|
|
116
127
|
const payload = { __optimus_autofix: true, autofix_message: autofixMessage };
|
|
117
128
|
if (restartCommands.length > 0)
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { readFileCollectionVscdbContract, writeFileCollectionVscdbContract, } from '../runtime/management_storage.js';
|
|
4
|
+
/**
|
|
5
|
+
* ItemTable keys that store a bare JSON boolean; compliance paths use `${key}.${field}`.
|
|
6
|
+
* Kept in sync with `coerceItemTableValueToObjectRoot` / `serializeItemTableValueForWrite` in remediation_sync.
|
|
7
|
+
*/
|
|
8
|
+
export const CURSOR_SCALAR_ITEMTABLE_FIELDS = {
|
|
9
|
+
'cursor/thirdPartyExtensibilityEnabled': 'thirdPartyExtensibilityEnabled',
|
|
10
|
+
'cursorai/donotchange/privacyMode': 'privacyMode',
|
|
11
|
+
'cursor/autoOpenLocalhostUrls': 'autoOpenLocalhostUrls',
|
|
12
|
+
};
|
|
4
13
|
function querySqlite(dbPath, key) {
|
|
5
14
|
const safe = key.replace(/'/g, "''");
|
|
6
15
|
return execSync(`sqlite3 "${dbPath}" "SELECT value FROM ItemTable WHERE key='${safe}'"`, {
|
|
@@ -90,12 +99,13 @@ export function readVscdbItemTableJson(dbPath, itemKey) {
|
|
|
90
99
|
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
91
100
|
return { [itemKey]: parsed };
|
|
92
101
|
}
|
|
93
|
-
// Bare JSON primitives (
|
|
102
|
+
// Bare JSON primitives. Scalar toggles must be wrapped so getByPath(key.field) works in compliance checks.
|
|
94
103
|
if (typeof parsed === 'boolean' || typeof parsed === 'number' || typeof parsed === 'string') {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
if (typeof parsed === 'boolean') {
|
|
105
|
+
const field = CURSOR_SCALAR_ITEMTABLE_FIELDS[itemKey];
|
|
106
|
+
if (field) {
|
|
107
|
+
return { [itemKey]: { [field]: parsed } };
|
|
108
|
+
}
|
|
99
109
|
}
|
|
100
110
|
return { [itemKey]: parsed };
|
|
101
111
|
}
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* ComplianceStatus; the prompt gate and tests use that return value. Autofix may write config files
|
|
6
6
|
* and remediation_instructions.json via applyAutofixViolations / enforceRemediation.
|
|
7
7
|
*
|
|
8
|
-
* Prompt gate: compliance_prompt_gate runs runLocalRemediationComplianceCheck
|
|
9
|
-
* Background: compliance_check_runner runs syncRemediations (network)
|
|
8
|
+
* Prompt gate: compliance_prompt_gate runs runLocalRemediationComplianceCheck then applyAutofixViolations.
|
|
9
|
+
* Background: compliance_check_runner runs syncRemediations (network), the same local check, then
|
|
10
|
+
* applyAutofixViolations + executeTrustedRestartCommands so remediations apply even when only the
|
|
11
|
+
* background path runs (sqlite/vscdb updates remain Cursor-specific; restarts use the shared allowlist).
|
|
10
12
|
*/
|
|
11
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
12
14
|
import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
|
|
13
|
-
import { readRemediationInstructionsFile, writeRemediationInstructionsFile } from './management_storage.js';
|
|
15
|
+
import { getRemediationInstructionsPath, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
|
|
16
|
+
import { executeTrustedRestartCommands } from './trusted_restarts.js';
|
|
14
17
|
import { complianceRunnerDiag, hookRunLog } from './hook_logger.js';
|
|
15
18
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
16
19
|
import { resolveHardwareUuid, tryResolveHardwareUuid } from './hardware_uuid.js';
|
|
@@ -431,9 +434,22 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
431
434
|
}
|
|
432
435
|
return { removed, reportPromises };
|
|
433
436
|
}
|
|
437
|
+
/** Same staleness window as compliance_prompt_gate: avoid applying very old local manifests. */
|
|
438
|
+
const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
439
|
+
function getManifestStalenessMs() {
|
|
440
|
+
try {
|
|
441
|
+
const p = getRemediationInstructionsPath();
|
|
442
|
+
if (!existsSync(p))
|
|
443
|
+
return null;
|
|
444
|
+
return Date.now() - statSync(p).mtimeMs;
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
434
450
|
/**
|
|
435
|
-
* Background refresh: server sync
|
|
436
|
-
*
|
|
451
|
+
* Background refresh: server sync, local evaluation, then autofix + trusted restarts (same enforcement
|
|
452
|
+
* as the prompt gate, without blocking stdin/stdout for the IDE).
|
|
437
453
|
*/
|
|
438
454
|
export async function runComplianceCheck() {
|
|
439
455
|
try {
|
|
@@ -442,5 +458,37 @@ export async function runComplianceCheck() {
|
|
|
442
458
|
catch (err) {
|
|
443
459
|
hookRunLog(`compliance_check: remediation_sync unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
444
460
|
}
|
|
445
|
-
runLocalRemediationComplianceCheck();
|
|
461
|
+
const status = runLocalRemediationComplianceCheck();
|
|
462
|
+
if (status.status === 'ok' || status.violations.length === 0) {
|
|
463
|
+
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
464
|
+
if (pruned.removed > 0) {
|
|
465
|
+
await Promise.allSettled(pruned.reportPromises);
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const staleMs = getManifestStalenessMs();
|
|
470
|
+
if (staleMs !== null && staleMs > MANIFEST_STALE_MS) {
|
|
471
|
+
const staleDays = Math.floor(staleMs / (24 * 60 * 60 * 1000));
|
|
472
|
+
hookRunLog(`compliance_check_runner: skip autofix — local remediation manifest is stale (${staleDays} days old)`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const { fixed, restartCommands, failedViolations, reportPromises } = applyAutofixViolations(status.violations);
|
|
476
|
+
if (fixed === 0) {
|
|
477
|
+
if (failedViolations.length > 0) {
|
|
478
|
+
hookRunLog(`compliance_check_runner: autofix failed for ${failedViolations.length} violation(s) (see autofix logs above)`);
|
|
479
|
+
}
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
await Promise.allSettled(reportPromises);
|
|
483
|
+
if (failedViolations.length > 0) {
|
|
484
|
+
hookRunLog(`compliance_check_runner: autofix partial failure — ${failedViolations.length} violation(s) still unresolved`);
|
|
485
|
+
}
|
|
486
|
+
if (restartCommands.length > 0) {
|
|
487
|
+
hookRunLog(`compliance_check_runner: executing ${restartCommands.length} trusted restart command(s)`);
|
|
488
|
+
executeTrustedRestartCommands(restartCommands);
|
|
489
|
+
}
|
|
490
|
+
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
491
|
+
if (pruned.removed > 0) {
|
|
492
|
+
await Promise.allSettled(pruned.reportPromises);
|
|
493
|
+
}
|
|
446
494
|
}
|
|
@@ -8,7 +8,7 @@ import { readStoredAuthKey } from '../auth/auth_key_store.js';
|
|
|
8
8
|
import { createSignature } from '../sender/signing.js';
|
|
9
9
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
10
10
|
import { tryResolveHardwareUuid } from './hardware_uuid.js';
|
|
11
|
-
import { persistVscdbComposerContractFromPatternsResponse, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
|
|
11
|
+
import { CURSOR_SCALAR_ITEMTABLE_FIELDS, persistVscdbComposerContractFromPatternsResponse, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
|
|
12
12
|
import { sendConfigFile } from '../sender/batch_sender.js';
|
|
13
13
|
import { getFileCollectionPatterns } from '../../endpoint_client/registry_api.js';
|
|
14
14
|
function reactiveStorageItemKeyFromContract() {
|
|
@@ -410,12 +410,6 @@ function resolveCursorComposerSqliteOp(dbPath, sqliteOp) {
|
|
|
410
410
|
return sqliteOp;
|
|
411
411
|
}
|
|
412
412
|
/** Apply sqlite merge: dot-path, or array match where `json_path` is `…container.arrayKey` (e.g. `modes4` or `composerState.modes4`). */
|
|
413
|
-
/** ItemTable keys that store a JSON primitive; map to one field for merge + serialize. */
|
|
414
|
-
const CURSOR_SCALAR_ITEMTABLE_FIELDS = {
|
|
415
|
-
'cursor/thirdPartyExtensibilityEnabled': 'thirdPartyExtensibilityEnabled',
|
|
416
|
-
'cursorai/donotchange/privacyMode': 'privacyMode',
|
|
417
|
-
'cursor/autoOpenLocalhostUrls': 'autoOpenLocalhostUrls',
|
|
418
|
-
};
|
|
419
413
|
function coerceScalarForItemTableField(parsed) {
|
|
420
414
|
if (typeof parsed === 'boolean')
|
|
421
415
|
return parsed;
|