Cross-Site GA Contamination & Modal Widget Refactor: Anatomy of a Production Incident
Last week we discovered a critical issue: Google Search Console was reporting that quickdumpnow.com (QDN) was being accessed and tracked via jadasailing@gmail.com—the business account for sailjada.com (JADA). While GA tags themselves weren't technically mixed on homepages, the ownership and access control was broken, and we had secondary GA tag gaps on QDN subpages. This post covers the incident investigation, the root cause analysis, and the architectural refactor we performed to fix it.
The Problem: Ownership vs. Implementation
When we audited GA tags across both properties, the initial findings looked clean at first glance:
sailjada.com→G-N6HKL4KLKT(JADA GA4 property)queenofsandiego.com→G-N6HKL4KLKT(same, shared business account—correct)quickdumpnow.com→G-539T97NM1Z(QDN GA4 property, different account)
The homepages themselves had the right tags. But when we scanned subpages—specifically /book, /track, and service area pages like /service-areas/carlsbad/index.html—we found:
- QDN subpages missing GA entirely: Critical booking and tracking pages had no GA tag at all
- GSC ownership problem: The QDN property in Google Search Console was accessible only via
jadasailing@gmail.com, not the QDN business account - Secondary issue discovered during crew page audit: A separate modal widget bug on
sailjada.com/crew/was making the booking flow look unprofessional
Root Cause: Template Generation and Access Control Drift
The GA tag gaps on QDN subpages traced back to how pages were being generated. We found two bulk-injection points:
/Users/cb/Documents/repos/tools/ensure_ga_tag.py— responsible for guaranteeing GA tags on production files/Users/cb/Documents/repos/tools/inject_structured_data.py— handling SAILJADA subpage generation specifically
When QDN pages were pushed to S3 (s3://quickdumpnow-prod-web/), they bypassed the ensure_ga_tag pipeline. This happened because the deployment script (/Users/cb/Documents/repos/tools/deploy_qos.sh) didn't enforce tag validation for QDN properties—it was originally written for JADA only.
The GSC ownership issue was organizational: when quickdumpnow.com's domain was initially set up, it was added to the JADA Google Workspace account instead of creating a separate admin account. This meant all Search Console and Analytics permissions flowed through jadasailing@gmail.com, creating a security and audit nightmare.
The Crew Page Modal Widget Bug
During the investigation, we discovered why sailjada.com/crew/ looked broken. The booking modal should appear when users click any CTA button (Reserve, Check Availability, etc.), but the modal wasn't being loaded on subpages—only on the homepage.
The root cause was a shared JavaScript asset problem. The modal widget was defined inline in the JADA homepage HTML, but subpages included a separate `jada-modal.js` that had several issues:
- Missing global function references for Stripe and GA event tracking
- Incorrect anchor ID mapping for the modal container
- Relative path to a QR code image that didn't exist in subpage contexts
- CSS brace mismatch in f-string template rendering (tracked in the generator)
This is where the f-string bug manifested. In inject_structured_data.py, a line like:
css_block = f"{base_css} .modal {{ display: block; }}"
was being generated without proper brace escaping, resulting in malformed CSS when the template expanded.
Our Fix: Three-Pronged Approach
1. GA Tag Enforcement in Deployment
We modified deploy_qos.sh to call a lint check before any S3 upload:
#!/bin/bash
# deploy_qos.sh (excerpt)
BUCKET="${1}"
SOURCE_DIR="${2}"
# Run GA tag validation
python3 tools/lint_format_template.py --check-ga "${SOURCE_DIR}" || {
echo "ERROR: GA tag validation failed. Aborting deployment."
exit 4
}
# Proceed with S3 sync
aws s3 sync "${SOURCE_DIR}" "s3://${BUCKET}/" --delete
The lint script (tools/lint_format_template.py) now enforces that every HTML file has either a GA4 tag matching its domain's property OR a documented exemption. QDN pages must have G-539T97NM1Z, JADA pages must have G-N6HKL4KLKT.
2. Shared Modal Widget Asset Creation
We extracted the modal widget logic from the homepage and created a universal jada-modal.js asset served from CloudFront distribution d2xjf4k5n7p9r2q1.cloudfront.net:
// jada-modal.js (shared asset)
(function() {
window.openJADABookingModal = function(options) {
// Initialize Stripe if needed
if (!window.Stripe) {
console.error('Stripe.js not loaded');
return;
}
// Show modal, trigger GA event
gtag('event', 'booking_modal_open', {
page_location: window.location.href
});
// ... modal rendering logic
};
})();
Every subpage now includes a single script tag pointing to this asset, plus a small inline script that binds all CTA buttons to the modal trigger.
3. QDN Subpage GA Tag Patching
We synced down the five QDN pages that were missing GA tags from S3 (s3://quickdumpnow-prod-web/):
/book/index.html/track/index.html/service-areas/carlsbad/index.html/service-areas/oceanside/index.html/service-areas/escondido/index.html
Each received the correct GA4 measurement ID injected into the <head> section, then redeployed to staging for validation before promotion to production.
Infrastructure & Deployment Flow
Our deployment pipeline now looks