feat: enhance skill security scan for forked PRs

- Added handling for missing SKILL_SCANNER_LLM_API_KEY in forked pull requests.
- Implemented a sticky comment to inform users when the scan is skipped due to the absence of the API key.
- Updated workflow documentation to clarify the behavior of the scanner in forked contexts.
This commit is contained in:
Timothy Kassis
2026-08-10 16:14:49 -07:00
parent 7eb9c23c32
commit 3956e5408b
2 changed files with 29 additions and 0 deletions

View File

@@ -73,6 +73,8 @@ jobs:
if: steps.changed.outputs.skill_dirs != ''
run: uv sync --python 3.13
# Fork PRs do not receive SKILL_SCANNER_LLM_API_KEY. scan_pr_skills.py
# detects the missing key, writes an explanatory sticky comment, and exits 0.
- name: Run scanner on changed skills
if: steps.changed.outputs.skill_dirs != ''
id: scan

View File

@@ -15,6 +15,7 @@ on the pull request. It exits non-zero when any scanned skill has a finding at
from __future__ import annotations
import argparse
import os
import sys
import time
from datetime import datetime, timezone
@@ -31,6 +32,21 @@ load_dotenv()
SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO", "SAFE"]
COMMENT_MARKER = "<!-- skill-security-scan -->"
SKIP_NO_API_KEY_COMMENT = f"""\
{COMMENT_MARKER}
## 🛡️ Skill Security Scan
_Skipped:_ `SKILL_SCANNER_LLM_API_KEY` is not available in this workflow run.
GitHub does not expose repository secrets to `pull_request` workflows from forks,
so the LLM analyzer cannot start. This is not a finding about the diff.
A maintainer can re-run the scan from a trusted context (for example after the
branch is pushed to this repository, or via a maintainer-triggered run with
secrets). Structural CI checks (spec validation, repo-wide contract, skill suites)
are unaffected.
"""
def _sev_str(obj) -> str:
sev = getattr(obj, "max_severity", None) or getattr(obj, "severity", None)
@@ -212,6 +228,17 @@ def main() -> int:
Path(args.output).write_text(md)
return 0
if not os.getenv("SKILL_SCANNER_LLM_API_KEY"):
# Fork PRs never receive repository secrets under pull_request; fail open
# with an explanatory sticky comment instead of ValueError from LLMAnalyzer.
print(
"SKILL_SCANNER_LLM_API_KEY unset — skipping scan "
"(typical for pull_request workflows from forks)."
)
Path(args.output).write_text(SKIP_NO_API_KEY_COMMENT)
print(f"Comment written to {args.output}")
return 0
print("Building scanner (LLM + behavioral + trigger + balanced policy)...")
scanner = build_scanner()
print(f"Analyzers: {scanner.list_analyzers()}\n")