@synkro-sh/cli 1.6.99 → 1.7.0
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 +55 -15
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -1050,16 +1050,18 @@ export function log(msg: string): void {
|
|
|
1050
1050
|
// \u2500\u2500\u2500 JWT Management \u2500\u2500\u2500
|
|
1051
1051
|
|
|
1052
1052
|
export function loadJwt(): string | null {
|
|
1053
|
-
//
|
|
1054
|
-
// accepted
|
|
1055
|
-
// cli/*), so
|
|
1056
|
-
//
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1053
|
+
// Prefer the long-lived (1yr) Synkro MCP token in BOTH cloud and local mode.
|
|
1054
|
+
// It's org+user scoped and accepted across the hook surface (grade/submit,
|
|
1055
|
+
// cwe-rules, hook/*, cli/*), so a single 'synkro login' keeps grading + CWE
|
|
1056
|
+
// detection alive for a year with NO refresh. The alternative \u2014 the ~5-min
|
|
1057
|
+
// WorkOS access_token \u2014 rides a rotating refresh chain that dies with the
|
|
1058
|
+
// WorkOS session (invalid_grant "session has already ended"), which silently
|
|
1059
|
+
// disabled CWE findings while editGuard/cveScan (which don't need this token)
|
|
1060
|
+
// kept working. jwtIsExpired sees a year of runway, so we never hit refreshJwt.
|
|
1061
|
+
try {
|
|
1062
|
+
const mcp = readFileSync(MCP_JWT_PATH, 'utf-8').trim();
|
|
1063
|
+
if (mcp) return mcp;
|
|
1064
|
+
} catch { /* no mcp token on disk \u2014 fall back to the login token below */ }
|
|
1063
1065
|
try {
|
|
1064
1066
|
if (!existsSync(CREDS_PATH)) return null;
|
|
1065
1067
|
const creds = JSON.parse(readFileSync(CREDS_PATH, 'utf-8'));
|
|
@@ -5126,7 +5128,9 @@ async function main() {
|
|
|
5126
5128
|
const findings = Array.isArray(cweResp?.findings) ? cweResp.findings : [];
|
|
5127
5129
|
if (cweResp?.action === 'deny' && findings.length > 0) {
|
|
5128
5130
|
const activeCweIds = findings
|
|
5129
|
-
|
|
5131
|
+
// CWE is a separate, always-ask grader — a detected weakness is ALWAYS
|
|
5132
|
+
// surfaced regardless of the rule's stored mode (fix/blocking/ask).
|
|
5133
|
+
// Only path exemptions suppress it (matches the local-route branch).
|
|
5130
5134
|
.map((f: any) => f.cwe)
|
|
5131
5135
|
.filter((id: string) => !exemptedCwes.has(id.toUpperCase()));
|
|
5132
5136
|
|
|
@@ -5297,6 +5301,40 @@ async function main() {
|
|
|
5297
5301
|
let anyFailed = false;
|
|
5298
5302
|
|
|
5299
5303
|
for (const gradeResp of gradeResponses) {
|
|
5304
|
+
// The CWE grader emits findings, but its wrapper VARIES per call:
|
|
5305
|
+
// {"findings":[...]} | <findings>[...]</findings> | a bare [...] array.
|
|
5306
|
+
// Extract findings from any of them. Without this, parseVerdict sees no
|
|
5307
|
+
// <synkro-verdict> wrapper and defaults to ok:true, silently dropping every
|
|
5308
|
+
// detected CWE (a format mismatch). The <synkro-verdict> XML path below is
|
|
5309
|
+
// the fallback for the legacy format.
|
|
5310
|
+
let jsonFindings: any[] | null = null;
|
|
5311
|
+
try {
|
|
5312
|
+
const tagM = gradeResp.match(/<findings>([\s\S]*?)<\/findings>/);
|
|
5313
|
+
const src = tagM ? tagM[1] : gradeResp;
|
|
5314
|
+
const objM = src.match(/\{[\s\S]*"findings"[\s\S]*\}/);
|
|
5315
|
+
const arrM = src.match(/\[[\s\S]*\]/);
|
|
5316
|
+
if (objM) { const p = JSON.parse(objM[0]); if (Array.isArray(p.findings)) jsonFindings = p.findings; }
|
|
5317
|
+
if (!jsonFindings && arrM) { const p = JSON.parse(arrM[0]); if (Array.isArray(p)) jsonFindings = p; }
|
|
5318
|
+
} catch { /* not parseable JSON — fall through to XML parsing */ }
|
|
5319
|
+
if (jsonFindings) {
|
|
5320
|
+
if (jsonFindings.length > 0) {
|
|
5321
|
+
anyFailed = true;
|
|
5322
|
+
for (const f of jsonFindings.slice(0, 8)) {
|
|
5323
|
+
const id = String(f.cwe || f.rule_id || '').trim().replace(/^cwe-/i, 'CWE-');
|
|
5324
|
+
// Validate the shape (CWE-<number>) before using it as an object key —
|
|
5325
|
+
// the grade response is model output, so reject anything that isn't a
|
|
5326
|
+
// real CWE id (also blocks __proto__/constructor key injection).
|
|
5327
|
+
if (!/^CWE-\d+$/.test(id)) continue;
|
|
5328
|
+
if (!cweIds.includes(id)) cweIds.push(id);
|
|
5329
|
+
if (f.detail && !fixes[id]) fixes[id] = String(f.detail);
|
|
5330
|
+
if (f.code_snippet && !snippets[id]) snippets[id] = String(f.code_snippet);
|
|
5331
|
+
if (!mergedReason && (f.summary || f.detail)) mergedReason = String(f.summary || f.detail);
|
|
5332
|
+
if (!mergedSeverity && f.severity) mergedSeverity = String(f.severity);
|
|
5333
|
+
if (!mergedCategory && f.category) mergedCategory = String(f.category);
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5336
|
+
continue;
|
|
5337
|
+
}
|
|
5300
5338
|
const v = parseVerdict(gradeResp);
|
|
5301
5339
|
if (!v.ok) {
|
|
5302
5340
|
anyFailed = true;
|
|
@@ -10668,6 +10706,7 @@ function captureClaudeSetupToken() {
|
|
|
10668
10706
|
const isMac = platform3() === "darwin";
|
|
10669
10707
|
const bin = "script";
|
|
10670
10708
|
const args2 = isMac ? ["-q", tmpFile, "claude", "setup-token"] : ["-qec", "claude setup-token", tmpFile];
|
|
10709
|
+
const OAUTH_HINT = 'The browser approval did not return a token. This usually means claude.ai rejected the OAuth request (its "Authorization failed \u2014 Unsupported media type" page), most often because a browser extension (Grammarly, ad/script blockers, AI-assistant toolbars) stripped the request headers, or you approved on the wrong Claude account. Fix: retry in a clean incognito window with extensions disabled, and approve on the Claude account you want the cloud workers to use.';
|
|
10671
10710
|
return new Promise((resolve4, reject) => {
|
|
10672
10711
|
const proc = nodeSpawn(bin, args2, {
|
|
10673
10712
|
stdio: "inherit",
|
|
@@ -10687,7 +10726,7 @@ function captureClaudeSetupToken() {
|
|
|
10687
10726
|
} catch {
|
|
10688
10727
|
}
|
|
10689
10728
|
if (code !== 0) {
|
|
10690
|
-
reject(new Error(`claude setup-token exited with code ${code}`));
|
|
10729
|
+
reject(new Error(`claude setup-token exited with code ${code}. ${OAUTH_HINT}`));
|
|
10691
10730
|
return;
|
|
10692
10731
|
}
|
|
10693
10732
|
const TOKEN_YELLOW = "2;255;193;7";
|
|
@@ -10731,7 +10770,8 @@ function captureClaudeSetupToken() {
|
|
|
10731
10770
|
i += 1;
|
|
10732
10771
|
}
|
|
10733
10772
|
if (!token) {
|
|
10734
|
-
|
|
10773
|
+
const reason = raw.length < 200 ? OAUTH_HINT : `is the terminal emitting color? (captured ${raw.length}b but no token-yellow run)`;
|
|
10774
|
+
reject(new Error(`Captured no setup token from claude setup-token output. ${reason}`));
|
|
10735
10775
|
return;
|
|
10736
10776
|
}
|
|
10737
10777
|
resolve4(token);
|
|
@@ -11410,7 +11450,7 @@ function writeConfigEnv(opts) {
|
|
|
11410
11450
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
11411
11451
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
11412
11452
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
11413
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.
|
|
11453
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.7.0")}`
|
|
11414
11454
|
];
|
|
11415
11455
|
if (safeSynkroBin) lines.push(`SYNKRO_CLI_BIN=${shellQuoteSingle(safeSynkroBin)}`);
|
|
11416
11456
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
@@ -15473,7 +15513,7 @@ var args = process.argv.slice(2);
|
|
|
15473
15513
|
var cmd = args[0] || "";
|
|
15474
15514
|
var subArgs = args.slice(1);
|
|
15475
15515
|
function printVersion() {
|
|
15476
|
-
console.log("1.
|
|
15516
|
+
console.log("1.7.0");
|
|
15477
15517
|
}
|
|
15478
15518
|
function printHelp2() {
|
|
15479
15519
|
console.log(`Synkro CLI \u2014 runtime safety for AI coding agents
|