@synkro-sh/cli 1.3.49 → 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 +209 -25
- package/dist/bootstrap.js.map +1 -1
- package/package.json +1 -1
package/dist/bootstrap.js
CHANGED
|
@@ -816,16 +816,20 @@ if [ "$USE_LOCAL" = "true" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
816
816
|
--arg severity "$SEVERITY" \\
|
|
817
817
|
--arg risk_level "\${RISK_LEVEL:-low}" \\
|
|
818
818
|
--arg category "$CATEGORY" \\
|
|
819
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
819
820
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
820
821
|
--arg tool_name "$TOOL_NAME" \\
|
|
821
822
|
--arg repo "\${GIT_REPO:-}" \\
|
|
822
823
|
--arg session_id "$SESSION_ID" \\
|
|
824
|
+
--arg tool_use_id "\${TOOL_USE_ID:-}" \\
|
|
825
|
+
--arg cwd "\${CWD:-}" \\
|
|
823
826
|
--arg mech_cat "$MECH_CAT" \\
|
|
824
827
|
--arg biz_cat "$BIZ_CAT" \\
|
|
825
828
|
--arg capture_depth "$SYNKRO_CAPTURE_DEPTH" \\
|
|
826
829
|
--arg command "$COMMAND" \\
|
|
827
830
|
--arg reasoning "$REASONING" \\
|
|
828
831
|
--arg alternative "$ALTERNATIVE" \\
|
|
832
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
829
833
|
--argjson rules_checked "\${ORG_RULES:-[]}" \\
|
|
830
834
|
--argjson recent_user_messages "\${RECENT_USER_MESSAGES:-[]}" \\
|
|
831
835
|
'{
|
|
@@ -839,9 +843,13 @@ if [ "$USE_LOCAL" = "true" ] && [ -n "$VERDICT_KIND" ]; then
|
|
|
839
843
|
model: $model,
|
|
840
844
|
tool_name: $tool_name,
|
|
841
845
|
capture_depth: $capture_depth,
|
|
842
|
-
rules_checked: $rules_checked
|
|
846
|
+
rules_checked: $rules_checked,
|
|
847
|
+
cc_model: (if ($cc_model | length) > 0 then $cc_model else null end),
|
|
848
|
+
cc_usage: $cc_usage
|
|
843
849
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
844
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)
|
|
845
853
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
846
854
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)
|
|
847
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)')
|
|
@@ -899,6 +907,21 @@ SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
|
|
|
899
907
|
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
900
908
|
CWD=$(echo "$PAYLOAD" | jq -r '.cwd // empty' 2>/dev/null)
|
|
901
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
|
|
902
925
|
# Detect git remote origin \u2192 repo identity (e.g. "owner/repo")
|
|
903
926
|
GIT_REPO=""
|
|
904
927
|
if command -v git >/dev/null 2>&1; then
|
|
@@ -1333,11 +1356,13 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
|
|
|
1333
1356
|
--arg severity "$LOCAL_SEVERITY" \\
|
|
1334
1357
|
--arg category "$LOCAL_CATEGORY" \\
|
|
1335
1358
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
1359
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
1336
1360
|
--arg tool_name "$TOOL_NAME" \\
|
|
1337
1361
|
--arg repo "\${GIT_REPO:-}" \\
|
|
1338
1362
|
--arg session_id "$SESSION_ID" \\
|
|
1339
1363
|
--arg mech_cat "$MECH_CAT" \\
|
|
1340
1364
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1365
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
1341
1366
|
'{
|
|
1342
1367
|
event_id: $event_id,
|
|
1343
1368
|
timestamp: $timestamp,
|
|
@@ -1346,9 +1371,11 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ] && [ -n "$DECISION" ]; then
|
|
|
1346
1371
|
severity: $severity,
|
|
1347
1372
|
category: $category,
|
|
1348
1373
|
model: $model,
|
|
1349
|
-
tool_name: $tool_name
|
|
1374
|
+
tool_name: $tool_name,
|
|
1375
|
+
cc_usage: $cc_usage
|
|
1350
1376
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1351
1377
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1378
|
+
+ (if $cc_model != "" then {cc_model: $cc_model} else {} end)
|
|
1352
1379
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1353
1380
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)')
|
|
1354
1381
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
@@ -1405,6 +1432,22 @@ TOOL_INPUT=$(echo "$PAYLOAD" | jq -c '.tool_input // {}' 2>/dev/null)
|
|
|
1405
1432
|
SESSION_ID=$(echo "$PAYLOAD" | jq -r '.session_id // empty' 2>/dev/null)
|
|
1406
1433
|
TOOL_USE_ID=$(echo "$PAYLOAD" | jq -r '.tool_use_id // empty' 2>/dev/null)
|
|
1407
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
|
|
1408
1451
|
# Detect git remote origin \u2192 repo identity (e.g. "owner/repo")
|
|
1409
1452
|
GIT_REPO=""
|
|
1410
1453
|
if command -v git >/dev/null 2>&1; then
|
|
@@ -1435,10 +1478,32 @@ if [ -z "$DIFF_FIELD" ] || [ "$DIFF_FIELD" = "null" ] || [ "$DIFF_FIELD" = "{}"
|
|
|
1435
1478
|
DIFF_FIELD="null"
|
|
1436
1479
|
fi
|
|
1437
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
|
+
|
|
1438
1500
|
BODY=$(jq -n \\
|
|
1439
1501
|
--arg file_path "$FILE_PATH" \\
|
|
1440
1502
|
--arg content "$FILE_CONTENT" \\
|
|
1441
1503
|
--argjson diff "$DIFF_FIELD" \\
|
|
1504
|
+
--argjson deps "$DEPS_JSON" \\
|
|
1505
|
+
--argjson cve_allowlist "$CVE_ALLOWLIST" \\
|
|
1506
|
+
--argjson cve_min_severity "$CVE_MIN_SEVERITY" \\
|
|
1442
1507
|
--arg session_id "$SESSION_ID" \\
|
|
1443
1508
|
--arg tool_use_id "$TOOL_USE_ID" \\
|
|
1444
1509
|
--arg cwd "$CWD" \\
|
|
@@ -1447,6 +1512,9 @@ BODY=$(jq -n \\
|
|
|
1447
1512
|
file_path: $file_path,
|
|
1448
1513
|
content: $content,
|
|
1449
1514
|
diff: $diff,
|
|
1515
|
+
dependencies: $deps,
|
|
1516
|
+
cve_allowlist: $cve_allowlist,
|
|
1517
|
+
cve_min_severity: $cve_min_severity,
|
|
1450
1518
|
session_id: (if ($session_id | length) > 0 then $session_id else null end),
|
|
1451
1519
|
tool_use_id: (if ($tool_use_id | length) > 0 then $tool_use_id else null end),
|
|
1452
1520
|
cwd: (if ($cwd | length) > 0 then $cwd else null end),
|
|
@@ -1547,28 +1615,43 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1547
1615
|
# \u2500\u2500\u2500 LOCAL GRADING: grade via the persistent claude daemon (mode=edit). \u2500\u2500\u2500
|
|
1548
1616
|
|
|
1549
1617
|
RULES_CACHE="$HOME/.synkro/.rules-cache-edit-capture"
|
|
1618
|
+
RULES_RESP=""
|
|
1550
1619
|
if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
1551
|
-
|
|
1552
|
-
-H "Authorization: Bearer $JWT" --max-time 3 2>/dev/null
|
|
1553
|
-
| jq -c '[.rules[]? | {rule_id, text, severity, category}]' 2>/dev/null || echo "[]")
|
|
1554
|
-
if [ -n "$ORG_RULES" ] && [ "$ORG_RULES" != "null" ] && [ "$ORG_RULES" != "[]" ]; then
|
|
1555
|
-
printf '%s' "$ORG_RULES" > "$RULES_CACHE" 2>/dev/null || true
|
|
1556
|
-
elif [ -f "$RULES_CACHE" ]; then
|
|
1557
|
-
ORG_RULES=$(cat "$RULES_CACHE" 2>/dev/null || echo "[]")
|
|
1558
|
-
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 "")
|
|
1559
1622
|
else
|
|
1560
|
-
|
|
1623
|
+
RULES_RESP=$(printf '%s' "$FILE_CONTENT" | head -c 8000 \\
|
|
1561
1624
|
| jq -Rs '{content: .}' \\
|
|
1562
1625
|
| curl -sS "\${GATEWAY_URL}/api/v1/cli/pr-rules?top_k=20" \\
|
|
1563
1626
|
-X POST -H "Content-Type: application/json" \\
|
|
1564
1627
|
-H "Authorization: Bearer $JWT" \\
|
|
1565
|
-
-d @- --max-time 2 2>/dev/null
|
|
1566
|
-
|
|
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 "[]")
|
|
1567
1635
|
fi
|
|
1568
1636
|
if [ -z "$ORG_RULES" ] || [ "$ORG_RULES" = "null" ]; then ORG_RULES="[]"; fi
|
|
1569
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
|
+
|
|
1570
1653
|
GRADER_PROMPT_FILE=$(mktemp -t synkro-edit-capture.XXXXXX)
|
|
1571
|
-
trap "rm -f \\"$GRADER_PROMPT_FILE\\"" EXIT
|
|
1654
|
+
trap "rm -f \\"$GRADER_PROMPT_FILE\\" \\"$CVE_RESULT_FILE\\"" EXIT
|
|
1572
1655
|
printf 'File: %s\\n' "$FILE_PATH" > "$GRADER_PROMPT_FILE"
|
|
1573
1656
|
printf 'Org rules: %s\\n\\n' "$ORG_RULES" >> "$GRADER_PROMPT_FILE"
|
|
1574
1657
|
printf 'Content:\\n' >> "$GRADER_PROMPT_FILE"
|
|
@@ -1579,6 +1662,14 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1579
1662
|
else
|
|
1580
1663
|
CC_RESP=$(claude --print --model claude-sonnet-4-6 --no-session-persistence < "$GRADER_PROMPT_FILE" 2>/dev/null || echo "")
|
|
1581
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
|
+
|
|
1582
1673
|
# Wrapper extraction (greedy \u2014 tolerates nested XML tags).
|
|
1583
1674
|
V_INNER=$(printf '%s' "$CC_RESP" | tr '\\n' ' ' | sed -nE 's|.*<synkro-verdict>(.*)</synkro-verdict>.*|\\1|p' | tail -1)
|
|
1584
1675
|
if [ -n "$V_INNER" ]; then
|
|
@@ -1593,6 +1684,17 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1593
1684
|
LOCAL_SEV=$(printf '%s' "$FIRST_V" | sed -nE 's|.*<severity>(.*)</severity>.*|\\1|p' | head -1)
|
|
1594
1685
|
LOCAL_CAT=$(printf '%s' "$FIRST_V" | sed -nE 's|.*<category>(.*)</category>.*|\\1|p' | head -1)
|
|
1595
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
|
|
1596
1698
|
# Convert to JSON shape downstream code expects.
|
|
1597
1699
|
RESP=$(jq -n \\
|
|
1598
1700
|
--arg ok "$LOCAL_OK" \\
|
|
@@ -1607,6 +1709,10 @@ if [ "$USE_LOCAL" = "true" ]; then
|
|
|
1607
1709
|
end')
|
|
1608
1710
|
else
|
|
1609
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
|
|
1610
1716
|
fi
|
|
1611
1717
|
else
|
|
1612
1718
|
# \u2500\u2500\u2500 Server-side grading. \u2500\u2500\u2500
|
|
@@ -1674,17 +1780,21 @@ if [ "$SYNKRO_CAPTURE_DEPTH" = "local_only" ]; then
|
|
|
1674
1780
|
--arg risk_level "$LOCAL_RISK" \\
|
|
1675
1781
|
--arg category "$CATEGORY" \\
|
|
1676
1782
|
--arg model "\${CC_MODEL:-claude-sonnet-4-6}" \\
|
|
1783
|
+
--arg cc_model "\${CC_MODEL:-}" \\
|
|
1677
1784
|
--arg tool_name "$TOOL_NAME" \\
|
|
1678
1785
|
--arg repo "\${GIT_REPO:-}" \\
|
|
1679
1786
|
--arg session_id "$SESSION_ID" \\
|
|
1680
1787
|
--arg mech_cat "$MECH_CAT" \\
|
|
1681
1788
|
--arg biz_cat "$BIZ_CAT" \\
|
|
1789
|
+
--argjson cc_usage "\${CC_USAGE:-{}}" \\
|
|
1682
1790
|
'{
|
|
1683
1791
|
event_id: $event_id, timestamp: $timestamp, hook_type: $hook_type,
|
|
1684
1792
|
verdict: $verdict, severity: $severity, risk_level: $risk_level,
|
|
1685
|
-
category: $category, model: $model, tool_name: $tool_name
|
|
1793
|
+
category: $category, model: $model, tool_name: $tool_name,
|
|
1794
|
+
cc_usage: $cc_usage
|
|
1686
1795
|
} + (if $repo != "" then {repo: $repo} else {} end)
|
|
1687
1796
|
+ (if $session_id != "" then {session_id: $session_id} else {} end)
|
|
1797
|
+
+ (if $cc_model != "" then {cc_model: $cc_model} else {} end)
|
|
1688
1798
|
+ (if $mech_cat != "" then {mechanism_category: $mech_cat} else {} end)
|
|
1689
1799
|
+ (if $biz_cat != "" then {business_category: $biz_cat} else {} end)')
|
|
1690
1800
|
curl -sS -X POST "\${GATEWAY_URL}/api/v1/events/local-verdict" \\
|
|
@@ -3857,7 +3967,7 @@ function writeConfigEnv(opts) {
|
|
|
3857
3967
|
`SYNKRO_CREDENTIALS_PATH=${shellQuoteSingle(credsPath)}`,
|
|
3858
3968
|
`SYNKRO_TIER=${shellQuoteSingle(safeTier)}`,
|
|
3859
3969
|
`SYNKRO_INFERENCE=${shellQuoteSingle(safeInference)}`,
|
|
3860
|
-
`SYNKRO_VERSION=${shellQuoteSingle("1.3.
|
|
3970
|
+
`SYNKRO_VERSION=${shellQuoteSingle("1.3.50")}`
|
|
3861
3971
|
];
|
|
3862
3972
|
if (safeUserId) lines.push(`SYNKRO_USER_ID=${shellQuoteSingle(safeUserId)}`);
|
|
3863
3973
|
if (safeOrgId) lines.push(`SYNKRO_ORG_ID=${shellQuoteSingle(safeOrgId)}`);
|
|
@@ -4788,6 +4898,8 @@ __export(scanPr_exports, {
|
|
|
4788
4898
|
scanPrCommand: () => scanPrCommand
|
|
4789
4899
|
});
|
|
4790
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";
|
|
4791
4903
|
function parseMatchSpec(condition) {
|
|
4792
4904
|
if (!condition.startsWith("match_spec:")) return null;
|
|
4793
4905
|
try {
|
|
@@ -5265,6 +5377,70 @@ function shouldFail(findings, threshold) {
|
|
|
5265
5377
|
const thresholdIdx = order.indexOf(threshold);
|
|
5266
5378
|
return findings.some((f) => order.indexOf(f.severity) >= thresholdIdx);
|
|
5267
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
|
+
}
|
|
5268
5444
|
async function postEventToBackend(opts) {
|
|
5269
5445
|
try {
|
|
5270
5446
|
await fetch(`${opts.gatewayUrl.replace(/\/$/, "")}/api/v1/events/pr-scan`, {
|
|
@@ -5372,6 +5548,10 @@ async function scanPrCommand() {
|
|
|
5372
5548
|
return;
|
|
5373
5549
|
}
|
|
5374
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
|
+
});
|
|
5375
5555
|
const results = await processInBatches(eligible, MAX_PARALLEL_FILES, async (file, idx, total) => {
|
|
5376
5556
|
process.stdout.write(`[${idx + 1}/${total}] ${file.filename}...`);
|
|
5377
5557
|
const literalFindings = applyLiteralMatchNegative(literalNegativeRules, file);
|
|
@@ -5380,8 +5560,12 @@ async function scanPrCommand() {
|
|
|
5380
5560
|
console.log(` ${merged.length === 0 ? "clean" : `${merged.length} finding(s)`} (${(llmResult.latencyMs / 1e3).toFixed(1)}s)`);
|
|
5381
5561
|
return { findings: merged, latencyMs: llmResult.latencyMs };
|
|
5382
5562
|
});
|
|
5563
|
+
const cveFindings = await cvePromise;
|
|
5564
|
+
if (cveFindings.length > 0) {
|
|
5565
|
+
console.log(`CVE scan: ${cveFindings.length} vulnerable dependency finding(s).`);
|
|
5566
|
+
}
|
|
5383
5567
|
const totalLatencyMs = Date.now() - t0;
|
|
5384
|
-
const allFindings = results.flatMap((r) => r.findings);
|
|
5568
|
+
const allFindings = [...results.flatMap((r) => r.findings), ...cveFindings];
|
|
5385
5569
|
console.log(`
|
|
5386
5570
|
Total: ${allFindings.length} finding(s) across ${eligible.length} file(s) in ${totalLatencyMs}ms
|
|
5387
5571
|
`);
|
|
@@ -5459,9 +5643,9 @@ var disconnect_exports = {};
|
|
|
5459
5643
|
__export(disconnect_exports, {
|
|
5460
5644
|
disconnectCommand: () => disconnectCommand
|
|
5461
5645
|
});
|
|
5462
|
-
import { existsSync as
|
|
5646
|
+
import { existsSync as existsSync11, rmSync } from "fs";
|
|
5463
5647
|
import { homedir as homedir8 } from "os";
|
|
5464
|
-
import { join as
|
|
5648
|
+
import { join as join10 } from "path";
|
|
5465
5649
|
function disconnectCommand(args2 = []) {
|
|
5466
5650
|
const purge = args2.includes("--purge");
|
|
5467
5651
|
console.log("Synkro disconnect starting...\n");
|
|
@@ -5479,13 +5663,13 @@ function disconnectCommand(args2 = []) {
|
|
|
5479
5663
|
console.log(`${mcpRemoved ? "\u2713" : "\xB7"} MCP guardrails server: ${mcpRemoved ? "removed entry from ~/.claude.json" : "no Synkro MCP entry found"}`);
|
|
5480
5664
|
}
|
|
5481
5665
|
if (purge) {
|
|
5482
|
-
if (
|
|
5666
|
+
if (existsSync11(SYNKRO_DIR5)) {
|
|
5483
5667
|
rmSync(SYNKRO_DIR5, { recursive: true, force: true });
|
|
5484
5668
|
console.log(`\u2713 Removed ${SYNKRO_DIR5}`);
|
|
5485
5669
|
} else {
|
|
5486
5670
|
console.log(`\xB7 ${SYNKRO_DIR5} already gone, nothing to remove`);
|
|
5487
5671
|
}
|
|
5488
|
-
} else if (
|
|
5672
|
+
} else if (existsSync11(SYNKRO_DIR5)) {
|
|
5489
5673
|
console.log(`Config preserved at ${SYNKRO_DIR5}. Run with --purge to remove.`);
|
|
5490
5674
|
}
|
|
5491
5675
|
console.log("\nSynkro disconnected.");
|
|
@@ -5497,7 +5681,7 @@ var init_disconnect = __esm({
|
|
|
5497
5681
|
init_agentDetect();
|
|
5498
5682
|
init_ccHookConfig();
|
|
5499
5683
|
init_mcpConfig();
|
|
5500
|
-
SYNKRO_DIR5 =
|
|
5684
|
+
SYNKRO_DIR5 = join10(homedir8(), ".synkro");
|
|
5501
5685
|
}
|
|
5502
5686
|
});
|
|
5503
5687
|
|
|
@@ -5539,15 +5723,15 @@ var init_reinstall = __esm({
|
|
|
5539
5723
|
});
|
|
5540
5724
|
|
|
5541
5725
|
// cli/bootstrap.js
|
|
5542
|
-
import { readFileSync as
|
|
5726
|
+
import { readFileSync as readFileSync9, existsSync as existsSync12 } from "fs";
|
|
5543
5727
|
import { resolve } from "path";
|
|
5544
5728
|
var envCandidates = [
|
|
5545
5729
|
resolve(process.cwd(), ".env"),
|
|
5546
5730
|
resolve(process.env.HOME ?? "", ".synkro", "config.env")
|
|
5547
5731
|
];
|
|
5548
5732
|
for (const envPath of envCandidates) {
|
|
5549
|
-
if (!
|
|
5550
|
-
const envContent =
|
|
5733
|
+
if (!existsSync12(envPath)) continue;
|
|
5734
|
+
const envContent = readFileSync9(envPath, "utf-8");
|
|
5551
5735
|
for (const line of envContent.split("\n")) {
|
|
5552
5736
|
const trimmed = line.trim();
|
|
5553
5737
|
if (!trimmed || trimmed.startsWith("#")) continue;
|