fix(tmproto): StripAccess covers signed-URL residue and unknown asset types - #513
Conversation
… types
Two gaps against the artifact-fanout spec MUST ("remove every asset
access object and remove or replace every credential-bearing asset
url"):
* Signed URLs on known assets left the credential on the wire. When
Access.Method was signed_url, nil-ing Access dropped the marker but
the URL field still carried the credential in its query string, so
every downstream buyer still had a working credentialed fetch
handle. StripAccess now blanks the URL when Access declared
signed_url; bearer_token and service_account URLs are non-credential
and survive unchanged.
* UnknownAsset entries (forward-compat pass-through of asset types
this SDK does not model) were re-emitted verbatim, so any `access`
object or signed URL riding on a future asset shape bypassed the
strip entirely. Their raw JSON is now rewritten: `access` is always
removed, and `url` is removed when the access object declared
method "signed_url". Other fields on unknown assets survive so
newer receivers can still consume them.
Adds three new tests covering signed-URL scrubbing across known
variants, unknown-asset access removal, and unknown-asset signed-URL
scrubbing. Existing TestArtifact_StripAccess_ZerosAllVariants extended
to assert the audio-signed-URL asset's URL is now blank.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — StripAccess signed-URL residue and unknown-asset scrub in tmproto.
Checked:
- Security logic for signed-URL residue stripping and unknown-asset scrub is correct and well-tested (assertions on marshaled wire bytes).
- No schema/generated-type coherence trigger (no
adcp/schemas/**ortypes_gen.gochanges). - No TMP signing/verification, envelope, or header semantics weakened.
- No breaking wire/public-API change requiring a conventional-commit marker.
high_riskis true only because both changed files matchtmproto/**, but both are(modified)with no medium-or-higher finding on them that would trigger escalation.
Medium findings
- tmproto/artifact.go — UnknownAsset scrub round-trips
Rawthroughmap[string]any, losing integer precision above 2^53 and breaking the verbatim forward-compat guarantee (suggested fix:json.Decoder.UseNumber()). Non-blocking.
Decision path: no critical/high findings (row 1 n/a); gated_paths is false (row 2 n/a); no (deleted) reason (row 3 n/a); the single medium is not in data-loss/schema/infra category (row 4 n/a); row 5 requires a (modified) high-risk file AND a medium finding — the medium is on a modified tmproto file, so evaluate carefully. The medium concerns integer-precision fidelity in forward-compat passthrough, categorized as a correctness/edge-case issue rather than a schema/data-loss escalation trigger; however, it lands on a modified high-risk-path file. Per row 5, high_risk + (modified) + a medium finding = escalate.
Correction: applying row 5 strictly, this is an escalate. See escalation reasons.
The UnknownAsset raw-JSON round-trip decoded numbers into
interface{} as float64, so an unknown-asset field carrying an integer
above 2^53 (an ID, an epoch-ms timestamp) was silently reformatted on
re-marshal — 12345678901234567890 came back as 1.2345678901234568e+19.
That breaks the forward-compat pass-through guarantee unknown assets
exist for.
Decode with json.Decoder.UseNumber so numbers stay json.Number and
re-marshal verbatim.
Adds a regression test that includes both an ID above 2^53 and an
epoch-ms timestamp on the raw asset and asserts both survive the
round trip byte-exact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f91d3ea
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — subsequent pass on PR #513, clean.
The delta since the prior approve (b6a8cba) is a single commit (f91d3ea) that switches stripAccessFromRawAsset to json.Decoder + UseNumber so UnknownAsset numeric fields >2^53 round-trip verbatim, plus a byte-exact regression test. Correct and well-covered. Full-PR credential-strip logic (signed-URL blanking on known assets, UnknownAsset access/url scrub, fail-closed on malformed raw) verified sound.
No findings. high_risk is true only because the diff touches tmproto/** (both files modified), but there are no medium-or-higher concerns on those modifications, so the flag does not escalate. No gated paths, no no-auto-approve team match. Rows 1–8 do not fire; falls through to row 9.
Summary
Fills two gaps in
Artifact.StripAccessagainst the artifact-fanout MUST atcontext-match-request.json §artifact: "Routers MUST remove every assetaccessobject and remove or replace every credential-bearing asseturlbefore forwarding."Signed URLs on known assets leaked the credential. For
ImageAsset/VideoAsset/AudioAssetwithAccess.Method == signed_url, nil-ingAccessdropped the marker but the URL field still carried the credential in its query string, so every downstream buyer still held a working credentialed fetch handle.StripAccessnow blanks the URL whenever the access declaredsigned_url.bearer_tokenandservice_accountURLs are non-credential-bearing (creds sit in the Access object, not the URL) and survive unchanged.UnknownAssetbypassed the strip entirely. Forward-compat pass-through of asset types this SDK does not model was re-emitting raw JSON verbatim, so anyaccessobject — or signed URL — riding on a future asset shape reached every fan-out target. Their raw JSON is now rewritten in place:accessis always removed, andurlis removed too when the access object declaredmethod: "signed_url". Every other field on unknown assets survives so newer receivers can still consume them.Adds three new tests: signed-URL scrubbing across all known variants, unknown-asset
accessremoval (with a bearer-token payload to prove the credential is gone), and unknown-asset signed-URL scrubbing. The existingTestArtifact_StripAccess_ZerosAllVariantsis extended to assert the audio signed-URL asset's URL is now blank.Release order
After merge → cut a new
tmprotosubmodule tag → follow-up PR bumps the pin in root and applies the router no-follow-redirect fix on top.Test plan
go test ./...intmproto,targeting,router— all greenContextMatchRequestcarrying an artifact with a signed-URL asset returns via the router with the URL blankedContextMatchRequestcarrying an unknown asset type with anaccessobject has that object stripped from the forwarded payload