Builds on the SARIF 2.1.0 emitter (#626): give each SARIF rule one or more
`stride:<leg>` tags (Spoofing / Tampering / Repudiation / Information
disclosure / Denial of service / Elevation of privilege) derived from the
finding's CWE, so consumers — the GitHub code-scanning Security tab, ASPM
dashboards, coverage reports — can group and filter findings by
threat-model leg. SARIF results inherit their rule's tags via ruleId, so
tagging the rule is sufficient.
- _CWE_TO_STRIDE maps common CWEs to legs (dominant leg first where a CWE
spans several); unmapped / no-CWE findings fall back to a default
(tampering + information-disclosure) so every finding carries >=1 leg
and downstream reports have no coverage gaps.
- Includes mappings for CWEs surfaced by real scans: 798 (hardcoded
creds), 862 (missing authz), 259 (hardcoded password), 1391 (weak
credential).
Tests: tests/report/test_sarif_stride.py (14 cases — mapping, normalization
of CWE-306/306/"cwe: 306" forms, default fallback, rule-tag emission).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* 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>