log-llm-config 1.3.2 → 1.3.6

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.
@@ -1,20 +1,17 @@
1
1
  /**
2
2
  * Synchronous pre-prompt gate: local compliance only (stdout = single JSON line for IDE hooks).
3
3
  * stderr must stay clean; logs go via hookRunLog (file).
4
+ *
5
+ * When autofix returns restart_commands, this process does not spawn them — the shell hook pipes
6
+ * the same JSON line to execute_trusted_restarts (TS allowlist + spawn).
4
7
  */
5
8
  import { applyAutofixViolations, pruneSatisfiedOneTimeRemediations, runLocalRemediationComplianceCheck, } from './log_config_files/runtime/compliance_check.js';
6
9
  import { existsSync, statSync } from 'node:fs';
7
- import { spawn } from 'node:child_process';
8
10
  import { pathToFileURL } from 'node:url';
9
11
  import { resolve } from 'node:path';
10
12
  import { getRemediationInstructionsPath } from './log_config_files/runtime/management_storage.js';
11
- import { hookLogSessionBanner, hookRunLog } from './log_config_files/runtime/hook_logger.js';
13
+ import { hookLogSessionBanner } from './log_config_files/runtime/hook_logger.js';
12
14
  const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
13
- /** Set by optimus-compliance-check.sh only for Claude Desktop (dumb TERM). Hook runs allowlisted restart after osascript. */
14
- function claudeDesktopHookRunsRestart() {
15
- const v = process.env.OPTIMUS_CLAUDE_DESKTOP_DEFER_GATE_RESTART?.trim().toLowerCase();
16
- return v === '1' || v === 'true' || v === 'yes';
17
- }
18
15
  function parseIde() {
19
16
  const eq = process.argv.find((a) => a.startsWith('--ide='));
20
17
  if (eq) {
@@ -51,13 +48,6 @@ function blockPayload(ide, violationMessage) {
51
48
  }
52
49
  return JSON.stringify({ continue: false, user_message: text });
53
50
  }
54
- function fireRestartCommands(commands) {
55
- for (const cmd of commands) {
56
- hookRunLog(`restart: firing command="${cmd}"`);
57
- const child = spawn('sh', ['-c', cmd], { detached: true, stdio: 'ignore' });
58
- child.unref();
59
- }
60
- }
61
51
  function getManifestStalenessMs() {
62
52
  try {
63
53
  const path = getRemediationInstructionsPath();
@@ -127,12 +117,7 @@ export async function runCompliancePromptGate() {
127
117
  if (restartCommands.length > 0)
128
118
  payload.restart_commands = restartCommands;
129
119
  console.log(JSON.stringify(payload));
130
- // Cursor: .cursor/hooks runs restart after the osascript dialog avoid double SIGKILL here.
131
- // Claude Desktop: same — optimus-compliance-check.sh shows the dialog then evals allowlisted restarts.
132
- // Claude Code (CLI): no shell restart path; gate must spawn here.
133
- const fireInGate = ide !== 'cursor' && !(ide === 'claude' && claudeDesktopHookRunsRestart());
134
- if (fireInGate)
135
- fireRestartCommands(restartCommands);
120
+ // Restarts: always executed by optimus-compliance-check.sh via execute_trusted_restarts (TS allowlist + spawn).
136
121
  return;
137
122
  }
138
123
  const msg = recheck.violations[0]?.message ?? 'A security policy violation has been detected.';
@@ -0,0 +1,56 @@
1
+ /**
2
+ * CLI: read one JSON line from stdin (same shape as compliance_prompt_gate stdout for __optimus_autofix),
3
+ * parse restart_commands[], allowlist each in TS, spawn detached. Invoked by optimus-compliance-check.sh only.
4
+ * stderr stays quiet; failures logged via hookRunLog.
5
+ */
6
+ import { readFileSync } from 'node:fs';
7
+ import { pathToFileURL } from 'node:url';
8
+ import { resolve } from 'node:path';
9
+ import { hookRunLog } from './log_config_files/runtime/hook_logger.js';
10
+ import { executeTrustedRestartCommands } from './log_config_files/runtime/trusted_restarts.js';
11
+ function isRunAsCliModule() {
12
+ const entry = process.argv[1];
13
+ if (!entry)
14
+ return false;
15
+ try {
16
+ return import.meta.url === pathToFileURL(resolve(entry)).href;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ function main() {
23
+ let raw;
24
+ try {
25
+ raw = readFileSync(0, 'utf8');
26
+ }
27
+ catch {
28
+ hookRunLog('execute_trusted_restarts: could not read stdin');
29
+ return;
30
+ }
31
+ const first = raw.trim().split(/\r?\n/)[0]?.trim() ?? '';
32
+ if (!first)
33
+ return;
34
+ let j;
35
+ try {
36
+ j = JSON.parse(first);
37
+ }
38
+ catch {
39
+ hookRunLog('execute_trusted_restarts: stdin is not valid JSON');
40
+ return;
41
+ }
42
+ const cmds = j.restart_commands;
43
+ if (!Array.isArray(cmds))
44
+ return;
45
+ const strings = cmds.filter((c) => typeof c === 'string');
46
+ executeTrustedRestartCommands(strings);
47
+ }
48
+ if (isRunAsCliModule()) {
49
+ try {
50
+ main();
51
+ }
52
+ catch (e) {
53
+ hookRunLog(`execute_trusted_restarts: ${e instanceof Error ? e.message : String(e)}`);
54
+ }
55
+ process.exit(0);
56
+ }
@@ -92,6 +92,11 @@ export function readVscdbItemTableJson(dbPath, itemKey) {
92
92
  }
93
93
  // Bare JSON primitives (e.g. cursor/thirdPartyExtensibilityEnabled stores `true`/`false`).
94
94
  if (typeof parsed === 'boolean' || typeof parsed === 'number' || typeof parsed === 'string') {
95
+ const leaf = itemKey.split('/').pop() ?? '';
96
+ // Legacy cursorai/donotchange/privacyMode row is bare true/false; compliance paths use …/privacyMode.privacyMode.
97
+ if (typeof parsed === 'boolean' && leaf === 'privacyMode') {
98
+ return { [itemKey]: { privacyMode: parsed } };
99
+ }
95
100
  return { [itemKey]: parsed };
96
101
  }
97
102
  return { [itemKey]: {} };
@@ -20,6 +20,14 @@ import { readStoredAuthKey } from '../auth/auth_key_store.js';
20
20
  // ---------------------------------------------------------------------------
21
21
  // Helpers
22
22
  // ---------------------------------------------------------------------------
23
+ /**
24
+ * ItemTable keys use slashes (e.g. cursorai/donotchange/privacyMode) and do not contain dots.
25
+ * Compliance setting_path is `${itemKey}.${nested.path}` — take the segment before the first `.`.
26
+ */
27
+ export function itemTableKeyFromSettingPath(settingPath) {
28
+ const i = settingPath.indexOf('.');
29
+ return i === -1 ? settingPath : settingPath.slice(0, i);
30
+ }
23
31
  /** Traverse a JSON object using dot-notation path. Returns undefined if any segment is missing. */
24
32
  export function getByPath(obj, path) {
25
33
  const parts = path.split('.');
@@ -85,20 +93,25 @@ function verifyOpsApplied(configJson, settingPath, ops) {
85
93
  }
86
94
  return { ok: true, expected: null };
87
95
  }
88
- /** Plain JSON file or virtual `…/state.vscdb#composerState` path for ItemTable-backed settings. */
89
- function loadRemediationConfigJson(configFilePath) {
96
+ /** Plain JSON file or virtual `…/state.vscdb#itemKey` path for ItemTable-backed settings. */
97
+ function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
90
98
  const hashIdx = configFilePath.indexOf('#');
91
99
  if (hashIdx >= 0) {
92
100
  const dbPath = configFilePath.slice(0, hashIdx);
93
- const itemKey = configFilePath.slice(hashIdx + 1).trim();
94
- if (!itemKey)
101
+ const itemKeyFromPath = configFilePath.slice(hashIdx + 1).trim();
102
+ if (!itemKeyFromPath)
95
103
  return { ok: false, reason: 'empty_vscdb_key' };
96
104
  if (!existsSync(dbPath))
97
105
  return { ok: false, reason: 'db_not_found' };
98
- const wrapped = readVscdbItemTableJson(dbPath, itemKey);
99
- if (wrapped === null)
100
- return { ok: false, reason: 'vscdb_read_failed' };
101
- return { ok: true, json: wrapped };
106
+ const keys = new Set([itemKeyFromPath, ...checkSettingPaths.map(itemTableKeyFromSettingPath)].filter(Boolean));
107
+ const merged = {};
108
+ for (const k of keys) {
109
+ const wrapped = readVscdbItemTableJson(dbPath, k);
110
+ if (wrapped === null)
111
+ return { ok: false, reason: 'vscdb_read_failed' };
112
+ Object.assign(merged, wrapped);
113
+ }
114
+ return { ok: true, json: merged };
102
115
  }
103
116
  if (!existsSync(configFilePath))
104
117
  return { ok: false, reason: 'file_not_found' };
@@ -137,7 +150,7 @@ export function runLocalRemediationComplianceCheck() {
137
150
  const checks = compliance.checks ?? [];
138
151
  if (checks.length === 0)
139
152
  continue;
140
- const loaded = loadRemediationConfigJson(entry.config_file_path);
153
+ const loaded = loadRemediationConfigJson(entry.config_file_path, checks.map((c) => c.setting_path));
141
154
  if (!loaded.ok) {
142
155
  const msg = loaded.reason === 'file_not_found'
143
156
  ? `compliance_check: config file not found, skipping uuid=${entry.uuid}`
@@ -374,7 +387,7 @@ export function pruneSatisfiedOneTimeRemediations() {
374
387
  remaining.push(raw);
375
388
  continue;
376
389
  }
377
- const prLoaded = loadRemediationConfigJson(inst.config_file_path);
390
+ const prLoaded = loadRemediationConfigJson(inst.config_file_path, checks.map((c) => c.setting_path));
378
391
  if (!prLoaded.ok) {
379
392
  remaining.push(raw);
380
393
  continue;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Execute autofix restart_command strings only if allowlisted (same rules as gate enqueue).
3
+ * Used by the compliance shell hook via execute_trusted_restarts CLI — single place for allowlist + spawn.
4
+ */
5
+ import { spawn } from 'node:child_process';
6
+ import { hookRunLog } from './hook_logger.js';
7
+ import { isTrustedRestartCommandForAutofix } from './remediation_sync.js';
8
+ /** Spawn each trusted command detached (same pattern as former compliance_prompt_gate fireRestartCommands). */
9
+ export function executeTrustedRestartCommands(commands) {
10
+ for (const cmd of commands) {
11
+ const t = cmd.trim();
12
+ if (!t)
13
+ continue;
14
+ if (!isTrustedRestartCommandForAutofix(cmd)) {
15
+ hookRunLog(`execute_trusted_restarts: rejected (not allowlisted) prefix=${t.slice(0, 120)}`);
16
+ continue;
17
+ }
18
+ hookRunLog(`execute_trusted_restarts: firing command="${t}"`);
19
+ const child = spawn('sh', ['-c', t], { detached: true, stdio: 'ignore' });
20
+ child.unref();
21
+ }
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.3.2",
3
+ "version": "1.3.6",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  "log_uuid": "dist/log_uuid/index.js",
9
9
  "log_config_files": "dist/log_config_files/index.js",
10
10
  "log_sensitive_paths_audit": "dist/log_sensitive_paths_audit.js",
11
- "apply-deferred-vscdb": "dist/apply_deferred_vscdb.js"
11
+ "apply-deferred-vscdb": "dist/apply_deferred_vscdb.js",
12
+ "execute-trusted-restarts": "dist/execute_trusted_restarts.js"
12
13
  },
13
14
  "scripts": {
14
15
  "build": "tsc -p tsconfig.json",