Conversation
- Add `GamIntegrationConfig` with enabled, bidders, and force_render options - Implement `IntegrationHeadInjector` to inject GAM config script into <head> - Register GAM integration in the integration builder system - Add unit tests for config script generation Configuration example: ```toml [integrations.gam] enabled = true bidders = ["mocktioneer"] # Only intercept these bidders, empty = all force_render = false # Force render even if GAM has a line item ``` The injected script sets `window.tsGamConfig` which is picked up by the client-side GAM interceptor on initialization.
- Add client-side GAM interceptor that intercepts GPT slotRenderEnded events
- Render Prebid creatives when GAM doesn't have matching line items
- Support multiple rendering methods: iframe src replacement, doc.write, pbjs.renderAd
- Add GamConfig interface for configuration via tsjs.setConfig({ gam: {...} })
- Forward GAM config from core config to GAM integration via lazy loader
- Add comprehensive tests for iframe attribute extraction and interceptor behavior
The GAM interceptor supports:
- Filtering by specific bidders (bidders option)
- Force rendering even when GAM has a line item (forceRender option)
- Auto-initialization on module load
- Stats tracking for debugging
|
|
||
| // Verify this is mostly just an iframe (no complex content after) | ||
| // Allow trailing whitespace, newlines, and closing tag | ||
| const afterIframe = trimmed.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/i, '').trim(); |
Check failure
Code scanning / CodeQL
Incomplete multi-character sanitization High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 5 days ago
In general, to fix incomplete multi‑character sanitization, either (a) apply the replacement repeatedly until it no longer changes the string, or (b) avoid complex multi-character patterns when validating and instead use a simpler, more robust approach. Here, we do not actually need to “sanitize” the whole HTML; we only need to verify that the markup is essentially just a single iframe wrapper (no significant extra content). So we can improve the validation so that it cannot be bypassed by having remaining <iframe text after a single replacement.
The best targeted fix here is to adjust how afterIframe is computed. Currently it does a single replace of one <iframe...>...</iframe> pair. Instead, we can strip all iframe blocks and then check whether anything non-whitespace remains. We can do that by using the same regex with the global (g) modifier and looping until no further changes are made (safe and explicit), or by using a simpler loop. This keeps the external behavior the same (we still accept only creatives that are “mostly just an iframe”) but closes the incomplete sanitization gap. The change is entirely within extractIframeAttrs in crates/js/lib/src/integrations/gam/index.ts, around the existing afterIframe logic. No new imports or exports are required.
Concretely: replace the single-call trimmed.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/i, '').trim(); with a small loop that repeatedly removes all occurrences of <iframe...>...</iframe> until the string stops changing, then trims and checks the result as before. This guarantees that if any <iframe constructs are present, they are all removed before we perform the “significant content” test, eliminating the incomplete multi-character replacement.
| @@ -104,7 +104,14 @@ | ||
|
|
||
| // Verify this is mostly just an iframe (no complex content after) | ||
| // Allow trailing whitespace, newlines, and closing tag | ||
| const afterIframe = trimmed.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/i, '').trim(); | ||
| let afterIframe = trimmed; | ||
| const iframePattern = /<iframe[^>]*>[\s\S]*?<\/iframe>/gi; | ||
| let previous: string; | ||
| do { | ||
| previous = afterIframe; | ||
| afterIframe = afterIframe.replace(iframePattern, ''); | ||
| } while (afterIframe !== previous); | ||
| afterIframe = afterIframe.trim(); | ||
| if (afterIframe.length > 0 && !afterIframe.match(/^[\s\n]*$/)) { | ||
| // Has significant content after iframe, not a simple wrapper | ||
| return null; |
Summary
Combines #240 (backend) and #241 (frontend) into a single PR.
GamIntegrationConfigwithenabled,bidders, andforce_renderoptions. ImplementsIntegrationHeadInjectorto inject GAM config script (window.tsGamConfig) into<head>. Registers GAM integration in the integration builder system.slotRenderEndedevents and renders Prebid creatives when GAM doesn't have matching line items. Supports multiple rendering methods (iframe src replacement, doc.write, iframe.src, pbjs.renderAd fallback). Forwards GAM config from core config to GAM integration via lazy loader.Configuration
Server-side (TOML):
Client-side (JS):
Test plan
npx vitest runpasses (162 tests across 17 files)cargo test -p trusted-server-common -- gampasses (3 tests)Closes #248
Closes #249
Supersedes #240 and #241
Related to #179