import { constants } from "node:fs";
import { createHmac, randomUUID } from "node:crypto";
import { lookup } from "node:dns/promises";
import { access, lstat, realpath } from "node:fs/promises";
import { isIP } from "node:net";
import { isAbsolute, join, relative, resolve } from "node:path";
import { chromium, errors } from "playwright";
const METHOD_VERSION = "ad-verification-v1";
const LAST_REVIEWED = "2026-07-18";
const CANONICAL_PAGE = "https://trueproxies.com/use-cases/ad-verification/";
const SELECTOR_OBSERVATION_WINDOW_MS = 2_500;
const ISO_3166_1_ALPHA_2 = new Set([
"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ",
"BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", "BZ",
"CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ",
"DE", "DJ", "DK", "DM", "DO", "DZ",
"EC", "EE", "EG", "EH", "ER", "ES", "ET",
"FI", "FJ", "FK", "FM", "FO", "FR",
"GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY",
"HK", "HM", "HN", "HR", "HT", "HU",
"ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT",
"JE", "JM", "JO", "JP",
"KE", "KG", "KH", "KI", "KM", "KN", "KP", "KR", "KW", "KY", "KZ",
"LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY",
"MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ",
"NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ",
"OM",
"PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY",
"QA",
"RE", "RO", "RS", "RU", "RW",
"SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ",
"TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ",
"UA", "UG", "UM", "US", "UY", "UZ",
"VA", "VC", "VE", "VG", "VI", "VN", "VU",
"WF", "WS",
"YE", "YT",
"ZA", "ZM", "ZW",
]);
function safe_identifier(value, fallback) {
return /^[a-zA-Z0-9_-]{1,64}$/.test(value ?? "") ? value : fallback;
}
function required_string(value, name) {
if (typeof value !== "string" || value.length === 0) {
throw new Error(name + " is required");
}
return value;
}
function finite_integer(value, name, minimum, maximum) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || !Number.isInteger(parsed)
|| parsed < minimum || parsed > maximum) {
throw new Error(name + " must be an integer from " + minimum + " to " + maximum);
}
return parsed;
}
function strict_boolean(value, name, fallback) {
if (value === undefined) return fallback;
if (value !== "true" && value !== "false") {
throw new Error(name + " must be true or false");
}
return value === "true";
}
function validated_origin(value) {
const url = new URL(required_string(value, "APPROVED_AD_ORIGIN"));
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password
|| url.pathname !== "/" || url.search || url.hash) {
throw new Error("APPROVED_AD_ORIGIN must be a credential-free HTTP(S) origin");
}
return url.origin;
}
function has_path_capability(pathname) {
return pathname.split("/").some((segment) =>
/(?:^|[-_.])(?:auth|capability|download|invite|key|password|secret|session|share|signed|token)(?:$|[-_.])/i.test(segment)
|| /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i.test(segment)
|| (segment.length >= 24 && /^[A-Za-z0-9_-]+$/.test(segment)
&& new Set(segment).size >= 10));
}
function validated_safe_paths(value) {
let paths;
try {
paths = JSON.parse(required_string(value, "APPROVED_AD_PATHS_JSON"));
} catch {
throw new Error("APPROVED_AD_PATHS_JSON must be a JSON array");
}
if (!Array.isArray(paths) || paths.length === 0 || paths.length > 64
|| paths.some((path) => typeof path !== "string"
|| !/^/(?:[A-Za-z0-9._~-]+/)*[A-Za-z0-9._~-]*$/.test(path)
|| path.includes("[redacted-path]")
|| has_path_capability(path))
|| new Set(paths).size !== paths.length) {
throw new Error("APPROVED_AD_PATHS_JSON must contain unique exact safe paths");
}
return new Set(paths);
}
function validated_configured_url(value, name, approved_origin, approved_paths) {
const url = new URL(required_string(value, name));
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password
|| url.origin !== approved_origin || url.search || url.hash
|| !approved_paths.has(url.pathname)) {
throw new Error(name + " must use the approved origin and an exact declared safe path");
}
return url.toString();
}
function validated_fixed_url(value, name) {
const url = new URL(required_string(value, name));
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password
|| url.search || url.hash) {
throw new Error(name + " must be a credential-free HTTP(S) URL without query or fragment");
}
return url.toString();
}
function sanitized_url(value, approved_origin, approved_paths) {
try {
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return null;
if (url.origin !== approved_origin || !approved_paths.has(url.pathname)) {
return url.origin + "/[redacted-path]";
}
return approved_origin + url.pathname;
} catch {
return null;
}
}
function sanitized_country(value) {
return typeof value === "string" && ISO_3166_1_ALPHA_2.has(value) ? value : null;
}
function validated_route_origin(value) {
const origin = value ?? "unknown";
if (!["residential_ipv4", "datacenter", "control", "unknown"].includes(origin)) {
throw new Error("ROUTE_ORIGIN is unsupported");
}
return origin;
}
function validated_route_selector(value) {
if (value === undefined || value === "unknown") return "unknown";
const selector = sanitized_country(value);
if (!selector) throw new Error("ROUTE_SELECTOR must be an uppercase country code or unknown");
return selector;
}
function validated_selector(value, name) {
const selector = value ?? "";
const safe_css = /^(?:#[A-Za-z][A-Za-z0-9_-]*|.[A-Za-z][A-Za-z0-9_-]*|[data-[a-z0-9_-]+(?:=(?:"[^"]{1,128}"|'[^']{1,128}'))?])$/;
if (selector && (selector.length > 200 || !safe_css.test(selector))) {
throw new Error(name + " must be an approved bounded CSS selector");
}
return selector;
}
function validated_attribute(value, name, fallback = "") {
const attribute = value ?? fallback;
if (attribute && !/^(?:data-[a-z0-9_-]+|id|class|aria-[a-z0-9_-]+)$/.test(attribute)) {
throw new Error(name + " is not an approved attribute");
}
return attribute;
}
function validated_reference_key(value) {
const key = required_string(value, "EVIDENCE_REFERENCE_KEY");
if (Buffer.byteLength(key, "utf8") < 32 || new Set(key).size < 12
|| /^(?:changeme|example|password|secret|test)/i.test(key)) {
throw new Error("EVIDENCE_REFERENCE_KEY must be a strong secret with at least 32 bytes");
}
return key;
}
function is_globally_routable_ipv4(value) {
if (isIP(value) !== 4) return false;
const [a, b, c] = value.split(".").map(Number);
return !(a === 0
|| a === 10
|| a === 127
|| (a === 100 && b >= 64 && b <= 127)
|| (a === 169 && b === 254)
|| (a === 172 && b >= 16 && b <= 31)
|| (a === 192 && b === 0 && c === 0)
|| (a === 192 && b === 0 && c === 2)
|| (a === 192 && b === 88 && c === 99)
|| (a === 192 && b === 168)
|| (a === 198 && (b === 18 || b === 19))
|| (a === 198 && b === 51 && c === 100)
|| (a === 203 && b === 0 && c === 113)
|| a >= 224);
}
function is_globally_routable_ip(value) {
const version = isIP(value);
if (version === 4) return is_globally_routable_ipv4(value);
if (version !== 6) return false;
const normalized = value.toLowerCase();
return !(normalized === "::" || normalized === "::1"
|| normalized.startsWith("fc") || normalized.startsWith("fd")
|| /^fe[89ab]/.test(normalized)
|| normalized.startsWith("2001:db8:"));
}
const public_host_cache = new Map();
async function is_public_request_url(value) {
let url;
try {
url = new URL(value);
} catch {
return false;
}
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return false;
if (isIP(url.hostname)) return is_globally_routable_ip(url.hostname);
if (url.hostname === "localhost" || url.hostname.endsWith(".localhost")
|| url.hostname.endsWith(".local") || url.hostname.endsWith(".internal")) return false;
if (!public_host_cache.has(url.hostname)) {
public_host_cache.set(url.hostname, lookup(url.hostname, { all: true })
.then((answers) => answers.length > 0
&& answers.every((answer) => is_globally_routable_ip(answer.address)))
.catch(() => false));
}
return public_host_cache.get(url.hostname);
}
function reference_from_exit_ip(value, key) {
if (!is_globally_routable_ipv4(value)) return null;
return "hmac-sha256:" + createHmac("sha256", key).update(value).digest("hex");
}
async function validated_private_directory(value) {
const directory = required_string(value, "PRIVATE_SCREENSHOT_DIR");
if (!isAbsolute(directory)) {
throw new Error("PRIVATE_SCREENSHOT_DIR must be absolute");
}
if ((await lstat(directory)).isSymbolicLink()) {
throw new Error("PRIVATE_SCREENSHOT_DIR must not be a symbolic link");
}
const resolved_directory = await realpath(directory);
const workspace_relative = relative(resolve(process.cwd()), resolved_directory);
if (workspace_relative === ""
|| (!workspace_relative.startsWith("../") && !isAbsolute(workspace_relative))) {
throw new Error("PRIVATE_SCREENSHOT_DIR must be outside the current workspace");
}
const metadata = await lstat(resolved_directory);
if (!metadata.isDirectory() || metadata.isSymbolicLink()
|| (metadata.mode & 0o077) !== 0 || (metadata.mode & 0o700) !== 0o700) {
throw new Error("PRIVATE_SCREENSHOT_DIR must be a private writable directory with mode 0700");
}
await access(resolved_directory, constants.W_OK);
return resolved_directory;
}
function evaluate_success(observation, rule) {
const final_path = observation.final_url
? new URL(observation.final_url).pathname
: "";
const checks = [
observation.failure_code === null,
Number.isInteger(observation.http_status),
observation.http_status >= rule.accepted_http_status_min,
observation.http_status <= rule.accepted_http_status_max,
rule.required_final_path.length > 0,
final_path === rule.required_final_path,
typeof observation.observed_exit_reference === "string"
&& observation.observed_exit_reference.startsWith("hmac-sha256:"),
observation.route_country_requested === null
|| observation.observed_exit_country === observation.route_country_requested,
];
if (rule.required_creative_id) {
checks.push(observation.creative_id === rule.required_creative_id);
}
if (rule.require_no_challenge) {
checks.push(observation.challenge_result === "not_observed");
}
return checks.every(Boolean);
}
const batch_id = safe_identifier(process.env.AD_BATCH_ID, randomUUID());
const approved_origin = validated_origin(process.env.APPROVED_AD_ORIGIN);
const approved_paths = validated_safe_paths(process.env.APPROVED_AD_PATHS_JSON);
const placement_url = validated_configured_url(
process.env.AD_PLACEMENT_URL,
"AD_PLACEMENT_URL",
approved_origin,
approved_paths,
);
const ip_echo_url = validated_fixed_url(process.env.APPROVED_IP_ECHO_URL, "APPROVED_IP_ECHO_URL");
const evidence_reference_key = validated_reference_key(process.env.EVIDENCE_REFERENCE_KEY);
const screenshot_directory = await validated_private_directory(process.env.PRIVATE_SCREENSHOT_DIR);
const route_origin = validated_route_origin(process.env.ROUTE_ORIGIN);
const route_selector = validated_route_selector(process.env.ROUTE_SELECTOR);
const settling_window_ms = finite_integer(
process.env.SETTLING_WINDOW_MS ?? "1000",
"SETTLING_WINDOW_MS",
0,
5_000,
);
const accepted_http_status_min = finite_integer(
process.env.SUCCESS_STATUS_MIN ?? "200",
"SUCCESS_STATUS_MIN",
100,
599,
);
const accepted_http_status_max = finite_integer(
process.env.SUCCESS_STATUS_MAX ?? "399",
"SUCCESS_STATUS_MAX",
100,
599,
);
if (accepted_http_status_min > accepted_http_status_max) {
throw new Error("SUCCESS_STATUS_MIN must not exceed SUCCESS_STATUS_MAX");
}
const required_final_path = required_string(process.env.SUCCESS_FINAL_PATH, "SUCCESS_FINAL_PATH");
if (!approved_paths.has(required_final_path)) {
throw new Error("SUCCESS_FINAL_PATH must be an exact declared safe path");
}
const success_rule = {
accepted_http_status_min,
accepted_http_status_max,
required_final_path,
required_creative_id: process.env.SUCCESS_CREATIVE_ID ?? "",
require_no_challenge: strict_boolean(
process.env.SUCCESS_REQUIRE_NO_CHALLENGE,
"SUCCESS_REQUIRE_NO_CHALLENGE",
false,
),
};
const challenge_indicator = {
selector: validated_selector(process.env.CHALLENGE_SELECTOR, "CHALLENGE_SELECTOR"),
attribute: validated_attribute(process.env.CHALLENGE_ATTRIBUTE, "CHALLENGE_ATTRIBUTE"),
expected_value: process.env.CHALLENGE_EXPECTED_VALUE ?? "",
};
if (Boolean(challenge_indicator.attribute) !== Boolean(challenge_indicator.expected_value)) {
throw new Error("CHALLENGE_ATTRIBUTE and CHALLENGE_EXPECTED_VALUE must be paired");
}
if (!challenge_indicator.selector
&& (challenge_indicator.attribute || challenge_indicator.expected_value)) {
throw new Error("Challenge attribute/value require a declared selector");
}
const creative_selector = validated_selector(process.env.CREATIVE_SELECTOR, "CREATIVE_SELECTOR");
const creative_attribute = validated_attribute(
process.env.CREATIVE_ATTRIBUTE,
"CREATIVE_ATTRIBUTE",
"data-creative-id",
);
const placement_selector = validated_selector(process.env.PLACEMENT_SELECTOR, "PLACEMENT_SELECTOR");
const placement_attribute = validated_attribute(
process.env.PLACEMENT_ATTRIBUTE,
"PLACEMENT_ATTRIBUTE",
"data-placement-id",
);
const batch_plan = {
batch_id,
campaign_reference: safe_identifier(process.env.CAMPAIGN_REFERENCE, "not_recorded"),
expected_market: route_selector,
schedule: process.env.TEST_SCHEDULE ?? "one controlled observation",
declared_sample_size: finite_integer(
process.env.DECLARED_SAMPLE_SIZE ?? "1",
"DECLARED_SAMPLE_SIZE",
1,
10_000,
),
route_origin,
route_selector,
approved_url_origin: approved_origin,
approved_url_paths_json: JSON.stringify([...approved_paths]),
success_rule,
challenge_indicator,
settling_window_ms,
selector_observation_window_ms: SELECTOR_OBSERVATION_WINDOW_MS,
batch_plan_limitation_notes: [
"A declared plan does not make one observation representative.",
],
};
async function bounded_attribute(page, selector, attribute) {
if (!selector) return { state: "not_tested", value: null };
try {
const locator = page.locator(selector).first();
await locator.waitFor({
state: "attached",
timeout: SELECTOR_OBSERVATION_WINDOW_MS,
});
return {
state: "observed",
value: await locator.getAttribute(attribute, {
timeout: SELECTOR_OBSERVATION_WINDOW_MS,
}),
};
} catch (error) {
if (error instanceof errors.TimeoutError) return { state: "timed_out", value: null };
throw error;
}
}
async function observe_challenge(page, indicator) {
if (!indicator.selector) return "not_tested";
const result = await bounded_attribute(page, indicator.selector, indicator.attribute || "class");
if (result.state === "timed_out") return "not_observed";
if (!indicator.attribute) return "observed";
return result.value === indicator.expected_value ? "observed" : "not_observed";
}
let browser = null;
try {
browser = await chromium.launch({
proxy: {
server: required_string(process.env.TRUEPROXIES_PROXY_URL, "TRUEPROXIES_PROXY_URL"),
username: required_string(process.env.TRUEPROXIES_USERNAME, "TRUEPROXIES_USERNAME"),
password: required_string(process.env.TRUEPROXIES_PASSWORD, "TRUEPROXIES_PASSWORD"),
},
});
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
locale: "en-US",
});
await context.route("**/*", async (route) => {
if (await is_public_request_url(route.request().url())) {
await route.continue();
} else {
await route.abort("blockedbyclient");
}
});
const page = await context.newPage();
const observation_id = randomUUID();
const observation_limitation_notes = [];
let observed_exit_reference = null;
let observed_exit_country = null;
try {
const exit_response = await page.goto(ip_echo_url, {
waitUntil: "domcontentloaded",
timeout: 15_000,
});
if (!exit_response) {
observation_limitation_notes.push("Exit response was not received; reference and country are unavailable.");
} else if (!exit_response.ok()) {
observation_limitation_notes.push("Exit response was not successful; reference and country are unavailable.");
} else {
const exit_payload = await exit_response.json();
observed_exit_reference = reference_from_exit_ip(exit_payload?.ip, evidence_reference_key);
if (!observed_exit_reference) {
observation_limitation_notes.push("Exit response lacked a globally routable IPv4 value; reference is unavailable.");
}
observed_exit_country = sanitized_country(exit_payload?.country_code);
if (!observed_exit_country) {
observation_limitation_notes.push("Exit response lacked a valid country code; country is unavailable.");
}
}
} catch {
observation_limitation_notes.push("Exit request failed; reference and country are unavailable.");
}
if (route_selector !== "unknown" && observed_exit_country
&& observed_exit_country !== route_selector) {
observation_limitation_notes.push("Observed exit country did not match the declared route selector.");
}
const requested_url = sanitized_url(placement_url, approved_origin, approved_paths);
const redirect_chain = [];
let response = null;
let failure_code = null;
const navigation_attempted_at_utc = new Date().toISOString();
try {
response = await page.goto(placement_url, {
waitUntil: "domcontentloaded",
timeout: 30_000,
});
let request = response?.request() ?? null;
while (request) {
const sanitized = sanitized_url(request.url(), approved_origin, approved_paths);
if (sanitized) redirect_chain.unshift(sanitized);
request = request.redirectedFrom();
}
} catch {
failure_code = "navigation_failed";
observation_limitation_notes.push("Navigation failed; raw exception text was not recorded.");
}
if (!failure_code && !response) {
failure_code = "navigation_no_response";
observation_limitation_notes.push("Navigation returned no target response; target evidence was not tested.");
}
let creative_result = { state: "not_tested", value: null };
let placement_result = { state: "not_tested", value: null };
let challenge_result = "not_tested";
let screenshot_reference = null;
if (!failure_code && response) {
await page.waitForTimeout(settling_window_ms);
creative_result = await bounded_attribute(page, creative_selector, creative_attribute);
if (creative_result.state === "timed_out") {
observation_limitation_notes.push("Creative selector timed out within the declared observation window.");
}
placement_result = await bounded_attribute(page, placement_selector, placement_attribute);
if (placement_result.state === "timed_out") {
observation_limitation_notes.push("Placement selector timed out within the declared observation window.");
}
challenge_result = await observe_challenge(page, challenge_indicator);
const screenshot_candidate = "ad-check-" + observation_id + ".png";
try {
await page.screenshot({
path: join(screenshot_directory, screenshot_candidate),
fullPage: true,
});
screenshot_reference = screenshot_candidate;
} catch {
observation_limitation_notes.push("Screenshot capture failed; no screenshot reference was recorded.");
}
} else {
observation_limitation_notes.push(
"Target selectors, challenge indicator, and screenshot were not tested after navigation failure.",
);
}
const observation = {
observation_batch_id: batch_id,
observation_id,
navigation_attempted_at_utc,
route_country_requested: route_selector === "unknown" ? null : route_selector,
route_selector_notes: route_selector === "unknown"
? "No supported route selector was declared."
: "Validated country selector; exit IP recorded separately.",
observed_exit_reference,
observed_exit_country,
network_origin: route_origin,
browser_name: "chromium",
browser_version: browser.version(),
viewport_width_px: page.viewportSize()?.width ?? null,
viewport_height_px: page.viewportSize()?.height ?? null,
locale: "en-US",
consent_state: process.env.AD_TEST_CONSENT_STATE ?? "not_recorded",
account_state: ["signed_out", "authorized_test_account", "not_recorded"].includes(
process.env.AD_TEST_ACCOUNT_STATE ?? "",
) ? process.env.AD_TEST_ACCOUNT_STATE : "not_recorded",
requested_url,
final_url: failure_code ? null : sanitized_url(page.url(), approved_origin, approved_paths),
redirect_chain_json: JSON.stringify(redirect_chain),
http_status: response?.status() ?? null,
creative_id: creative_result.value,
placement_id: placement_result.value,
screenshot_reference,
challenge_result,
success: false,
failure_code,
observation_limitation_notes,
};
observation.success = evaluate_success(observation, success_rule);
if (!observation.success && !observation.failure_code) {
observation.failure_code = "declared_rule_not_met";
}
const observations = [observation];
const challenge_tested = observations.filter(
(item) => item.challenge_result !== "not_tested",
);
const challenge_observed = challenge_tested.filter(
(item) => item.challenge_result === "observed",
);
const batch_summary = {
summary_declared_sample_size: batch_plan.declared_sample_size,
summary_observed_sample_size: observations.length,
summary_successful_observations: observations.filter((item) => item.success).length,
summary_success_rate: observations.length
? observations.filter((item) => item.success).length / observations.length
: null,
summary_challenge_tested_observations: challenge_tested.length,
summary_challenge_observed_observations: challenge_observed.length,
summary_challenge_test_coverage: observations.length
? challenge_tested.length / observations.length
: null,
summary_challenge_rate: challenge_tested.length
? challenge_observed.length / challenge_tested.length
: null,
summary_failed_observations: observations.filter((item) => !item.success).length,
batch_summary_limitation_notes: [
"Challenge rate excludes not_tested observations and must be read with test coverage.",
],
};
const evidence = {
schema_version: 1,
method_version: METHOD_VERSION,
last_reviewed: LAST_REVIEWED,
canonical_page: CANONICAL_PAGE,
artifact_state: "local_observation",
batch_plan,
observations,
batch_summary,
};
console.log(JSON.stringify(evidence, null, 2));
} finally {
await browser?.close();
}