Skip to content

fix(schemas): accept ad-server macros in VAST/DAAST tag URLs#5504

Merged
EmmaLouise2018 merged 2 commits into
mainfrom
EmmaLouise2018/vast-tag-uri-validation-fail
Jun 13, 2026
Merged

fix(schemas): accept ad-server macros in VAST/DAAST tag URLs#5504
EmmaLouise2018 merged 2 commits into
mainfrom
EmmaLouise2018/vast-tag-uri-validation-fail

Conversation

@EmmaLouise2018

@EmmaLouise2018 EmmaLouise2018 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Problem

create_media_buy rejects valid VAST tags:

Validation failed for field '/packages/0/creatives/0/assets/vast_tag/url':
format: must match format "uri"

The failing tag was a standard verification-wrapped CTV tag (IAS wrapping an Extreme Reach VAST endpoint) carrying unsubstituted macros:

https://xx.xx.com/v2/.../...?omidPartner=[OMIDPARTNER]&bundleId=[BUNDLEID]
  &blockedAdTracking=${DC_BLOCKED_AD}&...&gdpr_consent=${GDPR_CONSENT_1002}&...

vast-asset.json and daast-asset.json validate the delivery_type: "url" branch's url with format: "uri", i.e. strict RFC 3986. Square brackets are only legal in a URI as IPv6 authority delimiters, and curly braces are not legal anywhere unencoded — so [OMIDPARTNER] and ${GDPR} fail. But these are exactly the macro conventions the IAB VAST spec and privacy frameworks (TCF, GPP, US Privacy) use: players substitute macros before treating the string as a URL, so the pre-substitution string is never meant to be a syntactically valid URI.

Pre-encoding the delimiters is not a workaround — players and verification vendors match the literal macro token, so an encoded %5BOMIDPARTNER%5D silently never gets substituted. In practice this meant essentially every verification-wrapped CTV tag was unsubmittable via vast_tag URL delivery, with inline XML delivery as the only escape hatch.

Fix

Switch both url fields from format: "uri" to format: "uri-template" (RFC 6570) — the convention url-asset.json already established for AdCP universal macros, so this aligns the tag assets with existing repo precedent rather than inventing a new mechanism.

RFC 6570 makes this work without any custom pattern:

  • [ (0x5B) and ] (0x5D) are permitted literal characters
  • ${GDPR_CONSENT_1002} parses as a literal $ followed by a {GDPR_CONSENT_1002} template expression

Verified against ajv + ajv-formats (the validator stack used in CI): the exact failing production tag passes uri-template and fails uri; junk strings (raw spaces, unbalanced braces) are still rejected.

Schema descriptions on both fields now document that macros are accepted as-is and that buyers MUST NOT pre-encode macro delimiters.

Changes

  • static/schemas/source/core/assets/vast-asset.jsonurl format uriuri-template, description documents macro handling
  • static/schemas/source/core/assets/daast-asset.json — same fix for the audio analogue
  • tests/schema-validation.test.cjs — regression test validating the real verification-wrapped tag against both asset schemas, plus a negative case confirming malformed URLs still fail
  • .changeset/vast-daast-tag-url-macro-validation.md — patch changeset

Known limitation

GAM-style %%MACRO%% placeholders still fail validation — a bare % must be percent-encoded under RFC 6570. VAST 4.x official macros are [MACRO] and privacy conventions use ${...}, so the common cases are covered. Supporting %% would require dropping format validation entirely in favor of a loose pattern; left out of scope here deliberately, flagging for maintainers in case that's wanted.

Testing

  • test:schemas — 19/19 pass (includes the new regression test)
  • test:json-schema — 284 doc blocks pass
  • test:examples — 55 pass
  • Canonical fixture validation (15) and negative fixtures (38) — pass
  • build:schemas + typecheck — clean
  • Pre-existing test:snippets failures (live-SDK snippets in get_products.mdx / sync_creatives.mdx) reproduce identically on a clean tree — unrelated

Note on released versions

This fixes static/schemas/source/ (/schemas/latest/). The error in the report cited the released 3.0.12 bundled schema, which is immutable — callers pinned to 3.0.x get the fix when the next release is cut. Until then, the workaround for macro-laden tags is delivery_type: "inline".

Real-world VAST tag URLs carry unsubstituted macros — [OMIDPARTNER]-style
VAST macros and ${GDPR_CONSENT}-style privacy macros — whose square
brackets and curly braces are illegal in strict RFC 3986 URIs. The
vast-asset and daast-asset url fields used format: "uri", so any
verification-wrapped CTV tag failed create_media_buy validation even
though the tag is valid per the IAB VAST spec (players substitute
macros before treating the string as a URL).

Switch both fields to format: "uri-template" (RFC 6570), the same
convention url-asset.json already uses for AdCP universal macros.
RFC 6570 permits [...] as literals and parses ${MACRO} as a literal
$ plus a {MACRO} expression, so macro-laden tags validate while
malformed strings (raw spaces, control chars) are still rejected.

Adds a regression test validating a real IAS-wrapped CTV tag against
both asset schemas, with a negative case for malformed URLs.
@EmmaLouise2018 EmmaLouise2018 requested a review from bokelley June 12, 2026 21:12

@aao-release-bot aao-release-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Correct fix for the right reason. format: "uri" was the wrong shape for a field that holds a pre-substitution template — the player resolves macros before dereferencing, so the stored tag was never meant to be a syntactically valid RFC 3986 URI.

Things I checked

  • Precedent is real. url-asset.json:16 already uses format: "uri-template" for AdCP universal macros (release-notes #2801, the identical uriuri-template relaxation). This aligns the tag assets with established repo convention rather than inventing a mechanism.
  • Non-breaking → patch is correct. Every formerly-valid RFC 3986 URI still validates under RFC 6570 (literals are a superset for the back-compat direction that matters). Pure relaxation, no previously-accepted value newly rejected. ad-tech-protocol-expert: sound-with-caveats; code-reviewer: clean.
  • oneOf is untouched and stays discriminated (propertyName: delivery_type, vast-asset.json). No undiscriminated oneOf added, no walker regression.
  • Test 12B tightly proves the constraint. The negative fixture (url: 'not a valid uri template') is fully valid except the raw space, and ajv-formats' uri-template regex excludes \x00-\x20 — so only the url format can reject it. It can't false-pass. Every ${...} token in the positive fixture is [A-Z0-9_] (valid RFC 6570 expressions); brackets are permitted literals. Genuine validation, not a silent pass.
  • Changeset present and correctly typed (.changeset/vast-daast-tag-url-macro-validation.md, patch).

Follow-ups (non-blocking — file as issues)

  • mdx sync gap. docs/creative/asset-types.mdx:306 (VAST) and :394 (DAAST) still read "URL endpoint that returns VAST/DAAST XML" with no mention that macros are accepted as-is or that buyers MUST NOT pre-encode delimiters. The schema descriptions now carry that guidance; the human-facing reference should mirror it per the schema↔mdx sync invariant. Accurate-but-incomplete, not drift — doesn't block.
  • Surface the %%MACRO%% limitation where integrators look. The GAM %%...%% gap (bare % illegal under RFC 6570) lives only in the changeset. An integrator hitting that validation failure won't read changesets — consider a line in the schema description or docs.

Minor nits (non-blocking)

  1. Strengthen the negative case. Test 12B's malformed fixture proves spaces are rejected; adding a GAM %%CACHEBUSTER%% URL as a second rejection would pin the documented known-limitation in the test, so a future format widening can't silently regress it. tests/schema-validation.test.cjs ~L1668.

The test plan reports test:schemas 19/19 and build:schemas clean; neither expert could execute the suite locally (no installed deps). Ship it once CI validates green.

Approving on the strength of the #2801 precedent plus a regression test that can't false-pass.

@EmmaLouise2018 EmmaLouise2018 merged commit e0e03fc into main Jun 13, 2026
27 checks passed
@EmmaLouise2018 EmmaLouise2018 deleted the EmmaLouise2018/vast-tag-uri-validation-fail branch June 13, 2026 00:02
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.

2 participants