@synkro-sh/cli 1.6.15 → 1.6.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.
- package/dist/bootstrap.js +225 -94
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -116,6 +116,17 @@ function installCCHooks(settingsPath, config) {
|
|
|
116
116
|
settings.hooks.SessionEnd = settings.hooks.SessionEnd ?? [];
|
|
117
117
|
settings.hooks.SessionStart = settings.hooks.SessionStart ?? [];
|
|
118
118
|
settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit ?? [];
|
|
119
|
+
settings.hooks.PreToolUse.push({
|
|
120
|
+
matcher: "Bash",
|
|
121
|
+
hooks: [
|
|
122
|
+
{
|
|
123
|
+
type: "command",
|
|
124
|
+
command: config.installScanScriptPath,
|
|
125
|
+
timeout: 8
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
[SYNKRO_MARKER]: true
|
|
129
|
+
});
|
|
119
130
|
settings.hooks.PreToolUse.push({
|
|
120
131
|
matcher: "Bash|Read|Grep|Glob",
|
|
121
132
|
hooks: [
|
|
@@ -319,6 +330,12 @@ function installCursorHooks(hooksJsonPath, config) {
|
|
|
319
330
|
pushCcHook(h, "beforeSubmitPrompt", config.userPromptSubmitScriptPath, { timeout: 5 });
|
|
320
331
|
pushCcHook(h, "stop", config.transcriptSyncScriptPath, { timeout: 3 });
|
|
321
332
|
h.beforeShellExecution = h.beforeShellExecution ?? [];
|
|
333
|
+
h.beforeShellExecution.push({
|
|
334
|
+
command: cursorCcCmd(config.installScanScriptPath),
|
|
335
|
+
timeout: 8,
|
|
336
|
+
failClosed: false,
|
|
337
|
+
[SYNKRO_MARKER2]: true
|
|
338
|
+
});
|
|
322
339
|
h.beforeShellExecution.push({
|
|
323
340
|
command: bunRunCmd(config.bashJudgeScriptPath),
|
|
324
341
|
timeout: 15,
|
|
@@ -327,6 +344,13 @@ function installCursorHooks(hooksJsonPath, config) {
|
|
|
327
344
|
});
|
|
328
345
|
pushCcHook(h, "afterShellExecution", config.bashFollowupScriptPath, { timeout: 10 });
|
|
329
346
|
h.preToolUse = h.preToolUse ?? [];
|
|
347
|
+
h.preToolUse.push({
|
|
348
|
+
command: cursorCcCmd(config.installScanScriptPath),
|
|
349
|
+
timeout: 8,
|
|
350
|
+
failClosed: false,
|
|
351
|
+
matcher: "Shell|Bash|terminal|run_terminal_cmd|execute_command",
|
|
352
|
+
[SYNKRO_MARKER2]: true
|
|
353
|
+
});
|
|
330
354
|
h.preToolUse.push({
|
|
331
355
|
command: bunRunCmd(config.bashJudgeScriptPath),
|
|
332
356
|
timeout: 15,
|
|
@@ -727,7 +751,7 @@ synkro_post_with_retry() {
|
|
|
727
751
|
});
|
|
728
752
|
|
|
729
753
|
// cli/installer/hookScriptsTs.ts
|
|
730
|
-
var SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS, CURSOR_EDIT_CAPTURE_TS;
|
|
754
|
+
var SYNKRO_COMMON_TS, EDIT_PRECHECK_TS, CWE_PRECHECK_TS, CVE_PRECHECK_TS, INSTALL_SCAN_TS, BASH_JUDGE_TS, AGENT_JUDGE_TS, PLAN_JUDGE_TS, STOP_SUMMARY_TS, SESSION_START_TS, BASH_FOLLOWUP_TS, TRANSCRIPT_SYNC_TS, USER_PROMPT_SUBMIT_TS, CURSOR_BASH_JUDGE_TS, CURSOR_EDIT_CAPTURE_TS;
|
|
731
755
|
var init_hookScriptsTs = __esm({
|
|
732
756
|
"cli/installer/hookScriptsTs.ts"() {
|
|
733
757
|
"use strict";
|
|
@@ -975,12 +999,19 @@ export function normalizeMode(m?: string): 'ask' | 'fix' {
|
|
|
975
999
|
|
|
976
1000
|
// \u2500\u2500\u2500 Config Loading \u2500\u2500\u2500
|
|
977
1001
|
|
|
1002
|
+
export interface RuleExample {
|
|
1003
|
+
text: string;
|
|
1004
|
+
verdict: 'violation' | 'ok';
|
|
1005
|
+
note?: string;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
978
1008
|
export interface Rule {
|
|
979
1009
|
rule_id: string;
|
|
980
1010
|
text: string;
|
|
981
1011
|
severity: string;
|
|
982
1012
|
category: string;
|
|
983
1013
|
mode: string;
|
|
1014
|
+
examples?: RuleExample[];
|
|
984
1015
|
}
|
|
985
1016
|
|
|
986
1017
|
export interface HookConfig {
|
|
@@ -1032,6 +1063,7 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1032
1063
|
severity: r.severity || '',
|
|
1033
1064
|
category: r.category || '',
|
|
1034
1065
|
mode: normalizeMode(r.mode),
|
|
1066
|
+
examples: Array.isArray(r.examples) ? r.examples : undefined,
|
|
1035
1067
|
}));
|
|
1036
1068
|
}
|
|
1037
1069
|
config.silent = raw.silent === true;
|
|
@@ -1072,6 +1104,7 @@ export async function loadConfig(jwt: string, query?: string): Promise<HookConfi
|
|
|
1072
1104
|
severity: r.severity || '',
|
|
1073
1105
|
category: r.category || '',
|
|
1074
1106
|
mode: normalizeMode(r.mode),
|
|
1107
|
+
examples: Array.isArray(r.examples) ? r.examples : undefined,
|
|
1075
1108
|
}));
|
|
1076
1109
|
}
|
|
1077
1110
|
} catch {}
|
|
@@ -2271,6 +2304,8 @@ import {
|
|
|
2271
2304
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2272
2305
|
import { basename, dirname, join } from 'node:path';
|
|
2273
2306
|
|
|
2307
|
+
const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
|
|
2308
|
+
|
|
2274
2309
|
async function main() {
|
|
2275
2310
|
setupCursorHookSignals();
|
|
2276
2311
|
try {
|
|
@@ -2362,7 +2397,7 @@ async function main() {
|
|
|
2362
2397
|
|
|
2363
2398
|
let gradeResp: string;
|
|
2364
2399
|
try {
|
|
2365
|
-
gradeResp = await localGrade('edit', graderPrompt);
|
|
2400
|
+
gradeResp = await localGrade('edit', graderPrompt, undefined, agentKind);
|
|
2366
2401
|
} catch (err) {
|
|
2367
2402
|
logGraderUnavailable('editGuard', fileShort, (err as Error).message || String(err));
|
|
2368
2403
|
outputJson({ systemMessage: tagStr + ' editGuard ' + fileShort + ' \u2192 local grader unavailable, skipped' });
|
|
@@ -2483,6 +2518,7 @@ import {
|
|
|
2483
2518
|
import { basename, extname, resolve, join, dirname } from 'node:path';
|
|
2484
2519
|
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
2485
2520
|
|
|
2521
|
+
const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
|
|
2486
2522
|
|
|
2487
2523
|
interface PackageCapability {
|
|
2488
2524
|
name: string;
|
|
@@ -2743,8 +2779,8 @@ async function main() {
|
|
|
2743
2779
|
const chunk2 = cweContent.slice(mid - OVERLAP);
|
|
2744
2780
|
try {
|
|
2745
2781
|
const [resp1, resp2] = await Promise.all([
|
|
2746
|
-
localGradeCwe(buildCwePrompt(chunk1)),
|
|
2747
|
-
localGradeCwe(buildCwePrompt(chunk2)),
|
|
2782
|
+
localGradeCwe(buildCwePrompt(chunk1), agentKind),
|
|
2783
|
+
localGradeCwe(buildCwePrompt(chunk2), agentKind),
|
|
2748
2784
|
]);
|
|
2749
2785
|
gradeResponses = [resp1, resp2];
|
|
2750
2786
|
} catch (gradeErr: any) {
|
|
@@ -2755,7 +2791,7 @@ async function main() {
|
|
|
2755
2791
|
}
|
|
2756
2792
|
} else {
|
|
2757
2793
|
try {
|
|
2758
|
-
gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent))];
|
|
2794
|
+
gradeResponses = [await localGradeCwe(buildCwePrompt(cweContent), agentKind)];
|
|
2759
2795
|
} catch (gradeErr: any) {
|
|
2760
2796
|
const reason = gradeErr?.message || String(gradeErr);
|
|
2761
2797
|
logGraderUnavailable('cweGuard', fileShort, reason);
|
|
@@ -3116,6 +3152,96 @@ async function main() {
|
|
|
3116
3152
|
}
|
|
3117
3153
|
}
|
|
3118
3154
|
|
|
3155
|
+
main();
|
|
3156
|
+
`;
|
|
3157
|
+
INSTALL_SCAN_TS = `#!/usr/bin/env bun
|
|
3158
|
+
import {
|
|
3159
|
+
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag,
|
|
3160
|
+
readStdin, runInstallScan, dispatchFinding, dispatchCapture, hashCommand,
|
|
3161
|
+
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, log, GATEWAY_URL,
|
|
3162
|
+
} from './_synkro-common.ts';
|
|
3163
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3164
|
+
import { join } from 'node:path';
|
|
3165
|
+
|
|
3166
|
+
const SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';
|
|
3167
|
+
|
|
3168
|
+
async function main() {
|
|
3169
|
+
setupCursorHookSignals();
|
|
3170
|
+
try {
|
|
3171
|
+
const input = await readStdin();
|
|
3172
|
+
if (!input.trim()) { outputEmpty(); return; }
|
|
3173
|
+
|
|
3174
|
+
const payload = JSON.parse(input);
|
|
3175
|
+
const toolName = payload.tool_name || '';
|
|
3176
|
+
if (!isShellTool(toolName)) { outputEmpty(); return; }
|
|
3177
|
+
|
|
3178
|
+
const toolInput = payload.tool_input || {};
|
|
3179
|
+
const command = typeof payload.command === 'string' ? payload.command : (toolInput.command || '');
|
|
3180
|
+
if (!command) { outputEmpty(); return; }
|
|
3181
|
+
|
|
3182
|
+
let jwt = loadJwt();
|
|
3183
|
+
if (!jwt) { outputEmpty(); return; }
|
|
3184
|
+
jwt = await ensureFreshJwt(jwt);
|
|
3185
|
+
|
|
3186
|
+
const scan = await runInstallScan(command, jwt);
|
|
3187
|
+
|
|
3188
|
+
if (scan.scanned) {
|
|
3189
|
+
try {
|
|
3190
|
+
mkdirSync(SCAN_CACHE_DIR, { recursive: true });
|
|
3191
|
+
writeFileSync(join(SCAN_CACHE_DIR, hashCommand(command)), JSON.stringify(scan), 'utf-8');
|
|
3192
|
+
} catch {}
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
if (!scan.scanned) { outputEmpty(); return; }
|
|
3196
|
+
|
|
3197
|
+
const sessionId = hookSessionId(payload);
|
|
3198
|
+
const cwd = payload.cwd || '';
|
|
3199
|
+
const repo = detectRepo(cwd);
|
|
3200
|
+
const config = await loadConfig(jwt);
|
|
3201
|
+
const rt = await route(config);
|
|
3202
|
+
const tagStr = tag(rt, config);
|
|
3203
|
+
|
|
3204
|
+
if (scan.action === 'block') {
|
|
3205
|
+
for (const f of scan.findings) {
|
|
3206
|
+
dispatchFinding(jwt, {
|
|
3207
|
+
session_id: sessionId,
|
|
3208
|
+
file_path: command,
|
|
3209
|
+
finding_type: 'cve' as const,
|
|
3210
|
+
finding_id: f.advisoryId + ':' + f.name,
|
|
3211
|
+
severity: f.severity,
|
|
3212
|
+
status: 'open',
|
|
3213
|
+
detail: f.detail,
|
|
3214
|
+
package_name: f.name,
|
|
3215
|
+
package_version: f.version,
|
|
3216
|
+
}, config.captureDepth);
|
|
3217
|
+
}
|
|
3218
|
+
dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',
|
|
3219
|
+
'Bash', repo, sessionId, config.captureDepth, {
|
|
3220
|
+
command, reasoning: scan.blockContext.slice(0, 200),
|
|
3221
|
+
violatedRules: scan.violatedIds,
|
|
3222
|
+
});
|
|
3223
|
+
outputJson({
|
|
3224
|
+
systemMessage: '[synkro:installScan] ' + scan.summary,
|
|
3225
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '[synkro:installScan] ' + scan.summary },
|
|
3226
|
+
});
|
|
3227
|
+
} else if (scan.action === 'warn') {
|
|
3228
|
+
outputJson({
|
|
3229
|
+
systemMessage: '[synkro:installScan] ' + scan.summary,
|
|
3230
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '[synkro:installScan] ' + scan.summary },
|
|
3231
|
+
});
|
|
3232
|
+
} else {
|
|
3233
|
+
const label = scan.scannedLabel || command.slice(0, 80);
|
|
3234
|
+
outputJson({
|
|
3235
|
+
systemMessage: '[synkro:installScan] ' + label + ' \\u2192 clean',
|
|
3236
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '[synkro:installScan] ' + label + ' \\u2192 clean' },
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
} catch (err) {
|
|
3240
|
+
process.stderr.write('[synkro] installScan error: ' + String(err) + '\\n');
|
|
3241
|
+
outputEmpty();
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3119
3245
|
main();
|
|
3120
3246
|
`;
|
|
3121
3247
|
BASH_JUDGE_TS = String.raw`#!/usr/bin/env bun
|
|
@@ -3126,9 +3252,43 @@ import {
|
|
|
3126
3252
|
extractTranscript, readLastPrompt, appendSessionAction, readSessionLog, compressSessionLog, log,
|
|
3127
3253
|
outputJson, outputEmpty, setupCursorHookSignals, isShellTool, hookSessionId, GATEWAY_URL,
|
|
3128
3254
|
logGraderUnavailable, filterRules, normalizeMode, appendLocalTelemetry, isSafeInRepoRead,
|
|
3129
|
-
|
|
3255
|
+
hashCommand,
|
|
3130
3256
|
type HookConfig, type Rule,
|
|
3131
3257
|
} from './_synkro-common.ts';
|
|
3258
|
+
import { createHash } from 'node:crypto';
|
|
3259
|
+
import { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
3260
|
+
import { join } from 'node:path';
|
|
3261
|
+
|
|
3262
|
+
const SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';
|
|
3263
|
+
|
|
3264
|
+
function readCachedScan(command: string): any | null {
|
|
3265
|
+
try {
|
|
3266
|
+
const path = join(SCAN_CACHE_DIR, hashCommand(command));
|
|
3267
|
+
if (!existsSync(path)) return null;
|
|
3268
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
3269
|
+
unlinkSync(path);
|
|
3270
|
+
return data;
|
|
3271
|
+
} catch { return null; }
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
const DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';
|
|
3275
|
+
const DEDUP_TTL_MS = 3000;
|
|
3276
|
+
|
|
3277
|
+
function isDuplicate(command: string, sessionId: string): boolean {
|
|
3278
|
+
const hash = createHash('md5').update(sessionId + ':' + command).digest('hex').slice(0, 12);
|
|
3279
|
+
const marker = DEDUP_DIR + '/' + hash;
|
|
3280
|
+
try {
|
|
3281
|
+
if (existsSync(marker)) {
|
|
3282
|
+
const age = Date.now() - statSync(marker).mtimeMs;
|
|
3283
|
+
if (age < DEDUP_TTL_MS) return true;
|
|
3284
|
+
}
|
|
3285
|
+
} catch {}
|
|
3286
|
+
try {
|
|
3287
|
+
mkdirSync(DEDUP_DIR, { recursive: true });
|
|
3288
|
+
writeFileSync(marker, '', { flag: 'w' });
|
|
3289
|
+
} catch {}
|
|
3290
|
+
return false;
|
|
3291
|
+
}
|
|
3132
3292
|
|
|
3133
3293
|
async function main() {
|
|
3134
3294
|
setupCursorHookSignals();
|
|
@@ -3166,6 +3326,12 @@ async function main() {
|
|
|
3166
3326
|
}
|
|
3167
3327
|
if (!command) { outputEmpty(); return; }
|
|
3168
3328
|
|
|
3329
|
+
if (isDuplicate(command, sessionId)) {
|
|
3330
|
+
log('bashGuard skip (dedup): ' + command.slice(0, 80));
|
|
3331
|
+
outputEmpty();
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3169
3335
|
const cmdShort = command.slice(0, 80);
|
|
3170
3336
|
log('bashGuard checking: ' + cmdShort);
|
|
3171
3337
|
|
|
@@ -3211,45 +3377,20 @@ async function main() {
|
|
|
3211
3377
|
return;
|
|
3212
3378
|
}
|
|
3213
3379
|
|
|
3214
|
-
// ─── Install protection:
|
|
3215
|
-
//
|
|
3216
|
-
//
|
|
3217
|
-
//
|
|
3218
|
-
//
|
|
3219
|
-
let installScanMsg = '';
|
|
3380
|
+
// ─── Install protection: read cached scan from the install-scan hook ───
|
|
3381
|
+
// The install-scan hook (INSTALL_SCAN_TS) runs before this hook, calls
|
|
3382
|
+
// runInstallScan(), outputs its own system message, and caches the result.
|
|
3383
|
+
// We just read the cache to feed scanConcern into the grader prompt so
|
|
3384
|
+
// the consent-carryover flow works.
|
|
3220
3385
|
let scanConcern = '';
|
|
3221
3386
|
let scanBlockContext = '';
|
|
3222
3387
|
if (toolName === 'Bash') {
|
|
3223
|
-
const scan =
|
|
3224
|
-
if (scan.action === 'block') {
|
|
3225
|
-
|
|
3226
|
-
dispatchFinding(jwt, {
|
|
3227
|
-
session_id: sessionId,
|
|
3228
|
-
file_path: command,
|
|
3229
|
-
finding_type: 'cve' as const,
|
|
3230
|
-
finding_id: f.advisoryId + ':' + f.name,
|
|
3231
|
-
severity: f.severity,
|
|
3232
|
-
status: 'open',
|
|
3233
|
-
detail: f.detail,
|
|
3234
|
-
package_name: f.name,
|
|
3235
|
-
package_version: f.version,
|
|
3236
|
-
}, config.captureDepth);
|
|
3237
|
-
}
|
|
3238
|
-
dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',
|
|
3239
|
-
'Bash', gitRepo, sessionId, config.captureDepth, {
|
|
3240
|
-
command,
|
|
3241
|
-
reasoning: scan.blockContext.slice(0, 200),
|
|
3242
|
-
violatedRules: scan.violatedIds,
|
|
3243
|
-
ccModel: transcript.ccModel,
|
|
3244
|
-
});
|
|
3245
|
-
scanBlockContext = scan.blockContext;
|
|
3388
|
+
const scan = readCachedScan(command);
|
|
3389
|
+
if (scan && scan.action === 'block') {
|
|
3390
|
+
scanBlockContext = scan.blockContext || '';
|
|
3246
3391
|
scanConcern = 'PACKAGE SCANNER FLAG (authoritative — do NOT re-evaluate whether the vulnerability is real): '
|
|
3247
|
-
+
|
|
3392
|
+
+ scanBlockContext
|
|
3248
3393
|
+ ' For this concern you MUST return ok=false with rule_id "SYNKRO_PKGSCAN", rule_mode "ask", and the reason above — UNLESS the user has explicitly consented in this conversation to installing this despite the warning, in which case return ok=true.';
|
|
3249
|
-
} else if (scan.scanned && scan.action === 'warn') {
|
|
3250
|
-
installScanMsg = '[synkro:installScan] ' + scan.summary;
|
|
3251
|
-
} else if (scan.scanned) {
|
|
3252
|
-
installScanMsg = '[synkro:installScan] ' + (scan.scannedLabel || cmdShort) + ' → clean';
|
|
3253
3394
|
}
|
|
3254
3395
|
}
|
|
3255
3396
|
|
|
@@ -3305,10 +3446,9 @@ async function main() {
|
|
|
3305
3446
|
const blockMsg = mode === 'fix'
|
|
3306
3447
|
? tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Fix this before retrying — do not ask the user.'
|
|
3307
3448
|
: tagStr + ' bashGuard → blocked' + (verdict.ruleId ? ' (' + verdict.ruleId + ')' : '') + ': ' + (verdict.reason || 'policy violation') + '. Ask the user for explicit consent before retrying.';
|
|
3308
|
-
const combined = (installScanMsg ? installScanMsg + '\\n' : '') + blockMsg;
|
|
3309
3449
|
outputJson({
|
|
3310
|
-
systemMessage:
|
|
3311
|
-
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext:
|
|
3450
|
+
systemMessage: blockMsg,
|
|
3451
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: blockMsg, additionalContext: blockMsg },
|
|
3312
3452
|
});
|
|
3313
3453
|
dispatchCapture(jwt, 'bash', 'block', verdict.severity || 'critical', verdict.category || 'security',
|
|
3314
3454
|
toolName, gitRepo, sessionId, config.captureDepth, {
|
|
@@ -3317,8 +3457,10 @@ async function main() {
|
|
|
3317
3457
|
});
|
|
3318
3458
|
} else {
|
|
3319
3459
|
const reason = tagStr + ' bashGuard → pass: ' + (verdict.reason || 'no policy violations detected');
|
|
3320
|
-
|
|
3321
|
-
|
|
3460
|
+
outputJson({
|
|
3461
|
+
systemMessage: reason,
|
|
3462
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: reason },
|
|
3463
|
+
});
|
|
3322
3464
|
dispatchCapture(jwt, 'bash', 'pass', 'clean', verdict.category || 'trivial_utility',
|
|
3323
3465
|
toolName, gitRepo, sessionId, config.captureDepth, {
|
|
3324
3466
|
command, reasoning: verdict.reason || 'no policy violations detected',
|
|
@@ -3370,30 +3512,16 @@ async function main() {
|
|
|
3370
3512
|
|
|
3371
3513
|
if (!resp) {
|
|
3372
3514
|
log('bashGuard ' + cmdShort + ' → error (timeout)');
|
|
3373
|
-
|
|
3374
|
-
outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });
|
|
3375
|
-
} else { outputEmpty(); }
|
|
3515
|
+
outputEmpty();
|
|
3376
3516
|
return;
|
|
3377
3517
|
}
|
|
3378
3518
|
|
|
3379
3519
|
if (!resp.hook_response || typeof resp.hook_response !== 'object') {
|
|
3380
3520
|
log('bashGuard ' + cmdShort + ' → pass (no hook_response)');
|
|
3381
|
-
|
|
3382
|
-
outputJson({ systemMessage: installScanMsg, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: installScanMsg } });
|
|
3383
|
-
} else { outputEmpty(); }
|
|
3521
|
+
outputEmpty();
|
|
3384
3522
|
return;
|
|
3385
3523
|
}
|
|
3386
3524
|
|
|
3387
|
-
if (installScanMsg) {
|
|
3388
|
-
const existing = resp.hook_response.systemMessage || '';
|
|
3389
|
-
resp.hook_response.systemMessage = installScanMsg + (existing ? '\\n' + existing : '');
|
|
3390
|
-
if (resp.hook_response.hookSpecificOutput) {
|
|
3391
|
-
const existingCtx = resp.hook_response.hookSpecificOutput.additionalContext || '';
|
|
3392
|
-
resp.hook_response.hookSpecificOutput.additionalContext = installScanMsg + (existingCtx ? '\\n' + existingCtx : '');
|
|
3393
|
-
} else {
|
|
3394
|
-
resp.hook_response.hookSpecificOutput = { hookEventName: 'PreToolUse', additionalContext: resp.hook_response.systemMessage };
|
|
3395
|
-
}
|
|
3396
|
-
}
|
|
3397
3525
|
outputJson(resp.hook_response);
|
|
3398
3526
|
} catch (err) {
|
|
3399
3527
|
process.stderr.write('[synkro] bashGuard error: ' + String(err) + '\\n');
|
|
@@ -3413,6 +3541,8 @@ import {
|
|
|
3413
3541
|
type HookConfig, type Rule,
|
|
3414
3542
|
} from './_synkro-common.ts';
|
|
3415
3543
|
|
|
3544
|
+
const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
|
|
3545
|
+
|
|
3416
3546
|
async function main() {
|
|
3417
3547
|
setupCursorHookSignals();
|
|
3418
3548
|
try {
|
|
@@ -3482,7 +3612,7 @@ async function main() {
|
|
|
3482
3612
|
|
|
3483
3613
|
let gradeResp: string;
|
|
3484
3614
|
try {
|
|
3485
|
-
gradeResp = await localGrade('bash', graderPrompt);
|
|
3615
|
+
gradeResp = await localGrade('bash', graderPrompt, undefined, agentKind);
|
|
3486
3616
|
} catch (err) {
|
|
3487
3617
|
logGraderUnavailable('agentGuard', subagentType || (description || '').slice(0, 100), (err as Error).message || String(err));
|
|
3488
3618
|
outputJson({ systemMessage: tagStr + ' agentGuard \u2192 local grader unavailable, skipped' });
|
|
@@ -3581,6 +3711,8 @@ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from '
|
|
|
3581
3711
|
import { join } from 'node:path';
|
|
3582
3712
|
import { homedir } from 'node:os';
|
|
3583
3713
|
|
|
3714
|
+
const agentKind = process.env.SYNKRO_HOOK_FORMAT === 'cursor' ? 'cursor' : 'claude_code';
|
|
3715
|
+
|
|
3584
3716
|
function findLatestPlanInDir(plansDir: string): string | null {
|
|
3585
3717
|
if (!existsSync(plansDir)) return null;
|
|
3586
3718
|
try {
|
|
@@ -3672,7 +3804,7 @@ async function main() {
|
|
|
3672
3804
|
|
|
3673
3805
|
let gradeResp: string;
|
|
3674
3806
|
try {
|
|
3675
|
-
gradeResp = await localGrade('plan', graderPrompt);
|
|
3807
|
+
gradeResp = await localGrade('plan', graderPrompt, undefined, agentKind);
|
|
3676
3808
|
} catch {
|
|
3677
3809
|
outputJson({ systemMessage: tagStr + ' planReview \u2192 local grader unavailable, skipped' });
|
|
3678
3810
|
return;
|
|
@@ -4163,13 +4295,26 @@ main();
|
|
|
4163
4295
|
import {
|
|
4164
4296
|
loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
|
|
4165
4297
|
parseVerdict, dispatchCapture, dispatchFinding, ruleMode, normalizeMode, filterRules,
|
|
4166
|
-
isSafeInRepoRead,
|
|
4298
|
+
isSafeInRepoRead, postWithRetry, readStdin, hashCommand,
|
|
4167
4299
|
extractTranscript, readLastPrompt, readSessionLog, compressSessionLog,
|
|
4168
4300
|
appendLocalTelemetry, logGraderUnavailable, log, GATEWAY_URL,
|
|
4169
4301
|
type Rule,
|
|
4170
4302
|
} from './_synkro-common.ts';
|
|
4171
4303
|
import { createHash } from 'node:crypto';
|
|
4172
|
-
import { existsSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
4304
|
+
import { existsSync, statSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
4305
|
+
import { join } from 'node:path';
|
|
4306
|
+
|
|
4307
|
+
const SCAN_CACHE_DIR = (process.env.HOME || '/tmp') + '/.synkro/.scan-cache';
|
|
4308
|
+
|
|
4309
|
+
function readCachedScan(command: string): any | null {
|
|
4310
|
+
try {
|
|
4311
|
+
const path = join(SCAN_CACHE_DIR, hashCommand(command));
|
|
4312
|
+
if (!existsSync(path)) return null;
|
|
4313
|
+
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
4314
|
+
unlinkSync(path);
|
|
4315
|
+
return data;
|
|
4316
|
+
} catch { return null; }
|
|
4317
|
+
}
|
|
4173
4318
|
|
|
4174
4319
|
const DEDUP_DIR = process.env.HOME + '/.synkro/.dedup';
|
|
4175
4320
|
const DEDUP_TTL_MS = 3000;
|
|
@@ -4298,33 +4443,16 @@ async function main() {
|
|
|
4298
4443
|
const rt = await route(config);
|
|
4299
4444
|
const tagStr = tag(rt, config);
|
|
4300
4445
|
|
|
4301
|
-
// Install protection \u2014
|
|
4302
|
-
// A block is handed to the grader as an authoritative concern so the
|
|
4303
|
-
// normal ask + consent-carryover flow can let the user override it.
|
|
4446
|
+
// Install protection \u2014 read cached scan from the install-scan hook.
|
|
4304
4447
|
let scanConcern = '';
|
|
4305
4448
|
let scanBlockContext = '';
|
|
4306
4449
|
if (SHELL_TOOL_NAMES.has(toolName)) {
|
|
4307
|
-
const scan =
|
|
4308
|
-
if (scan.action === 'block') {
|
|
4309
|
-
|
|
4310
|
-
dispatchFinding(jwt, {
|
|
4311
|
-
session_id: sessionId, file_path: command,
|
|
4312
|
-
finding_type: 'cve' as const, finding_id: f.advisoryId + ':' + f.name,
|
|
4313
|
-
severity: f.severity, status: 'open', detail: f.detail,
|
|
4314
|
-
package_name: f.name, package_version: f.version,
|
|
4315
|
-
}, config.captureDepth);
|
|
4316
|
-
}
|
|
4317
|
-
dispatchCapture(jwt, 'pkg', 'block', 'critical', 'security',
|
|
4318
|
-
'Bash', repo, sessionId, config.captureDepth, {
|
|
4319
|
-
command, reasoning: scan.blockContext.slice(0, 200),
|
|
4320
|
-
violatedRules: scan.violatedIds, ccModel: model,
|
|
4321
|
-
});
|
|
4322
|
-
scanBlockContext = scan.blockContext;
|
|
4450
|
+
const scan = readCachedScan(command);
|
|
4451
|
+
if (scan && scan.action === 'block') {
|
|
4452
|
+
scanBlockContext = scan.blockContext || '';
|
|
4323
4453
|
scanConcern = 'PACKAGE SCANNER FLAG (authoritative \u2014 do NOT re-evaluate whether the vulnerability is real): '
|
|
4324
|
-
+
|
|
4454
|
+
+ scanBlockContext
|
|
4325
4455
|
+ ' For this concern you MUST return ok=false with rule_id "SYNKRO_PKGSCAN", rule_mode "ask", and the reason above \u2014 UNLESS the user has explicitly consented in this conversation to installing this despite the warning, in which case return ok=true.';
|
|
4326
|
-
} else if (scan.scanned && scan.action === 'warn') {
|
|
4327
|
-
log('bashGuard installScan warn: ' + scan.summary);
|
|
4328
4456
|
}
|
|
4329
4457
|
}
|
|
4330
4458
|
|
|
@@ -5680,8 +5808,6 @@ async function dockerInstall(opts = {}) {
|
|
|
5680
5808
|
`127.0.0.1:${HOST_GRADER_PORT}:8929`,
|
|
5681
5809
|
"-p",
|
|
5682
5810
|
`127.0.0.1:${HOST_CWE_PORT}:8930`,
|
|
5683
|
-
"-p",
|
|
5684
|
-
`127.0.0.1:${HOST_PG_PORT}:5433`,
|
|
5685
5811
|
"-v",
|
|
5686
5812
|
`${PGDATA_PATH}:/data/pgdata`,
|
|
5687
5813
|
"-v",
|
|
@@ -5910,7 +6036,7 @@ function checkPgdata() {
|
|
|
5910
6036
|
if (!hasPgControl) return { healthy: false, details: "pg_control/global directory missing" };
|
|
5911
6037
|
return { healthy: true, details: `${entries.length} entries, WAL present, no stale PID` };
|
|
5912
6038
|
}
|
|
5913
|
-
var SYNKRO_DIR2, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT,
|
|
6039
|
+
var SYNKRO_DIR2, MCP_JWT_PATH, PGDATA_PATH, CLAUDE_HOST_STATE_DIR, CLAUDE_HOST_STATE_FILE, HOST_MCP_PORT, HOST_GRADER_PORT, HOST_CWE_PORT, CONTAINER_NAME, DEFAULT_IMAGE, DockerInstallError, BACKUP_DIR;
|
|
5914
6040
|
var init_dockerInstall = __esm({
|
|
5915
6041
|
"cli/local-cc/dockerInstall.ts"() {
|
|
5916
6042
|
"use strict";
|
|
@@ -5924,7 +6050,6 @@ var init_dockerInstall = __esm({
|
|
|
5924
6050
|
HOST_MCP_PORT = parseInt(process.env.SYNKRO_HOST_MCP_PORT || "18931", 10);
|
|
5925
6051
|
HOST_GRADER_PORT = parseInt(process.env.SYNKRO_HOST_GRADER_PORT || "18929", 10);
|
|
5926
6052
|
HOST_CWE_PORT = parseInt(process.env.SYNKRO_HOST_CWE_PORT || "18930", 10);
|
|
5927
|
-
HOST_PG_PORT = parseInt(process.env.SYNKRO_HOST_PG_PORT || "15433", 10);
|
|
5928
6053
|
CONTAINER_NAME = "synkro-server";
|
|
5929
6054
|
DEFAULT_IMAGE = "ghcr.io/synkro-sh/synkro-server:latest";
|
|
5930
6055
|
DockerInstallError = class extends Error {
|
|
@@ -6440,6 +6565,7 @@ function writeHookScripts() {
|
|
|
6440
6565
|
const userPromptSubmitScriptPath = join8(HOOKS_DIR, "cc-user-prompt-submit.ts");
|
|
6441
6566
|
const commonScriptPath = join8(HOOKS_DIR, "_synkro-common.ts");
|
|
6442
6567
|
const commonBashScriptPath = join8(HOOKS_DIR, "_synkro-common.sh");
|
|
6568
|
+
const installScanScriptPath = join8(HOOKS_DIR, "cc-install-scan.ts");
|
|
6443
6569
|
const cursorBashJudgePath = join8(HOOKS_DIR, "cursor-bash-judge.ts");
|
|
6444
6570
|
const cursorEditCapturePath = join8(HOOKS_DIR, "cursor-edit-capture.ts");
|
|
6445
6571
|
const mcpStdioProxyPath = join8(HOOKS_DIR, "mcp-stdio-proxy.ts");
|
|
@@ -6456,6 +6582,7 @@ function writeHookScripts() {
|
|
|
6456
6582
|
writeFileSync7(userPromptSubmitScriptPath, USER_PROMPT_SUBMIT_TS, "utf-8");
|
|
6457
6583
|
writeFileSync7(commonScriptPath, SYNKRO_COMMON_TS, "utf-8");
|
|
6458
6584
|
writeFileSync7(commonBashScriptPath, SYNKRO_COMMON_SCRIPT, "utf-8");
|
|
6585
|
+
writeFileSync7(installScanScriptPath, INSTALL_SCAN_TS, "utf-8");
|
|
6459
6586
|
writeFileSync7(cursorBashJudgePath, CURSOR_BASH_JUDGE_TS, "utf-8");
|
|
6460
6587
|
writeFileSync7(cursorEditCapturePath, CURSOR_EDIT_CAPTURE_TS, "utf-8");
|
|
6461
6588
|
writeFileSync7(mcpStdioProxyPath, MCP_STDIO_PROXY_SRC, "utf-8");
|
|
@@ -6472,6 +6599,7 @@ function writeHookScripts() {
|
|
|
6472
6599
|
chmodSync2(userPromptSubmitScriptPath, 493);
|
|
6473
6600
|
chmodSync2(commonScriptPath, 493);
|
|
6474
6601
|
chmodSync2(commonBashScriptPath, 493);
|
|
6602
|
+
chmodSync2(installScanScriptPath, 493);
|
|
6475
6603
|
chmodSync2(cursorBashJudgePath, 493);
|
|
6476
6604
|
chmodSync2(cursorEditCapturePath, 493);
|
|
6477
6605
|
chmodSync2(mcpStdioProxyPath, 493);
|
|
@@ -6487,6 +6615,7 @@ function writeHookScripts() {
|
|
|
6487
6615
|
sessionStartScript: sessionStartScriptPath,
|
|
6488
6616
|
transcriptSyncScript: transcriptSyncScriptPath,
|
|
6489
6617
|
userPromptSubmitScript: userPromptSubmitScriptPath,
|
|
6618
|
+
installScanScript: installScanScriptPath,
|
|
6490
6619
|
cursorBashJudgeScript: cursorBashJudgePath,
|
|
6491
6620
|
cursorEditCaptureScript: cursorEditCapturePath
|
|
6492
6621
|
};
|
|
@@ -6520,7 +6649,7 @@ function writeConfigEnv(opts) {
|
|
|
6520
6649
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
6521
6650
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
6522
6651
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
6523
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.6.
|
|
6652
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.6.17")}`
|
|
6524
6653
|
];
|
|
6525
6654
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
6526
6655
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -6762,6 +6891,7 @@ async function installCommand(opts = {}) {
|
|
|
6762
6891
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
6763
6892
|
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
6764
6893
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
6894
|
+
installScanScriptPath: scripts.installScanScript,
|
|
6765
6895
|
skipTranscriptSync: !transcriptConsent
|
|
6766
6896
|
});
|
|
6767
6897
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
@@ -6779,7 +6909,8 @@ async function installCommand(opts = {}) {
|
|
|
6779
6909
|
stopSummaryScriptPath: scripts.stopSummaryScript,
|
|
6780
6910
|
sessionStartScriptPath: scripts.sessionStartScript,
|
|
6781
6911
|
userPromptSubmitScriptPath: scripts.userPromptSubmitScript,
|
|
6782
|
-
transcriptSyncScriptPath: scripts.transcriptSyncScript
|
|
6912
|
+
transcriptSyncScriptPath: scripts.transcriptSyncScript,
|
|
6913
|
+
installScanScriptPath: scripts.installScanScript
|
|
6783
6914
|
});
|
|
6784
6915
|
console.log(`Configured ${agent.name} hooks at ${agent.settingsPath}`);
|
|
6785
6916
|
}
|
|
@@ -9185,7 +9316,7 @@ var args = process.argv.slice(2);
|
|
|
9185
9316
|
var cmd = args[0] || "";
|
|
9186
9317
|
var subArgs = args.slice(1);
|
|
9187
9318
|
function printVersion() {
|
|
9188
|
-
console.log("1.6.
|
|
9319
|
+
console.log("1.6.17");
|
|
9189
9320
|
}
|
|
9190
9321
|
function printHelp2() {
|
|
9191
9322
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|