Skip to content

fix(security): close unauth info-disclosure on setup-status, pat-status, app-state, /health#44

Open
dgokeeffe wants to merge 1 commit into
mainfrom
fix/info-disclosure-trim
Open

fix(security): close unauth info-disclosure on setup-status, pat-status, app-state, /health#44
dgokeeffe wants to merge 1 commit into
mainfrom
fix/info-disclosure-trim

Conversation

@dgokeeffe
Copy link
Copy Markdown
Collaborator

Summary

Closes the three P3/P4 info-disclosure findings from the 2026-05-17 pen test, bundled because each is a single-line/single-block change in the same file. Companion to the merged hotfix #42 (configure-pat owner-gate).

The findings (from .humantokens/2026-05-17-pentest-findings.md)

Endpoint Before After
/api/setup-status Auth-exempt; returned full `setup_state` including 500-char stderr from any failed setup step Owner-gated. Frontend still polls (it's auth'd, has SSO cookies).
/api/pat-status Auth-exempt; returned `workspace_host` Owner-gated.
/api/app-state Auth-exempt; returned `app_owner` email + last PAT rotation timestamp Owner-gated. Note: also fixed in PR #40; including here lets it ship sooner; PR #40 will rebase cleanly.
/health Auth-exempt; returned version, session count, setup status, timeout config Trimmed to `{"status": "healthy"}`. Version exposure enabled CVE-targeted exploit selection.

Why these changes are safe

  • All three previously-exempt endpoints are only polled by the frontend (`static/index.html` lines 1475, 1565–1593). The frontend loads from `/`, which IS auth'd — so by the time it polls these endpoints, it already has valid SSO cookies. Removing them from the exempt list breaks no legitimate flow.
  • `/health` has zero callers in `static/` and the Apps platform uses its own internal liveness mechanism upstream of the app. No external system was depending on the rich response. Confirmed by grep.

Tests

  • 5 new tests in `TestInfoDisclosureEndpoints`:
    • `test_setup_status_denied_for_non_owner` → 403
    • `test_setup_status_allowed_for_owner` → 200
    • `test_pat_status_denied_for_non_owner` → 403
    • `test_app_state_denied_for_non_owner` → 403
    • `test_health_minimal_response_no_version` — explicitly asserts that `version`, `setup_status`, `active_sessions` are NOT in the response (regression-proofs the leak against future "let me add version back for debugging" refactors)
  • Updated `test_session_linger.py`: the 24h timeout assertion now reads `SESSION_TIMEOUT_SECONDS` from the module directly. The old test asserted via `/health`'s rich response, which conflated "value is configured correctly" with "value is publicly visible." Stronger test, smaller attack surface.
  • 236/236 total unit tests pass.

What this PR is NOT

  • Not a hotfix. The findings are P3/P4 (info disclosure, not exploitable for privilege escalation). Standard PR review process.
  • Not an attempt to clean up other adjacent code. Surgical scope: one auth-exempt list, one `/health` handler.

Test plan

  • Unit tests pass (236/236)
  • Manual frontend trace via grep — confirmed all polling callers are auth'd
  • Sathish review

This pull request and its description were written by Isaac.

…t-status, app-state, /health)

Closes the three P3/P4 info-disclosure findings from the 2026-05-17
pen test:

1. /api/setup-status was auth-exempt and returned the full setup_state
   dict including step error messages (stderr trimmed to 500 chars). If a
   setup script failed with leaky stderr — paths, version strings, env-var
   echoes — anyone hitting the URL could read it.

2. /api/pat-status was auth-exempt and returned workspace_host. Already
   known to anyone who reached the URL (they're auth'd to the same
   workspace), but principle-of-least-info says trim it.

3. /api/app-state was auth-exempt and returned app_owner email + last
   PAT rotation timestamp. PR #40 also removes this — including the same
   one-line fix here lets it ship sooner; PR #40 will rebase cleanly.

4. /health was auth-exempt and returned version, active_sessions,
   setup_status, session_timeout_seconds. Version exposure enables
   CVE-targeted exploit selection. Trimmed to {"status": "healthy"} —
   no internal or external caller needs the rich shape (the frontend
   doesn't poll /health; the Apps platform uses its own liveness
   mechanism upstream of the app).

Removed setup-status, pat-status, and app-state from the
before_request auth-exempt list. The frontend continues to poll all
three because it loads from / (auth'd), so it already has SSO cookies.

Test changes:
- 5 new tests in TestInfoDisclosureEndpoints covering: setup-status
  denied/allowed, pat-status denied, app-state denied, /health
  minimal-response anti-leak (explicit assertions that version,
  setup_status, active_sessions are NOT in the response).
- Updated test_session_linger.py: the 24h timeout assertion now reads
  SESSION_TIMEOUT_SECONDS from the module directly instead of via
  /health, since exposing the value to unauth callers was the leak
  we're closing. Stronger test, smaller attack surface.

236/236 unit tests pass.

Co-authored-by: Isaac
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