@dreamdata_io/verify-tracking 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +756 -0
- package/dist/ai/checker.js +222 -0
- package/dist/ai/checker.js.map +1 -0
- package/dist/ai/checklist.js +190 -0
- package/dist/ai/checklist.js.map +1 -0
- package/dist/ai/cli-presets.js +27 -0
- package/dist/ai/cli-presets.js.map +1 -0
- package/dist/ai/evidence-view.js +143 -0
- package/dist/ai/evidence-view.js.map +1 -0
- package/dist/ai/local-cli.js +58 -0
- package/dist/ai/local-cli.js.map +1 -0
- package/dist/ai/openai-compatible.js +78 -0
- package/dist/ai/openai-compatible.js.map +1 -0
- package/dist/ai/response-parser.js +105 -0
- package/dist/ai/response-parser.js.map +1 -0
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/analysis/consent-mode.js +225 -0
- package/dist/analysis/consent-mode.js.map +1 -0
- package/dist/analysis/deployment.js +2658 -0
- package/dist/analysis/deployment.js.map +1 -0
- package/dist/analysis/diagnosis/copy.en.js +303 -0
- package/dist/analysis/diagnosis/copy.en.js.map +1 -0
- package/dist/analysis/diagnosis/diagnose.js +172 -0
- package/dist/analysis/diagnosis/diagnose.js.map +1 -0
- package/dist/analysis/diagnosis/docs.js +118 -0
- package/dist/analysis/diagnosis/docs.js.map +1 -0
- package/dist/analysis/diagnosis/index.js +5 -0
- package/dist/analysis/diagnosis/index.js.map +1 -0
- package/dist/analysis/diagnosis/rules.js +656 -0
- package/dist/analysis/diagnosis/rules.js.map +1 -0
- package/dist/analysis/diagnosis/types.js +2 -0
- package/dist/analysis/diagnosis/types.js.map +1 -0
- package/dist/cli.js +344 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.js +100 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/preflight.js +167 -0
- package/dist/config/preflight.js.map +1 -0
- package/dist/config/user-config.js +79 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/evidence/collect.js +527 -0
- package/dist/evidence/collect.js.map +1 -0
- package/dist/evidence/consent-accept.js +377 -0
- package/dist/evidence/consent-accept.js.map +1 -0
- package/dist/evidence/consent.js +340 -0
- package/dist/evidence/consent.js.map +1 -0
- package/dist/evidence/constants.js +615 -0
- package/dist/evidence/constants.js.map +1 -0
- package/dist/evidence/globals.js +132 -0
- package/dist/evidence/globals.js.map +1 -0
- package/dist/evidence/gtm-container.js +432 -0
- package/dist/evidence/gtm-container.js.map +1 -0
- package/dist/evidence/heuristic-banner.js +107 -0
- package/dist/evidence/heuristic-banner.js.map +1 -0
- package/dist/evidence/http-probe.js +139 -0
- package/dist/evidence/http-probe.js.map +1 -0
- package/dist/evidence/init-script.js +112 -0
- package/dist/evidence/init-script.js.map +1 -0
- package/dist/evidence/network.js +365 -0
- package/dist/evidence/network.js.map +1 -0
- package/dist/evidence/preference-center.js +368 -0
- package/dist/evidence/preference-center.js.map +1 -0
- package/dist/evidence/script.js +425 -0
- package/dist/evidence/script.js.map +1 -0
- package/dist/evidence/second-page.js +332 -0
- package/dist/evidence/second-page.js.map +1 -0
- package/dist/evidence/selector-probe.js +53 -0
- package/dist/evidence/selector-probe.js.map +1 -0
- package/dist/evidence/stealth.js +21 -0
- package/dist/evidence/stealth.js.map +1 -0
- package/dist/evidence/types.js +14 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/interactive/prompt.js +31 -0
- package/dist/interactive/prompt.js.map +1 -0
- package/dist/mcp/server.js +308 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/summary.js +41 -0
- package/dist/mcp/summary.js.map +1 -0
- package/dist/report/context.js +184 -0
- package/dist/report/context.js.map +1 -0
- package/dist/report/projection.js +125 -0
- package/dist/report/projection.js.map +1 -0
- package/dist/report/quote-check.js +90 -0
- package/dist/report/quote-check.js.map +1 -0
- package/dist/report/render.js +178 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/serializers.js +124 -0
- package/dist/report/serializers.js.map +1 -0
- package/dist/report/store.js +139 -0
- package/dist/report/store.js.map +1 -0
- package/dist/run/verify.js +77 -0
- package/dist/run/verify.js.map +1 -0
- package/dist/utils/duration.js +10 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/errors.js +17 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.js +59 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/runTimingsLog.js +27 -0
- package/dist/utils/runTimingsLog.js.map +1 -0
- package/dist/utils/slug.js +27 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -0
- package/scripts/install.sh +314 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
import { CMP_SCRIPT_BLOCK_MARKERS, COOKIELESS_BUNDLE_ELEMENT_ID, DREAMDATA_COOKIELESS_SCRIPT_FILE, DREAMDATA_COOKIELESS_SCRIPT_HOST, DREAMDATA_NORMAL_SCRIPT_FILE, DREAMDATA_ORIGIN_IN_SNIPPET, GTM_CONTAINER_ID_IN_TEXT, GTM_CONTAINER_ID_PATTERN, gtmContainerUrl, HELPER_SCRIPT_PATH_MARKERS, isDreamdataUrl, NORMAL_BUNDLE_ELEMENT_ID, NORMAL_COMPANION_REFERENCE, THIRD_PARTY_SEGMENT_SCRIPT_HOST, WRITE_KEY_FROM_LOAD_CALL, WRITE_KEY_FROM_SCRIPT_SRC, WRITE_KEY_FROM_SNIPPET, } from "./constants.js";
|
|
3
|
+
import { parseGtmResource, readDreamdataTags } from "./gtm-container.js";
|
|
4
|
+
/** A page can name more containers than are worth fetching; keep it bounded. */
|
|
5
|
+
const MAX_CONTAINERS_SCANNED = 5;
|
|
6
|
+
function attr(tag, name) {
|
|
7
|
+
const match = new RegExp(`\\s${name}=["']([^"']*)["']`, "i").exec(tag);
|
|
8
|
+
return match ? match[1] : null;
|
|
9
|
+
}
|
|
10
|
+
/** Which CMP, if any, has neutralised this tag instead of removing it. */
|
|
11
|
+
function blockedBy(tag) {
|
|
12
|
+
return CMP_SCRIPT_BLOCK_MARKERS.find((marker) => marker.pattern.test(tag))?.vendor ?? null;
|
|
13
|
+
}
|
|
14
|
+
function isHelperScript(src) {
|
|
15
|
+
return HELPER_SCRIPT_PATH_MARKERS.some((marker) => src.includes(marker));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Classifies one tag/snippet by which bundle it actually loads.
|
|
19
|
+
*
|
|
20
|
+
* The two kinds are decided independently — the previous
|
|
21
|
+
* `normal = present && !cookieless` shortcut could only ever report one
|
|
22
|
+
* bundle per page, so a dual install read as cookieless-only and then as
|
|
23
|
+
* "cookieless by design".
|
|
24
|
+
*/
|
|
25
|
+
export function classifyBundleText(text) {
|
|
26
|
+
if (isHelperScript(text))
|
|
27
|
+
return "helper";
|
|
28
|
+
if (text.includes(DREAMDATA_COOKIELESS_SCRIPT_FILE) ||
|
|
29
|
+
text.includes(DREAMDATA_COOKIELESS_SCRIPT_HOST) ||
|
|
30
|
+
text.includes(COOKIELESS_BUNDLE_ELEMENT_ID)) {
|
|
31
|
+
return "cookieless";
|
|
32
|
+
}
|
|
33
|
+
if (text.includes(DREAMDATA_NORMAL_SCRIPT_FILE) || NORMAL_COMPANION_REFERENCE.test(text))
|
|
34
|
+
return "normal";
|
|
35
|
+
// A Dreamdata origin with no recognisable bundle filename: the tag belongs
|
|
36
|
+
// to the install (the caller already filtered by host) but which bundle it
|
|
37
|
+
// serves cannot be read off the text, so say so rather than guessing.
|
|
38
|
+
return "unknown";
|
|
39
|
+
}
|
|
40
|
+
function extractWriteKey(candidates) {
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
const key = WRITE_KEY_FROM_SCRIPT_SRC.exec(candidate)?.[1] ??
|
|
43
|
+
WRITE_KEY_FROM_SNIPPET.exec(candidate)?.[1] ??
|
|
44
|
+
WRITE_KEY_FROM_LOAD_CALL.exec(candidate)?.[1] ??
|
|
45
|
+
null;
|
|
46
|
+
if (key !== null)
|
|
47
|
+
return key;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Finds every installed tracking bundle. Matches script tags whose *src*
|
|
53
|
+
* points at a configured Dreamdata host, and inline snippets that reference
|
|
54
|
+
* one — never a tag that merely contains the word "dreamdata" (e.g. another
|
|
55
|
+
* vendor's `referer=dreamdata.io` query string).
|
|
56
|
+
*
|
|
57
|
+
* Tags a CMP has neutralised (type="text/plain" and friends) are still
|
|
58
|
+
* matched and reported with `blockedBy` set: "installed but gated" and "not
|
|
59
|
+
* installed" are the two states this whole tool has to tell apart.
|
|
60
|
+
*/
|
|
61
|
+
export async function readScriptPresence(page, extraHosts) {
|
|
62
|
+
return readScriptPresenceFromHtml(await page.content(), page.url(), extraHosts);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The same detection over markup we hold as a string rather than a live DOM.
|
|
66
|
+
*
|
|
67
|
+
* Split out because a rendered DOM is not the only place this question gets
|
|
68
|
+
* asked. When a bot wall serves the browser an interstitial instead of the
|
|
69
|
+
* site, the DOM has nothing in it and every field below reads "not
|
|
70
|
+
* installed" — while a plain HTTP GET of the same URL is served the real
|
|
71
|
+
* markup (see http-probe.ts). One detector, two sources.
|
|
72
|
+
*/
|
|
73
|
+
export function readScriptPresenceFromHtml(html, pageUrl, extraHosts) {
|
|
74
|
+
const resolve = (src) => {
|
|
75
|
+
try {
|
|
76
|
+
return new URL(src, pageUrl).toString();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return src;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
// Match every script tag with a src, then decide by its resolved *hostname*.
|
|
83
|
+
// Testing the whole URL would let a third party's `?referer=dreamdata.io`
|
|
84
|
+
// query string masquerade as an install.
|
|
85
|
+
const srcTags = [...html.matchAll(/<script[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi)]
|
|
86
|
+
.map((m) => ({ tag: m[0], src: resolve(m[1]) }))
|
|
87
|
+
.filter((entry) => isDreamdataUrl(entry.src, extraHosts));
|
|
88
|
+
// An inline snippet counts only when it writes out a Dreamdata origin, so a
|
|
89
|
+
// real Segment install (cdn.segment.com) is not mistaken for a Dreamdata one.
|
|
90
|
+
const inlineTags = [...html.matchAll(/<script(?![^>]*\ssrc=)[^>]*>([\s\S]*?)<\/script>/gi)]
|
|
91
|
+
.map((m) => ({ tag: m[0], code: m[1] }))
|
|
92
|
+
.filter((entry) => DREAMDATA_ORIGIN_IN_SNIPPET.test(entry.tag) || extraHosts.some((host) => entry.tag.includes(host)));
|
|
93
|
+
const matches = [
|
|
94
|
+
...srcTags.map((entry) => ({
|
|
95
|
+
kind: classifyBundleText(entry.src + entry.tag),
|
|
96
|
+
src: entry.src,
|
|
97
|
+
elementId: attr(entry.tag, "id"),
|
|
98
|
+
type: attr(entry.tag, "type"),
|
|
99
|
+
blockedBy: blockedBy(entry.tag),
|
|
100
|
+
inline: false,
|
|
101
|
+
})),
|
|
102
|
+
...inlineTags.map((entry) => ({
|
|
103
|
+
kind: classifyBundleText(entry.tag),
|
|
104
|
+
src: null,
|
|
105
|
+
elementId: attr(entry.tag, "id"),
|
|
106
|
+
type: attr(entry.tag, "type"),
|
|
107
|
+
blockedBy: blockedBy(entry.tag),
|
|
108
|
+
inline: true,
|
|
109
|
+
})),
|
|
110
|
+
];
|
|
111
|
+
const bundles = matches.filter((m) => m.kind !== "helper");
|
|
112
|
+
const present = bundles.length > 0;
|
|
113
|
+
// Several inline scripts may name a Dreamdata origin (a CSP string, an image
|
|
114
|
+
// URL). Prefer one that actually configures a writeKey — that is the loader
|
|
115
|
+
// snippet; anything else is an incidental mention.
|
|
116
|
+
const loaderSnippet = inlineTags.find((entry) => WRITE_KEY_FROM_SNIPPET.test(entry.tag) || WRITE_KEY_FROM_LOAD_CALL.test(entry.tag))
|
|
117
|
+
?.tag ?? null;
|
|
118
|
+
const primary = srcTags.find((entry) => !isHelperScript(entry.src))?.tag ?? loaderSnippet ?? inlineTags[0]?.tag ?? null;
|
|
119
|
+
// Look for the key in the bundle src, then in the loader snippet. Once the
|
|
120
|
+
// bundle has loaded, the src is the primary match but usually carries no
|
|
121
|
+
// key — falling back to the loader keeps the report stable across the pre-
|
|
122
|
+
// and post-consent loads of the same page.
|
|
123
|
+
const writeKey = extractWriteKey([
|
|
124
|
+
srcTags.find((entry) => !isHelperScript(entry.src))?.src,
|
|
125
|
+
loaderSnippet,
|
|
126
|
+
primary,
|
|
127
|
+
...matches.map((m) => m.src),
|
|
128
|
+
].filter((v) => typeof v === "string"));
|
|
129
|
+
/** A tag whose companion reference proves the *other* bundle is expected. */
|
|
130
|
+
const cookielessLoaderNamingCompanion = inlineTags.find((entry) => classifyBundleText(entry.tag) === "cookieless" && NORMAL_COMPANION_REFERENCE.test(entry.code));
|
|
131
|
+
return {
|
|
132
|
+
present,
|
|
133
|
+
writeKey,
|
|
134
|
+
htmlSnippet: primary?.slice(0, 2000) ?? null,
|
|
135
|
+
// Reported independently of which tag matched, so it stays true whether or
|
|
136
|
+
// not the bundle has loaded yet.
|
|
137
|
+
segmentCompatSnippet: loaderSnippet !== null,
|
|
138
|
+
helperScriptOnly: !present && matches.length > 0,
|
|
139
|
+
cookielessSnippet: matches.some((m) => m.kind === "cookieless"),
|
|
140
|
+
normalSnippet: matches.some((m) => m.kind === "normal"),
|
|
141
|
+
matches,
|
|
142
|
+
cookielessLoaderNamesNormalCompanion: cookielessLoaderNamingCompanion !== undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* A real (non-Dreamdata) Segment install: a `<script src>` resolving to
|
|
147
|
+
* Segment's own CDN.
|
|
148
|
+
*
|
|
149
|
+
* `readScriptPresenceFromHtml` above filters everything to Dreamdata-owned
|
|
150
|
+
* hosts by design, so a genuine Segment tag never reaches it — this is the
|
|
151
|
+
* separate, narrower check for exactly that tag. It exists because a
|
|
152
|
+
* customer whose Dreamdata connection is a server-side Segment destination
|
|
153
|
+
* (see ThirdPartySegmentEvidence) has NO Dreamdata-hosted script on the page
|
|
154
|
+
* at all; this is the one piece of client-side evidence such a deployment
|
|
155
|
+
* leaves.
|
|
156
|
+
*/
|
|
157
|
+
export function detectThirdPartySegmentScript(html, pageUrl) {
|
|
158
|
+
const resolve = (src) => {
|
|
159
|
+
try {
|
|
160
|
+
return new URL(src, pageUrl).toString();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return src;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const match = [...html.matchAll(/<script[^>]*\ssrc=["']([^"']+)["'][^>]*>/gi)]
|
|
167
|
+
.map((m) => resolve(m[1]))
|
|
168
|
+
.find((src) => {
|
|
169
|
+
try {
|
|
170
|
+
return new URL(src).hostname.toLowerCase() === THIRD_PARTY_SEGMENT_SCRIPT_HOST;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return { scriptDetected: match !== undefined, scriptSrc: match ?? null };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Every GTM container id a blob of text names.
|
|
180
|
+
*
|
|
181
|
+
* Frameworks often keep the id in page config and load GTM later (or not at
|
|
182
|
+
* all in a headless run), so the runtime object can be empty on a site whose
|
|
183
|
+
* container is exactly what we need to read. The id format is specific enough
|
|
184
|
+
* to lift straight out of the markup.
|
|
185
|
+
*/
|
|
186
|
+
export function gtmContainerIdsFromText(text) {
|
|
187
|
+
return [...new Set((text.match(GTM_CONTAINER_ID_IN_TEXT) ?? []).map((id) => id.toUpperCase()))].filter((id) => GTM_CONTAINER_ID_PATTERN.test(id));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Reads the published GTM container(s) the page loads.
|
|
191
|
+
*
|
|
192
|
+
* A tag-manager-injected bundle never enters the DOM until its trigger and
|
|
193
|
+
* consent conditions are met, so the DOM cannot answer "is a normal bundle
|
|
194
|
+
* part of this deployment?" for a GTM install — the container can. This is
|
|
195
|
+
* the difference between reporting workvivo.com as cookieless-by-design and
|
|
196
|
+
* reporting it as a normal bundle gated on analytics_storage.
|
|
197
|
+
*/
|
|
198
|
+
export async function scanTagManagerContainers(page, timeoutMs,
|
|
199
|
+
/** Shared across a run's phases so one container is fetched once, not per phase. */
|
|
200
|
+
cache = new Map(),
|
|
201
|
+
/**
|
|
202
|
+
* Container ids found somewhere other than this page — in practice the
|
|
203
|
+
* run's plain-HTTP fetch of the same URL. A bot wall leaves the DOM with no
|
|
204
|
+
* container id in it at all, and without these the scan reports
|
|
205
|
+
* `scanned: false` on a site whose container is the only place the bundle
|
|
206
|
+
* appears. See http-probe.ts.
|
|
207
|
+
*/
|
|
208
|
+
extraContainerIds = []) {
|
|
209
|
+
const errors = [];
|
|
210
|
+
const containerIds = await page
|
|
211
|
+
.evaluate(() => {
|
|
212
|
+
const w = window;
|
|
213
|
+
const fromRuntime = Object.keys(w.google_tag_manager ?? {});
|
|
214
|
+
// Server-side/first-party GTM loads gtm.js from the site's own domain,
|
|
215
|
+
// so the runtime object is often the only place its id shows up.
|
|
216
|
+
const fromTags = Array.from(document.querySelectorAll("script[src]"))
|
|
217
|
+
.map((s) => {
|
|
218
|
+
try {
|
|
219
|
+
return new URL(s.src).searchParams.get("id") ?? "";
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
.filter(Boolean);
|
|
226
|
+
return [...new Set([...fromRuntime, ...fromTags])];
|
|
227
|
+
})
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
errors.push(`Could not read tag manager container ids: ${err.message}`);
|
|
230
|
+
return [];
|
|
231
|
+
});
|
|
232
|
+
const fromMarkup = await page
|
|
233
|
+
.content()
|
|
234
|
+
.then(gtmContainerIdsFromText)
|
|
235
|
+
.catch(() => []);
|
|
236
|
+
const gtmIds = [...new Set([...containerIds, ...fromMarkup, ...extraContainerIds])]
|
|
237
|
+
.filter((id) => GTM_CONTAINER_ID_PATTERN.test(id))
|
|
238
|
+
.slice(0, MAX_CONTAINERS_SCANNED);
|
|
239
|
+
if (gtmIds.length === 0) {
|
|
240
|
+
return {
|
|
241
|
+
scanned: false,
|
|
242
|
+
containerIds: [],
|
|
243
|
+
containers: [],
|
|
244
|
+
normalBundleTagFound: false,
|
|
245
|
+
cookielessBundleTagFound: false,
|
|
246
|
+
thirdPartySegmentTagFound: false,
|
|
247
|
+
normalBundleConsentGate: null,
|
|
248
|
+
normalBundleBlockingTriggers: null,
|
|
249
|
+
errors,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const containers = [];
|
|
253
|
+
// Deliberately NOT derived from `containers`: these stay the substring read
|
|
254
|
+
// on the raw body, which is the broader signal. A container whose JSON this
|
|
255
|
+
// module cannot parse still plainly contains the filename, and narrowing
|
|
256
|
+
// these to the parsed view would make such an install read as having no
|
|
257
|
+
// Dreamdata tag at all — which is what cookielessAbsentByDesign turns into
|
|
258
|
+
// a not_applicable, so beckman.com's deliberate standard-only install would
|
|
259
|
+
// start reading as a failure.
|
|
260
|
+
let normalBundleTagFound = false;
|
|
261
|
+
let cookielessBundleTagFound = false;
|
|
262
|
+
let thirdPartySegmentTagFound = false;
|
|
263
|
+
let normalBundleConsentGate = null;
|
|
264
|
+
let normalBundleBlockingTriggers = null;
|
|
265
|
+
// gtmIds is already de-duplicated (see the Set above), so fetching every
|
|
266
|
+
// container concurrently is safe — nothing here writes to `cache` twice
|
|
267
|
+
// for the same id within one call.
|
|
268
|
+
const bodies = await Promise.all(gtmIds.map(async (id) => {
|
|
269
|
+
if (cache.has(id))
|
|
270
|
+
return [id, cache.get(id)];
|
|
271
|
+
const body = await page.request
|
|
272
|
+
.get(gtmContainerUrl(id), { timeout: Math.min(10_000, timeoutMs) })
|
|
273
|
+
.then((res) => (res.ok() ? res.text() : Promise.reject(new Error(`HTTP ${res.status()}`))))
|
|
274
|
+
.catch((err) => {
|
|
275
|
+
logger.debug(`GTM container ${id} unreadable`, err);
|
|
276
|
+
errors.push(`Container ${id} could not be read: ${err.message}`);
|
|
277
|
+
return null;
|
|
278
|
+
});
|
|
279
|
+
cache.set(id, body);
|
|
280
|
+
return [id, body];
|
|
281
|
+
}));
|
|
282
|
+
for (const [id, body] of bodies) {
|
|
283
|
+
if (body === null) {
|
|
284
|
+
// The fetch failure is already in `errors`; repeating it here is what
|
|
285
|
+
// lets a reader tell "this container has no Dreamdata tag" apart from
|
|
286
|
+
// "nobody could look" — credo.ai's second container 404s, and without
|
|
287
|
+
// this the report describes it the same way as a clean container.
|
|
288
|
+
containers.push({
|
|
289
|
+
containerId: id,
|
|
290
|
+
read: false,
|
|
291
|
+
error: errors.find((message) => message.startsWith(`Container ${id} `)) ?? "could not be read",
|
|
292
|
+
version: null,
|
|
293
|
+
bundles: [],
|
|
294
|
+
tags: [],
|
|
295
|
+
});
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const resource = parseGtmResource(body);
|
|
299
|
+
const tags = resource === null
|
|
300
|
+
? []
|
|
301
|
+
: readDreamdataTags(resource, {
|
|
302
|
+
normal: DREAMDATA_NORMAL_SCRIPT_FILE,
|
|
303
|
+
cookieless: DREAMDATA_COOKIELESS_SCRIPT_FILE,
|
|
304
|
+
});
|
|
305
|
+
containers.push({
|
|
306
|
+
containerId: id,
|
|
307
|
+
// A body that arrived but would not parse is still a failed read for
|
|
308
|
+
// the purposes below: nothing can be said about its tags.
|
|
309
|
+
read: resource !== null,
|
|
310
|
+
error: resource === null ? "the container body did not parse" : null,
|
|
311
|
+
version: resource?.version ?? null,
|
|
312
|
+
bundles: [...new Set(tags.flatMap((tag) => tag.bundles))],
|
|
313
|
+
tags,
|
|
314
|
+
});
|
|
315
|
+
if (body.includes(DREAMDATA_COOKIELESS_SCRIPT_FILE))
|
|
316
|
+
cookielessBundleTagFound = true;
|
|
317
|
+
// clio.com is the case this exists for: the container ships a tag
|
|
318
|
+
// loading Segment's own library, triggered on its own schedule, so
|
|
319
|
+
// neither the live DOM nor a request capture ever saw it within this
|
|
320
|
+
// crawl's window — this is the only place it was findable at all.
|
|
321
|
+
if (body.includes(THIRD_PARTY_SEGMENT_SCRIPT_HOST))
|
|
322
|
+
thirdPartySegmentTagFound = true;
|
|
323
|
+
const normalAt = body.indexOf(DREAMDATA_NORMAL_SCRIPT_FILE);
|
|
324
|
+
if (normalAt === -1)
|
|
325
|
+
continue;
|
|
326
|
+
normalBundleTagFound = true;
|
|
327
|
+
// Parsing the container properly is what surfaces the tag's BLOCKING
|
|
328
|
+
// triggers, which the consent list alone cannot show — see
|
|
329
|
+
// gtm-container.ts. Read off the same parse as `containers` above rather
|
|
330
|
+
// than parsing twice: brace-matching a published container is the
|
|
331
|
+
// expensive part of this scan. The regex below stays as the fallback for
|
|
332
|
+
// a body whose shape the parser does not recognise.
|
|
333
|
+
const parsed = tags.find((tag) => tag.bundles.includes("normal"));
|
|
334
|
+
if (parsed !== undefined) {
|
|
335
|
+
normalBundleConsentGate = parsed.consent;
|
|
336
|
+
if (parsed.blockedWhen.length > 0) {
|
|
337
|
+
normalBundleBlockingTriggers = [...(normalBundleBlockingTriggers ?? []), ...parsed.blockedWhen];
|
|
338
|
+
}
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
// GTM serialises each tag as one object with its consent requirements
|
|
342
|
+
// ahead of the HTML it injects, so the nearest preceding "consent" list
|
|
343
|
+
// is this tag's gate. A heuristic on a minified blob — reported as a
|
|
344
|
+
// corroborating signal, never as the sole basis for a verdict.
|
|
345
|
+
const window_ = body.slice(Math.max(0, normalAt - 4000), normalAt);
|
|
346
|
+
const consentMatch = /"consent":\["list"((?:,"[a-z_]+")+)\]/g;
|
|
347
|
+
let last = null;
|
|
348
|
+
for (let m = consentMatch.exec(window_); m !== null; m = consentMatch.exec(window_))
|
|
349
|
+
last = m;
|
|
350
|
+
if (last) {
|
|
351
|
+
normalBundleConsentGate = last[1]
|
|
352
|
+
.split(",")
|
|
353
|
+
.map((s) => s.replace(/"/g, "").trim())
|
|
354
|
+
.filter(Boolean);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
scanned: true,
|
|
359
|
+
containerIds: gtmIds,
|
|
360
|
+
containers,
|
|
361
|
+
normalBundleTagFound,
|
|
362
|
+
cookielessBundleTagFound,
|
|
363
|
+
thirdPartySegmentTagFound,
|
|
364
|
+
normalBundleConsentGate,
|
|
365
|
+
normalBundleBlockingTriggers: normalBundleBlockingTriggers === null ? null : [...new Set(normalBundleBlockingTriggers)],
|
|
366
|
+
errors,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Every reason to believe a normal (cookie-based) bundle is part of this
|
|
371
|
+
* deployment, in descending order of strength.
|
|
372
|
+
*
|
|
373
|
+
* A DOM snapshot alone cannot answer this: a consent-gated bundle is absent
|
|
374
|
+
* from the DOM by design, which is exactly when the question matters. Only an
|
|
375
|
+
* empty result justifies calling an install cookieless-by-design.
|
|
376
|
+
*/
|
|
377
|
+
export function deriveNormalBundleSignals(inputs) {
|
|
378
|
+
const { presence, tagManager, dreamdataIsStubQueue, normalRequestCount, httpMarkup = null } = inputs;
|
|
379
|
+
const signals = [];
|
|
380
|
+
if (normalRequestCount > 0) {
|
|
381
|
+
signals.push({ source: "request", detail: `${normalRequestCount} request(s) sent by the normal bundle` });
|
|
382
|
+
}
|
|
383
|
+
for (const match of presence.matches.filter((m) => m.kind === "normal")) {
|
|
384
|
+
signals.push(match.blockedBy === null
|
|
385
|
+
? { source: "dom-script", detail: `<script${match.src ? ` src=${match.src}` : " (inline)"}> is in the DOM` }
|
|
386
|
+
: {
|
|
387
|
+
source: "cmp-blocked-script",
|
|
388
|
+
detail: `<script${match.src ? ` src=${match.src}` : " (inline)"}> is present but neutralised by ${match.blockedBy}`,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
if (tagManager.normalBundleTagFound) {
|
|
392
|
+
const gate = tagManager.normalBundleConsentGate;
|
|
393
|
+
// The exception is named alongside the consent list: a tag can satisfy
|
|
394
|
+
// its consent gate and still be held back by one, and then it is not
|
|
395
|
+
// Consent Mode keeping tracking off before the banner is answered.
|
|
396
|
+
const blocking = tagManager.normalBundleBlockingTriggers;
|
|
397
|
+
signals.push({
|
|
398
|
+
source: "tag-manager-container",
|
|
399
|
+
detail: `GTM container ${tagManager.containerIds.join(", ")} ships a tag loading ${DREAMDATA_NORMAL_SCRIPT_FILE}${gate ? `, gated on ${gate.join(", ")}` : ""}${blocking?.length ? `, blocked whenever ${blocking.join("; or ")}` : ""}`,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
// Only worth stating when the DOM did not already show it: on a page the
|
|
403
|
+
// browser rendered normally these two agree, and one signal per reason is
|
|
404
|
+
// the point of this list.
|
|
405
|
+
if (httpMarkup?.normalSnippet && !presence.normalSnippet) {
|
|
406
|
+
signals.push({
|
|
407
|
+
source: "http-markup",
|
|
408
|
+
detail: `the markup served to a plain HTTP GET of this URL contains a tag loading ${DREAMDATA_NORMAL_SCRIPT_FILE}, though the rendered page did not`,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (presence.cookielessLoaderNamesNormalCompanion) {
|
|
412
|
+
signals.push({
|
|
413
|
+
source: "cookieless-loader-companion",
|
|
414
|
+
detail: `the cookieless loader checks for #${NORMAL_BUNDLE_ELEMENT_ID}, the normal bundle's tag`,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (dreamdataIsStubQueue) {
|
|
418
|
+
signals.push({
|
|
419
|
+
source: "stub-queue",
|
|
420
|
+
detail: "window.dreamdata is the loader's stub array — calls are queued for a normal bundle that never arrived",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return signals;
|
|
424
|
+
}
|
|
425
|
+
//# sourceMappingURL=script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script.js","sourceRoot":"","sources":["../../src/evidence/script.ts"],"names":[],"mappings":"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"}
|