@synkro-sh/cli 1.3.48 → 1.3.50
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 +216 -35
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -605,9 +605,7 @@ SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
|
605
605
|
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
|
|
606
606
|
|
|
607
607
|
USE_LOCAL=false
|
|
608
|
-
if
|
|
609
|
-
USE_LOCAL=true
|
|
610
|
-
elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
|
|
608
|
+
if command -v claude >/dev/null 2>&1; then
|
|
611
609
|
USE_LOCAL=true
|
|
612
610
|
fi
|
|
613
611
|
|
|
@@ -818,16 +816,22 @@ if [ "$USE_LOCAL" = "true" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
818
816
|
--arg severity "$SEVERITY" \\
|
|
819
817
|
--arg risk_level "\${RISK_LEVEL:-low}" \\
|
|
820
818
|
--arg category "$CATEGORY" \\
|
|
819
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
821
820
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
822
821
|
--arg tool_name "$TOOL_NAME" \\
|
|
823
822
|
--arg repo "\${GIT_REPO:-}" \\
|
|
824
823
|
--arg session_id "$SESSION_ID" \\
|
|
824
|
+
--arg tool_use_id "\${TOOL_USE_ID:-}" \\
|
|
825
|
+
--arg cwd "\${CWD:-}" \\
|
|
825
826
|
--arg mech_cat "$MECH_CAT" \\
|
|
826
827
|
--arg biz_cat "$BIZ_CAT" \\
|
|
827
828
|
--arg capture_depth "$SYNKRO_CAPTURE_DEPTH" \\
|
|
828
829
|
--arg command "$COMMAND" \\
|
|
829
830
|
--arg reasoning "$REASONING" \\
|
|
830
831
|
--arg alternative "$ALTERNATIVE" \\
|
|
832
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
833
|
+
--argjson rules_checked "\${ORG_RULES:-[]}" \\
|
|
834
|
+
--argjson recent_user_messages "\${RECENT_USER_MESSAGES:-[]}" \\
|
|
831
835
|
'{
|
|
832
836
|
event_id: $event_id,
|
|
833
837
|
timestamp: $timestamp,
|
|
@@ -838,12 +842,17 @@ if [ "$USE_LOCAL" = "true" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
838
842
|
category: $category,
|
|
839
843
|
model: $model,
|
|
840
844
|
tool_name: $tool_name,
|
|
841
|
-
capture_depth: $capture_depth
|
|
845
|
+
capture_depth: $capture_depth,
|
|
846
|
+
rules_checked: $rules_checked,
|
|
847
|
+
cc_model: (if ($cc_model | length) > 0 then $cc_model else null end),
|
|
848
|
+
cc_usage: $cc_usage
|
|
842
849
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
843
850
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
851
|
+
+ (if $tool_use_id != "" then {tool_use_id: $tool_use_id} else {} end)
|
|
852
|
+
+ (if $cwd != "" then {cwd: $cwd} else {} end)
|
|
844
853
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
845
854
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
846
|
-
+ (if $capture_depth != "local_only" then {command: $command, reasoning: $reasoning} + (if $alternative != "" then {alternative: $alternative} else {} end) else {} end)')
|
|
855
|
+
+ (if $capture_depth != "local_only" then {command: $command, reasoning: $reasoning, recent_user_messages: $recent_user_messages} + (if $alternative != "" then {alternative: $alternative} else {} end) else {} end)')
|
|
847
856
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
848
857
|
-H "Content-Type: application/json" \\
|
|
849
858
|
-H "Authorization: Bearer $JWT" \\
|
|
@@ -898,6 +907,21 @@ SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
|
|
|
898
907
|
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
899
908
|
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
900
909
|
TRANSCRIPT_PATH=$(echo "$PAYLOAD" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
910
|
+
|
|
911
|
+
CC_MODEL=""
|
|
912
|
+
CC_USAGE="{}"
|
|
913
|
+
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
914
|
+
_LAST_ASSISTANT=$(tail -50 "$TRANSCRIPT_PATH" | jq -c 'select(.type == "assistant")' 2>/dev/null | tail -1)
|
|
915
|
+
if [ -n "$_LAST_ASSISTANT" ]; then
|
|
916
|
+
CC_MODEL=$(echo "$_LAST_ASSISTANT" | jq -r '.message.model // empty' 2>/dev/null)
|
|
917
|
+
CC_USAGE=$(echo "$_LAST_ASSISTANT" | jq -c '{
|
|
918
|
+
input_tokens: .message.usage.input_tokens,
|
|
919
|
+
output_tokens: .message.usage.output_tokens,
|
|
920
|
+
cache_creation_input_tokens: .message.usage.cache_creation_input_tokens,
|
|
921
|
+
cache_read_input_tokens: .message.usage.cache_read_input_tokens
|
|
922
|
+
}' 2>/dev/null || echo "{}")
|
|
923
|
+
fi
|
|
924
|
+
fi
|
|
901
925
|
# Detect git remote origin \u2192 repo identity (e.g. "owner/repo")
|
|
902
926
|
GIT_REPO=""
|
|
903
927
|
if command -v git >/dev/null 2>&1; then
|
|
@@ -1107,9 +1131,7 @@ SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
|
1107
1131
|
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
|
|
1108
1132
|
|
|
1109
1133
|
USE_LOCAL=false
|
|
1110
|
-
if
|
|
1111
|
-
USE_LOCAL=true
|
|
1112
|
-
elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
|
|
1134
|
+
if command -v claude >/dev/null 2>&1; then
|
|
1113
1135
|
USE_LOCAL=true
|
|
1114
1136
|
fi
|
|
1115
1137
|
|
|
@@ -1334,11 +1356,13 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
|
|
|
1334
1356
|
--arg severity "$LOCAL_SEVERITY" \\
|
|
1335
1357
|
--arg category "$LOCAL_CATEGORY" \\
|
|
1336
1358
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
1359
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
1337
1360
|
--arg tool_name "$TOOL_NAME" \\
|
|
1338
1361
|
--arg repo "\${GIT_REPO:-}" \\
|
|
1339
1362
|
--arg session_id "$SESSION_ID" \\
|
|
1340
1363
|
--arg mech_cat "$MECH_CAT" \\
|
|
1341
1364
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1365
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
1342
1366
|
'{
|
|
1343
1367
|
event_id: $event_id,
|
|
1344
1368
|
timestamp: $timestamp,
|
|
@@ -1347,9 +1371,11 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
|
|
|
1347
1371
|
severity: $severity,
|
|
1348
1372
|
category: $category,
|
|
1349
1373
|
model: $model,
|
|
1350
|
-
tool_name: $tool_name
|
|
1374
|
+
tool_name: $tool_name,
|
|
1375
|
+
cc_usage: $cc_usage
|
|
1351
1376
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1352
1377
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1378
|
+
+ (if $cc_model != "" then {cc_model: $cc_model} else {} end)
|
|
1353
1379
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1354
1380
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)')
|
|
1355
1381
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
@@ -1406,6 +1432,22 @@ TOOL_INPUT=$(echo "$PAYLOAD" | jq -c '.tool_input // {}' 2>/dev/null)
|
|
|
1406
1432
|
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
|
|
1407
1433
|
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
1408
1434
|
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
1435
|
+
TRANSCRIPT_PATH=$(echo "$PAYLOAD" | jq -r '.transcript_path // empty' 2>/dev/null)
|
|
1436
|
+
|
|
1437
|
+
CC_MODEL=""
|
|
1438
|
+
CC_USAGE="{}"
|
|
1439
|
+
if [ -n "$TRANSCRIPT_PATH" ] && [ -f "$TRANSCRIPT_PATH" ]; then
|
|
1440
|
+
_LAST_ASSISTANT=$(tail -50 "$TRANSCRIPT_PATH" | jq -c 'select(.type == "assistant")' 2>/dev/null | tail -1)
|
|
1441
|
+
if [ -n "$_LAST_ASSISTANT" ]; then
|
|
1442
|
+
CC_MODEL=$(echo "$_LAST_ASSISTANT" | jq -r '.message.model // empty' 2>/dev/null)
|
|
1443
|
+
CC_USAGE=$(echo "$_LAST_ASSISTANT" | jq -c '{
|
|
1444
|
+
input_tokens: .message.usage.input_tokens,
|
|
1445
|
+
output_tokens: .message.usage.output_tokens,
|
|
1446
|
+
cache_creation_input_tokens: .message.usage.cache_creation_input_tokens,
|
|
1447
|
+
cache_read_input_tokens: .message.usage.cache_read_input_tokens
|
|
1448
|
+
}' 2>/dev/null || echo "{}")
|
|
1449
|
+
fi
|
|
1450
|
+
fi
|
|
1409
1451
|
# Detect git remote origin \u2192 repo identity (e.g. "owner/repo")
|
|
1410
1452
|
GIT_REPO=""
|
|
1411
1453
|
if command -v git >/dev/null 2>&1; then
|
|
@@ -1436,10 +1478,32 @@ if [ -z "$DIFF_FIELD" ] || [ "$DIFF_FIELD" = "null" ] || [ "$DIFF_FIELD" = "{}"
|
|
|
1436
1478
|
DIFF_FIELD="null"
|
|
1437
1479
|
fi
|
|
1438
1480
|
|
|
1481
|
+
# Resolve dependency versions + CVE config from nearest package.json / .synkro.json
|
|
1482
|
+
DEPS_JSON="{}"
|
|
1483
|
+
CVE_ALLOWLIST="[]"
|
|
1484
|
+
CVE_MIN_SEVERITY="null"
|
|
1485
|
+
_PKG_DIR=$(dirname "$FILE_PATH")
|
|
1486
|
+
while [ "$_PKG_DIR" != "/" ]; do
|
|
1487
|
+
if [ -f "$_PKG_DIR/package.json" ]; then
|
|
1488
|
+
DEPS_JSON=$(jq -s '.[0] * .[1]' \\
|
|
1489
|
+
<(jq '.dependencies // {}' "$_PKG_DIR/package.json" 2>/dev/null || echo "{}") \\
|
|
1490
|
+
<(jq '.devDependencies // {}' "$_PKG_DIR/package.json" 2>/dev/null || echo "{}") 2>/dev/null || echo "{}")
|
|
1491
|
+
if [ -f "$_PKG_DIR/.synkro.json" ]; then
|
|
1492
|
+
CVE_ALLOWLIST=$(jq '.cve_allowlist // []' "$_PKG_DIR/.synkro.json" 2>/dev/null || echo "[]")
|
|
1493
|
+
CVE_MIN_SEVERITY=$(jq '.cve_min_severity // null' "$_PKG_DIR/.synkro.json" 2>/dev/null || echo "null")
|
|
1494
|
+
fi
|
|
1495
|
+
break
|
|
1496
|
+
fi
|
|
1497
|
+
_PKG_DIR=$(dirname "$_PKG_DIR")
|
|
1498
|
+
done
|
|
1499
|
+
|
|
1439
1500
|
BODY=$(jq -n \\
|
|
1440
1501
|
--arg file_path "$FILE_PATH" \\
|
|
1441
1502
|
--arg content "$FILE_CONTENT" \\
|
|
1442
1503
|
--argjson diff "$DIFF_FIELD" \\
|
|
1504
|
+
--argjson deps "$DEPS_JSON" \\
|
|
1505
|
+
--argjson cve_allowlist "$CVE_ALLOWLIST" \\
|
|
1506
|
+
--argjson cve_min_severity "$CVE_MIN_SEVERITY" \\
|
|
1443
1507
|
--arg session_id "$SESSION_ID" \\
|
|
1444
1508
|
--arg tool_use_id "$TOOL_USE_ID" \\
|
|
1445
1509
|
--arg cwd "$CWD" \\
|
|
@@ -1448,6 +1512,9 @@ BODY=$(jq -n \\
|
|
|
1448
1512
|
file_path: $file_path,
|
|
1449
1513
|
content: $content,
|
|
1450
1514
|
diff: $diff,
|
|
1515
|
+
dependencies: $deps,
|
|
1516
|
+
cve_allowlist: $cve_allowlist,
|
|
1517
|
+
cve_min_severity: $cve_min_severity,
|
|
1451
1518
|
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
1452
1519
|
tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),
|
|
1453
1520
|
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
@@ -1540,9 +1607,7 @@ SYNKRO_INFERENCE_TIER="\${SYNKRO_INFERENCE_TIER:-fast}"
|
|
|
1540
1607
|
SYNKRO_CAPTURE_DEPTH="\${SYNKRO_CAPTURE_DEPTH:-full}"
|
|
1541
1608
|
|
|
1542
1609
|
USE_LOCAL=false
|
|
1543
|
-
if
|
|
1544
|
-
USE_LOCAL=true
|
|
1545
|
-
elif [ "$SYNKRO_INFERENCE_TIER" = "free" ] && command -v claude >/dev/null 2>&1; then
|
|
1610
|
+
if command -v claude >/dev/null 2>&1; then
|
|
1546
1611
|
USE_LOCAL=true
|
|
1547
1612
|
fi
|
|
1548
1613
|
|
|
@@ -1550,28 +1615,43 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1550
1615
|
# \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (mode=edit). \u2500\u2500\u2500
|
|
1551
1616
|
|
|
1552
1617
|
RULES_CACHE="$HOME/.synkro/.rules-cache-edit-capture"
|
|
1618
|
+
RULES_RESP=""
|
|
1553
1619
|
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
1554
|
-
|
|
1555
|
-
-H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null
|
|
1556
|
-
| jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
|
|
1557
|
-
if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
|
|
1558
|
-
printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
|
|
1559
|
-
elif [ -f "$RULES_CACHE" ]; then
|
|
1560
|
-
ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
|
|
1561
|
-
fi
|
|
1620
|
+
RULES_RESP=$(curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules" \\
|
|
1621
|
+
-H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null || echo "")
|
|
1562
1622
|
else
|
|
1563
|
-
|
|
1623
|
+
RULES_RESP=$(printf '%s' "$FILE_CONTENT" | head -c 8000 \\
|
|
1564
1624
|
| jq -Rs '{content: .}' \\
|
|
1565
1625
|
| curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
|
|
1566
1626
|
-X POST -H "Content-Type: application/json" \\
|
|
1567
1627
|
-H "Authorization: Bearer $JWT" \\
|
|
1568
|
-
-d @- --max-time 2 2>/dev/null
|
|
1569
|
-
|
|
1628
|
+
-d @- --max-time 2 2>/dev/null || echo "")
|
|
1629
|
+
fi
|
|
1630
|
+
ORG_RULES=$(echo "$RULES_RESP" | jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
|
|
1631
|
+
if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
|
|
1632
|
+
printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
|
|
1633
|
+
elif [ -f "$RULES_CACHE" ]; then
|
|
1634
|
+
ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
|
|
1570
1635
|
fi
|
|
1571
1636
|
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
1572
1637
|
|
|
1638
|
+
# Extract CVE config from rules response (allowlist + min_severity)
|
|
1639
|
+
CVE_ALLOWLIST=$(echo "$RULES_RESP" | jq -c '.cve_config.allowlist // []' 2>/dev/null || echo "[]")
|
|
1640
|
+
CVE_MIN_SEVERITY=$(echo "$RULES_RESP" | jq '.cve_config.min_severity // null' 2>/dev/null || echo "null")
|
|
1641
|
+
|
|
1642
|
+
# CVE scan \u2014 runs server-side in parallel with local LLM grading
|
|
1643
|
+
CVE_RESULT_FILE=$(mktemp -t synkro-cve.XXXXXX)
|
|
1644
|
+
(
|
|
1645
|
+
CVE_BODY=$(jq -n --arg fp "$FILE_PATH" --arg c "$FILE_CONTENT" --argjson deps "$DEPS_JSON" --argjson al "$CVE_ALLOWLIST" --argjson ms "$CVE_MIN_SEVERITY" '{file_path: $fp, content: $c, dependencies: $deps, cve_allowlist: $al, cve_min_severity: $ms}')
|
|
1646
|
+
curl -sS -X POST "\${GATEWAY_URL}/api/v1/cve-scan" \\
|
|
1647
|
+
-H "Content-Type: application/json" \\
|
|
1648
|
+
-H "Authorization: Bearer $JWT" \\
|
|
1649
|
+
-d "$CVE_BODY" --max-time 4 2>/dev/null > "$CVE_RESULT_FILE"
|
|
1650
|
+
) &
|
|
1651
|
+
CVE_PID=$!
|
|
1652
|
+
|
|
1573
1653
|
GRADER_PROMPT_FILE=$(mktemp -t synkro-edit-capture.XXXXXX)
|
|
1574
|
-
trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
|
|
1654
|
+
trap "rm -f \\"$GRADER_PROMPT_FILE\\" \\"$CVE_RESULT_FILE\\"" EXIT
|
|
1575
1655
|
printf 'File: %s\\n' "$FILE_PATH" > "$GRADER_PROMPT_FILE"
|
|
1576
1656
|
printf 'Org rules: %s\\n\\n' "$ORG_RULES" >> "$GRADER_PROMPT_FILE"
|
|
1577
1657
|
printf 'Content:\\n' >> "$GRADER_PROMPT_FILE"
|
|
@@ -1582,6 +1662,14 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1582
1662
|
else
|
|
1583
1663
|
CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
|
|
1584
1664
|
fi
|
|
1665
|
+
|
|
1666
|
+
# Wait for CVE scan
|
|
1667
|
+
wait $CVE_PID 2>/dev/null
|
|
1668
|
+
CVE_TEXT=""
|
|
1669
|
+
if [ -s "$CVE_RESULT_FILE" ]; then
|
|
1670
|
+
CVE_TEXT=$(jq -r '.summary // empty' "$CVE_RESULT_FILE" 2>/dev/null || echo "")
|
|
1671
|
+
fi
|
|
1672
|
+
|
|
1585
1673
|
# Wrapper extraction (greedy \u2014 tolerates nested XML tags).
|
|
1586
1674
|
V_INNER=$(printf '%s' "$CC_RESP" | tr '\\n' ' ' | sed -nE 's|.*<synkro-verdict>(.*)</synkro-verdict>.*|\\1|p' | tail -1)
|
|
1587
1675
|
if [ -n "$V_INNER" ]; then
|
|
@@ -1596,6 +1684,17 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1596
1684
|
LOCAL_SEV=$(printf '%s' "$FIRST_V" | sed -nE 's|.*<severity>(.*)</severity>.*|\\1|p' | head -1)
|
|
1597
1685
|
LOCAL_CAT=$(printf '%s' "$FIRST_V" | sed -nE 's|.*<category>(.*)</category>.*|\\1|p' | head -1)
|
|
1598
1686
|
LOCAL_REASON=$(printf '%s' "$FIRST_V" | sed -nE 's|.*<reason>(.*)</reason>.*|\\1|p' | head -1)
|
|
1687
|
+
# Merge CVE findings
|
|
1688
|
+
if [ -n "$CVE_TEXT" ]; then
|
|
1689
|
+
if [ "$LOCAL_OK" = "false" ]; then
|
|
1690
|
+
LOCAL_REASON="\${LOCAL_REASON}. Vulnerable dependencies: \${CVE_TEXT}"
|
|
1691
|
+
else
|
|
1692
|
+
LOCAL_OK="false"
|
|
1693
|
+
LOCAL_SEV="block"
|
|
1694
|
+
LOCAL_CAT="vulnerable_dependency"
|
|
1695
|
+
LOCAL_REASON="Vulnerable dependencies detected: \${CVE_TEXT}"
|
|
1696
|
+
fi
|
|
1697
|
+
fi
|
|
1599
1698
|
# Convert to JSON shape downstream code expects.
|
|
1600
1699
|
RESP=$(jq -n \\
|
|
1601
1700
|
--arg ok "$LOCAL_OK" \\
|
|
@@ -1610,6 +1709,10 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1610
1709
|
end')
|
|
1611
1710
|
else
|
|
1612
1711
|
RESP=""
|
|
1712
|
+
if [ -n "$CVE_TEXT" ]; then
|
|
1713
|
+
RESP=$(jq -n --arg reason "Vulnerable dependencies detected: $CVE_TEXT" \\
|
|
1714
|
+
'{ok: false, severity: "block", category: "vulnerable_dependency", reason: $reason}')
|
|
1715
|
+
fi
|
|
1613
1716
|
fi
|
|
1614
1717
|
else
|
|
1615
1718
|
# \u2500\u2500\u2500 Server-side grading. \u2500\u2500\u2500
|
|
@@ -1677,17 +1780,21 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1677
1780
|
--arg risk_level "$LOCAL_RISK" \\
|
|
1678
1781
|
--arg category "$CATEGORY" \\
|
|
1679
1782
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
1783
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
1680
1784
|
--arg tool_name "$TOOL_NAME" \\
|
|
1681
1785
|
--arg repo "\${GIT_REPO:-}" \\
|
|
1682
1786
|
--arg session_id "$SESSION_ID" \\
|
|
1683
1787
|
--arg mech_cat "$MECH_CAT" \\
|
|
1684
1788
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1789
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
1685
1790
|
'{
|
|
1686
1791
|
event_id: $event_id, timestamp: $timestamp, hook_type: $hook_type,
|
|
1687
1792
|
verdict: $verdict, severity: $severity, risk_level: $risk_level,
|
|
1688
|
-
category: $category, model: $model, tool_name: $tool_name
|
|
1793
|
+
category: $category, model: $model, tool_name: $tool_name,
|
|
1794
|
+
cc_usage: $cc_usage
|
|
1689
1795
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1690
1796
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1797
|
+
+ (if $cc_model != "" then {cc_model: $cc_model} else {} end)
|
|
1691
1798
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1692
1799
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)')
|
|
1693
1800
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
@@ -3860,7 +3967,7 @@ function writeConfigEnv(opts) {
|
|
|
3860
3967
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3861
3968
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3862
3969
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3863
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3970
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.50")}`
|
|
3864
3971
|
];
|
|
3865
3972
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3866
3973
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -4791,6 +4898,8 @@ __export(scanPr_exports, {
|
|
|
4791
4898
|
scanPrCommand: () => scanPrCommand
|
|
4792
4899
|
});
|
|
4793
4900
|
import { execSync as execSync5, spawn } from "child_process";
|
|
4901
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
4902
|
+
import { join as join9 } from "path";
|
|
4794
4903
|
function parseMatchSpec(condition) {
|
|
4795
4904
|
if (!condition.startsWith("match_spec:")) return null;
|
|
4796
4905
|
try {
|
|
@@ -5268,6 +5377,70 @@ function shouldFail(findings, threshold) {
|
|
|
5268
5377
|
const thresholdIdx = order.indexOf(threshold);
|
|
5269
5378
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
5270
5379
|
}
|
|
5380
|
+
function readRepoDeps() {
|
|
5381
|
+
const pkgPath = join9(process.cwd(), "package.json");
|
|
5382
|
+
if (!existsSync10(pkgPath)) return {};
|
|
5383
|
+
try {
|
|
5384
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
5385
|
+
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5386
|
+
} catch {
|
|
5387
|
+
return {};
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
function getFullFileContent(filename) {
|
|
5391
|
+
try {
|
|
5392
|
+
return execSync5(`git show HEAD:${filename}`, { encoding: "utf-8", maxBuffer: 128 * 1024 });
|
|
5393
|
+
} catch {
|
|
5394
|
+
return null;
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
async function scanCves(files, gatewayUrl, apiKey) {
|
|
5398
|
+
const deps = readRepoDeps();
|
|
5399
|
+
if (Object.keys(deps).length === 0) return [];
|
|
5400
|
+
const findings = [];
|
|
5401
|
+
for (const file of files) {
|
|
5402
|
+
const content = getFullFileContent(file.filename);
|
|
5403
|
+
if (!content) continue;
|
|
5404
|
+
try {
|
|
5405
|
+
const resp = await fetch(`${gatewayUrl.replace(/\/$/, "")}/api/v1/cve-scan`, {
|
|
5406
|
+
method: "POST",
|
|
5407
|
+
headers: {
|
|
5408
|
+
"Content-Type": "application/json",
|
|
5409
|
+
"x-synkro-api-key": apiKey
|
|
5410
|
+
},
|
|
5411
|
+
body: JSON.stringify({
|
|
5412
|
+
file_path: file.filename,
|
|
5413
|
+
content,
|
|
5414
|
+
dependencies: deps
|
|
5415
|
+
}),
|
|
5416
|
+
signal: AbortSignal.timeout(8e3)
|
|
5417
|
+
});
|
|
5418
|
+
if (!resp.ok) continue;
|
|
5419
|
+
const data = await resp.json();
|
|
5420
|
+
if (!data.findings?.length) continue;
|
|
5421
|
+
for (const pkg of data.packages ?? []) {
|
|
5422
|
+
const maxSev = data.findings.filter((f) => f.package === pkg.package).reduce((max, f) => {
|
|
5423
|
+
const n = parseFloat(f.severity);
|
|
5424
|
+
return !isNaN(n) && n > max ? n : max;
|
|
5425
|
+
}, 0);
|
|
5426
|
+
const severity = maxSev >= 9 ? "critical" : maxSev >= 7 ? "high" : maxSev >= 4 ? "medium" : "low";
|
|
5427
|
+
const topIds = pkg.ids.slice(0, 3).join(", ");
|
|
5428
|
+
const extra = pkg.ids.length > 3 ? ` +${pkg.ids.length - 3} more` : "";
|
|
5429
|
+
findings.push({
|
|
5430
|
+
file: file.filename,
|
|
5431
|
+
line: 1,
|
|
5432
|
+
severity,
|
|
5433
|
+
category: "vulnerable_dependency",
|
|
5434
|
+
description: `${pkg.package} has ${pkg.count} known CVEs (${topIds}${extra}).`,
|
|
5435
|
+
fix: data.findings.find((f) => f.package === pkg.package && f.fixed) ? `Upgrade ${pkg.package} to ${data.findings.find((f) => f.package === pkg.package && f.fixed).fixed}` : `Check https://osv.dev/list?q=${pkg.package} for fix versions.`
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
} catch {
|
|
5439
|
+
continue;
|
|
5440
|
+
}
|
|
5441
|
+
}
|
|
5442
|
+
return findings;
|
|
5443
|
+
}
|
|
5271
5444
|
async function postEventToBackend(opts) {
|
|
5272
5445
|
try {
|
|
5273
5446
|
await fetch(`${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/events/pr-scan`, {
|
|
@@ -5375,6 +5548,10 @@ async function scanPrCommand() {
|
|
|
5375
5548
|
return;
|
|
5376
5549
|
}
|
|
5377
5550
|
const t0 = Date.now();
|
|
5551
|
+
const cvePromise = scanCves(eligible, gatewayUrl, synkroApiKey).catch((err) => {
|
|
5552
|
+
console.warn("CVE scan failed (non-fatal):", err.message);
|
|
5553
|
+
return [];
|
|
5554
|
+
});
|
|
5378
5555
|
const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file, idx, total) => {
|
|
5379
5556
|
process.stdout.write(`[${idx + 1}/${total}] ${file.filename}...`);
|
|
5380
5557
|
const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);
|
|
@@ -5383,8 +5560,12 @@ async function scanPrCommand() {
|
|
|
5383
5560
|
console.log(` ${merged.length === 0 ? "clean" : `${merged.length} finding(s)`} (${(llmResult.latencyMs / 1e3).toFixed(1)}s)`);
|
|
5384
5561
|
return { findings: merged, latencyMs: llmResult.latencyMs };
|
|
5385
5562
|
});
|
|
5563
|
+
const cveFindings = await cvePromise;
|
|
5564
|
+
if (cveFindings.length > 0) {
|
|
5565
|
+
console.log(`CVE scan: ${cveFindings.length} vulnerable dependency finding(s).`);
|
|
5566
|
+
}
|
|
5386
5567
|
const totalLatencyMs = Date.now() - t0;
|
|
5387
|
-
const allFindings = results.flatMap((r) => r.findings);
|
|
5568
|
+
const allFindings = [...results.flatMap((r) => r.findings), ...cveFindings];
|
|
5388
5569
|
console.log(`
|
|
5389
5570
|
Total: ${allFindings.length} finding(s) across ${eligible.length} file(s) in ${totalLatencyMs}ms
|
|
5390
5571
|
`);
|
|
@@ -5462,9 +5643,9 @@ var disconnect_exports = {};
|
|
|
5462
5643
|
__export(disconnect_exports, {
|
|
5463
5644
|
disconnectCommand: () => disconnectCommand
|
|
5464
5645
|
});
|
|
5465
|
-
import { existsSync as
|
|
5646
|
+
import { existsSync as existsSync11, rmSync } from "fs";
|
|
5466
5647
|
import { homedir as homedir8 } from "os";
|
|
5467
|
-
import { join as
|
|
5648
|
+
import { join as join10 } from "path";
|
|
5468
5649
|
function disconnectCommand(args2 = []) {
|
|
5469
5650
|
const purge = args2.includes("--purge");
|
|
5470
5651
|
console.log("Synkro disconnect starting...\n");
|
|
@@ -5482,13 +5663,13 @@ function disconnectCommand(args2 = []) {
|
|
|
5482
5663
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
5483
5664
|
}
|
|
5484
5665
|
if (purge) {
|
|
5485
|
-
if (
|
|
5666
|
+
if (existsSync11(SYNKRO_DIR5)) {
|
|
5486
5667
|
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
5487
5668
|
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
5488
5669
|
} else {
|
|
5489
5670
|
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
5490
5671
|
}
|
|
5491
|
-
} else if (
|
|
5672
|
+
} else if (existsSync11(SYNKRO_DIR5)) {
|
|
5492
5673
|
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
5493
5674
|
}
|
|
5494
5675
|
console.log("\nSynkro disconnected.");
|
|
@@ -5500,7 +5681,7 @@ var init_disconnect = __esm({
|
|
|
5500
5681
|
init_agentDetect();
|
|
5501
5682
|
init_ccHookConfig();
|
|
5502
5683
|
init_mcpConfig();
|
|
5503
|
-
SYNKRO_DIR5 =
|
|
5684
|
+
SYNKRO_DIR5 = join10(homedir8(), ".synkro");
|
|
5504
5685
|
}
|
|
5505
5686
|
});
|
|
5506
5687
|
|
|
@@ -5542,15 +5723,15 @@ var init_reinstall = __esm({
|
|
|
5542
5723
|
});
|
|
5543
5724
|
|
|
5544
5725
|
// cli/bootstrap.js
|
|
5545
|
-
import { readFileSync as
|
|
5726
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
|
|
5546
5727
|
import { resolve } from "path";
|
|
5547
5728
|
var envCandidates = [
|
|
5548
5729
|
resolve(process.cwd(), ".env"),
|
|
5549
5730
|
resolve(process.env.HOME ?? "", ".synkro", "config.env")
|
|
5550
5731
|
];
|
|
5551
5732
|
for (const envPath of envCandidates) {
|
|
5552
|
-
if (!
|
|
5553
|
-
const envContent =
|
|
5733
|
+
if (!existsSync12(envPath)) continue;
|
|
5734
|
+
const envContent = readFileSync9(envPath, "utf-8");
|
|
5554
5735
|
for (const line of envContent.split("\n")) {
|
|
5555
5736
|
const trimmed = line.trim();
|
|
5556
5737
|
if (!trimmed || trimmed.startsWith("#")) continue;
|