log-llm-config 1.4.12 → 1.5.2
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.
- package/dist/apply_deferred_vscdb.js +2 -2
- package/dist/bootstrap_constants.js +10 -0
- package/dist/cli.js +31 -1
- package/dist/compliance_check_runner.js +7 -2
- package/dist/compliance_prompt_gate.js +46 -17
- package/dist/log_config_files/auth/auth_flow.js +1 -1
- package/dist/log_config_files/auth/auth_key_store.js +1 -1
- package/dist/log_config_files/collection/config_collector.js +12 -0
- package/dist/log_config_files/collection/ensure_cursor_user_settings_snapshot.js +50 -0
- package/dist/log_config_files/paths/pattern_resolver.js +1 -1
- package/dist/log_config_files/runtime/client_event_reporter.js +347 -0
- package/dist/log_config_files/runtime/compliance_check.js +67 -20
- package/dist/log_config_files/runtime/compliance_session_log.js +22 -16
- package/dist/log_config_files/runtime/hook_logger.js +33 -20
- package/dist/log_config_files/runtime/log_metadata.js +122 -0
- package/dist/log_config_files/runtime/main_runner.js +25 -5
- package/dist/log_config_files/runtime/management_storage.js +36 -7
- package/dist/log_config_files/runtime/remediation_apply_tracking.js +4 -2
- package/dist/log_config_files/runtime/remediation_config_path.js +12 -1
- package/dist/log_config_files/runtime/remediation_sync.js +76 -8
- package/dist/log_config_files/sender/batch_sender.js +1 -1
- package/dist/log_sensitive_paths_audit.js +3 -3
- package/dist/log_uuid/auth_key_store.js +1 -1
- package/dist/log_uuid/startup_sender.js +21 -3
- package/package.json +1 -1
- package/dist/log_config_files/readers/vscdb_item_table_registry.js +0 -82
- package/dist/log_config_files/runtime/secretlint_scan.js +0 -69
- package/dist/post_restart_verify.js +0 -48
|
@@ -25,13 +25,13 @@ applyDeferredVscdbFromDisk()
|
|
|
25
25
|
catch (e) {
|
|
26
26
|
hookRunLog(`apply_deferred_vscdb: post_apply_verification error: ${e instanceof Error ? e.message : String(e)}`);
|
|
27
27
|
if (complianceLogPath) {
|
|
28
|
-
await finalizeAndUploadComplianceSessionLog(complianceLogPath, '
|
|
28
|
+
await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
else if (complianceLogPath) {
|
|
33
33
|
hookRunLog('apply_deferred_vscdb: apply failed');
|
|
34
|
-
await finalizeAndUploadComplianceSessionLog(complianceLogPath, '
|
|
34
|
+
await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
|
|
35
35
|
}
|
|
36
36
|
process.exit(ok ? 0 : 1);
|
|
37
37
|
})
|
|
@@ -3,3 +3,13 @@
|
|
|
3
3
|
* Used by auth, hook logging, and audit output. Do not add agent-specific paths here.
|
|
4
4
|
*/
|
|
5
5
|
export const OPT_AI_SEC_MANAGEMENT_REL = 'opt-ai-sec/management';
|
|
6
|
+
/**
|
|
7
|
+
* All hook-written log files live under this subdirectory, grouped by type
|
|
8
|
+
* (see LOG_GROUP_* below). Keeping logs in one subtree leaves the management
|
|
9
|
+
* root limited to secrets/identity/state (auth_key, organization-uuid, contracts).
|
|
10
|
+
*/
|
|
11
|
+
export const OPT_AI_SEC_LOGS_REL = `${OPT_AI_SEC_MANAGEMENT_REL}/logs`;
|
|
12
|
+
/** Log group subdirectories under {@link OPT_AI_SEC_LOGS_REL}. */
|
|
13
|
+
export const LOG_GROUP_HOOK = 'hook';
|
|
14
|
+
export const LOG_GROUP_COMPLIANCE = 'compliance';
|
|
15
|
+
export const LOG_GROUP_AUDIT = 'audit';
|
package/dist/cli.js
CHANGED
|
@@ -111,4 +111,34 @@ function parseAndSetLogUuidEnvVars() {
|
|
|
111
111
|
if (agentArg)
|
|
112
112
|
process.env.OPTIMUS_AGENT = agentArg;
|
|
113
113
|
}
|
|
114
|
-
|
|
114
|
+
/** Map each cli command to its functional unit for failure telemetry. */
|
|
115
|
+
const COMMAND_UNIT = {
|
|
116
|
+
log_uuid: 'auth_version',
|
|
117
|
+
log_config_files: 'inventory',
|
|
118
|
+
log_sensitive_paths_audit: 'inventory',
|
|
119
|
+
compliance_prompt_gate: 'compliance',
|
|
120
|
+
compliance_check_runner: 'compliance',
|
|
121
|
+
'apply-deferred-vscdb': 'compliance',
|
|
122
|
+
'execute-trusted-restarts': 'compliance',
|
|
123
|
+
dialog_prefs: 'compliance',
|
|
124
|
+
};
|
|
125
|
+
main().catch(async (err) => {
|
|
126
|
+
// Top-level safety net: any uncaught error in a cli command is reported as a failed
|
|
127
|
+
// FunctionalRun (awaited so the POST lands before exit), then surfaced + non-zero exit.
|
|
128
|
+
try {
|
|
129
|
+
const cmd = args[0] || '';
|
|
130
|
+
const unit = COMMAND_UNIT[cmd] || 'inventory';
|
|
131
|
+
const { reportFunctionalFailureAwait } = await import('./log_config_files/runtime/client_event_reporter.js');
|
|
132
|
+
await reportFunctionalFailureAwait(unit, cmd, err);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* never let telemetry mask the original error */
|
|
136
|
+
}
|
|
137
|
+
console.error(err instanceof Error ? err.stack || err.message : String(err));
|
|
138
|
+
try {
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* process.exit may be mocked in tests */
|
|
143
|
+
}
|
|
144
|
+
});
|
|
@@ -3,6 +3,7 @@ import { complianceRunnerRunnerLine, hookLogAppendSection } from './log_config_f
|
|
|
3
3
|
import { normalizeAgentToken, runComplianceCheck, } from './log_config_files/runtime/compliance_check.js';
|
|
4
4
|
import { finalizeAndUploadComplianceSessionLog, initComplianceSessionForRunner, isFinalizeComplianceSessionArgv, isInflightComplianceLogPath, parseComplianceLogArg, resolveComplianceSessionLogPath, } from './log_config_files/runtime/compliance_session_log.js';
|
|
5
5
|
import { existsSync } from 'node:fs';
|
|
6
|
+
import { getStoryId, flushBufferedClientEvents, reportFunctionalRun, reportFunctionalFailureAwait, } from './log_config_files/runtime/client_event_reporter.js';
|
|
6
7
|
function parseAgentArg() {
|
|
7
8
|
const eq = process.argv.find((a) => a.startsWith('--agent='));
|
|
8
9
|
if (!eq)
|
|
@@ -24,7 +25,6 @@ function parseAgentArg() {
|
|
|
24
25
|
complianceLogPath = initComplianceSessionForRunner(resolved);
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
|
-
// Gate may have finalized+moved the session before this background runner started.
|
|
28
28
|
complianceLogPath = null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -32,9 +32,13 @@ function parseAgentArg() {
|
|
|
32
32
|
hookLogAppendSection('compliance_check_runner (background sync + check)');
|
|
33
33
|
}
|
|
34
34
|
complianceRunnerRunnerLine('compliance_check_runner: start');
|
|
35
|
+
// Background runner: opportunistically flush any telemetry the (non-blocking) gate buffered.
|
|
36
|
+
getStoryId();
|
|
37
|
+
await flushBufferedClientEvents();
|
|
35
38
|
try {
|
|
36
39
|
await runComplianceCheck(parseAgentArg());
|
|
37
40
|
complianceRunnerRunnerLine('compliance_check_runner: finished ok');
|
|
41
|
+
await reportFunctionalRun({ unit: 'compliance', command: 'compliance_check_runner', outcome: 'ok' });
|
|
38
42
|
}
|
|
39
43
|
catch (err) {
|
|
40
44
|
const detail = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
@@ -42,8 +46,9 @@ function parseAgentArg() {
|
|
|
42
46
|
complianceRunnerRunnerLine(`compliance_check_runner: stack_or_detail ${detail.slice(0, 4000)}`);
|
|
43
47
|
process.stderr.write(`compliance_check_runner error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
44
48
|
if (complianceLogPath) {
|
|
45
|
-
await finalizeAndUploadComplianceSessionLog(complianceLogPath, '
|
|
49
|
+
await finalizeAndUploadComplianceSessionLog(complianceLogPath, 'failures');
|
|
46
50
|
}
|
|
51
|
+
await reportFunctionalFailureAwait('compliance', 'compliance_check_runner', err);
|
|
47
52
|
process.exit(1);
|
|
48
53
|
}
|
|
49
54
|
if (complianceLogPath) {
|
|
@@ -6,17 +6,20 @@
|
|
|
6
6
|
* When autofix returns restart_commands, this process does not spawn them — the shell hook pipes
|
|
7
7
|
* the same JSON line to execute_trusted_restarts (TS allowlist + spawn).
|
|
8
8
|
*/
|
|
9
|
-
import { applyAutofixViolations, confirmAppliedAutofixVerified, normalizeAgentToken, pruneSatisfiedOneTimeRemediations, reportPostRestartVerificationOutcomes, runLocalRemediationComplianceCheck, uploadSatisfiedManifestConfigs, } from './log_config_files/runtime/compliance_check.js';
|
|
9
|
+
import { applyAutofixViolations, confirmAppliedAutofixVerified, normalizeAgentToken, pruneSatisfiedOneTimeRemediations, reportCompliantRemediationVerifiedStatus, reportPostRestartVerificationOutcomes, runLocalRemediationComplianceCheck, uploadSatisfiedManifestConfigs, } from './log_config_files/runtime/compliance_check.js';
|
|
10
10
|
import { isRemediationQuarantined } from './log_config_files/runtime/remediation_apply_tracking.js';
|
|
11
11
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
12
12
|
import { getRemediationInstructionsPath } from './log_config_files/runtime/management_storage.js';
|
|
13
13
|
import { hookLogSessionBanner, hookRunLog, logRemediationApplyFailure } from './log_config_files/runtime/hook_logger.js';
|
|
14
|
+
import { getStoryId, bufferFunctionalRun } from './log_config_files/runtime/client_event_reporter.js';
|
|
14
15
|
import { finalizeAndUploadComplianceSessionLog, initComplianceSessionForGate, parseComplianceLogArg, } from './log_config_files/runtime/compliance_session_log.js';
|
|
15
16
|
import { isThisCliModule } from './cli_invocation_match.js';
|
|
16
17
|
import { ensureAuthentication } from './log_config_files/auth/auth_flow.js';
|
|
17
18
|
import { sendConfigFile } from './log_config_files/sender/batch_sender.js';
|
|
19
|
+
import { parseJsonWithJsoncFallback } from './log_config_files/readers/file_readers.js';
|
|
20
|
+
import { PORTABLE_CURSOR_USER_SETTINGS, resolveRemediationConfigPath, resolveRemediationUploadFileType, } from './log_config_files/runtime/remediation_config_path.js';
|
|
18
21
|
import { tryResolveHardwareUuid } from './log_config_files/runtime/hardware_uuid.js';
|
|
19
|
-
import { syncRemediations } from './log_config_files/runtime/remediation_sync.js';
|
|
22
|
+
import { cursorAutofixDefersInlineVerify, syncRemediations, } from './log_config_files/runtime/remediation_sync.js';
|
|
20
23
|
import { loadEndpointBase } from './log_config_files/sender/endpoint_config.js';
|
|
21
24
|
import { formatRemediationChangePreviewForApplied } from './remediation_change_preview.js';
|
|
22
25
|
const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -146,9 +149,9 @@ export function formatCopilotAutofixDialog(appliedViolations) {
|
|
|
146
149
|
export function formatOpenCodeAutofixDialog(appliedViolations) {
|
|
147
150
|
return formatPreventiveAutofixDialog(appliedViolations, 'OpenCode will now apply this policy to your environment.');
|
|
148
151
|
}
|
|
149
|
-
/** Cursor restart
|
|
152
|
+
/** Cursor restart after local autofix (all JSON settings + vscdb paths use restart_commands). */
|
|
150
153
|
export function formatCursorRestartAutofixDialog(appliedViolations) {
|
|
151
|
-
return formatPreventiveAutofixDialog(appliedViolations, 'Cursor will
|
|
154
|
+
return formatPreventiveAutofixDialog(appliedViolations, 'Cursor will restart so the running app reloads policy from disk. Your context will be retained.');
|
|
152
155
|
}
|
|
153
156
|
/**
|
|
154
157
|
* Upload a secondary compliance file to the backend so the server can resolve the finding.
|
|
@@ -156,16 +159,23 @@ export function formatCursorRestartAutofixDialog(appliedViolations) {
|
|
|
156
159
|
*/
|
|
157
160
|
async function _uploadSecondaryFile(entry) {
|
|
158
161
|
const { uuid, config_file_path, file_type } = entry;
|
|
159
|
-
|
|
162
|
+
const uploadFileType = resolveRemediationUploadFileType(config_file_path, file_type ?? undefined);
|
|
163
|
+
if (!uploadFileType) {
|
|
160
164
|
hookRunLog(`secondary_upload: skipping uuid=${uuid} — no file_type on secondary group`);
|
|
161
165
|
return;
|
|
162
166
|
}
|
|
167
|
+
const diskPath = resolveRemediationConfigPath(config_file_path);
|
|
163
168
|
let rawContent;
|
|
164
169
|
try {
|
|
165
|
-
|
|
170
|
+
const parsed = parseJsonWithJsoncFallback(readFileSync(diskPath, 'utf8'));
|
|
171
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
172
|
+
hookRunLog(`secondary_upload: invalid JSON uuid=${uuid} path=${diskPath}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
rawContent = parsed;
|
|
166
176
|
}
|
|
167
177
|
catch {
|
|
168
|
-
hookRunLog(`secondary_upload: could not read file uuid=${uuid} path=${
|
|
178
|
+
hookRunLog(`secondary_upload: could not read file uuid=${uuid} path=${diskPath}`);
|
|
169
179
|
return;
|
|
170
180
|
}
|
|
171
181
|
const hw = tryResolveHardwareUuid();
|
|
@@ -173,13 +183,16 @@ async function _uploadSecondaryFile(entry) {
|
|
|
173
183
|
hookRunLog(`secondary_upload: hardware UUID unavailable, skipping uuid=${uuid}`);
|
|
174
184
|
return;
|
|
175
185
|
}
|
|
186
|
+
const portablePath = config_file_path.includes('Cursor/User/settings.json')
|
|
187
|
+
? PORTABLE_CURSOR_USER_SETTINGS
|
|
188
|
+
: config_file_path.trim() || diskPath;
|
|
176
189
|
try {
|
|
177
190
|
const authKey = await ensureAuthentication(hw);
|
|
178
|
-
const sent = await sendConfigFile({ file_type, file_path:
|
|
179
|
-
hookRunLog(`secondary_upload: uuid=${uuid} path=${
|
|
191
|
+
const sent = await sendConfigFile({ file_type: uploadFileType, file_path: portablePath, raw_content: rawContent }, hw, authKey);
|
|
192
|
+
hookRunLog(`secondary_upload: uuid=${uuid} path=${portablePath} sent=${sent}`);
|
|
180
193
|
}
|
|
181
194
|
catch (err) {
|
|
182
|
-
hookRunLog(`secondary_upload: upload failed uuid=${uuid} path=${
|
|
195
|
+
hookRunLog(`secondary_upload: upload failed uuid=${uuid} path=${portablePath} err=${err instanceof Error ? err.message : String(err)}`);
|
|
183
196
|
}
|
|
184
197
|
}
|
|
185
198
|
/**
|
|
@@ -188,7 +201,22 @@ async function _uploadSecondaryFile(entry) {
|
|
|
188
201
|
* {@link isRunAsCliModule} is false and the gate must be invoked explicitly from cli.ts.
|
|
189
202
|
*/
|
|
190
203
|
export async function runCompliancePromptGateCli() {
|
|
191
|
-
|
|
204
|
+
getStoryId();
|
|
205
|
+
try {
|
|
206
|
+
await runCompliancePromptGate();
|
|
207
|
+
// Gate blocks prompt submit — buffer telemetry (no inline POST); the background runner flushes it.
|
|
208
|
+
bufferFunctionalRun({ unit: 'compliance', command: 'compliance_prompt_gate', outcome: 'ok' });
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
bufferFunctionalRun({
|
|
212
|
+
unit: 'compliance',
|
|
213
|
+
command: 'compliance_prompt_gate',
|
|
214
|
+
outcome: 'failed',
|
|
215
|
+
message: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
|
|
216
|
+
detail: err instanceof Error && err.stack ? err.stack : String(err),
|
|
217
|
+
});
|
|
218
|
+
printAllow(parseIde());
|
|
219
|
+
}
|
|
192
220
|
}
|
|
193
221
|
/** Exported for tests; runs when `dist/compliance_prompt_gate.js` is the process entry (argv[1]). */
|
|
194
222
|
export async function runCompliancePromptGate() {
|
|
@@ -202,8 +230,6 @@ export async function runCompliancePromptGate() {
|
|
|
202
230
|
hookLogSessionBanner('compliance_prompt_gate (before submit)');
|
|
203
231
|
}
|
|
204
232
|
let status = runLocalRemediationComplianceCheck(agent);
|
|
205
|
-
// Post-restart verify + server reports BEFORE sync so a manifest UUID swap cannot delay
|
|
206
|
-
// verified / activity-log until the following prompt.
|
|
207
233
|
const postRestartVerify = reportPostRestartVerificationOutcomes(status.violations);
|
|
208
234
|
if (postRestartVerify.outcomes.length > 0) {
|
|
209
235
|
await Promise.allSettled(postRestartVerify.reportPromises);
|
|
@@ -290,12 +316,14 @@ export async function runCompliancePromptGate() {
|
|
|
290
316
|
// disk synchronously; a restart_command, if present, only reloads the already-compliant file
|
|
291
317
|
// into the running app — it is not the mechanism that makes the fix take effect. So report
|
|
292
318
|
// "verified" now instead of leaving the finding "pending" in the UI until the next prompt.
|
|
293
|
-
// Cursor
|
|
294
|
-
// Cursor still verifies inline only when no restart is pending.
|
|
319
|
+
// Cursor JSON: disk recheck OK → verified before restart. Cursor deferred vscdb → verify after restart.
|
|
295
320
|
const immediateJsonAgent = ide === 'claude' || ide === 'copilot' || ide === 'opencode';
|
|
321
|
+
const diskProvenCompliant = recheckOk || claudeRecheckStaleAfterImmediateApply;
|
|
322
|
+
const cursorRestartVerifyPending = cursorAutofixDefersInlineVerify(ide, restartCommands.length, deferredSqlitePending === true, diskProvenCompliant);
|
|
296
323
|
const immediateVerified = !deferredSqlitePending &&
|
|
297
|
-
|
|
298
|
-
|
|
324
|
+
!cursorRestartVerifyPending &&
|
|
325
|
+
diskProvenCompliant &&
|
|
326
|
+
(restartCommands.length === 0 || immediateJsonAgent || ide === 'cursor');
|
|
299
327
|
if (immediateVerified) {
|
|
300
328
|
confirmAppliedAutofixVerified(appliedViolations, reportPromises);
|
|
301
329
|
await Promise.allSettled(reportPromises);
|
|
@@ -360,6 +388,7 @@ export async function runCompliancePromptGate() {
|
|
|
360
388
|
await Promise.allSettled(pruned.reportPromises);
|
|
361
389
|
}
|
|
362
390
|
await Promise.allSettled(uploadSatisfiedManifestConfigs(agent));
|
|
391
|
+
await Promise.allSettled(reportCompliantRemediationVerifiedStatus(agent));
|
|
363
392
|
printAllow(ide);
|
|
364
393
|
}
|
|
365
394
|
if (isRunAsCliModule()) {
|
|
@@ -4,7 +4,7 @@ import { ensureAuthentication as ensureTofuAuthentication } from '../../tofu.js'
|
|
|
4
4
|
import { loadEndpointBase } from '../sender/endpoint_config.js';
|
|
5
5
|
import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
6
6
|
import { hookRunLog } from '../runtime/hook_logger.js';
|
|
7
|
-
const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth_key.txt');
|
|
7
|
+
const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth', 'auth_key.txt');
|
|
8
8
|
/** Ensure authentication — verify stored key or request new one via handshake. */
|
|
9
9
|
async function ensureAuthentication(hardwareUuid, endpointBase) {
|
|
10
10
|
const key = await ensureTofuAuthentication({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { getAuthKeyPath as getSharedAuthKeyPath, readStoredAuthKey as readSharedStoredAuthKey, } from '../../tofu.js';
|
|
3
3
|
import { OPT_AI_SEC_MANAGEMENT_REL } from '../../bootstrap_constants.js';
|
|
4
|
-
const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth_key.txt');
|
|
4
|
+
const AUTH_KEY_RELATIVE_PATH = path.join(OPT_AI_SEC_MANAGEMENT_REL, 'auth', 'auth_key.txt');
|
|
5
5
|
const AUTH_KEY_OPTIONS = { authRelativePath: AUTH_KEY_RELATIVE_PATH };
|
|
6
6
|
/** Return absolute path to auth_key.txt, or null if home dir is unavailable. */
|
|
7
7
|
function getAuthKeyPath() {
|
|
@@ -86,6 +86,12 @@ function readContentByFormat(path, format) {
|
|
|
86
86
|
// json (default) — fall back to markdown for .md files from agents not yet annotated
|
|
87
87
|
return readJSONFile(path) ?? readMCPConfig(path) ?? (path.endsWith('.md') ? readMarkdownFile(path) : null);
|
|
88
88
|
}
|
|
89
|
+
function isPortableCursorUserSettingsTarget(t) {
|
|
90
|
+
const p = (t.logicalFilePath ?? t.path).replace(/\\/g, '/').toLowerCase();
|
|
91
|
+
return (p === 'cursor/user/settings.json' ||
|
|
92
|
+
p.endsWith('/cursor/user/settings.json') ||
|
|
93
|
+
p.includes('application support/cursor/user/settings.json'));
|
|
94
|
+
}
|
|
89
95
|
function collectRegularFileEntry(t, enrichByFileType) {
|
|
90
96
|
const format = t.content_format || 'json';
|
|
91
97
|
let content = readContentByFormat(t.path, format);
|
|
@@ -120,6 +126,12 @@ function collectRegularFileEntry(t, enrichByFileType) {
|
|
|
120
126
|
return null;
|
|
121
127
|
raw = filtered;
|
|
122
128
|
}
|
|
129
|
+
if (t.file_type === 'vscode_settings' &&
|
|
130
|
+
isPortableCursorUserSettingsTarget(t) &&
|
|
131
|
+
Object.keys(raw).length === 0) {
|
|
132
|
+
console.warn(`Skipping empty Cursor User/settings.json upload (path=${t.path}); global ignore would be invisible to the server.`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
123
135
|
return { file_type: t.file_type, file_path: t.logicalFilePath ?? t.path, raw_content: raw };
|
|
124
136
|
}
|
|
125
137
|
function collectMetadataEntry(t) {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readJSONFile } from '../readers/file_readers.js';
|
|
3
|
+
import { PORTABLE_CURSOR_USER_SETTINGS, cursorUserSettingsAbsolutePaths, } from '../runtime/remediation_config_path.js';
|
|
4
|
+
export function isPortableCursorUserSettingsUploadPath(filePath) {
|
|
5
|
+
const p = filePath.replace(/\\/g, '/').toLowerCase();
|
|
6
|
+
return (p === 'cursor/user/settings.json' ||
|
|
7
|
+
p.endsWith('/cursor/user/settings.json') ||
|
|
8
|
+
p.includes('application support/cursor/user/settings.json'));
|
|
9
|
+
}
|
|
10
|
+
function settingsPayloadHasGlobalIgnoreListKey(raw) {
|
|
11
|
+
if (!raw || typeof raw !== 'object')
|
|
12
|
+
return false;
|
|
13
|
+
const nested = raw.cursor
|
|
14
|
+
?.general?.globalCursorIgnoreList;
|
|
15
|
+
if (Array.isArray(nested))
|
|
16
|
+
return true;
|
|
17
|
+
const flat = raw['cursor.general.globalCursorIgnoreList'];
|
|
18
|
+
return Array.isArray(flat);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sensitive Directories scans need full User/settings.json (globalCursorIgnoreList).
|
|
22
|
+
* Pattern collection can omit the key or send an empty object; merge disk when the batch
|
|
23
|
+
* lacks an explicit globalCursorIgnoreList array (including []).
|
|
24
|
+
*/
|
|
25
|
+
export function ensureCursorUserSettingsSnapshotInBatch(configFiles) {
|
|
26
|
+
const hasIgnoreKeyInBatch = configFiles.some((c) => c.file_type === 'vscode_settings' &&
|
|
27
|
+
isPortableCursorUserSettingsUploadPath(c.file_path) &&
|
|
28
|
+
settingsPayloadHasGlobalIgnoreListKey(c.raw_content));
|
|
29
|
+
if (hasIgnoreKeyInBatch)
|
|
30
|
+
return;
|
|
31
|
+
for (const abs of cursorUserSettingsAbsolutePaths()) {
|
|
32
|
+
if (!existsSync(abs))
|
|
33
|
+
continue;
|
|
34
|
+
const raw = readJSONFile(abs);
|
|
35
|
+
if (!raw || Object.keys(raw).length === 0)
|
|
36
|
+
continue;
|
|
37
|
+
const entry = {
|
|
38
|
+
file_type: 'vscode_settings',
|
|
39
|
+
file_path: PORTABLE_CURSOR_USER_SETTINGS,
|
|
40
|
+
raw_content: raw,
|
|
41
|
+
};
|
|
42
|
+
const idx = configFiles.findIndex((c) => c.file_type === 'vscode_settings' &&
|
|
43
|
+
isPortableCursorUserSettingsUploadPath(c.file_path));
|
|
44
|
+
if (idx >= 0)
|
|
45
|
+
configFiles[idx] = entry;
|
|
46
|
+
else
|
|
47
|
+
configFiles.push(entry);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -9,7 +9,7 @@ function normalizePathSkipPrefixes(prefixes) {
|
|
|
9
9
|
return prefixes.filter((p) => typeof p === 'string' && p.length > 0);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Expands glob patterns
|
|
12
|
+
* Expands glob patterns with double-asterisk (recursive directory traversal).
|
|
13
13
|
*
|
|
14
14
|
* Example: ~/.cursor/plugins/cache/ ** /skills/ recursively finds all skills
|
|
15
15
|
* directories under the cache folder, up to RECURSIVE_GLOB_MAX_DEPTH.
|