Skip to content

fix(security): PAT org binding, subscribe throttle, invite lookup, SNMP secret - #699

Merged
fclairamb merged 21 commits into
mainfrom
security/scan-specs-2026-10-28
Oct 28, 2026
Merged

fclairamb merged 21 commits into
mainfrom
security/scan-specs-2026-10-28

Conversation

@fclairamb

@fclairamb fclairamb commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

Security fixes from the daily security scans (specs filed in this PR, now implemented).

What changed

  • X1 (high): a personal access token is bound to one org. It can no longer call switch-org, mint sessions or device-consent tokens, or change sign-in factors (password, 2FA, passkeys, email). It gets 403 PAT_FORBIDDEN. sp auth switch-org on a PAT login explains what to do. Registering a passkey now emails the owner.
  • X2 (medium): status-page subscribe no longer un-confirms a confirmed subscriber. Confirm emails are budgeted (3 per recipient, 100 per page, 20 per IP per hour, configurable via SP_STATUS_SUBSCRIBERS_CONFIRM_PER_*_PER_HOUR). Unconfirmed rows expire after 7 days (new column confirm_sent_at in migration 031).
  • X3 (medium): invite-token lookup is one keyed lookup, not a scan of every organization. Breaking: invitations created before this release are no longer found; admins must re-send them.
  • X4 (low): the SNMP community string is stored as a secret and no longer returned by the API or exports.
  • Batch gate: fixed the env-var docs accessors for the new subscribe budgets. Full Go suite, lint and Playwright (980 passed) green.

Decided by autopilot

  • X1, PAT calling switch-org: not allowed at all (recommended answer).
  • X1, DELETE /auth/2fa keeps sharing the login budget (recommended answer).
  • X2, budgets 3 / 100 / 20 per hour as config values (recommended answer).
  • X3, no fallback for pre-upgrade invites; noted in the commit for the changelog (recommended answer).

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fclairamb and others added 5 commits October 28, 2026 07:24
…gn-in factor

A personal access token could call switch-org to get a full session in
any org of its owner, approve a device grant for another org, and add
sign-in factors (passkey, password, 2FA). RequireAuth now refuses those
routes to a PAT with 403 PAT_FORBIDDEN, startSession refuses to mint a
session from a PAT request, and registering a passkey emails the owner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.70306% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.74%. Comparing base (ea540e8) to head (a1bd9dc).

Files with missing lines Patch % Lines
...ver/internal/handlers/statussubscribers/service.go 89.55% 7 Missing ⚠️
server/internal/handlers/auth/service.go 81.25% 6 Missing ⚠️
server/internal/handlers/auth/device_handler.go 0.00% 2 Missing ⚠️
...erver/internal/db/models/status_page_subscriber.go 83.33% 1 Missing ⚠️
...ver/internal/db/postgres/status_page_subscriber.go 92.85% 1 Missing ⚠️
...erver/internal/db/sqlite/status_page_subscriber.go 92.85% 1 Missing ⚠️
server/internal/handlers/auth/handler.go 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #699      +/-   ##
==========================================
+ Coverage   89.72%   89.74%   +0.01%     
==========================================
  Files        1292     1295       +3     
  Lines      131787   131964     +177     
==========================================
+ Hits       118247   118432     +185     
+ Misses      13531    13523       -8     
  Partials        9        9              
Files with missing lines Coverage Δ
server/internal/app/server.go 92.02% <100.00%> (+<0.01%) ⬆️
...nternal/checkers/checksnmp/config/secret_fields.go 100.00% <100.00%> (ø)
server/internal/config/config.go 94.00% <100.00%> (+0.01%) ⬆️
server/internal/config/envvars.go 100.00% <100.00%> (ø)
server/internal/config/status_subscribers.go 100.00% <100.00%> (ø)
server/internal/email/supportreply.go 92.68% <ø> (ø)
server/internal/handlers/auth/device_service.go 90.17% <100.00%> (+0.11%) ⬆️
server/internal/handlers/auth/email_change.go 89.18% <100.00%> (+0.14%) ⬆️
server/internal/handlers/auth/join_policy.go 87.38% <100.00%> (+0.63%) ⬆️
server/internal/handlers/auth/passkey_notice.go 100.00% <100.00%> (ø)
... and 16 more

... and 26 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

fclairamb and others added 15 commits October 28, 2026 07:50
…llow the PAT binding

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the owner's other orgs and add sign-in factors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry and confirm-mail budget config

status_page_subscriber.confirm_sent_at records when the current confirm
token was issued; DeleteExpiredUnconfirmedSubscribers reaps never-confirmed
rows past it. SP_STATUS_SUBSCRIBERS_CONFIRM_PER_{RECIPIENT,PAGE,IP}_PER_HOUR
budget the double opt-in mail (defaults 3, 100, 20).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bscribers confirmed

The public subscribe endpoint no longer mails on every call: the confirm
mail is budgeted per recipient (all pages), per page and per trusted client
IP, and a live confirmed subscription is left untouched by a repeat
subscribe. Both cases still answer 202 pending_confirmation, so the
endpoint reveals nothing. Confirm links expire after 7 days and the
state-cleanup job reaps the unconfirmed rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kup out of Subscribe

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rottled confirm emails and un-confirms existing subscribers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rg scan

The public GET /auth/invite/:token and anonymous POST /auth/accept-invite
loaded every organization and probed two state entries per org, so an
unknown token cost about 2 x N + 1 queries. CreateInvitation now also
writes a global index entry (invite:<hash>, no organization, value
{orgUID}); findInvite reads it, then the org-scoped entry. Acceptance,
federated join and revocation delete both. The legacy plaintext-key branch
(TODO remove after 2026-10-10) is gone.

Operator action: invitations created before this release have no index
entry and are no longer found; admins must re-send them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s every organization

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Declare community in SNMPConfig.SecretFields so it is encrypted, redacted
from GET/export/snapshots, and rendered as a password input in dash0.
credmigrate.RunPlaintext runs at every startup and re-splits rows whose
private column is empty, so existing v1/v2c rows move automatically; past
version snapshots are scrubbed on read via HiddenConfigKeys.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…urned as public check config

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@fclairamb fclairamb changed the title chore(specs): security scan findings fix(security): PAT org binding, subscribe throttle, invite lookup, SNMP secret Oct 28, 2026
@fclairamb
fclairamb merged commit cdf59bb into main Oct 28, 2026
35 checks passed
@fclairamb
fclairamb deleted the security/scan-specs-2026-10-28 branch October 28, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant