feat: type the full TMP content disclosure ladder - #59
Merged
Conversation
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>
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.
Summary
json.RawMessage/map[string]anypassthroughs with typed Go structs for all three rungs ofContextMatchRequest:artifact,artifact_refs,context_signalsAssetAccessagainst both log leakage (%vredaction) and wire-format cross-contamination (MarshalJSONemits only fields matching the currentMethod)Validate()methods enforcing schema constraints the wire layer silently accepted before, plus(*Artifact).StripAccess()for the router MUST-strip contractResolves #28.
What's typed
context_signals*json.RawMessage*ContextSignalswith topics/taxonomy/sentiment/keywords/embedding fieldsartifact_refs[]map[string]any[]ArtifactRef+ArtifactRefTypeenumartifact*json.RawMessage*Artifact+Assetdiscriminated union (TextAsset/ImageAsset/VideoAsset/AudioAsset/UnknownAssetpassthrough) +AssetAccessfor the three credential methodsHardening (from expert review pass)
AssetAccess.String()/GoString()return<redacted>; verified against%s %v %+v %#vAssetAccess.MarshalJSONdrops fields that don't belong to the current method (e.g., a strayTokenset alongsideMethod: signed_urldoes not reach the wire)Typefield;MarshalJSONwrites the const fromAssetTag(), preventing silent wire corruptionUnknownAsset{Type, Raw}and re-marshal verbatim, so older routers survive newer publisher payloadsAssets.UnmarshalJSONenforcesMaxAssets = 200(schema-matched)Validate()methods cover sentiment enum, BCP-47 language pattern, embedding triad (all three must be set together; dims 64-2048),heading_level1-6,maxLength/maxItems, and themethod-variant required fields onAssetAccess(*Artifact).StripAccess()zerosAccesson every asset; nil-safeFollow-ups filed
ArtifactRef{Type:url}should reject user-specific path/query/fragment; asset URLs need a fetch contract)UntrustedTextwrapper type for prompt-injection carriers (TextAsset.Content, transcripts,ContextSignals.Summary)GCPServiceAccountCredentials/AWSServiceAccountCredentials) — blocked on this PR'sAssetAccessreshapeTest plan
go test ./...in root modulego test ./...ine2e/submodulego test ./...in eachreference/*submodulego vet ./...cleanArtifactReftype, perAssetvariant, perAssetAccessmethodAssetAccessdrops cross-variant fields on marshal%s %v %+v %#vincluding whenAssetAccessis nested insideImageAssetUnknownAssetround-trip preserves unknown fields verbatim on re-marshalMaxAssetsbound rejects oversized arraysValidate()tables cover happy path + each constraint violationContextMatchRequest)🤖 Generated with Claude Code