* feat(report): SARIF 2.1.0 emitter for CI / code-scanning integration
Strix emits CSV + markdown + JSON but no SARIF, so findings can't feed
GitHub code-scanning, an ASPM, or any SARIF-consuming CI gate. Add a
stdlib-only emitter (strix/report/sarif.py) and always write findings.sarif
from ReportState._save_artifacts, beside the existing artifacts.
Design invariants (learned from running this in production):
- Stable partialFingerprints.primaryLocationLineHash per finding, so a
re-scan that re-words a title doesn't churn code-scanning alert IDs.
- Class/category hashing so the same vuln class maps to a stable ruleId
across scans rather than drifting.
- Findings with no code location anchor to SECURITY.md with a synthetic
location marker instead of being silently dropped.
- Always emit (even with zero findings) so a clean re-scan overwrites a
stale findings.sarif and code-scanning auto-resolves fixed alerts.
- tool.driver.version reports the strix package version.
- Fully isolated in its own try/except: a SARIF build error must never
break the CSV/MD/run-record path.
Verified end-to-end on v1.0.4 against a SQLi/cmd-inj/weak-hash fixture:
3 findings -> valid SARIF 2.1.0, 3 results, real code locations, distinct
per-finding fingerprints.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(report): complete SARIF code scanning metadata
---------
Co-authored-by: bearsyankees <bearsyankees@gmail.com>
* fix: wrap acompletion in asyncio.wait_for to prevent indefinite hangs
litellm's timeout parameter doesn't always propagate to the underlying
httpx transport for Bedrock converse streaming. When Bedrock accepts the
TCP connection but never starts streaming chunks, the acompletion call
hangs indefinitely with all connections in CLOSED state.
This wraps the acompletion call in asyncio.wait_for() using the
configured LLM_TIMEOUT (default 300s). TimeoutError is already retryable
via _should_retry (status_code=None), so the retry loop handles it.
Diagnosed via faulthandler thread dump showing the main asyncio event
loop blocked in selectors.select() with no pending callbacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add per-chunk timeout to streaming loop
Addresses review feedback: the initial asyncio.wait_for only guards the
acompletion call. If Bedrock returns headers but stalls mid-stream, the
async for loop could still hang indefinitely.
Replaces async for with explicit __anext__ calls wrapped in
asyncio.wait_for, using the same configured timeout. Mid-stream stalls
now raise TimeoutError and trigger the existing retry logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Sean Turner <sean.turner@zerohash.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>