@synkro-sh/cli 1.5.3 → 1.5.4

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
@@ -2849,6 +2849,59 @@ async function main() {
2849
2849
  const cmdShort = command.slice(0, 80);
2850
2850
  log('bashGuard checking: ' + cmdShort);
2851
2851
 
2852
+ // ─── Hook-side short-circuit for safe in-repo reads ───
2853
+ // The judge primer already deterministically allows these, but the round
2854
+ // trip + batch queue still costs 1–25s per call. Skipping the grade for
2855
+ // unambiguously read-only operations removes that latency for ~half of
2856
+ // typical commands (cat/grep/git status/ls/etc.) and unblocks the worker
2857
+ // pool to grade the operations that actually need judgment.
2858
+ function isSafeInRepoRead(tName: string, cmd: string): boolean {
2859
+ // CC's native read tools are inherently safe.
2860
+ if (tName === 'Read' || tName === 'Grep' || tName === 'Glob') return true;
2861
+ if (tName !== 'Bash' && tName !== 'Shell' && tName !== 'terminal' &&
2862
+ tName !== 'run_terminal_cmd' && tName !== 'execute_command') return false;
2863
+ // Reject any shell metacharacter that could turn a "read" into a write
2864
+ // or chain to an unsafe consumer: redirects, pipes, sequencing, command
2865
+ // substitution, sudo/su.
2866
+ if (/[>;&|\\\`]|\\$\\(|<<|\\bsudo\\b|\\bsu\\b|\\brm\\b|\\bmv\\b|\\bcp\\b|\\bchmod\\b|\\bchown\\b|\\btee\\b|\\bsed\\s+-i\\b|\\bkill\\b/.test(cmd)) return false;
2867
+ const SAFE_VERBS = new Set([
2868
+ 'cat','head','tail','less','more','grep','egrep','fgrep','rg','ag',
2869
+ 'find','fd','ls','wc','cmp','diff','file','stat','which','whereis','type',
2870
+ 'pwd','whoami','id','date','echo','printf','env','true','false',
2871
+ 'jq','yq','awk','sort','uniq','cut','tr','xxd','hexdump','od','column',
2872
+ 'node','npm','pnpm','yarn','bun','python','python3','ruby','go','rustc','cargo',
2873
+ 'git',
2874
+ ]);
2875
+ const tokens = cmd.trim().split(/\\s+/);
2876
+ const verb = tokens[0] || '';
2877
+ if (!SAFE_VERBS.has(verb)) return false;
2878
+ // For multi-mode tools, only allow read subcommands / version flags.
2879
+ if (verb === 'git') {
2880
+ const SAFE_GIT = new Set(['log','show','diff','blame','status','branch','tag','remote','config','rev-parse','ls-files','ls-tree','cat-file','shortlog','reflog','describe','symbolic-ref']);
2881
+ const sub = tokens[1] || '';
2882
+ return SAFE_GIT.has(sub);
2883
+ }
2884
+ if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {
2885
+ // Only allow plain version/info/list/why probes — block install/add/update/run/exec.
2886
+ const sub = tokens[1] || '';
2887
+ const SAFE_PKG = new Set(['--version','-v','version','list','ls','why','view','show','info','outdated','-h','--help','help']);
2888
+ return SAFE_PKG.has(sub);
2889
+ }
2890
+ if (['node','python','python3','ruby','rustc'].includes(verb)) {
2891
+ const sub = tokens[1] || '';
2892
+ return sub === '--version' || sub === '-v' || sub === '-V';
2893
+ }
2894
+ // sed without -i flag is read-only by definition; we already excluded
2895
+ // sed -i above. Anything else with a SAFE_VERB and no metachars is fine.
2896
+ return true;
2897
+ }
2898
+
2899
+ if (isSafeInRepoRead(toolName, command)) {
2900
+ log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');
2901
+ outputJson({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro: safe in-repo read, deterministic allow.' } });
2902
+ return;
2903
+ }
2904
+
2852
2905
  let jwt = loadJwt();
2853
2906
  if (!jwt) { outputEmpty(); return; }
2854
2907
  jwt = await ensureFreshJwt(jwt);
@@ -5570,6 +5623,9 @@ async function dockerInstall(opts = {}) {
5570
5623
  `${CLAUDE_HOST_STATE_DIR}:/data/claude-host-state:ro`,
5571
5624
  "-e",
5572
5625
  `WORKERS_PER_POOL=${workers}`,
5626
+ // Pass through the batch-size lever if the operator set it. Defaults
5627
+ // inside the container to 5; clamped to [1, 20] by synkro-server.ts.
5628
+ ...process.env.SYNKRO_MAX_BATCH_SIZE ? ["-e", `SYNKRO_MAX_BATCH_SIZE=${process.env.SYNKRO_MAX_BATCH_SIZE}`] : [],
5573
5629
  image
5574
5630
  ];
5575
5631
  const run = spawnSync2("docker", args2, { encoding: "utf-8", stdio: "inherit", timeout: 6e4 });
@@ -5796,7 +5852,7 @@ function writeConfigEnv(opts) {
5796
5852
  `SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
5797
5853
  `SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
5798
5854
  `SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
5799
- `SYNKRO_VERSION=${shellQuoteSingle("1.5.3")}`
5855
+ `SYNKRO_VERSION=${shellQuoteSingle("1.5.4")}`
5800
5856
  ];
5801
5857
  if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
5802
5858
  if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
@@ -6203,7 +6259,7 @@ async function installCommand(opts = {}) {
6203
6259
  process.exit(1);
6204
6260
  }
6205
6261
  console.log("Installing Synkro server container...");
6206
- const workersPerPool = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "4", 10);
6262
+ const workersPerPool = parseInt(process.env.SYNKRO_WORKERS_PER_POOL || "8", 10);
6207
6263
  const { image, hostMcpPort, hostGraderPort, hostCwePort } = await dockerInstall({ workersPerPool });
6208
6264
  console.log(` \u2713 pulled ${image}`);
6209
6265
  console.log(` container started \u2014 MCP=${hostMcpPort} general=${hostGraderPort} CWE=${hostCwePort}`);
@@ -7186,7 +7242,7 @@ var args = process.argv.slice(2);
7186
7242
  var cmd = args[0] || "";
7187
7243
  var subArgs = args.slice(1);
7188
7244
  function printVersion() {
7189
- console.log("1.5.3");
7245
+ console.log("1.5.4");
7190
7246
  }
7191
7247
  function printHelp() {
7192
7248
  console.log(`Synkro CLI \u2014 runtime safety for AI coding agents