@synkro-sh/cli 1.5.4 → 1.5.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.
- package/dist/bootstrap.js +85 -30
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1110,7 +1110,7 @@ const ROLE_MAP: Record<string, GradeRole> = {
|
|
|
1110
1110
|
edit: 'grade-edit', bash: 'grade-bash', plan: 'grade-plan', cwe: 'grade-cwe',
|
|
1111
1111
|
};
|
|
1112
1112
|
|
|
1113
|
-
async function channelGrade(role: GradeRole, prompt: string, _jwt: string, port: number, timeoutMs =
|
|
1113
|
+
async function channelGrade(role: GradeRole, prompt: string, _jwt: string, port: number, timeoutMs = 30000): Promise<string> {
|
|
1114
1114
|
const body = JSON.stringify({ role, payload: prompt, content: prompt });
|
|
1115
1115
|
|
|
1116
1116
|
const resp = await fetch('http://127.0.0.1:' + port + '/submit', {
|
|
@@ -1130,7 +1130,7 @@ async function channelGrade(role: GradeRole, prompt: string, _jwt: string, port:
|
|
|
1130
1130
|
return String(data.result || '');
|
|
1131
1131
|
}
|
|
1132
1132
|
|
|
1133
|
-
export async function localGrade(surface: string, prompt: string, timeoutMs =
|
|
1133
|
+
export async function localGrade(surface: string, prompt: string, timeoutMs = 30000): Promise<string> {
|
|
1134
1134
|
if (!(await channelUp())) throw new Error('SYNKRO_CHANNEL_DOWN');
|
|
1135
1135
|
const jwt = loadJwt();
|
|
1136
1136
|
if (!jwt) throw new Error('NO_JWT');
|
|
@@ -2855,48 +2855,103 @@ async function main() {
|
|
|
2855
2855
|
// unambiguously read-only operations removes that latency for ~half of
|
|
2856
2856
|
// typical commands (cat/grep/git status/ls/etc.) and unblocks the worker
|
|
2857
2857
|
// pool to grade the operations that actually need judgment.
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2858
|
+
// Returning FALSE just means "don't short-circuit — let the LLM grade it."
|
|
2859
|
+
// Never blocks. The judge sees the command, applies rules, returns its
|
|
2860
|
+
// own verdict. Path scoping below: STRICT, only short-circuit when every
|
|
2861
|
+
// absolute path is under the linked repo root.
|
|
2862
|
+
function isSafeBashSegment(seg: string, repoRoot: string): boolean {
|
|
2863
|
+
const UNSAFE_CHARS = ['>', ';', '&', '\`'];
|
|
2864
|
+
for (const ch of UNSAFE_CHARS) { if (seg.indexOf(ch) !== -1) return false; }
|
|
2865
|
+
const padded = ' ' + seg + ' ';
|
|
2866
|
+
const UNSAFE_WORDS = [
|
|
2867
|
+
' sudo ', ' su ', ' rm ', ' mv ', ' cp ', ' chmod ', ' chown ',
|
|
2868
|
+
' tee ', ' kill ', ' sed -i', ' sed --in-place',
|
|
2869
|
+
' sh -c', ' bash -c', ' zsh -c', ' eval ', ' exec ',
|
|
2870
|
+
'\$(',
|
|
2871
|
+
];
|
|
2872
|
+
for (const w of UNSAFE_WORDS) { if (padded.indexOf(w) !== -1) return false; }
|
|
2873
|
+
|
|
2874
|
+
// Narrowed verb set. Removed:
|
|
2875
|
+
// awk: has system() / |& shell-spawn
|
|
2876
|
+
// env: \`env FOO=bar evil_cmd\` runs evil_cmd
|
|
2877
|
+
// sed: scripting + -i write capability; not worth parsing
|
|
2867
2878
|
const SAFE_VERBS = new Set([
|
|
2868
2879
|
'cat','head','tail','less','more','grep','egrep','fgrep','rg','ag',
|
|
2869
2880
|
'find','fd','ls','wc','cmp','diff','file','stat','which','whereis','type',
|
|
2870
|
-
'pwd','whoami','id','date','echo','printf','
|
|
2871
|
-
'jq','yq','
|
|
2881
|
+
'pwd','whoami','id','date','echo','printf','true','false',
|
|
2882
|
+
'jq','yq','sort','uniq','cut','tr','xxd','hexdump','od','column',
|
|
2872
2883
|
'node','npm','pnpm','yarn','bun','python','python3','ruby','go','rustc','cargo',
|
|
2873
2884
|
'git',
|
|
2874
2885
|
]);
|
|
2875
|
-
const tokens =
|
|
2886
|
+
const tokens = seg.trim().split(' ').filter(t => t.length > 0);
|
|
2876
2887
|
const verb = tokens[0] || '';
|
|
2877
2888
|
if (!SAFE_VERBS.has(verb)) return false;
|
|
2878
|
-
|
|
2889
|
+
|
|
2890
|
+
// find/fd: reject any execution / mutation action flag.
|
|
2891
|
+
if (verb === 'find' || verb === 'fd') {
|
|
2892
|
+
const BAD = new Set([
|
|
2893
|
+
'-exec','-execdir','-ok','-okdir','-delete',
|
|
2894
|
+
'-fprint','-fprintf','-fprint0','-fls',
|
|
2895
|
+
'--exec','--exec-batch',
|
|
2896
|
+
]);
|
|
2897
|
+
for (const t of tokens) { if (BAD.has(t)) return false; }
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
// git: only pure-read subcommands. branch/tag/remote/config dropped —
|
|
2901
|
+
// each has flag combinations that mutate state.
|
|
2879
2902
|
if (verb === 'git') {
|
|
2880
|
-
const SAFE_GIT = new Set([
|
|
2903
|
+
const SAFE_GIT = new Set([
|
|
2904
|
+
'log','show','diff','blame','status','rev-parse',
|
|
2905
|
+
'ls-files','ls-tree','cat-file','shortlog','reflog',
|
|
2906
|
+
'describe','symbolic-ref','--version',
|
|
2907
|
+
]);
|
|
2881
2908
|
const sub = tokens[1] || '';
|
|
2882
|
-
|
|
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.
|
|
2909
|
+
if (!SAFE_GIT.has(sub)) return false;
|
|
2910
|
+
} else if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {
|
|
2886
2911
|
const sub = tokens[1] || '';
|
|
2887
|
-
const SAFE_PKG = new Set([
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2912
|
+
const SAFE_PKG = new Set([
|
|
2913
|
+
'--version','-v','version','list','ls','why','view','show','info','outdated',
|
|
2914
|
+
'-h','--help','help',
|
|
2915
|
+
]);
|
|
2916
|
+
if (!SAFE_PKG.has(sub)) return false;
|
|
2917
|
+
} else if (['node','python','python3','ruby','rustc'].includes(verb)) {
|
|
2891
2918
|
const sub = tokens[1] || '';
|
|
2892
|
-
|
|
2919
|
+
if (sub !== '--version' && sub !== '-v' && sub !== '-V') return false;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
// STRICT path scoping. Absolute paths MUST resolve under repoRoot.
|
|
2923
|
+
// Home-relative (~/...) paths fall through to the LLM. Relative paths
|
|
2924
|
+
// are implicitly under cwd which is the repo root for the agent session.
|
|
2925
|
+
if (!repoRoot) return false;
|
|
2926
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
2927
|
+
const t = tokens[i];
|
|
2928
|
+
const stripped = t.replace(/^['"]/, '').replace(/['"]$/, '');
|
|
2929
|
+
if (stripped.startsWith('~')) return false;
|
|
2930
|
+
if (stripped.startsWith('/')) {
|
|
2931
|
+
if (!isPathUnder(stripped, repoRoot)) return false;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
return true;
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
function isSafeInRepoRead(tName: string, cmd: string, repoRoot: string): boolean {
|
|
2938
|
+
if (tName === 'Read' || tName === 'Grep' || tName === 'Glob') return true;
|
|
2939
|
+
if (tName !== 'Bash' && tName !== 'Shell' && tName !== 'terminal' &&
|
|
2940
|
+
tName !== 'run_terminal_cmd' && tName !== 'execute_command') return false;
|
|
2941
|
+
if (!cmd || !repoRoot) return false;
|
|
2942
|
+
// Allow pipes only if EVERY segment is safe on its own. Catches
|
|
2943
|
+
// \`grep ... | head\`, \`cat foo | wc -l\`, \`git log | less\`, etc.
|
|
2944
|
+
// Empty segments (from \`||\`) cause rejection.
|
|
2945
|
+
const segments = cmd.split('|');
|
|
2946
|
+
for (const seg of segments) {
|
|
2947
|
+
const t = seg.trim();
|
|
2948
|
+
if (t.length === 0) return false;
|
|
2949
|
+
if (!isSafeBashSegment(t, repoRoot)) return false;
|
|
2893
2950
|
}
|
|
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
2951
|
return true;
|
|
2897
2952
|
}
|
|
2898
2953
|
|
|
2899
|
-
if (isSafeInRepoRead(toolName, command)) {
|
|
2954
|
+
if (isSafeInRepoRead(toolName, command, cwd)) {
|
|
2900
2955
|
log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');
|
|
2901
2956
|
outputJson({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro: safe in-repo read, deterministic allow.' } });
|
|
2902
2957
|
return;
|
|
@@ -5852,7 +5907,7 @@ function writeConfigEnv(opts) {
|
|
|
5852
5907
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
5853
5908
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
5854
5909
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
5855
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.5.
|
|
5910
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.5.6")}`
|
|
5856
5911
|
];
|
|
5857
5912
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
5858
5913
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -7242,7 +7297,7 @@ var args = process.argv.slice(2);
|
|
|
7242
7297
|
var cmd = args[0] || "";
|
|
7243
7298
|
var subArgs = args.slice(1);
|
|
7244
7299
|
function printVersion() {
|
|
7245
|
-
console.log("1.5.
|
|
7300
|
+
console.log("1.5.6");
|
|
7246
7301
|
}
|
|
7247
7302
|
function printHelp() {
|
|
7248
7303
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|