@synkro-sh/cli 1.5.5 → 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 -32
- 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,50 +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
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
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
|
+
];
|
|
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
|
|
2872
2878
|
const SAFE_VERBS = new Set([
|
|
2873
2879
|
'cat','head','tail','less','more','grep','egrep','fgrep','rg','ag',
|
|
2874
2880
|
'find','fd','ls','wc','cmp','diff','file','stat','which','whereis','type',
|
|
2875
|
-
'pwd','whoami','id','date','echo','printf','
|
|
2876
|
-
'jq','yq','
|
|
2881
|
+
'pwd','whoami','id','date','echo','printf','true','false',
|
|
2882
|
+
'jq','yq','sort','uniq','cut','tr','xxd','hexdump','od','column',
|
|
2877
2883
|
'node','npm','pnpm','yarn','bun','python','python3','ruby','go','rustc','cargo',
|
|
2878
|
-
'git',
|
|
2884
|
+
'git',
|
|
2879
2885
|
]);
|
|
2880
|
-
const tokens =
|
|
2886
|
+
const tokens = seg.trim().split(' ').filter(t => t.length > 0);
|
|
2881
2887
|
const verb = tokens[0] || '';
|
|
2882
2888
|
if (!SAFE_VERBS.has(verb)) return false;
|
|
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.
|
|
2883
2902
|
if (verb === 'git') {
|
|
2884
|
-
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
|
+
]);
|
|
2885
2908
|
const sub = tokens[1] || '';
|
|
2886
|
-
|
|
2887
|
-
}
|
|
2888
|
-
if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {
|
|
2909
|
+
if (!SAFE_GIT.has(sub)) return false;
|
|
2910
|
+
} else if (['npm','pnpm','yarn','bun','cargo','go'].includes(verb)) {
|
|
2889
2911
|
const sub = tokens[1] || '';
|
|
2890
|
-
const SAFE_PKG = new Set([
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
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)) {
|
|
2894
2918
|
const sub = tokens[1] || '';
|
|
2895
|
-
|
|
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;
|
|
2896
2950
|
}
|
|
2897
|
-
// sed: only safe without -i (we filtered that above).
|
|
2898
2951
|
return true;
|
|
2899
2952
|
}
|
|
2900
2953
|
|
|
2901
|
-
if (isSafeInRepoRead(toolName, command)) {
|
|
2954
|
+
if (isSafeInRepoRead(toolName, command, cwd)) {
|
|
2902
2955
|
log('bashGuard ' + cmdShort + ' → instant allow (safe in-repo read)');
|
|
2903
2956
|
outputJson({ hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: 'Synkro: safe in-repo read, deterministic allow.' } });
|
|
2904
2957
|
return;
|
|
@@ -5854,7 +5907,7 @@ function writeConfigEnv(opts) {
|
|
|
5854
5907
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
5855
5908
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
5856
5909
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
5857
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.5.
|
|
5910
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.5.6")}`
|
|
5858
5911
|
];
|
|
5859
5912
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
5860
5913
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -7244,7 +7297,7 @@ var args = process.argv.slice(2);
|
|
|
7244
7297
|
var cmd = args[0] || "";
|
|
7245
7298
|
var subArgs = args.slice(1);
|
|
7246
7299
|
function printVersion() {
|
|
7247
|
-
console.log("1.5.
|
|
7300
|
+
console.log("1.5.6");
|
|
7248
7301
|
}
|
|
7249
7302
|
function printHelp() {
|
|
7250
7303
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|