Skip to content

fix(tmproto): StripAccess covers signed-URL residue and unknown asset types - #513

Merged
ohalushchak-exadel merged 2 commits into
mainfrom
ohalushchak-exadel/tmproto-strip-access-signed-url-and-unknown-asset
Sep 8, 2026
Merged

fix(tmproto): StripAccess covers signed-URL residue and unknown asset types#513
ohalushchak-exadel merged 2 commits into
mainfrom
ohalushchak-exadel/tmproto-strip-access-signed-url-and-unknown-asset

Conversation

@ohalushchak-exadel

Copy link
Copy Markdown
Collaborator

Summary

Fills two gaps in Artifact.StripAccess against the artifact-fanout MUST at context-match-request.json §artifact: "Routers MUST remove every asset access object and remove or replace every credential-bearing asset url before forwarding."

Signed URLs on known assets leaked the credential. For ImageAsset / VideoAsset / AudioAsset with Access.Method == 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 held a working credentialed fetch handle. StripAccess now blanks the URL whenever the access declared signed_url. bearer_token and service_account URLs are non-credential-bearing (creds sit in the Access object, not the URL) and survive unchanged.

UnknownAsset bypassed the strip entirely. Forward-compat pass-through of asset types this SDK does not model was re-emitting raw JSON verbatim, so any access object — or signed URL — riding on a future asset shape reached every fan-out target. Their raw JSON is now rewritten in place: access is always removed, and url is removed too when the access object declared method: "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 access removal (with a bearer-token payload to prove the credential is gone), and unknown-asset signed-URL scrubbing. The existing TestArtifact_StripAccess_ZerosAllVariants is extended to assert the audio signed-URL asset's URL is now blank.

Release order

After merge → cut a new tmproto submodule tag → follow-up PR bumps the pin in root and applies the router no-follow-redirect fix on top.

Test plan

  • go test ./... in tmproto, targeting, router — all green
  • CI green after push
  • Manual sanity: a ContextMatchRequest carrying an artifact with a signed-URL asset returns via the router with the URL blanked
  • Manual sanity: a ContextMatchRequest carrying an unknown asset type with an access object has that object stripped from the forwarded payload

… 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>
Comment thread tmproto/artifact.go
aao-secretariat[bot]
aao-secretariat Bot previously approved these changes Sep 8, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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/** or types_gen.go changes).
  • No TMP signing/verification, envelope, or header semantics weakened.
  • No breaking wire/public-API change requiring a conventional-commit marker.
  • high_risk is true only because both changed files match tmproto/**, 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 Raw through map[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>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@ohalushchak-exadel
ohalushchak-exadel merged commit 7f1a640 into main Sep 8, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant