Tokens and the per-VM endpoint stub - #36
Merged
Merged
Conversation
lex00
added a commit
that referenced
this pull request
Jul 31, 2026
main gained the mkdocs site and the strict docs gate (#27) and the floci architecture findings (#28) after this branch was cut, and the two sides had both reformatted 60-connectors.json. The conflict is a genuine union rather than a pick. main added the subset:floci tag, correcting which cases the floci subset covers. This branch added the live-recorded request members the connector create and update actually need — AssociatedComputeResourceTypes, NetworkProtocol, ClientToken and OperatorRole, with the operatorRoleArn param behind them. Both are kept; dropping either would lose a real finding. This also unblocks CI on the PR. A conflicting pull request has no mergeable ref for pull_request workflows to run against, which is why #34 showed no checks at all while #35 and #36 were green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X6T4MeDN1RBiWaNtud6x77
lex00
force-pushed
the
feature/11-suspend-resume
branch
from
July 31, 2026 04:52
6802911 to
f2076c2
Compare
CreateMicrovmAuthToken mints the recorded token shape: a JWE in compact
serialization, five parts with an empty encrypted-key segment because the
header says alg "dir", the header carrying a kid uuid and enc A256GCM.
m80 builds that shape from random bytes and validates by table lookup
rather than by decrypting. A client that carries the token around cannot
tell the difference; one that parses the header for a kid finds it where
the service puts it. Nothing is encrypted in there and nothing should be
read out of it past the header.
The request rules come from the vendored model rather than guesswork.
expirationInMinutes and allowedPorts are both required, expiry is
documented at a maximum of 60, allowedPorts has a minimum length of one,
and each member is a union — exactly one of port, range or allPorts. The
endpoint enforces the port grant, because a token scoped to one port that
opened another would pass requests the real service rejects.
Issuing against a SUSPENDED VM returns a full token, recorded, and that
is the order a client meaning to wake a VM by calling it has to work in.
Against a TERMINATED one it is the recorded 400 ValidationException —
unrecorded for this operation specifically, but it is the recorded rule
for every other mutation and a token against a VM that no longer exists
could only mislead.
CreateMicrovmShellAuthToken can only ever fail, and that is the honest
implementation. SHELL_INGRESS is absent from the service model entirely,
so no request exists that would make it succeed. 501 would be worse: the
operation is implemented, the recorded rejection is its one observable
behavior, and a consumer that handles it is correctly exercised.
The per-VM endpoint is answered from the same process. A VM's endpoint is
a different host, and ServeMux host patterns cannot carry a wildcard, so
api.Server grew an Intercept hook consulted before the route table. It
routes by hostname, or by a /_m80/vm/{id}/ path prefix for callers that
cannot forge a Host header. The default body and an X-M80-State-Marker
header both carry #11's state marker, so a caller that replaced the body
with its own payload can still read it.
Almost none of the endpoint's answers are recorded and none of them could
have been: the runner signs and addresses control-plane requests, so it
cannot call a host that is not the control plane. Every guess is in one
table in endpoint.go and repeated in docs/api-surface.md, nowhere else.
The auto-resume row is an inference rather than a guess — a suspended VM
issues tokens, which is the order waking a VM by calling it requires, and
autoResumeEnabled is the member saying whether it may. The 401/403 split
is the guess most worth arguing with.
Also widens the normalizer's credential rule to cover "auth". TokenParts
keys are header names — the model says so, because some schemes return
several auth headers — and the recorded one is X-aws-proxy-auth, which
contains none of token/secret/credential/password. The value therefore
survived normalization and the fixture held a literal JWE that no
generated token could ever equal. Compare normalizes the fixture too, so
both sides now collapse and no re-recording is needed.
Conformance: 58 pass, 0 fail, 24 of 29 operations exercised, up from 52
and 23. Both token fixtures match, including auth-token-while-suspended,
which stops being the optional step that only ever reported unimplemented.
Closes #12
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X6T4MeDN1RBiWaNtud6x77
lex00
force-pushed
the
feature/12-tokens-endpoint
branch
from
July 31, 2026 04:58
4cea72c to
6547908
Compare
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.
Stacked on #35, which is stacked on #34. Review those first.
Tokens
The recorded token is a JWE in compact serialization — five parts, empty encrypted-key segment because the header says
alg: dir, header carrying akiduuid andenc: A256GCM. m80 builds that shape from random bytes and validates by table lookup rather than by decrypting. A client that carries the token around cannot tell the difference; one that parses the header for akidfinds it where the service puts it. Nothing is encrypted in there and nothing should be read out of it past the header.Request rules came from the vendored service model rather than guesswork:
expirationInMinutesandallowedPortsboth required, expiry documented at a maximum of 60,allowedPortsminimum length one, each member a union of exactly one ofport/range/allPorts. The endpoint enforces the port grant — a token scoped to one port that opened another would pass requests the real service rejects.Issuing against a
SUSPENDEDVM returns a full token (recorded). Against aTERMINATEDone it is400 ValidationException— unrecorded for this operation specifically, but the recorded rule for every other mutation.CreateMicrovmShellAuthTokencan only ever fail, and that is the honest implementation.SHELL_INGRESSis absent from the service model entirely, so no request exists that would make it succeed. Answering 501 would be worse: the operation is implemented, the recorded rejection is its one observable behavior, and a consumer that handles it is correctly exercised by it.The endpoint stub
A VM's endpoint is a different host answered by the same process, and
ServeMuxhost patterns cannot carry a wildcard, soapi.Servergrew anIntercepthook consulted before the route table. Routing is by hostname, or by a/_m80/vm/{id}/path prefix for callers that cannot forge aHostheader. The default body and anX-M80-State-Markerheader both carry #11's state marker, so a caller who replaced the body with their own payload can still read it.Almost none of the endpoint's answers are recorded, and none of them could have been — the runner signs and addresses control-plane requests, so it cannot call a host that is not the control plane. Every guess sits in one table in
endpoint.goand is repeated indocs/api-surface.md, nowhere else:404401403allowedPorts403TERMINATED410PENDING503SUSPENDED,autoResumeEnabled200SUSPENDED, noautoResumeEnabled503RUNNING, token good200+ stub bodyAuto-resume is an inference rather than a guess: a suspended VM issues tokens, which is the order a client meaning to wake a VM by calling it has to work in, and
autoResumeEnabledis the member that says whether it may. The 401/403 split is the guess most worth arguing with — one flat 403 would have been safer, but a missing credential and a rejected one are different failures to a client retrying with a fresh token.Normalizer: a seventh instance of the recorded class
reSecretKeymatchedtoken|secret|credential|passwordagainst the key.TokenPartskeys are header names — the model says so, because some schemes return several auth headers — and the recorded one isX-aws-proxy-auth, which contains none of those words. The value survived normalization, so the fixture held a literal JWE that no generated token could ever equal.authis now in the rule. Compare normalizes the fixture too, so both sides collapse and nothing needed re-recording.Result
58 pass, 0 fail, 24/29 operations exercised, up from 52 and 23. Both token fixtures match, including
auth-token-while-suspended, which stops being the optional step that only ever reported unimplemented.go build,go vet,gofmt,go test -raceclean.Acceptance asked for a curl against a live VM, so:
The marker running 2 → 3 → 4 straight through the suspend is the evidence #12 exists to produce.
Closes #12