Skip to content

fix(core): require a first-party application for account reads that need recent authentication - #9773

Open
wangsijie wants to merge 1 commit into
masterfrom
wangsijie-account-reads-first-party-only
Open

wangsijie wants to merge 1 commit into
masterfrom
wangsijie-account-reads-first-party-only

Conversation

@wangsijie

@wangsijie wangsijie commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Stack: this is the first PR of a stack. #9774 builds on this branch, and the feature, #9771, sits on top. Merge in that order.

Summary

The Account API reads that need recent authentication — GET /my-account/mfa-verifications/backup-codes, GET /my-account/sessions, GET /my-account/grants, and GET /my-account/trusted-devices — only asserted identityVerified, so any caller holding a valid verification record could read them. Every account mutation has required a first-party application since #9430; these four reads did not, which is the one path left where a third-party application could reach account data.

Each route now asserts a first-party client right after the identity check, the same placement the mutation routes use:

  • A caller without proof keeps the existing 401 verification_record.permission_denied.
  • A third-party caller that does present a valid record now gets 403 auth.third_party_application_forbidden instead of the data.
  • A first-party caller is unaffected.

A third-party application cannot mint the record itself, since the Verification API refuses a third-party caller (also from #9430); the record is owned by the user, so this closes the path where a record minted by another client is presented.

The koaGuard status lists accept the new status — without that, any status outside the list becomes a 500 outside production — and the OpenAPI supplements document the new 403.

Part of LOG-14124.

Testing

Unit tests, integration tests.

  • @logto/core: trusted-device.test.ts gains the third-party GET case, which also holds the route's status list to the new status.
  • third-party-guard.test.ts gains the case that fails without the fix: a third-party caller presenting a valid verification record gets 403 from all four reads. The record is minted through a first-party client, and left to CI along with the rest of the suite.

Checklist

  • .changeset
  • unit tests
  • integration tests
  • necessary TSDoc comments

…eed recent authentication

Backup codes, sessions, grants, and trusted devices are the reads that need
recent authentication, and they only checked for a valid verification record. An
application that is not first-party could therefore read them while holding one.
Each route now asserts a first-party client next to the identity check: a caller
without proof keeps the existing 401, and a third-party caller with proof gets
403 instead of the data.

A third-party application cannot mint the record itself, since the Verification
API refuses it; this closes the path where a record minted by another client is
presented.

The guard status lists accept the new status, and the unit test, the integration
coverage, and the OpenAPI supplements describe it.

Amp-Thread-ID: https://ampcode.com/threads/T-01a14383-fded-744a-b90a-56f48926c865
Copilot AI balanced review requested due to automatic review settings October 16, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants