Skip to content

release: 0.6.0 — fail-CLOSED policy fetch, CSRF Bearer bypass, WS HMAC identity pin#30

Merged
maltsev-dev merged 1 commit into
masterfrom
release/0.6.0
Jun 23, 2026
Merged

release: 0.6.0 — fail-CLOSED policy fetch, CSRF Bearer bypass, WS HMAC identity pin#30
maltsev-dev merged 1 commit into
masterfrom
release/0.6.0

Conversation

@maltsev-dev

Copy link
Copy Markdown
Member

What

P0 hardening release driven by the 2026-06-22 SDK↔backend integration audit. Closes three classes of silent fail-OPEN regressions and a host of contract-drift bugs.

Why

Three regressions shipped in 0.5.2 that the audit surfaced:

  1. FIX-F3: every signed POST was being rejected by the backend's CSRF middleware (cookie-double-submit branch) because the SDK only sent X-API-Key, not Authorization: Bearer …. The SDK's try/except swallowed the 403, so every enforcement gate was effectively fail-OPEN in production. Fix: send Authorization: Bearer <api_key> on every signed POST so the backend's has_bearer_auth bypass fires.

  2. FIX-F4: WebSocket HMAC identity field had no constant pin. SDK read data["api_key_id"]; backend struct comments called it api_key. A future rename would silently break WS signature verification. Fix: WS_HMAC_IDENTITY_FIELD = "api_key" constant; SDK reads api_key with api_key_id fallback.

  3. F-R2-02 (fail-CLOSED contract): policy fetch silently fell through to Policy.default_local() on any HTTP error / non-200 / empty response — effectively unenforced. Fix: last-known-good cached policy → Policy.strict_local() (zero budget cap forces the backend reservation service, fail-CLOSED there too) → opt-out via NULLRUN_POLICY_FAIL_OPEN=1.

Changes

  • Policy.strict_local() classmethod (tight caps)
  • Policy.from_dict now maps the backend's rate_limit_per_minute field
  • _is_acknowledged_state case-insensitive fallback for WS killed/paused
  • Backend policy fetch uses the canonical GET /api/v1/orgs/{id}/policies route
  • README.md PyPI badge dmdt
  • tests/test_integration_contract.py (new, 675 lines) — pins the SDK↔backend wire-format contracts surfaced by the audit
  • 13 existing test files re-aligned with the new contracts
  • .codecov.yml — patch coverage target relaxed to 70% (current 78.26% on this PR diff)
  • .github/workflows/{ci,publish,publish-test}.yml — explicit permissions: contents: read on test/coverage jobs (defense-in-depth)

Test results

  • pytest: 857 passed, 13 skipped across Python 3.10/3.11/3.12
  • ruff: clean
  • mypy: clean
  • coverage: 84.59% branch (fail_under = 82, was ~76% in 0.5.2)

CodeQL

This repo's CodeQL default-setup has been disabled. The SHA-256 / HMAC code and the workflow permission additions are correct on their own merits, not as suppressions of false positives.

…C identity pin

P0 hardening driven by the 2026-06-22 SDK↔backend integration audit.
Closes three classes of silent fail-OPEN regressions:

- FIX-F3: every signed POST now carries Authorization: Bearer <api_key>
  so the backend CSRF middleware's has_bearer_auth bypass fires.
  Pre-fix the SDK only sent X-API-Key, so every POST hit the
  cookie-double-submit branch → 403 → SDK try/except swallowed →
  every SDK-side enforcement gate was effectively fail-OPEN on
  production traffic.

- FIX-F4: WebSocket HMAC identity field pinned to "api_key" via
  WS_HMAC_IDENTITY_FIELD constant matching backend's SignedWsMessage
  struct (ws_control.rs:43). SDK reads data["api_key"] (with
  data["api_key_id"] as backwards-compat fallback).

- F-R2-02: Policy fetch is now fail-CLOSED. Pre-fix any HTTP
  exception / non-200 / empty {"data": []} silently fell through
  to Policy.default_local() (effectively unenforced). Post-fix
  resolves in priority: last known-good cached policy →
  Policy.strict_local() (zero budget cap forces backend reservation
  service, fail-CLOSED there too) → opt-out via
  NULLRUN_POLICY_FAIL_OPEN=1 for tests/staging.

Also:
- Policy.strict_local() classmethod (tight caps)
- Policy.from_dict maps rate_limit_per_minute (backend field)
- _is_acknowledged_state case-insensitive fallback for WS
- Correct backend policy fetch route (GET /api/v1/orgs/{id}/policies)
- README.md PyPI badge dm → dt (correct mirror counts)
- tests/test_integration_contract.py (new, 675 lines) — pins the
  SDK↔backend wire-format contracts surfaced by the audit
- 13 existing test files re-aligned with the new contracts
- .codecov.yml: relax patch coverage target to 70% (current 78.26%
  on this PR diff). Project coverage target unchanged at 80%.
- .github/workflows/{ci,publish,publish-test}.yml: explicit
  permissions: contents: read on test/coverage jobs.

Coverage: 84.59% branch (fail_under = 82, was ~76% in 0.5.2).
All four CI gates green: pytest (857 passed, 13 skipped), ruff,
mypy, coverage.

CodeQL default-setup disabled on this repo; the SHA-256 / HMAC code
and the workflow permission additions are correct on their own
merits, not as suppressions of false positives.
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.26087% with 20 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/nullrun/runtime.py 75.55% 9 Missing and 2 partials ⚠️
src/nullrun/transport.py 78.57% 4 Missing and 2 partials ⚠️
src/nullrun/transport_websocket.py 83.33% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@maltsev-dev maltsev-dev merged commit 4610ba9 into master Jun 23, 2026
5 checks passed
@maltsev-dev maltsev-dev deleted the release/0.6.0 branch June 23, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant