feat(forms): gate Form A production embedding

This commit is contained in:
DeCentN2Madness committed 2026-10-28 18:29:32 -07:00
1 parent 1a56cd7603
commit 0eb3fcc3f6
17 files changed
+284 -47

No files matched your search

+64 -9
View File
@@ -36,18 +36,41 @@ function hasTerminalSlash(url) {
return pathname !== '/' && pathname.endsWith('/');
}
function unsafeSignals(html) {
const formAProductionUrls = new Map([
['/contact', 'https://form.jotform.com/262946078314059?formContext=contact&language=en'],
['/es/contact', 'https://form.jotform.com/262946078314059?formContext=contact&language=es'],
['/lp/aba-therapy', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['/lp/aba-insurance', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['/lp/aba-ahcccs', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['/lp/aba-private-pay', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
['/lp/early-intervention', 'https://form.jotform.com/262946078314059?formContext=landing&language=en'],
]);
function iframeSources(html) {
// Astro correctly escapes query separators in serialized HTML attributes.
// Normalize that representation before comparing the fixed allowlisted URL.
return [...html.matchAll(/<iframe\b[^>]*\bsrc=["']([^"']+)["'][^>]*>/gi)].map((match) => match[1].replaceAll('&amp;', '&'));
}
function formAAdapters(html) {
return [...html.matchAll(/<section\b[^>]*\bdata-jotform-form-a\b[^>]*>/gi)].map((match) => match[0]);
}
function unsafeSignals(html, allowApprovedFormA = false) {
const signals = [];
if (/<iframe\b[^>]*\bsrc=["'][^"']*jotform/i.test(html)) signals.push('active Jotform iframe');
if (/262946078314059/i.test(html)) signals.push('isolated-test Form A identifier');
if (!allowApprovedFormA && /<iframe\b[^>]*\bsrc=["'][^"']*jotform/i.test(html)) signals.push('active Jotform iframe');
if (!allowApprovedFormA && /262946078314059/i.test(html)) signals.push('isolated-test Form A identifier');
if (/<script\b[^>]*\bsrc=["'][^"']*(?:jotform|googletagmanager|google-analytics|doubleclick)/i.test(html)) signals.push('active vendor tracking/form script');
if (/data-(?:form|forms)-mode=["']live["']/i.test(html)) signals.push('live form mode');
if (!allowApprovedFormA && /data-(?:form|forms)-mode=["']live["']/i.test(html)) signals.push('live form mode');
if (/api\.azinstitute4autism\.com/i.test(html)) signals.push('production API origin');
if (/AIza[0-9A-Za-z_-]{20,}|-----BEGIN (?:RSA|OPENSSH|EC) PRIVATE KEY-----|(?:api[_-]?key|secret|password)\s*[:=]/i.test(html)) signals.push('credential-like content');
return signals;
}
export async function verifyArtifact(root, mode = 'staging') {
const formAEnabled = mode === 'isolated-test' || mode === 'production';
const production = mode === 'production';
const stagingPolicy = mode === 'staging' || mode === 'isolated-test';
const htmlFiles = (await fg('**/*.html', { cwd: root, absolute: true })).sort();
const routes = [];
const canonicalMap = new Map();
@@ -56,6 +79,7 @@ export async function verifyArtifact(root, mode = 'staging') {
let hreflangLinks = 0;
let activeForms = 0;
let secretSignals = [];
let formAIframes = 0;
for (const file of htmlFiles) {
const html = await readText(file);
@@ -66,12 +90,22 @@ export async function verifyArtifact(root, mode = 'staging') {
const actions = formActions(html);
const nonSearchActions = actions.filter((action) => action && action !== '/search');
const trailingSlashUrls = rootRelativeUrls(html).filter(hasTerminalSlash);
const signals = unsafeSignals(html);
const expectedFormAUrl = formAEnabled ? formAProductionUrls.get(route) : undefined;
const signals = unsafeSignals(html, formAEnabled);
const jotformFrames = iframeSources(html).filter((src) => {
try {
return new URL(src).origin === 'https://form.jotform.com';
} catch {
return false;
}
});
const adapters = formAAdapters(html);
if (!title) errors.push(`${route}: missing title`);
if (!canonical) errors.push(`${route}: missing canonical`);
if (!robots) errors.push(`${route}: missing robots directive`);
if (trailingSlashUrls.length) errors.push(`${route}: root-relative URL(s) end in a slash: ${trailingSlashUrls.join(', ')}`);
if (mode === 'staging' && robots !== 'noindex,nofollow') errors.push(`${route}: staging robots is ${robots ?? 'missing'}`);
if (stagingPolicy && robots !== 'noindex,nofollow') errors.push(`${route}: staging robots is ${robots ?? 'missing'}`);
if (production && robots === 'noindex,nofollow') errors.push(`${route}: production artifact is noindex`);
if (canonical) {
const prior = canonicalMap.get(canonical);
if (prior) errors.push(`${route}: duplicate canonical ${canonical} (also ${prior})`);
@@ -79,6 +113,19 @@ export async function verifyArtifact(root, mode = 'staging') {
}
hreflangLinks += hrefValues(html, 'alternate').filter((href) => href).length;
if (signals.length) secretSignals.push({ route, signals });
if (formAEnabled) {
if (expectedFormAUrl) {
if (jotformFrames.length !== 1 || jotformFrames[0] !== expectedFormAUrl) errors.push(`${route}: missing or invalid approved Form A iframe URL`);
const expectedMode = production ? 'live' : 'isolated-test';
const expectedEnvironment = production ? 'production' : 'isolated-test';
if (adapters.length !== 1 || !adapters[0].includes(`data-form-mode="${expectedMode}"`) || !adapters[0].includes(`data-form-environment="${expectedEnvironment}"`)) {
errors.push(`${route}: Form A adapter lacks the approved ${expectedMode} ${expectedEnvironment} gate`);
}
formAIframes += jotformFrames.length;
} else if (jotformFrames.length || adapters.length || /262946078314059/i.test(html)) {
errors.push(`${route}: unauthorized Form A iframe or identifier in production artifact`);
}
}
if (nonSearchActions.length) activeForms += nonSearchActions.length;
routes.push({ route, title, robots, canonical, nativeForms: actions.length, nonSearchActions: nonSearchActions.length });
}
@@ -87,7 +134,7 @@ export async function verifyArtifact(root, mode = 'staging') {
const sitemap = await fs.readFile(sitemapFile, 'utf8').catch(() => '');
const sitemapRoutes = [...sitemap.matchAll(/<loc>([^<]+)<\/loc>/gi)].map((match) => match[1]);
const reviewIndex = htmlFiles.some((file) => routeFromHtml(file, root) === '/lp');
if (mode === 'staging') {
if (stagingPolicy) {
if (sitemapRoutes.length) errors.push(`staging sitemap contains ${sitemapRoutes.length} URLs`);
if (hreflangLinks) errors.push(`staging emits ${hreflangLinks} hreflang links`);
if (!reviewIndex) errors.push('staging /lp review index is missing');
@@ -112,7 +159,14 @@ export async function verifyArtifact(root, mode = 'staging') {
digest.update(createHash('sha256').update(await fs.readFile(file)).digest('hex'));
digest.update('\n');
}
if (formAResourceFiles.length) errors.push(`isolated-test Form A identifier appears in artifact: ${formAResourceFiles.join(', ')}`);
if (formAEnabled) {
const expectedFormAFiles = new Set([...formAProductionUrls.keys()].map((route) => route === '/' ? 'index.html' : `${route.slice(1)}/index.html`));
const unexpectedFormAFiles = formAResourceFiles.filter((file) => !expectedFormAFiles.has(file));
if (unexpectedFormAFiles.length) errors.push(`Form A identifier appears outside approved production routes: ${unexpectedFormAFiles.join(', ')}`);
if (formAIframes !== formAProductionUrls.size) errors.push(`production artifact has ${formAIframes} approved Form A iframe(s), expected ${formAProductionUrls.size}`);
} else if (formAResourceFiles.length) {
errors.push(`isolated-test Form A identifier appears in artifact: ${formAResourceFiles.join(', ')}`);
}
return {
mode,
htmlRoutes: htmlFiles.length,
@@ -120,6 +174,7 @@ export async function verifyArtifact(root, mode = 'staging') {
hreflangLinks,
reviewIndex,
activeNativeFormActions: activeForms,
formAIframes,
artifactFiles: files.length,
artifactSha256: digest.digest('hex'),
routes,
@@ -132,7 +187,7 @@ export async function verifyArtifact(root, mode = 'staging') {
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
const mode = process.argv.find((arg) => arg.startsWith('--mode='))?.slice('--mode='.length) ?? 'staging';
const root = process.argv.find((arg) => arg.startsWith('--root='))?.slice('--root='.length) ?? 'dist';
if (!['staging', 'indexing'].includes(mode)) throw new Error(`Unsupported mode: ${mode}`);
if (!['staging', 'indexing', 'isolated-test', 'production'].includes(mode)) throw new Error(`Unsupported mode: ${mode}`);
const result = await verifyArtifact(path.resolve(root), mode);
console.log(JSON.stringify(result, null, 2));
if (!result.passed) process.exitCode = 1;