Skip to content

feat: type the full TMP content disclosure ladder - #59

Merged
bokelley merged 2 commits into
mainfrom
bokelley/tmp-disclosure-ladder
Apr 19, 2026
Merged

feat: type the full TMP content disclosure ladder#59
bokelley merged 2 commits into
mainfrom
bokelley/tmp-disclosure-ladder

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Summary

  • Replaces json.RawMessage / map[string]any passthroughs with typed Go structs for all three rungs of ContextMatchRequest: artifact, artifact_refs, context_signals
  • Hardens the credential-carrying AssetAccess against both log leakage (%v redaction) and wire-format cross-contamination (MarshalJSON emits only fields matching the current Method)
  • Adds Validate() methods enforcing schema constraints the wire layer silently accepted before, plus (*Artifact).StripAccess() for the router MUST-strip contract

Resolves #28.

What's typed

Rung Before After
context_signals *json.RawMessage *ContextSignals with topics/taxonomy/sentiment/keywords/embedding fields
artifact_refs []map[string]any []ArtifactRef + ArtifactRefType enum
artifact *json.RawMessage *Artifact + Asset discriminated union (TextAsset / ImageAsset / VideoAsset / AudioAsset / UnknownAsset passthrough) + AssetAccess for the three credential methods

Hardening (from expert review pass)

  • Credential redactionAssetAccess.String() / GoString() return <redacted>; verified against %s %v %+v %#v
  • Variant isolationAssetAccess.MarshalJSON drops fields that don't belong to the current method (e.g., a stray Token set alongside Method: signed_url does not reach the wire)
  • No forgeable discriminator — Asset structs have no exported Type field; MarshalJSON writes the const from AssetTag(), preventing silent wire corruption
  • Forward-compat passthrough — unknown asset types decode to UnknownAsset{Type, Raw} and re-marshal verbatim, so older routers survive newer publisher payloads
  • DoS boundAssets.UnmarshalJSON enforces MaxAssets = 200 (schema-matched)
  • Spec validationValidate() methods cover sentiment enum, BCP-47 language pattern, embedding triad (all three must be set together; dims 64-2048), heading_level 1-6, maxLength / maxItems, and the method-variant required fields on AssetAccess
  • Router ergonomics(*Artifact).StripAccess() zeros Access on every asset; nil-safe

Follow-ups filed

Test plan

  • go test ./... in root module
  • go test ./... in e2e/ submodule
  • go test ./... in each reference/* submodule
  • go vet ./... clean
  • Round-trip tests per ArtifactRef type, per Asset variant, per AssetAccess method
  • Wire-format tests that AssetAccess drops cross-variant fields on marshal
  • Redaction tests covering %s %v %+v %#v including when AssetAccess is nested inside ImageAsset
  • UnknownAsset round-trip preserves unknown fields verbatim on re-marshal
  • MaxAssets bound rejects oversized arrays
  • Validate() tables cover happy path + each constraint violation
  • Full disclosure-ladder round-trip (all three rungs present on one ContextMatchRequest)

🤖 Generated with Claude Code

bokelley and others added 2 commits April 18, 2026 20:20
Replaces json.RawMessage / map[string]any passthroughs with typed Go structs
for ContextMatchRequest.artifact, artifact_refs, and context_signals.

- ContextSignals: flat struct matching the inline schema.
- ArtifactRef + ArtifactRefType enum (url, url_hash, eidr, gracenote, isrc,
  gtin, rss_guid, isbn, custom).
- Artifact + Asset discriminated union (TextAsset/ImageAsset/VideoAsset/
  AudioAsset) with UnknownAsset forward-compat passthrough.
- AssetAccess with variant-scoped MarshalJSON so a stray Token on
  signed_url can't leak to the wire; String/GoString redaction so %v
  logging can't leak credentials; constructors for each method variant.
- Artifact.StripAccess() for routers that MUST strip credentials before
  fanning out per the AdCP spec.
- Validate() methods on all ladder types enforcing schema constraints
  (sentiment enum, BCP-47 language, embedding triad, maxItems/maxLength,
  asset_access method-variant required fields).
- MaxAssets (200) enforced in Assets.UnmarshalJSON.

Resolves #28.

Follow-ups filed: #49 (URL validation + SSRF), #50 (UntrustedText wrapper
for prompt-injection carriers), #51 (typed per-provider credentials).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- #nosec G101 on the three fake token strings that exercise the AssetAccess
  redaction / drop-on-marshal behavior (the whole point is that these never
  reach logs or the wire).
- S1025: swap fmt.Sprintf("%s", a) for a.String() where %s is the whole
  format string. %v/%+v/%#v stay — they exercise formatter paths that
  differ from .String().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley
bokelley merged commit e96052f into main Apr 19, 2026
5 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.

context_signals field missing from Go TMP schemas

1 participant