refactor(deep-link): classify verified as an attenuated trust class - #155
Merged
Conversation
`silent` claims a route carries no capability at all. That is only true of `verified` by accident of how the link is built today: the portal spends the verification token server-side and hands the app an inert user id. Once the mail link opens the app directly the app receives the raw token, and `verified` leaves `silent` under its own definition — with `confirm` the only other home, which puts a consent sheet on a tap the user just asked for. Add a fourth trust class for a capability worn down by scope, lifetime and blast radius until acting on it unprompted is safe, and argue `verified` into it in the TRUST doc comment beside the other three. `silent` stays, unpopulated, for a route that genuinely carries nothing. No behaviour change: `verified` still carries `u=<userId>` and still runs unprompted. The four class guards collapse into one `isOfClass` factory rather than gaining a fourth copy, and `isUnpromptedIntent` lists its classes so a class added later must be admitted deliberately.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2B of #144 — the argument, not the link change.
TRUST'ssilentclass claims a route carries no capability.verifiedonly satisfies that by accident of how the link is built today: the portal spends the verification token server-side and hands the app an inert user id. Once the mail link opens the app directly the app receives the raw single-use token, andverifiedleavessilentundersilent's own definition — withconfirmthe only other home, which puts a consent sheet on a tap the user just asked for by pressing "verify my email".So: a fourth class,
attenuated— a real capability worn down by scope, lifetime and blast radius until acting on it unprompted is safe — argued into theTRUSTdoc comment beside the other three. The argued core is that a hostileverifiedlink carries the attacker's own token, so tapping it verifies the attacker's address (which they can already do unaided) and touches nothing of the tapper's; and that the class stops being honest the moment such a link carries something a forged one could spend against the tapper.silentstays, unpopulated, as the home for a route that genuinely carries nothing.No behaviour change.
verifiedstill carriesu=<userId>and still runs unprompted. The intent shape, the codec, the builder andhandleUnpromptedIntentare untouched.landing/app/open/fragment.tsin VoltiusApp/web needs no matching change — no route was added or renamed.Also in this commit:
isOfClassfactory instead of gaining a fourth copy of the same three-line predicate.isUnpromptedIntentlists its classes rather than testing "notconfirm", so a class added later has to be admitted deliberately instead of by default.deepLinkUrl.ts:183-184.Still gating Part 2B's shipping (not this PR)
Enterprise mail rewriters. Outlook SafeLinks / Defender ATP re-encode the link onto their own logging host, so the tap resolves against that host: no App Link fires, the mail client's browser follows the redirect, and the app never sees the link at all. Whether a fragment survives the round trip is undocumented — and SafeLinks is already known to mangle URL structure in other ways (it has re-encoded
?as&, breaking magic links). Any 2B design that actually moves the token into the mail link has to keep working when the link lands in a browser instead of the app.Verification
node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.json— clean, exit 0.CI=true npx vitest run— 480 files, 3720 tests, all passing, exit 0. (pluginBundleBuildincluded.)