@synkro-sh/cli 1.6.92 → 1.6.94

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 CHANGED
@@ -1775,6 +1775,27 @@ async function hostedGrade(role: GradeRole, prompt: string, jwt: string, timeout
1775
1775
  return String(data.result || '');
1776
1776
  }
1777
1777
 
1778
+ // Cloud task-adherence: fire the task-only 'task-drift' surface on the org's
1779
+ // container and return ONLY the progress systemMessage (informational \u2014 never
1780
+ // blocks an edit). Fail-silent: any hiccup returns '' so task tracking can't
1781
+ // break grading. The payload IS a ScanEnvelope; the container runs getActiveTask
1782
+ // (resolved per (org,user,repo) via active_tasks) + taskDrift and returns the
1783
+ // decision. Secrets are scrubbed before the envelope crosses the network.
1784
+ export async function cloudTaskDrift(envelope: Record<string, unknown>, jwt: string, timeoutMs = 20000): Promise<string> {
1785
+ try {
1786
+ const body = JSON.stringify({ role: 'task-drift', payload: scrubSecrets(JSON.stringify(envelope)), content: '' });
1787
+ const resp = await fetch(CONTAINERS_URL + '/grade/submit', {
1788
+ method: 'POST',
1789
+ headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + jwt },
1790
+ body,
1791
+ signal: AbortSignal.timeout(timeoutMs),
1792
+ });
1793
+ if (!resp.ok) return '';
1794
+ const j = await resp.json() as { systemMessage?: unknown };
1795
+ return typeof j?.systemMessage === 'string' ? j.systemMessage : '';
1796
+ } catch { return ''; }
1797
+ }
1798
+
1778
1799
  // Option B: when a cloud grade fails (esp. a timeout), the REAL reason lives
1779
1800
  // inside the container, not on the wire \u2014 the client only ever sees "timed out".
1780
1801
  // Pull the org's per-worker claude/cursor sick reasons + log tails from the
@@ -4265,7 +4286,7 @@ export function outputEmpty(): void {
4265
4286
  `;
4266
4287
  EDIT_PRECHECK_TS = `#!/usr/bin/env bun
4267
4288
  import {
4268
- loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade,
4289
+ loadJwt, ensureFreshJwt, detectRepo, loadConfig, route, tag, localGrade, cloudTaskDrift,
4269
4290
  parseVerdict, parseCombinedVerdict, combinedEditGradeEnabled, dispatchCapture, dispatchFinding, ruleMode, reconstructContent, editSecretDenial, isPathUnder, postWithRetry,
4270
4291
  readStdin, extractTranscript, readLastPrompt, findNearestDeps, filePathFromToolInput,
4271
4292
  appendSessionAction, readSessionLog, compressSessionLog, sessionLogForGrade, log,
@@ -4422,6 +4443,15 @@ async function main() {
4422
4443
  ].join('\\n');
4423
4444
  const graderPrompt = buildGraderPrompt(proposedShort);
4424
4445
 
4446
+ // Cloud task-adherence (additive, informational): fire the task-only grade in
4447
+ // PARALLEL with the rule grade so it adds no latency, then merge its progress
4448
+ // message into the pass output. Fail-silent ('' on any issue). Sent as a
4449
+ // synthetic Write of the reconstructed content so the container needs no base
4450
+ // bytes. cwd = the repo identifier the container keys active_tasks on.
4451
+ const taskDriftP: Promise<string> = process.env.SYNKRO_DEPLOY_LOCATION === 'cloud'
4452
+ ? cloudTaskDrift({ payload: { tool_name: 'Write', tool_input: { file_path: filePath, content: proposedShort } }, harness: agentKind, cwd: gitRepo, sessionId }, jwt)
4453
+ : Promise.resolve('');
4454
+
4425
4455
  // \u2500\u2500\u2500 Combined org-rules + CWE in ONE inference (SYNKRO_COMBINED_EDIT_GRADE) \u2500\u2500\u2500
4426
4456
  // Self-contained early-return branch \u2014 the default two-grade path below is
4427
4457
  // left untouched. Appends the CWE rule set to the rules prompt, runs one
@@ -4527,7 +4557,8 @@ async function main() {
4527
4557
  ccModel: captureModel, ...lineMetrics,
4528
4558
  });
4529
4559
  const cPassLine = tagStr + ' editGuard ' + fileShort + ' \u2192 pass: ' + (ruleVerdict.reason || 'clean (rules + CWE)');
4530
- outputJson({ systemMessage: cPassLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro combined edit judge (rules + CWE). ' + (ruleVerdict.reason || 'clean') } });
4560
+ const cTaskMsg = await taskDriftP;
4561
+ outputJson({ systemMessage: cPassLine + (cTaskMsg ? '\\n' + cTaskMsg : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro combined edit judge (rules + CWE). ' + (ruleVerdict.reason || 'clean') } });
4531
4562
  return;
4532
4563
  }
4533
4564
 
@@ -4573,7 +4604,8 @@ async function main() {
4573
4604
  ccModel: captureModel, ...lineMetrics,
4574
4605
  });
4575
4606
  const passLine = tagStr + ' editGuard ' + fileShort + ' \u2192 pass: ' + (verdict.reason || 'no policy violations detected');
4576
- outputJson({ systemMessage: passLine, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge. ' + (verdict.reason || 'no policy violations detected') } });
4607
+ const rTaskMsg = await taskDriftP;
4608
+ outputJson({ systemMessage: passLine + (rTaskMsg ? '\\n' + rTaskMsg : ''), hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro local edit judge. ' + (verdict.reason || 'no policy violations detected') } });
4577
4609
  return;
4578
4610
  }
4579
4611
 
@@ -5796,35 +5828,14 @@ async function main() {
5796
5828
  return;
5797
5829
  }
5798
5830
 
5799
- if (isSafeInRepoRead(toolName, command, cwd)) {
5800
- log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');
5801
- appendLocalTelemetry({
5802
- capture_type: 'local_verdict',
5803
- verdict: 'pass',
5804
- hook_type: 'bash',
5805
- category: 'safe_read',
5806
- tool_name: toolName,
5807
- command: command.slice(0, 200),
5808
- session_id: sessionId,
5809
- repo: gitRepo || cwd,
5810
- cc_model: transcript.ccModel,
5811
- reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',
5812
- });
5813
- outputJson({
5814
- systemMessage: tagStr + ' bashGuard → pass: safe in-repo read',
5815
- hookSpecificOutput: {
5816
- hookEventName: 'PreToolUse',
5817
- additionalContext: tagStr + ' bashGuard pass: safe in-repo read.',
5818
- },
5819
- });
5820
- return;
5821
- }
5822
-
5823
5831
  // ─── Secret firewall: BLOCK a literal credential in the command BEFORE it runs
5824
5832
  // or reaches the grader — the value never lands in a file, shell history, OR the
5825
5833
  // LLM provider (no leak to Anthropic/Cursor). Deterministic + local, no model
5826
5834
  // call. High-confidence formats only (a false block stops the user), and
5827
- // $VAR / placeholder refs never trip it. ───
5835
+ // $VAR / placeholder refs never trip it. MUST run BEFORE the safe-read fast-path
5836
+ // below — otherwise an echo/printf of a literal secret is treated as a safe read
5837
+ // and waved through. A genuine read carries no literal secret, so this never
5838
+ // false-blocks legitimate reads. ───
5828
5839
  const secretHits = detectHardSecrets(command);
5829
5840
  if (secretHits.length) {
5830
5841
  const types = secretHits.map(h => h.type).join(', ');
@@ -5849,6 +5860,32 @@ async function main() {
5849
5860
  return;
5850
5861
  }
5851
5862
 
5863
+ // Safe in-repo read fast-path — runs AFTER the secret firewall above so a
5864
+ // literal credential is never waved through as a "safe read".
5865
+ if (isSafeInRepoRead(toolName, command, cwd)) {
5866
+ log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');
5867
+ appendLocalTelemetry({
5868
+ capture_type: 'local_verdict',
5869
+ verdict: 'pass',
5870
+ hook_type: 'bash',
5871
+ category: 'safe_read',
5872
+ tool_name: toolName,
5873
+ command: command.slice(0, 200),
5874
+ session_id: sessionId,
5875
+ repo: gitRepo || cwd,
5876
+ cc_model: transcript.ccModel,
5877
+ reasoning: 'Safe in-repo read — auto-allowed without an LLM grade.',
5878
+ });
5879
+ outputJson({
5880
+ systemMessage: tagStr + ' bashGuard → pass: safe in-repo read',
5881
+ hookSpecificOutput: {
5882
+ hookEventName: 'PreToolUse',
5883
+ additionalContext: tagStr + ' bashGuard pass: safe in-repo read.',
5884
+ },
5885
+ });
5886
+ return;
5887
+ }
5888
+
5852
5889
  // ─── Install protection: install-scan runs first and owns block traces ───
5853
5890
  let scanConcern = '';
5854
5891
  let scanBlockContext = '';
@@ -6872,6 +6909,25 @@ async function main() {
6872
6909
 
6873
6910
  // Instant-allow read-only tool calls + safe in-repo bash reads \u2014 no grade,
6874
6911
  // no network. Critical under Cursor's tight 15s beforeShellExecution budget.
6912
+ // \u2500\u2500\u2500 Secret firewall: BLOCK a literal credential in the command BEFORE it runs
6913
+ // or reaches the grader \u2014 never leaks to a file, shell history, or the model
6914
+ // (Anthropic/Cursor). Local + deterministic; $VAR / placeholder refs never trip
6915
+ // it. MUST run BEFORE the safe-read fast-path below \u2014 else an echo/printf of a
6916
+ // literal secret is waved through as a "safe read". \u2500\u2500\u2500
6917
+ const secretHits = detectHardSecrets(command);
6918
+ if (secretHits.length) {
6919
+ const types = secretHits.map(h => h.type).join(', ');
6920
+ const remediation = secretRemediation(command, secretHits);
6921
+ log('bashGuard ' + cmdShort + ' \u2192 BLOCKED: literal credential (' + types + ')');
6922
+ appendLocalTelemetry({
6923
+ capture_type: 'local_verdict', verdict: 'block', hook_type: 'bash',
6924
+ category: 'secret_exposure', tool_name: toolName, command: scrubSecrets(command).slice(0, 200),
6925
+ session_id: sessionId, repo: repo || cwd, cc_model: model,
6926
+ reasoning: 'Literal credential (' + types + ') in the proposed command \u2014 blocked locally before grading; never sent to the model.',
6927
+ });
6928
+ finishWith({ permission: 'deny', user_message: '[synkro] bashGuard \u2192 BLOCKED: literal credential in command (' + types + '). Use an env var reference instead.', agent_message: remediation });
6929
+ }
6930
+
6875
6931
  if (isSafeInRepoRead(toolName, command, cwd)) {
6876
6932
  log('bashGuard ' + cmdShort + ' \u2192 instant allow (safe in-repo read)');
6877
6933
  appendLocalTelemetry({
@@ -6899,23 +6955,6 @@ async function main() {
6899
6955
  const rt = await route(config, synkroFile);
6900
6956
  const tagStr = tag(rt, config, graderPool);
6901
6957
 
6902
- // Secret firewall: BLOCK a literal credential in the command BEFORE it runs or
6903
- // reaches the grader \u2014 never leaks to a file, shell history, or the model
6904
- // (Anthropic/Cursor). Local + deterministic; $VAR / placeholder refs never trip it.
6905
- const secretHits = detectHardSecrets(command);
6906
- if (secretHits.length) {
6907
- const types = secretHits.map(h => h.type).join(', ');
6908
- const remediation = secretRemediation(command, secretHits);
6909
- log('bashGuard ' + cmdShort + ' \u2192 BLOCKED: literal credential (' + types + ')');
6910
- appendLocalTelemetry({
6911
- capture_type: 'local_verdict', verdict: 'block', hook_type: 'bash',
6912
- category: 'secret_exposure', tool_name: toolName, command: scrubSecrets(command).slice(0, 200),
6913
- session_id: sessionId, repo: repo || cwd, cc_model: model,
6914
- reasoning: 'Literal credential (' + types + ') in the proposed command \u2014 blocked locally before grading; never sent to the model.',
6915
- });
6916
- finishWith({ permission: 'deny', user_message: tagStr + ' bashGuard \u2192 BLOCKED: literal credential in command (' + types + '). Use an env var reference instead.', agent_message: remediation });
6917
- }
6918
-
6919
6958
  // Install protection \u2014 install-scan runs first and owns block traces.
6920
6959
  let scanConcern = '';
6921
6960
  let scanBlockContext = '';
@@ -11230,7 +11269,7 @@ function writeConfigEnv(opts) {
11230
11269
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
11231
11270
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
11232
11271
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
11233
- `SYNKRO_VERSION=${shellQuoteSingle("1.6.92")}`
11272
+ `SYNKRO_VERSION=${shellQuoteSingle("1.6.94")}`
11234
11273
  ];
11235
11274
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
11236
11275
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -15295,7 +15334,7 @@ var args = process.argv.slice(2);
15295
15334
  var cmd = args[0] || "";
15296
15335
  var subArgs = args.slice(1);
15297
15336
  function printVersion() {
15298
- console.log("1.6.92");
15337
+ console.log("1.6.94");
15299
15338
  }
15300
15339
  function printHelp2() {
15301
15340
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents