torb owner --step-up: approve owner changes with a passkey #280
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
An account whose only second factor is a passkey cannot use
torb owner add|remove(#22 sendsTorb-Codefrom--code/$TORB_CODE, an authenticator app or recovery code). Add the step-up flow of #275 to the client:torb owner add|remove --step-up.Scope
POST /api/1/step-up {call: "owners"}, print theuserCodeand the account page, wait for the person to approve it with the passkey and press Enter (standard input, refused when it is not a terminal), then sendTorb-Code: step-up:<secret>on the one call. The secret is never printed or stored; a grant that expired (300 s) or was refused is reported in the registry's words.Acceptance criteria
tools/registry/tests/passkey-step-up.test.trbapproves one:owner addsucceeds with--step-up, the grant is used up, a second call needs a new onetorb-owner.mdsection on passkeysDesign record point
RELEASE.md 7.6 ("a stolen token"),
tools/registry/README.md(issue #275). Seed impact: none. One PR, one commitfeat(package): step-up for torb owner.Depends on #22.
login,logout,ownerandyankcommands #22login,logout,ownerandyankcommands #22[loop:torbscript] intake: in scope (author orion, label loop, split from #22 / #275), not a duplicate, no collision with a design record. Depends on #22 (the owner command) and is refined after it lands. next: waiting (dependency #22)
[loop:torbscript] claim refine by session:loop-torbscript-20261009 until 2026-10-09T23:40Z (dependency #22 landed in
6f9d340b)torb deprecate: flag a whole package as deprecated #279[loop:torbscript] refine: ready, one PR of two commits (read against main at
6f9d340b, after #22).Goal.
torb owner add|remove --step-uplets an account whose only second factor is a passkey change an owner's members: the client starts a step-up (#275), the person approves it at the account page, and the one call carriesTorb-Code: step-up:<secret>.Acceptance criteria (testable)
tools/registry/tests/passkey-step-up.test.trbapproves one (recorded assertion, registry methods or the account page's form):torb owner add <owner> <account> --step-upprints the user code and the account page, waits for a line on standard input, then succeeds; the grant row isused; a secondowner add --step-upstarts a new grant and needs a new approval.owner remove --step-upworks the same.curl/wget, no linetorbprints, and no file left under the work directory (the header file and the answer file are empty or gone afterwards).--step-upwithlist,--step-uptogether with--code; a registry that answers409(no passkey) or403(operator) is reported in its words, exit 1.torb-owner.mdsection "A passkey", regenerated skills, one sentence in RELEASE.md 7.13 and the registry README.Design record point. RELEASE.md 7.6 ("A stolen token": the step-up approved at the account page within five minutes, single use),
tools/registry/README.md(issue #275,POST /api/1/step-up). Seed impact: none (TorbScript only, no new native, syntax, driver flag or std name).Size. One PR, two Conventional Commits (a finding below makes the first necessary):
fix(package): keep the registry's answer out of the work directoryfeat(package): step-up for torb ownerFinding the issue did not have: the secret would be left in a file.
requestincompiler/src/package/upload.trbwrites every answer to<cache>/upload/answerwith the default mode and never clears it (only the header files are emptied). The answer ofPOST /api/1/step-upholds the secret, so the acceptance criterion "in no file" fails without a change there. Commit 1 creates the answer file private (writePrivate, mode0600before writing, as for the headers) and empties it after the reply is read, for every request; the test of the header file intools/registry/tests/commands.test.trbgrows an assertion for the answer file.Files touched.
compiler/src/package/upload.trb,tools/registry/tests/commands.test.trb.compiler/src/cli/registry.trb(--step-upflag andRegistryOptions.stepUp,ownerProblem, astepUpCodeForbesidesecondFactorCode,StepUpAnswer/StepUpBodytypes,runOwner),compiler/src/cli/usage.trb,compiler/tests/registry-commands.test.trb(option parsing), newtools/registry/tests/owner-step-up.test.trb, docsdocs/tooling/torb-owner.md,docs/design/RELEASE.md(7.13 row),tools/registry/README.md, regeneratedskills/**(torb-owner.mdcopies),.pdisc-sidecar.yamlifpdiscasks.Test plan (forge CI, no local suites).
compiler/tests/registry-commands.test.trb:--step-upparsing and each usage error above.tools/registry/tests/owner-step-up.test.trb: registry in-process as inpasskey-step-up.test.trb(reusetests/passkey-vectors.trb; found the organisation before the passkey is registered so no code is needed for it),torbrun as a child with open pipes (Process.start, assrc/program.trbdoes), not throughran, which feeds all input at once: read its output until the user code appears, approve that grant (beginStepUpApproval, the recorded challenge put in place,finishStepUpApproval), then write the line. Cases: approved add, used-up grant and a second add needing a new one, remove, unapproved, expired, no passkey (409),--step-upwithlistand with--code. Thecurlstand-in ofcommands.test.trbadditionally copies each header file and answer file at call time into the test's own log; that is how the test learns the secret (the registry keeps only its SHA-256) to assert it is absent from arguments, output and leftover files.format --check,docs skill --check, pdisc.Implementer. opus / high (
compiler/srcand secret handling in the registry client).Dependencies and overlap. #22 landed (
6f9d340b); no dependency on #276. PR #282 touches only registry service files andstep-up.test.trb, none of these (the new test is a separate file on purpose,passkey-step-up.test.trbstays untouched). Textual overlap with #279 incompiler/src/cli/registry.trb,cli/usage.trb, RELEASE.md 7.13 and the registry README; whichever lands second rebases.[loop:torbscript] decided by the loop: standard input need not be a terminal. The issue said the Enter is refused when stdin is not a terminal. That protects nothing: the grant is redeemed by a single
UPDATE ... WHERE approved = 1 AND used = 0, so an Enter pressed (or piped) before the approval gets the registry's "not approved" refusal and burns no grant. Requiring a terminal would only make the loopback test impossible and a wrapper script (an editor's terminal pane,expect) awkward. Instead an end of input without a line cancels: nothing is sent, exit 1, with a word saying the grant stays open until it expires.[loop:torbscript] decided by the loop:
--step-upexcludes--codeand the ownerlist(usage errors, exit 2).TORB_CODEin the environment is ignored when--step-upis given: the explicit flag over ambient state, and an exportedTORB_CODEmust not turn a passkey flow into a wrong-code attempt that counts against the account's five tries.[loop:torbscript] decided by the loop: what is printed. The user code and the account page (the
pagethe registry answers) on standard output, the prompt on standard error when it is a terminal (aslogindoes); theid,secretandexpiresof the answer are never printed, only held in memory until the header is written. A refusal of the start (401,403operator,409without passkey,429at three live grants) and of the call is reported withrefusalOf, in the registry's words. The client does not time the 300 seconds itself.next: implement
[loop:torbscript] claim implement by session:loop-torbscript-20261009 until 2026-10-10T05:45Z
login,logout,ownerandyankcommands #22[loop:torbscript] implement: PR #287 open, CI green
Two commits:
37a958ecfix(package): keep the registry's answer out of the work directory;18a24952feat(package): step-up for torb owner. The architect review found no blockers; its fixes are in the PR (a privateuploaddirectory, files made anew, every file emptied, a--codevalue that is a flag refused, the docs on an early Enter). Its out-of-scope points are #288. The newowner-step-up.test.trbpasses in CI, and locally against the patched compiler (10/10).Tokens: one implement run of about 0.3M, within the loop's weekly budget.
next: waiting (merge gate review of PR #287)