Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/scripts/classify-vulnerability-issues.jq
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,6 +12,8 @@ def finding_id($prefix):
| .findingId as $findingId
| if .findingId == "" then
. + { action: "ignore" }
elif ($skipCodeql and (.findingId | startswith("codeql:"))) then
. + { action: "ignore" }
elif ($currentIds | index($findingId)) != null then
. + { action: "keep" }
else
Expand Down
70 changes: 70 additions & 0 deletions .github/scripts/linear-graphql-request.sh
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ -z "${LINEAR_API_KEY:-}" ]]; then
echo "LINEAR_API_KEY is required" >&2
exit 1
fi

payload=$(cat)
configured_attempts="${LINEAR_GRAPHQL_ATTEMPTS:-4}"
retry_delay="${LINEAR_GRAPHQL_RETRY_DELAY_SECONDS:-2}"
endpoint="${LINEAR_GRAPHQL_ENDPOINT:-https://api.linear.app/graphql}"
is_mutation=$(jq -r '(.query // "") | test("^\\s*mutation(?:\\s|\\(|\\{)")' <<<"$payload")

# Retrying a mutation after an ambiguous transport failure can replay a write
# that Linear already committed. Queries are safe to retry; mutations fail
# visibly after one attempt and rely on the workflow's reconciliation pass.
if [[ "$is_mutation" == "true" ]]; then
attempts=1
else
attempts="$configured_attempts"
fi

for ((attempt = 1; attempt <= attempts; attempt++)); do
response_file=$(mktemp)
http_code=""

if http_code=$(curl \
--silent \
--show-error \
--output "$response_file" \
--write-out '%{http_code}' \
--connect-timeout 10 \
--max-time 45 \
-X POST "$endpoint" \
-H "Content-Type: application/json" \
-H "Authorization: $LINEAR_API_KEY" \
-d "$payload"); then
response=$(<"$response_file")
rm -f "$response_file"

if [[ "$http_code" =~ ^2[0-9][0-9]$ ]] && jq -e . >/dev/null 2>&1 <<<"$response"; then
printf '%s' "$response"
exit 0
fi

if [[ "$http_code" =~ ^(408|429|5[0-9][0-9])$ ]]; then
echo "Linear GraphQL returned transient HTTP $http_code (attempt $attempt/$attempts)." >&2
elif jq -e . >/dev/null 2>&1 <<<"$response"; then
# Preserve structured non-retryable errors so the workflow can report
# the GraphQL response rather than replacing it with a transport error.
printf '%s' "$response"
exit 0
else
echo "Linear GraphQL returned a non-JSON response (HTTP $http_code, attempt $attempt/$attempts)." >&2
fi
else
curl_status=$?
response=$(<"$response_file")
rm -f "$response_file"
echo "Linear GraphQL request failed (curl $curl_status, HTTP ${http_code:-unknown}, attempt $attempt/$attempts)." >&2
fi

if ((attempt < attempts)); then
sleep "$retry_delay"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
done

echo "Linear GraphQL did not return a valid JSON response after $attempts attempts." >&2
exit 1
90 changes: 88 additions & 2 deletions .github/scripts/test-vulnerability-triage.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,6 +5,8 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
LINEAR_REQUEST="$SCRIPT_DIR/linear-graphql-request.sh"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All@@ -20,6 +22,86 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
assert_workflow_contains "uses the retrying Linear GraphQL client" '.vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'

FAKE_CURL_DIR=$(mktemp -d)
FAKE_CURL_COUNT=$(mktemp)
trap 'rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
printf '0\n' > "$FAKE_CURL_COUNT"

cat > "$FAKE_CURL_DIR/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

output_file=""
while (($# > 0)); do
case "$1" in
--output)
output_file="$2"
shift 2
;;
*)
shift
;;
esac
done

count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
if ((count < 3)); then
printf 'Bad Gateway' > "$output_file"
printf '502'
else
printf '{"data":{"ok":true}}' > "$output_file"
printf '200'
fi
EOF
chmod +x "$FAKE_CURL_DIR/curl"

LINEAR_RESPONSE=$(
PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"query { viewer { id } }"}'
)
assert_json "retries non-JSON Linear responses" "$LINEAR_RESPONSE" '{"data":{"ok":true}}'
if [[ "$(<"$FAKE_CURL_COUNT")" != "3" ]]; then
echo "FAIL: expected Linear request helper to retry twice"
exit 1
fi

printf '0\n' > "$FAKE_CURL_COUNT"
if PATH="$FAKE_CURL_DIR:$PATH" \
FAKE_CURL_COUNT="$FAKE_CURL_COUNT" \
LINEAR_API_KEY="test-key" \
LINEAR_GRAPHQL_ATTEMPTS=3 \
LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
"$LINEAR_REQUEST" <<<'{"query":"mutation { issueCreate(input: {}) { success } }"}' >/dev/null 2>&1; then
echo "FAIL: ambiguous Linear mutations must not be retried"
exit 1
fi
if [[ "$(<"$FAKE_CURL_COUNT")" != "1" ]]; then
echo "FAIL: expected exactly one Linear mutation attempt"
exit 1
fi

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand DownExpand Up@@ -119,13 +201,17 @@ OPEN_ISSUES='[
}
]'

CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "keeps every exact current finding" "$(jq -c '[.[] | select(.action == "keep") | .id]' <<<"$CLASSIFIED")" '["keep-cve","keep-codeql"]'
assert_json "closes only resolved managed findings" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED")" '["close-cve","close-prefix-collision"]'
assert_json "ignores unrelated titles and repositories" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED")" '["ignore-other-repo","ignore-unmanaged"]'

EMPTY_FINDINGS='{"cves":[]}'
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
CLASSIFIED_EMPTY="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql false --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "closes all managed issues when a complete scan is clean" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_EMPTY")" '["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'

CLASSIFIED_WITH_CODEQL_SKIPPED="$(jq -c --arg prefix "$PREFIX" --argjson skipCodeql true --slurpfile findings <(printf '%s\n' "$EMPTY_FINDINGS") -f "$CLASSIFY_FILTER" <<<"$OPEN_ISSUES")"
assert_json "preserves CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "ignore") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
assert_json "still closes resolved non-CodeQL issues when CodeQL is unavailable" "$(jq -c '[.[] | select(.action == "close") | .id]' <<<"$CLASSIFIED_WITH_CODEQL_SKIPPED")" '["keep-cve","close-cve","close-prefix-collision"]'

echo "All vulnerability triage reconciliation tests passed."
Loading
Loading