standdown 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/POLICIES.md +44 -0
- package/README.md +171 -0
- package/dist/chunk-25A7QG5Z.mjs +274 -0
- package/dist/chunk-25A7QG5Z.mjs.map +1 -0
- package/dist/chunk-SEZFAOUU.mjs +382 -0
- package/dist/chunk-SEZFAOUU.mjs.map +1 -0
- package/dist/chunk-SVN6UWA4.mjs +857 -0
- package/dist/chunk-SVN6UWA4.mjs.map +1 -0
- package/dist/content.cjs +1288 -0
- package/dist/content.cjs.map +1 -0
- package/dist/content.d.cts +61 -0
- package/dist/content.d.ts +61 -0
- package/dist/content.mjs +201 -0
- package/dist/content.mjs.map +1 -0
- package/dist/index.cjs +1375 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.mjs +242 -0
- package/dist/index.mjs.map +1 -0
- package/dist/policies.cjs +620 -0
- package/dist/policies.cjs.map +1 -0
- package/dist/policies.d.cts +1517 -0
- package/dist/policies.d.ts +1517 -0
- package/dist/policies.mjs +606 -0
- package/dist/policies.mjs.map +1 -0
- package/dist/session-C5gH-LaQ.d.ts +21 -0
- package/dist/session-n0px3Agb.d.cts +21 -0
- package/dist/stores-CELX9aHN.d.ts +70 -0
- package/dist/stores-CKCdKDdX.d.cts +70 -0
- package/dist/types-A9OJ7SWj.d.cts +190 -0
- package/dist/types-A9OJ7SWj.d.ts +190 -0
- package/dist/webext.cjs +1733 -0
- package/dist/webext.cjs.map +1 -0
- package/dist/webext.d.cts +110 -0
- package/dist/webext.d.ts +110 -0
- package/dist/webext.mjs +346 -0
- package/dist/webext.mjs.map +1 -0
- package/package.json +77 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1375 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/validation.ts
|
|
4
|
+
var BEHAVIORS = /* @__PURE__ */ new Set([
|
|
5
|
+
"suppress-prompts",
|
|
6
|
+
"no-cookie-write",
|
|
7
|
+
"no-redirect",
|
|
8
|
+
"no-background-tracking"
|
|
9
|
+
]);
|
|
10
|
+
var PARAM_MATCHES = /* @__PURE__ */ new Set(["exists", "equals", "contains"]);
|
|
11
|
+
var COOKIE_MATCHES = /* @__PURE__ */ new Set(["exact", "substring"]);
|
|
12
|
+
var DOMAIN_KINDS = /* @__PURE__ */ new Set(["suffix", "regex"]);
|
|
13
|
+
var REFERRER_CLASSES = /* @__PURE__ */ new Set([
|
|
14
|
+
"own-site",
|
|
15
|
+
"organic",
|
|
16
|
+
"direct",
|
|
17
|
+
"advertiser-internal",
|
|
18
|
+
"other"
|
|
19
|
+
]);
|
|
20
|
+
var ACTIVATION_REFERRER_CLASSES = /* @__PURE__ */ new Set(["own-site", "organic", "direct"]);
|
|
21
|
+
function validatePolicy(value) {
|
|
22
|
+
const policy = object(value, "policy");
|
|
23
|
+
string(policy.id, "policy.id");
|
|
24
|
+
literal(policy.schemaVersion, 3, "policy.schemaVersion");
|
|
25
|
+
string(policy.policyVersion, "policy.policyVersion");
|
|
26
|
+
const network = object(policy.network, "policy.network");
|
|
27
|
+
string(network.id, "policy.network.id");
|
|
28
|
+
string(network.name, "policy.network.name");
|
|
29
|
+
if (network.policyUrl !== void 0) {
|
|
30
|
+
urlString(network.policyUrl, "policy.network.policyUrl");
|
|
31
|
+
}
|
|
32
|
+
const detection = object(policy.detection, "policy.detection");
|
|
33
|
+
optionalArray(detection.advertiserHosts, "policy.detection.advertiserHosts").forEach(
|
|
34
|
+
validateDomainRule
|
|
35
|
+
);
|
|
36
|
+
optionalArray(detection.landingParams, "policy.detection.landingParams").forEach(
|
|
37
|
+
validateParamRule
|
|
38
|
+
);
|
|
39
|
+
optionalArray(detection.redirectDomains, "policy.detection.redirectDomains").forEach(
|
|
40
|
+
validateDomainRule
|
|
41
|
+
);
|
|
42
|
+
optionalArray(detection.cookiePatterns, "policy.detection.cookiePatterns").forEach(
|
|
43
|
+
validateCookieRule
|
|
44
|
+
);
|
|
45
|
+
optionalArray(detection.initiatorRules, "policy.detection.initiatorRules").forEach(
|
|
46
|
+
validateInitiatorRule
|
|
47
|
+
);
|
|
48
|
+
const standdown = object(policy.standdown, "policy.standdown");
|
|
49
|
+
literal(standdown.scope, "advertiser", "policy.standdown.scope");
|
|
50
|
+
if (standdown.sessionRule !== "session-or-min" && standdown.sessionRule !== "inactivity-window") {
|
|
51
|
+
throw new TypeError("policy.standdown.sessionRule is invalid");
|
|
52
|
+
}
|
|
53
|
+
nonNegativeFiniteNumber(
|
|
54
|
+
standdown.minDurationMs,
|
|
55
|
+
"policy.standdown.minDurationMs"
|
|
56
|
+
);
|
|
57
|
+
if (standdown.inactivityMs !== void 0) {
|
|
58
|
+
nonNegativeFiniteNumber(
|
|
59
|
+
standdown.inactivityMs,
|
|
60
|
+
"policy.standdown.inactivityMs"
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const behaviors = array(standdown.behaviors, "policy.standdown.behaviors");
|
|
64
|
+
for (const behavior of behaviors) {
|
|
65
|
+
if (!BEHAVIORS.has(behavior)) {
|
|
66
|
+
throw new TypeError(`policy.standdown.behaviors contains invalid behavior`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const activation = object(policy.activation, "policy.activation");
|
|
70
|
+
if (activation.mode !== "user-click" && activation.mode !== "never") {
|
|
71
|
+
throw new TypeError("policy.activation.mode is invalid");
|
|
72
|
+
}
|
|
73
|
+
if (activation.maxPromptsPerJourney !== void 0) {
|
|
74
|
+
nonNegativeFiniteNumber(
|
|
75
|
+
activation.maxPromptsPerJourney,
|
|
76
|
+
"policy.activation.maxPromptsPerJourney"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
for (const referrerClass of optionalArray(
|
|
80
|
+
activation.allowedReferrerClasses,
|
|
81
|
+
"policy.activation.allowedReferrerClasses"
|
|
82
|
+
)) {
|
|
83
|
+
if (!ACTIVATION_REFERRER_CLASSES.has(referrerClass)) {
|
|
84
|
+
throw new TypeError(
|
|
85
|
+
"policy.activation.allowedReferrerClasses contains invalid class"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const metadata = object(policy.metadata, "policy.metadata");
|
|
90
|
+
urlString(metadata.sourceUrl, "policy.metadata.sourceUrl");
|
|
91
|
+
dateString(metadata.lastVerified, "policy.metadata.lastVerified");
|
|
92
|
+
if (metadata.notes !== void 0) {
|
|
93
|
+
string(metadata.notes, "policy.metadata.notes");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validatePolicies(values) {
|
|
97
|
+
values.forEach(validatePolicy);
|
|
98
|
+
}
|
|
99
|
+
function validateParamRule(ruleValue) {
|
|
100
|
+
const rule = object(ruleValue, "ParamRule");
|
|
101
|
+
const anyOf = array(rule.anyOf, "ParamRule.anyOf");
|
|
102
|
+
if (anyOf.length === 0) {
|
|
103
|
+
throw new TypeError("ParamRule.anyOf must not be empty");
|
|
104
|
+
}
|
|
105
|
+
for (const groupValue of anyOf) {
|
|
106
|
+
const group = object(groupValue, "ParamRule.anyOf[]");
|
|
107
|
+
const allOf = array(group.allOf, "ParamRule.anyOf[].allOf");
|
|
108
|
+
if (allOf.length === 0) {
|
|
109
|
+
throw new TypeError("ParamRule.anyOf[].allOf must not be empty");
|
|
110
|
+
}
|
|
111
|
+
allOf.forEach(validateParamMatcher);
|
|
112
|
+
}
|
|
113
|
+
if (rule.reason !== void 0) {
|
|
114
|
+
string(rule.reason, "ParamRule.reason");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function validateParamMatcher(matcherValue) {
|
|
118
|
+
const matcher = object(matcherValue, "ParamMatcher");
|
|
119
|
+
string(matcher.name, "ParamMatcher.name");
|
|
120
|
+
if (matcher.name.trim() === "") {
|
|
121
|
+
throw new TypeError("ParamMatcher.name must not be empty");
|
|
122
|
+
}
|
|
123
|
+
if (matcher.value !== void 0) {
|
|
124
|
+
string(matcher.value, "ParamMatcher.value");
|
|
125
|
+
}
|
|
126
|
+
if (matcher.match !== void 0 && !PARAM_MATCHES.has(matcher.match)) {
|
|
127
|
+
throw new TypeError("ParamMatcher.match is invalid");
|
|
128
|
+
}
|
|
129
|
+
if ((matcher.match === "equals" || matcher.match === "contains") && matcher.value === void 0) {
|
|
130
|
+
throw new TypeError("ParamMatcher.value is required for equals/contains");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function validateDomainRule(ruleValue) {
|
|
134
|
+
const rule = object(ruleValue, "DomainRule");
|
|
135
|
+
string(rule.pattern, "DomainRule.pattern");
|
|
136
|
+
if (!DOMAIN_KINDS.has(rule.kind)) {
|
|
137
|
+
throw new TypeError("DomainRule.kind is invalid");
|
|
138
|
+
}
|
|
139
|
+
if (rule.kind === "regex") {
|
|
140
|
+
try {
|
|
141
|
+
new RegExp(rule.pattern);
|
|
142
|
+
} catch {
|
|
143
|
+
throw new TypeError("DomainRule.pattern must be a valid regex");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (rule.comment !== void 0) {
|
|
147
|
+
string(rule.comment, "DomainRule.comment");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function validateCookieRule(ruleValue) {
|
|
151
|
+
const rule = object(ruleValue, "CookieRule");
|
|
152
|
+
string(rule.name, "CookieRule.name");
|
|
153
|
+
if (!COOKIE_MATCHES.has(rule.match)) {
|
|
154
|
+
throw new TypeError("CookieRule.match is invalid");
|
|
155
|
+
}
|
|
156
|
+
if (rule.reason !== void 0) {
|
|
157
|
+
string(rule.reason, "CookieRule.reason");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function validateInitiatorRule(ruleValue) {
|
|
161
|
+
const rule = object(ruleValue, "InitiatorRule");
|
|
162
|
+
if (!REFERRER_CLASSES.has(rule.referrerClass)) {
|
|
163
|
+
throw new TypeError("InitiatorRule.referrerClass is invalid");
|
|
164
|
+
}
|
|
165
|
+
if (rule.reason !== void 0) {
|
|
166
|
+
string(rule.reason, "InitiatorRule.reason");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function object(value, path) {
|
|
170
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
171
|
+
throw new TypeError(`${path} must be an object`);
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function string(value, path) {
|
|
176
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
177
|
+
throw new TypeError(`${path} must be a non-empty string`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function literal(value, expected, path) {
|
|
181
|
+
if (value !== expected) {
|
|
182
|
+
throw new TypeError(`${path} must be ${String(expected)}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function array(value, path) {
|
|
186
|
+
if (!Array.isArray(value)) {
|
|
187
|
+
throw new TypeError(`${path} must be an array`);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
function optionalArray(value, path) {
|
|
192
|
+
if (value === void 0) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
return array(value, path);
|
|
196
|
+
}
|
|
197
|
+
function nonNegativeFiniteNumber(value, path) {
|
|
198
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
199
|
+
throw new TypeError(`${path} must be a non-negative finite number`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function urlString(value, path) {
|
|
203
|
+
string(value, path);
|
|
204
|
+
try {
|
|
205
|
+
new URL(value);
|
|
206
|
+
} catch {
|
|
207
|
+
throw new TypeError(`${path} must be an absolute URL`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function dateString(value, path) {
|
|
211
|
+
string(value, path);
|
|
212
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
213
|
+
throw new TypeError(`${path} must be YYYY-MM-DD`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/bundle.ts
|
|
218
|
+
var textEncoder = new TextEncoder();
|
|
219
|
+
var MAX_SIGNED_BUNDLE_REGEX_LENGTH = 256;
|
|
220
|
+
async function verifyPolicyBundle(current, update, publicKeyJwk) {
|
|
221
|
+
try {
|
|
222
|
+
validatePolicies(current);
|
|
223
|
+
validatePolicies(update.policies);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return { ok: false, violation: `malformed-policy: ${messageFromError(error)}` };
|
|
226
|
+
}
|
|
227
|
+
if (update.schemaVersion !== 1) {
|
|
228
|
+
return { ok: false, violation: "unsupported-bundle-version" };
|
|
229
|
+
}
|
|
230
|
+
const signatureOk = await verifySignature(
|
|
231
|
+
update.signature.algorithm,
|
|
232
|
+
publicKeyJwk,
|
|
233
|
+
update.signature.value,
|
|
234
|
+
canonicalPolicyBundlePayload(update)
|
|
235
|
+
);
|
|
236
|
+
if (!signatureOk) {
|
|
237
|
+
return { ok: false, violation: "bad-signature" };
|
|
238
|
+
}
|
|
239
|
+
const regexViolation = regexComplexityViolation(update.policies);
|
|
240
|
+
if (regexViolation !== void 0) {
|
|
241
|
+
return { ok: false, violation: regexViolation };
|
|
242
|
+
}
|
|
243
|
+
const monotonicityViolation = checkMonotonicity(current, update.policies);
|
|
244
|
+
if (monotonicityViolation !== void 0) {
|
|
245
|
+
return { ok: false, violation: monotonicityViolation };
|
|
246
|
+
}
|
|
247
|
+
return { ok: true, policies: update.policies.map(clonePolicy) };
|
|
248
|
+
}
|
|
249
|
+
function canonicalPolicyBundlePayload(bundle) {
|
|
250
|
+
return canonicalJson({
|
|
251
|
+
schemaVersion: bundle.schemaVersion,
|
|
252
|
+
policies: bundle.policies
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function canonicalJson(value) {
|
|
256
|
+
if (value === null || typeof value !== "object") {
|
|
257
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
258
|
+
throw new TypeError("canonical JSON only supports finite numbers");
|
|
259
|
+
}
|
|
260
|
+
return JSON.stringify(value);
|
|
261
|
+
}
|
|
262
|
+
if (Array.isArray(value)) {
|
|
263
|
+
return `[${value.map((item) => canonicalJson(item)).join(",")}]`;
|
|
264
|
+
}
|
|
265
|
+
const record = value;
|
|
266
|
+
const entries = Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`);
|
|
267
|
+
return `{${entries.join(",")}}`;
|
|
268
|
+
}
|
|
269
|
+
function checkMonotonicity(current, update) {
|
|
270
|
+
const updateById = new Map(update.map((policy) => [policy.id, policy]));
|
|
271
|
+
for (const currentPolicy of current) {
|
|
272
|
+
const updatedPolicy = updateById.get(currentPolicy.id);
|
|
273
|
+
if (updatedPolicy === void 0) {
|
|
274
|
+
return `policy-removed:${currentPolicy.id}`;
|
|
275
|
+
}
|
|
276
|
+
const detectionResult = detectionViolation(currentPolicy, updatedPolicy);
|
|
277
|
+
if (detectionResult !== void 0) {
|
|
278
|
+
return `${currentPolicy.id}:${detectionResult}`;
|
|
279
|
+
}
|
|
280
|
+
const standdownResult = standdownViolation(currentPolicy, updatedPolicy);
|
|
281
|
+
if (standdownResult !== void 0) {
|
|
282
|
+
return `${currentPolicy.id}:${standdownResult}`;
|
|
283
|
+
}
|
|
284
|
+
if (!sameJson(currentPolicy.activation, updatedPolicy.activation)) {
|
|
285
|
+
return `${currentPolicy.id}:activation-edited`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
290
|
+
function detectionViolation(currentPolicy, updatedPolicy) {
|
|
291
|
+
if (!domainRulesSurvive(
|
|
292
|
+
currentPolicy.detection.advertiserHosts,
|
|
293
|
+
updatedPolicy.detection.advertiserHosts,
|
|
294
|
+
{ advertiserHosts: true }
|
|
295
|
+
)) {
|
|
296
|
+
return "advertiser-hosts-narrowed";
|
|
297
|
+
}
|
|
298
|
+
if (!paramRulesSurvive(
|
|
299
|
+
currentPolicy.detection.landingParams,
|
|
300
|
+
updatedPolicy.detection.landingParams
|
|
301
|
+
)) {
|
|
302
|
+
return "landing-params-narrowed";
|
|
303
|
+
}
|
|
304
|
+
if (!domainRulesSurvive(
|
|
305
|
+
currentPolicy.detection.redirectDomains,
|
|
306
|
+
updatedPolicy.detection.redirectDomains
|
|
307
|
+
)) {
|
|
308
|
+
return "redirect-domains-narrowed";
|
|
309
|
+
}
|
|
310
|
+
if (!cookieRulesSurvive(
|
|
311
|
+
currentPolicy.detection.cookiePatterns,
|
|
312
|
+
updatedPolicy.detection.cookiePatterns
|
|
313
|
+
)) {
|
|
314
|
+
return "cookie-patterns-narrowed";
|
|
315
|
+
}
|
|
316
|
+
if (!initiatorRulesSurvive(
|
|
317
|
+
currentPolicy.detection.initiatorRules,
|
|
318
|
+
updatedPolicy.detection.initiatorRules
|
|
319
|
+
)) {
|
|
320
|
+
return "initiator-rules-narrowed";
|
|
321
|
+
}
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
function standdownViolation(currentPolicy, updatedPolicy) {
|
|
325
|
+
if (updatedPolicy.standdown.scope !== currentPolicy.standdown.scope) {
|
|
326
|
+
return "standdown-scope-edited";
|
|
327
|
+
}
|
|
328
|
+
if (updatedPolicy.standdown.sessionRule !== currentPolicy.standdown.sessionRule) {
|
|
329
|
+
return "session-rule-edited";
|
|
330
|
+
}
|
|
331
|
+
if (updatedPolicy.standdown.minDurationMs < currentPolicy.standdown.minDurationMs) {
|
|
332
|
+
return "min-duration-shortened";
|
|
333
|
+
}
|
|
334
|
+
if (currentPolicy.standdown.inactivityMs !== void 0 && (updatedPolicy.standdown.inactivityMs === void 0 || updatedPolicy.standdown.inactivityMs < currentPolicy.standdown.inactivityMs)) {
|
|
335
|
+
return "inactivity-duration-shortened";
|
|
336
|
+
}
|
|
337
|
+
for (const behavior of currentPolicy.standdown.behaviors) {
|
|
338
|
+
if (!updatedPolicy.standdown.behaviors.includes(behavior)) {
|
|
339
|
+
return "standdown-behavior-removed";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
function domainRulesSurvive(current, update, opts = {}) {
|
|
345
|
+
if (opts.advertiserHosts && current === void 0) {
|
|
346
|
+
return update === void 0;
|
|
347
|
+
}
|
|
348
|
+
if (current === void 0 || current.length === 0) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
if (opts.advertiserHosts && update === void 0) {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
if (update === void 0 || update.length === 0) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return current.every(
|
|
358
|
+
(rule) => update.some((candidate) => domainRuleCovers(rule, candidate))
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
function regexComplexityViolation(policies) {
|
|
362
|
+
for (const policy of policies) {
|
|
363
|
+
for (const [field, rules] of domainRuleEntries(policy)) {
|
|
364
|
+
for (const rule of rules ?? []) {
|
|
365
|
+
if (rule.kind === "regex" && isComplexRegex(rule.pattern)) {
|
|
366
|
+
return `${policy.id}:complex-regex:${field}`;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return void 0;
|
|
372
|
+
}
|
|
373
|
+
function domainRuleEntries(policy) {
|
|
374
|
+
return [
|
|
375
|
+
["detection.advertiserHosts", policy.detection.advertiserHosts],
|
|
376
|
+
["detection.redirectDomains", policy.detection.redirectDomains]
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
function isComplexRegex(pattern) {
|
|
380
|
+
return pattern.length > MAX_SIGNED_BUNDLE_REGEX_LENGTH || hasRegexBackreference(pattern) || hasRegexLookaround(pattern) || hasNestedUnboundedQuantifier(pattern);
|
|
381
|
+
}
|
|
382
|
+
function hasRegexBackreference(pattern) {
|
|
383
|
+
return /(^|[^\\])\\[1-9]/.test(pattern);
|
|
384
|
+
}
|
|
385
|
+
function hasRegexLookaround(pattern) {
|
|
386
|
+
return /\(\?(?:[=!]|<[=!])/.test(pattern);
|
|
387
|
+
}
|
|
388
|
+
function hasNestedUnboundedQuantifier(pattern) {
|
|
389
|
+
return /\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)(?:[+*]|\{\d*,?\})/.test(
|
|
390
|
+
pattern
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
function domainRuleCovers(current, update) {
|
|
394
|
+
if (current.kind === "suffix" && update.kind === "suffix") {
|
|
395
|
+
return domainSuffixMatches(current.pattern, update.pattern);
|
|
396
|
+
}
|
|
397
|
+
return sameJson(current, update);
|
|
398
|
+
}
|
|
399
|
+
function paramRulesSurvive(current, update) {
|
|
400
|
+
return exactRulesSurvive(current, update);
|
|
401
|
+
}
|
|
402
|
+
function cookieRulesSurvive(current, update) {
|
|
403
|
+
return exactRulesSurvive(current, update);
|
|
404
|
+
}
|
|
405
|
+
function initiatorRulesSurvive(current, update) {
|
|
406
|
+
return exactRulesSurvive(current, update);
|
|
407
|
+
}
|
|
408
|
+
function exactRulesSurvive(current, update) {
|
|
409
|
+
if (current === void 0 || current.length === 0) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
if (update === void 0 || update.length === 0) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const updatedRules = new Set(update.map((rule) => canonicalJson(rule)));
|
|
416
|
+
return current.every((rule) => updatedRules.has(canonicalJson(rule)));
|
|
417
|
+
}
|
|
418
|
+
async function verifySignature(algorithm, publicKeyJwk, signatureValue, payload) {
|
|
419
|
+
try {
|
|
420
|
+
const publicKey = await importVerificationKey(algorithm, publicKeyJwk);
|
|
421
|
+
const signature = base64UrlToBytes(signatureValue);
|
|
422
|
+
const data = bytesToArrayBuffer(textEncoder.encode(payload));
|
|
423
|
+
if (algorithm === "ECDSA-P256") {
|
|
424
|
+
return crypto.subtle.verify(
|
|
425
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
426
|
+
publicKey,
|
|
427
|
+
bytesToArrayBuffer(signature),
|
|
428
|
+
data
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return crypto.subtle.verify(
|
|
432
|
+
"Ed25519",
|
|
433
|
+
publicKey,
|
|
434
|
+
bytesToArrayBuffer(signature),
|
|
435
|
+
data
|
|
436
|
+
);
|
|
437
|
+
} catch {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async function importVerificationKey(algorithm, publicKeyJwk) {
|
|
442
|
+
if (algorithm === "ECDSA-P256") {
|
|
443
|
+
return crypto.subtle.importKey(
|
|
444
|
+
"jwk",
|
|
445
|
+
publicKeyJwk,
|
|
446
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
447
|
+
false,
|
|
448
|
+
["verify"]
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
return crypto.subtle.importKey("jwk", publicKeyJwk, "Ed25519", false, [
|
|
452
|
+
"verify"
|
|
453
|
+
]);
|
|
454
|
+
}
|
|
455
|
+
function base64UrlToBytes(value) {
|
|
456
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
457
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
458
|
+
const binary = atob(padded);
|
|
459
|
+
const bytes = new Uint8Array(binary.length);
|
|
460
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
461
|
+
bytes[index] = binary.charCodeAt(index);
|
|
462
|
+
}
|
|
463
|
+
return bytes;
|
|
464
|
+
}
|
|
465
|
+
function bytesToArrayBuffer(bytes) {
|
|
466
|
+
return bytes.buffer.slice(
|
|
467
|
+
bytes.byteOffset,
|
|
468
|
+
bytes.byteOffset + bytes.byteLength
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
function sameJson(left, right) {
|
|
472
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
473
|
+
}
|
|
474
|
+
function domainSuffixMatches(host, pattern) {
|
|
475
|
+
const normalizedHost = host.toLowerCase().replace(/^\./, "").replace(/\.$/, "");
|
|
476
|
+
const normalizedPattern = pattern.toLowerCase().replace(/^\./, "").replace(/\.$/, "");
|
|
477
|
+
return normalizedHost === normalizedPattern || normalizedHost.endsWith(`.${normalizedPattern}`);
|
|
478
|
+
}
|
|
479
|
+
function clonePolicy(policy) {
|
|
480
|
+
return JSON.parse(JSON.stringify(policy));
|
|
481
|
+
}
|
|
482
|
+
function messageFromError(error) {
|
|
483
|
+
return error instanceof Error ? error.message : String(error);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/detect.ts
|
|
487
|
+
var ORGANIC_REFERRER_SUFFIXES = [
|
|
488
|
+
"google.com",
|
|
489
|
+
"bing.com",
|
|
490
|
+
"duckduckgo.com",
|
|
491
|
+
"yahoo.com",
|
|
492
|
+
"ecosia.org",
|
|
493
|
+
"baidu.com",
|
|
494
|
+
"yandex.com"
|
|
495
|
+
];
|
|
496
|
+
function detect(signals, policies) {
|
|
497
|
+
const currentUrl = parseUrl(signals.url);
|
|
498
|
+
if (!currentUrl) {
|
|
499
|
+
return {
|
|
500
|
+
matched: [],
|
|
501
|
+
selfMatch: false,
|
|
502
|
+
failClosedReason: "invalid-url"
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const advertiserHost = currentUrl.hostname.toLowerCase();
|
|
506
|
+
const matched = [];
|
|
507
|
+
let selfMatch = false;
|
|
508
|
+
for (const policy of policies) {
|
|
509
|
+
const scopedSelfMatch = hasScopedSelfExemption(
|
|
510
|
+
currentUrl,
|
|
511
|
+
signals.selfPatterns,
|
|
512
|
+
policy
|
|
513
|
+
);
|
|
514
|
+
const unscopedSelfMatch = hasUnscopedSelfExemption(
|
|
515
|
+
currentUrl,
|
|
516
|
+
signals.selfPatterns
|
|
517
|
+
);
|
|
518
|
+
if (scopedSelfMatch || unscopedSelfMatch) {
|
|
519
|
+
selfMatch = true;
|
|
520
|
+
}
|
|
521
|
+
if (scopedSelfMatch) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
matched.push(
|
|
525
|
+
...collectPolicyMatches(policy, signals, currentUrl, advertiserHost)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
matched.sort((left, right) => kindPriority(left.kind) - kindPriority(right.kind));
|
|
529
|
+
const strongest = matched[0] ? {
|
|
530
|
+
policyId: matched[0].policyId,
|
|
531
|
+
advertiserHost: matched[0].advertiserHost,
|
|
532
|
+
reason: matched[0].reason
|
|
533
|
+
} : void 0;
|
|
534
|
+
return strongest ? { matched, selfMatch, strongest } : { matched, selfMatch };
|
|
535
|
+
}
|
|
536
|
+
function classifyReferrer(signals, advertiserHost) {
|
|
537
|
+
const candidate = signals.initiator ?? signals.referrer;
|
|
538
|
+
if (!candidate) {
|
|
539
|
+
return "direct";
|
|
540
|
+
}
|
|
541
|
+
const referrerHost = hostFromUrl(candidate);
|
|
542
|
+
if (!referrerHost) {
|
|
543
|
+
return "other";
|
|
544
|
+
}
|
|
545
|
+
if ((signals.publisherSites ?? []).some(
|
|
546
|
+
(site) => domainSuffixMatches2(referrerHost, site)
|
|
547
|
+
)) {
|
|
548
|
+
return "own-site";
|
|
549
|
+
}
|
|
550
|
+
if (domainSuffixMatches2(referrerHost, advertiserHost)) {
|
|
551
|
+
return "advertiser-internal";
|
|
552
|
+
}
|
|
553
|
+
if (ORGANIC_REFERRER_SUFFIXES.some(
|
|
554
|
+
(suffix) => domainSuffixMatches2(referrerHost, suffix)
|
|
555
|
+
)) {
|
|
556
|
+
return "organic";
|
|
557
|
+
}
|
|
558
|
+
return "other";
|
|
559
|
+
}
|
|
560
|
+
function domainRuleMatchesUrl(rule, value) {
|
|
561
|
+
const url = parseUrl(value);
|
|
562
|
+
const host = url?.hostname.toLowerCase() ?? value.toLowerCase();
|
|
563
|
+
if (rule.kind === "suffix") {
|
|
564
|
+
return domainSuffixMatches2(host, rule.pattern);
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
const regex = new RegExp(rule.pattern, "i");
|
|
568
|
+
return regex.test(host) || (url ? regex.test(url.href) : regex.test(value));
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function collectPolicyMatches(policy, signals, currentUrl, advertiserHost) {
|
|
574
|
+
const matches = [];
|
|
575
|
+
const advertiserHostMatches = policy.detection.advertiserHosts === void 0 || policy.detection.advertiserHosts.some(
|
|
576
|
+
(rule) => domainRuleMatchesUrl(rule, advertiserHost)
|
|
577
|
+
);
|
|
578
|
+
if (advertiserHostMatches) {
|
|
579
|
+
for (const rule of policy.detection.landingParams ?? []) {
|
|
580
|
+
if (paramRuleMatches(rule, currentUrl.searchParams)) {
|
|
581
|
+
matches.push(
|
|
582
|
+
matchedRule(
|
|
583
|
+
policy,
|
|
584
|
+
"landing-param",
|
|
585
|
+
describeParamRule(rule),
|
|
586
|
+
advertiserHost,
|
|
587
|
+
rule.reason ?? "landing parameter matched"
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const rule of policy.detection.redirectDomains ?? []) {
|
|
594
|
+
if ((signals.redirectChain ?? []).some((url) => domainRuleMatchesUrl(rule, url))) {
|
|
595
|
+
matches.push(
|
|
596
|
+
matchedRule(
|
|
597
|
+
policy,
|
|
598
|
+
"redirect-domain",
|
|
599
|
+
`${rule.kind}:${rule.pattern}`,
|
|
600
|
+
advertiserHost,
|
|
601
|
+
rule.comment ?? "redirect domain matched"
|
|
602
|
+
)
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (advertiserHostMatches) {
|
|
607
|
+
for (const rule of policy.detection.cookiePatterns ?? []) {
|
|
608
|
+
if (cookieRuleMatches(rule, signals.cookieNames ?? [])) {
|
|
609
|
+
matches.push(
|
|
610
|
+
matchedRule(
|
|
611
|
+
policy,
|
|
612
|
+
"cookie",
|
|
613
|
+
`${rule.match}:${rule.name}`,
|
|
614
|
+
advertiserHost,
|
|
615
|
+
rule.reason ?? "first-party cookie name matched"
|
|
616
|
+
)
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (advertiserHostMatches) {
|
|
622
|
+
for (const rule of policy.detection.initiatorRules ?? []) {
|
|
623
|
+
if (initiatorRuleMatches(rule, signals, advertiserHost)) {
|
|
624
|
+
matches.push(
|
|
625
|
+
matchedRule(
|
|
626
|
+
policy,
|
|
627
|
+
"initiator",
|
|
628
|
+
`referrer-class:${rule.referrerClass}`,
|
|
629
|
+
advertiserHost,
|
|
630
|
+
rule.reason ?? "initiator/referrer class matched"
|
|
631
|
+
)
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return matches;
|
|
637
|
+
}
|
|
638
|
+
function matchedRule(policy, kind, rule, advertiserHost, reason) {
|
|
639
|
+
return {
|
|
640
|
+
policyId: policy.id,
|
|
641
|
+
networkId: policy.network.id,
|
|
642
|
+
networkName: policy.network.name,
|
|
643
|
+
kind,
|
|
644
|
+
rule,
|
|
645
|
+
advertiserHost,
|
|
646
|
+
reason,
|
|
647
|
+
sourceUrl: policy.metadata.sourceUrl
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function kindPriority(kind) {
|
|
651
|
+
if (kind === "redirect-domain") {
|
|
652
|
+
return 0;
|
|
653
|
+
}
|
|
654
|
+
if (kind === "landing-param") {
|
|
655
|
+
return 1;
|
|
656
|
+
}
|
|
657
|
+
if (kind === "cookie") {
|
|
658
|
+
return 2;
|
|
659
|
+
}
|
|
660
|
+
return 3;
|
|
661
|
+
}
|
|
662
|
+
function paramRuleMatches(rule, params) {
|
|
663
|
+
return rule.anyOf.some(
|
|
664
|
+
(group) => group.allOf.every((matcher) => paramMatcherMatches(matcher, params))
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
function paramMatcherMatches(matcher, params) {
|
|
668
|
+
const values = params.getAll(matcher.name);
|
|
669
|
+
const mode = matcher.match ?? (matcher.value === void 0 ? "exists" : "equals");
|
|
670
|
+
if (mode === "exists") {
|
|
671
|
+
return values.length > 0;
|
|
672
|
+
}
|
|
673
|
+
const expectedValue = matcher.value;
|
|
674
|
+
if (expectedValue === void 0) {
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
if (mode === "equals") {
|
|
678
|
+
return values.some((value) => value === expectedValue);
|
|
679
|
+
}
|
|
680
|
+
return values.some((value) => value.includes(expectedValue));
|
|
681
|
+
}
|
|
682
|
+
function cookieRuleMatches(rule, cookieNames) {
|
|
683
|
+
if (rule.match === "exact") {
|
|
684
|
+
return cookieNames.some((name) => name === rule.name);
|
|
685
|
+
}
|
|
686
|
+
return cookieNames.some((name) => name.includes(rule.name));
|
|
687
|
+
}
|
|
688
|
+
function initiatorRuleMatches(rule, signals, advertiserHost) {
|
|
689
|
+
return classifyReferrer(signals, advertiserHost) === rule.referrerClass;
|
|
690
|
+
}
|
|
691
|
+
function hasScopedSelfExemption(currentUrl, selfPatterns, policy) {
|
|
692
|
+
return (selfPatterns ?? []).some((pattern) => {
|
|
693
|
+
const scopedToPolicy = pattern.policyId === policy.id;
|
|
694
|
+
const scopedToNetwork = pattern.networkId === policy.network.id;
|
|
695
|
+
return (scopedToPolicy || scopedToNetwork) && paramMatcherMatches(pattern, currentUrl.searchParams);
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
function hasUnscopedSelfExemption(currentUrl, selfPatterns) {
|
|
699
|
+
return (selfPatterns ?? []).some(
|
|
700
|
+
(pattern) => pattern.policyId === void 0 && pattern.networkId === void 0 && paramMatcherMatches(pattern, currentUrl.searchParams)
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
function domainSuffixMatches2(host, pattern) {
|
|
704
|
+
const normalizedHost = host.toLowerCase().replace(/\.$/, "");
|
|
705
|
+
const normalizedPattern = pattern.toLowerCase().replace(/^\./, "").replace(/\.$/, "");
|
|
706
|
+
return normalizedHost === normalizedPattern || normalizedHost.endsWith(`.${normalizedPattern}`);
|
|
707
|
+
}
|
|
708
|
+
function describeParamRule(rule) {
|
|
709
|
+
return rule.anyOf.map(
|
|
710
|
+
(group) => group.allOf.map((matcher) => {
|
|
711
|
+
const mode = matcher.match ?? (matcher.value === void 0 ? "exists" : "equals");
|
|
712
|
+
return matcher.value === void 0 ? `${matcher.name}:${mode}` : `${matcher.name}:${mode}:${matcher.value}`;
|
|
713
|
+
}).join("&")
|
|
714
|
+
).join("|");
|
|
715
|
+
}
|
|
716
|
+
function parseUrl(value) {
|
|
717
|
+
try {
|
|
718
|
+
return new URL(value);
|
|
719
|
+
} catch {
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function hostFromUrl(value) {
|
|
724
|
+
return parseUrl(value)?.hostname.toLowerCase();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/guard.ts
|
|
728
|
+
function guardActivation(req) {
|
|
729
|
+
try {
|
|
730
|
+
validatePolicy(req.policy);
|
|
731
|
+
} catch {
|
|
732
|
+
return { allowed: false, reason: "malformed-policy" };
|
|
733
|
+
}
|
|
734
|
+
if (req.policy.activation.mode === "never") {
|
|
735
|
+
return { allowed: false, reason: "policy-never-activates" };
|
|
736
|
+
}
|
|
737
|
+
if (req.decision.standDown) {
|
|
738
|
+
return { allowed: false, reason: "active-standdown" };
|
|
739
|
+
}
|
|
740
|
+
if (!isGenuineGesture(req.userGesture)) {
|
|
741
|
+
return { allowed: false, reason: "missing-trusted-user-gesture" };
|
|
742
|
+
}
|
|
743
|
+
if (req.benefit.description.trim() === "") {
|
|
744
|
+
return { allowed: false, reason: "missing-user-benefit" };
|
|
745
|
+
}
|
|
746
|
+
const maxPrompts = req.policy.activation.maxPromptsPerJourney;
|
|
747
|
+
if (maxPrompts !== void 0 && (req.decision.promptCount ?? 0) >= maxPrompts) {
|
|
748
|
+
return { allowed: false, reason: "max-prompts-exhausted" };
|
|
749
|
+
}
|
|
750
|
+
const allowedReferrerClasses = req.policy.activation.allowedReferrerClasses;
|
|
751
|
+
if (allowedReferrerClasses !== void 0) {
|
|
752
|
+
if (req.decision.referrerClass === void 0) {
|
|
753
|
+
return { allowed: false, reason: "missing-referrer-class" };
|
|
754
|
+
}
|
|
755
|
+
if (!isActivationReferrerClass(req.decision.referrerClass)) {
|
|
756
|
+
return { allowed: false, reason: "referrer-class-disallowed" };
|
|
757
|
+
}
|
|
758
|
+
if (!allowedReferrerClasses.includes(req.decision.referrerClass)) {
|
|
759
|
+
return { allowed: false, reason: "referrer-class-disallowed" };
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return { allowed: true, reason: "allowed" };
|
|
763
|
+
}
|
|
764
|
+
function isActivationReferrerClass(value) {
|
|
765
|
+
return value === "own-site" || value === "organic" || value === "direct";
|
|
766
|
+
}
|
|
767
|
+
function isGenuineGesture(gesture) {
|
|
768
|
+
return gesture.isTrusted === true && gesture.type.trim() !== "" && Number.isFinite(gesture.timeStamp);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/rakuten.ts
|
|
772
|
+
var DEFAULT_RAKUTEN_SESSION_MS = 18e5;
|
|
773
|
+
var DEFAULT_SOURCE_URL = "https://github.com/rakutenrewards/PublisherStandown-SDK";
|
|
774
|
+
var LAST_VERIFIED = "2026-07-10";
|
|
775
|
+
var STANDDOWN_BEHAVIORS = [
|
|
776
|
+
"suppress-prompts",
|
|
777
|
+
"no-cookie-write",
|
|
778
|
+
"no-redirect",
|
|
779
|
+
"no-background-tracking"
|
|
780
|
+
];
|
|
781
|
+
function fromRakutenPolicy(policy) {
|
|
782
|
+
if (policy.schemaVersion !== 2) {
|
|
783
|
+
throw new TypeError("Rakuten policy schemaVersion must be 2");
|
|
784
|
+
}
|
|
785
|
+
const detection = {};
|
|
786
|
+
const landingParams = landingParamsFromRakutenRules(policy.rules);
|
|
787
|
+
const redirectDomains = redirectDomainsFromRakutenRules(policy.rules);
|
|
788
|
+
if (landingParams !== void 0) {
|
|
789
|
+
detection.landingParams = landingParams;
|
|
790
|
+
}
|
|
791
|
+
if (redirectDomains !== void 0) {
|
|
792
|
+
detection.redirectDomains = redirectDomains;
|
|
793
|
+
}
|
|
794
|
+
const nativePolicy = {
|
|
795
|
+
id: policy.id,
|
|
796
|
+
schemaVersion: 3,
|
|
797
|
+
policyVersion: policy.policyVersion,
|
|
798
|
+
network: {
|
|
799
|
+
id: policy.network.id,
|
|
800
|
+
name: policy.network.name,
|
|
801
|
+
policyUrl: DEFAULT_SOURCE_URL
|
|
802
|
+
},
|
|
803
|
+
detection,
|
|
804
|
+
standdown: {
|
|
805
|
+
scope: "advertiser",
|
|
806
|
+
sessionRule: "session-or-min",
|
|
807
|
+
minDurationMs: policy.network.sessionDuration ?? DEFAULT_RAKUTEN_SESSION_MS,
|
|
808
|
+
behaviors: STANDDOWN_BEHAVIORS
|
|
809
|
+
},
|
|
810
|
+
activation: { mode: "user-click" },
|
|
811
|
+
metadata: {
|
|
812
|
+
sourceUrl: DEFAULT_SOURCE_URL,
|
|
813
|
+
lastVerified: LAST_VERIFIED,
|
|
814
|
+
notes: "Converted from Rakuten NetworkPolicy schemaVersion 2. Lossy mapping: Rakuten paths/patterns are represented as redirect-domain regex rules; Rakuten has no native fields for cookies, initiator rules, activation guard details, stand-down behaviors, citations, or audit semantics. Emitting back to Rakuten drops multi-group anyOf param rules and match:contains params."
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
try {
|
|
818
|
+
validatePolicy(nativePolicy);
|
|
819
|
+
} catch (error) {
|
|
820
|
+
throw new TypeError(
|
|
821
|
+
`Converted Rakuten policy is invalid: ${messageFromError2(error)}`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
return nativePolicy;
|
|
825
|
+
}
|
|
826
|
+
function toRakutenPolicy(policy) {
|
|
827
|
+
return {
|
|
828
|
+
id: policy.id,
|
|
829
|
+
schemaVersion: 2,
|
|
830
|
+
policyVersion: policy.policyVersion,
|
|
831
|
+
network: {
|
|
832
|
+
id: policy.network.id,
|
|
833
|
+
name: policy.network.name,
|
|
834
|
+
description: "Converted from standdown.js. Lossy mapping: cookies, initiator rules, advertiserHosts, activation guard details, behaviors, metadata, citations, audit semantics, multi-group anyOf param rules, and match:contains params are not representable in Rakuten NetworkPolicy v2. The bundled rakuten policy intentionally does not round-trip exactly.",
|
|
835
|
+
sessionDuration: policy.standdown.minDurationMs
|
|
836
|
+
},
|
|
837
|
+
rules: rakutenRulesFromPolicy(policy)
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function landingParamsFromRakutenRules(rules) {
|
|
841
|
+
const paramRules = rules.flatMap((rule) => {
|
|
842
|
+
const matchers = paramMatchersFromRakutenParams(rule.params);
|
|
843
|
+
if (matchers.length === 0) {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
return [
|
|
847
|
+
{
|
|
848
|
+
anyOf: [{ allOf: matchers }],
|
|
849
|
+
...rule.reason === void 0 ? {} : { reason: rule.reason }
|
|
850
|
+
}
|
|
851
|
+
];
|
|
852
|
+
});
|
|
853
|
+
return paramRules.length === 0 ? void 0 : paramRules;
|
|
854
|
+
}
|
|
855
|
+
function redirectDomainsFromRakutenRules(rules) {
|
|
856
|
+
const domainRules = [];
|
|
857
|
+
for (const rule of rules) {
|
|
858
|
+
if (rule.pattern !== void 0) {
|
|
859
|
+
domainRules.push({
|
|
860
|
+
pattern: rule.pattern,
|
|
861
|
+
kind: "regex",
|
|
862
|
+
...rule.reason === void 0 ? {} : { comment: rule.reason }
|
|
863
|
+
});
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (rule.domain === void 0) {
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
if (rule.paths !== void 0 && rule.paths.length > 0) {
|
|
870
|
+
domainRules.push({
|
|
871
|
+
pattern: domainAndPathsPattern(rule.domain, rule.paths),
|
|
872
|
+
kind: "regex",
|
|
873
|
+
...rule.reason === void 0 ? {} : { comment: rule.reason }
|
|
874
|
+
});
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
domainRules.push({
|
|
878
|
+
pattern: rule.domain,
|
|
879
|
+
kind: "suffix",
|
|
880
|
+
...rule.reason === void 0 ? {} : { comment: rule.reason }
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
return domainRules.length === 0 ? void 0 : domainRules;
|
|
884
|
+
}
|
|
885
|
+
function rakutenRulesFromPolicy(policy) {
|
|
886
|
+
const rules = [];
|
|
887
|
+
for (const rule of policy.detection.redirectDomains ?? []) {
|
|
888
|
+
if (rule.kind === "suffix") {
|
|
889
|
+
rules.push({
|
|
890
|
+
domain: rule.pattern,
|
|
891
|
+
...rule.comment === void 0 ? {} : { reason: rule.comment }
|
|
892
|
+
});
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
rules.push({
|
|
896
|
+
pattern: rule.pattern,
|
|
897
|
+
...rule.comment === void 0 ? {} : { reason: rule.comment }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
for (const rule of policy.detection.landingParams ?? []) {
|
|
901
|
+
const params = rakutenParamsFromParamRule(rule);
|
|
902
|
+
if (params !== void 0) {
|
|
903
|
+
rules.push({
|
|
904
|
+
params,
|
|
905
|
+
...rule.reason === void 0 ? {} : { reason: rule.reason }
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return rules;
|
|
910
|
+
}
|
|
911
|
+
function paramMatchersFromRakutenParams(params) {
|
|
912
|
+
if (params === void 0) {
|
|
913
|
+
return [];
|
|
914
|
+
}
|
|
915
|
+
if (Array.isArray(params)) {
|
|
916
|
+
return params.map((name) => ({ name }));
|
|
917
|
+
}
|
|
918
|
+
return Object.entries(params).map(([name, value]) => ({
|
|
919
|
+
name,
|
|
920
|
+
value,
|
|
921
|
+
match: "equals"
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
function rakutenParamsFromParamRule(rule) {
|
|
925
|
+
if (rule.anyOf.length !== 1) {
|
|
926
|
+
return void 0;
|
|
927
|
+
}
|
|
928
|
+
const group = rule.anyOf[0];
|
|
929
|
+
if (group === void 0) {
|
|
930
|
+
return void 0;
|
|
931
|
+
}
|
|
932
|
+
const params = [];
|
|
933
|
+
const valuedParams = {};
|
|
934
|
+
let hasValue = false;
|
|
935
|
+
for (const matcher of group.allOf) {
|
|
936
|
+
const mode = matcher.match ?? (matcher.value === void 0 ? "exists" : "equals");
|
|
937
|
+
if (mode === "exists" && matcher.value === void 0) {
|
|
938
|
+
params.push(matcher.name);
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (mode === "equals" && matcher.value !== void 0) {
|
|
942
|
+
valuedParams[matcher.name] = matcher.value;
|
|
943
|
+
hasValue = true;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
return void 0;
|
|
947
|
+
}
|
|
948
|
+
return hasValue ? valuedParams : params;
|
|
949
|
+
}
|
|
950
|
+
function domainAndPathsPattern(domain, paths) {
|
|
951
|
+
const escapedDomain = escapeRegex(domain);
|
|
952
|
+
const escapedPaths = paths.map((path) => escapeRegex(path)).join("|");
|
|
953
|
+
return `^https?://([^/]+\\.)?${escapedDomain}(${escapedPaths})([/?#]|$)`;
|
|
954
|
+
}
|
|
955
|
+
function escapeRegex(value) {
|
|
956
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
957
|
+
}
|
|
958
|
+
function messageFromError2(error) {
|
|
959
|
+
return error instanceof Error ? error.message : String(error);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/session.ts
|
|
963
|
+
var MemoryStateStore = class {
|
|
964
|
+
#state;
|
|
965
|
+
constructor(initialState) {
|
|
966
|
+
this.#state = initialState ? cloneState(initialState) : void 0;
|
|
967
|
+
}
|
|
968
|
+
async load() {
|
|
969
|
+
return this.#state ? cloneState(this.#state) : void 0;
|
|
970
|
+
}
|
|
971
|
+
async save(state) {
|
|
972
|
+
this.#state = cloneState(state);
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
var StanddownSession = class {
|
|
976
|
+
#store;
|
|
977
|
+
#auditLog;
|
|
978
|
+
#maxAuditEntries;
|
|
979
|
+
#readOnlyAuditLog = [];
|
|
980
|
+
constructor(store, opts) {
|
|
981
|
+
this.#store = store;
|
|
982
|
+
this.#auditLog = opts?.auditLog ?? true;
|
|
983
|
+
this.#maxAuditEntries = Math.max(0, opts?.maxAuditEntries ?? 1e3);
|
|
984
|
+
}
|
|
985
|
+
async ingest(signals, policies) {
|
|
986
|
+
const advertiserHost = hostFromUrl2(signals.url);
|
|
987
|
+
try {
|
|
988
|
+
validatePolicies(policies);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
return this.#failClosedWithAudit(
|
|
991
|
+
signals.now,
|
|
992
|
+
"ingest",
|
|
993
|
+
advertiserHost,
|
|
994
|
+
`malformed-policy: ${messageFromError3(error)}`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
const detection = detect(signals, policies);
|
|
998
|
+
if (detection.failClosedReason) {
|
|
999
|
+
return this.#failClosedWithAudit(
|
|
1000
|
+
signals.now,
|
|
1001
|
+
"ingest",
|
|
1002
|
+
advertiserHost,
|
|
1003
|
+
detection.failClosedReason,
|
|
1004
|
+
detection
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
return this.#withState(signals.now, (state) => {
|
|
1008
|
+
pruneExpiredSessions(state, signals.now);
|
|
1009
|
+
if (detection.strongest) {
|
|
1010
|
+
const matchedPolicies = policiesForDetection(policies, detection);
|
|
1011
|
+
if (matchedPolicies.length === 0) {
|
|
1012
|
+
return {
|
|
1013
|
+
decision: failClosedDecision("matched-policy-missing"),
|
|
1014
|
+
detection
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
const record = upsertSessionRecord(
|
|
1018
|
+
state,
|
|
1019
|
+
detection.strongest.advertiserHost,
|
|
1020
|
+
detection.strongest.policyId,
|
|
1021
|
+
matchedPolicies,
|
|
1022
|
+
signals.now
|
|
1023
|
+
);
|
|
1024
|
+
return {
|
|
1025
|
+
decision: decisionFromRecord(record, signals.now, {
|
|
1026
|
+
reason: detection.strongest.reason,
|
|
1027
|
+
referrerClass: classifyReferrer(signals, record.advertiserHost)
|
|
1028
|
+
}),
|
|
1029
|
+
detection
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
const activeDecision = advertiserHost ? activeDecisionForHost(state, advertiserHost, signals.now) : void 0;
|
|
1033
|
+
return {
|
|
1034
|
+
decision: activeDecision ?? {
|
|
1035
|
+
standDown: false,
|
|
1036
|
+
reason: detection.selfMatch ? "self-exempted-no-active-standdown" : "no-active-standdown",
|
|
1037
|
+
behaviors: [],
|
|
1038
|
+
referrerClass: advertiserHost ? classifyReferrer(signals, advertiserHost) : "other"
|
|
1039
|
+
},
|
|
1040
|
+
detection
|
|
1041
|
+
};
|
|
1042
|
+
}, "ingest");
|
|
1043
|
+
}
|
|
1044
|
+
async shouldStandDown(advertiserHost, now) {
|
|
1045
|
+
return this.#withState(
|
|
1046
|
+
now,
|
|
1047
|
+
(state) => {
|
|
1048
|
+
pruneExpiredSessions(state, now);
|
|
1049
|
+
return {
|
|
1050
|
+
decision: activeDecisionForHost(state, advertiserHost, now) ?? {
|
|
1051
|
+
standDown: false,
|
|
1052
|
+
reason: "no-active-standdown",
|
|
1053
|
+
behaviors: []
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
},
|
|
1057
|
+
"shouldStandDown",
|
|
1058
|
+
normalizeHost(advertiserHost),
|
|
1059
|
+
{ persist: false }
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
async recordActivity(now) {
|
|
1063
|
+
await this.#withState(
|
|
1064
|
+
now,
|
|
1065
|
+
(state) => {
|
|
1066
|
+
pruneExpiredSessions(state, now);
|
|
1067
|
+
for (const record of Object.values(state.sessions)) {
|
|
1068
|
+
if (record.sessionRule === "inactivity-window") {
|
|
1069
|
+
record.lastActivityAt = now;
|
|
1070
|
+
const expiresAt = computeExpiresAt(record);
|
|
1071
|
+
if (expiresAt !== void 0) {
|
|
1072
|
+
record.expiresAt = expiresAt;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return {
|
|
1077
|
+
decision: {
|
|
1078
|
+
standDown: false,
|
|
1079
|
+
reason: "activity-recorded",
|
|
1080
|
+
behaviors: []
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
},
|
|
1084
|
+
"recordActivity"
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
async exportAuditLog() {
|
|
1088
|
+
const state = await this.#loadState();
|
|
1089
|
+
return trimAuditLog(
|
|
1090
|
+
[...state.auditLog, ...this.#readOnlyAuditLog],
|
|
1091
|
+
this.#maxAuditEntries
|
|
1092
|
+
).map(cloneAuditEntry);
|
|
1093
|
+
}
|
|
1094
|
+
async #withState(now, fn, action, advertiserHost, opts = {}) {
|
|
1095
|
+
let state;
|
|
1096
|
+
try {
|
|
1097
|
+
state = await this.#loadState();
|
|
1098
|
+
} catch {
|
|
1099
|
+
return failClosedDecision("store-error");
|
|
1100
|
+
}
|
|
1101
|
+
const result = fn(state);
|
|
1102
|
+
if (this.#auditLog) {
|
|
1103
|
+
const entry = auditEntry({
|
|
1104
|
+
time: now,
|
|
1105
|
+
action,
|
|
1106
|
+
advertiserHost: advertiserHost ?? result.detection?.strongest?.advertiserHost,
|
|
1107
|
+
detection: result.detection,
|
|
1108
|
+
decision: result.decision
|
|
1109
|
+
});
|
|
1110
|
+
if (opts.persist === false) {
|
|
1111
|
+
appendAuditEntry(this.#readOnlyAuditLog, entry, this.#maxAuditEntries);
|
|
1112
|
+
} else {
|
|
1113
|
+
appendAuditEntry(state.auditLog, entry, this.#maxAuditEntries);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (opts.persist === false) {
|
|
1117
|
+
return result.decision;
|
|
1118
|
+
}
|
|
1119
|
+
try {
|
|
1120
|
+
await this.#store.save(state);
|
|
1121
|
+
} catch {
|
|
1122
|
+
return failClosedDecision("store-error");
|
|
1123
|
+
}
|
|
1124
|
+
return result.decision;
|
|
1125
|
+
}
|
|
1126
|
+
async #failClosedWithAudit(now, action, advertiserHost, reason, detection) {
|
|
1127
|
+
const decision = failClosedDecision(reason);
|
|
1128
|
+
if (!this.#auditLog) {
|
|
1129
|
+
return decision;
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const state = await this.#loadState();
|
|
1133
|
+
appendAuditEntry(
|
|
1134
|
+
state.auditLog,
|
|
1135
|
+
auditEntry({
|
|
1136
|
+
time: now,
|
|
1137
|
+
action,
|
|
1138
|
+
advertiserHost,
|
|
1139
|
+
detection,
|
|
1140
|
+
decision
|
|
1141
|
+
}),
|
|
1142
|
+
this.#maxAuditEntries
|
|
1143
|
+
);
|
|
1144
|
+
await this.#store.save(state);
|
|
1145
|
+
} catch {
|
|
1146
|
+
return failClosedDecision("store-error");
|
|
1147
|
+
}
|
|
1148
|
+
return decision;
|
|
1149
|
+
}
|
|
1150
|
+
async #loadState() {
|
|
1151
|
+
return await this.#store.load() ?? { sessions: {}, auditLog: [] };
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
function appendAuditEntry(auditLog, entry, maxEntries) {
|
|
1155
|
+
if (maxEntries === 0) {
|
|
1156
|
+
auditLog.length = 0;
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
auditLog.push(entry);
|
|
1160
|
+
if (auditLog.length > maxEntries) {
|
|
1161
|
+
auditLog.splice(0, auditLog.length - maxEntries);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function trimAuditLog(auditLog, maxEntries) {
|
|
1165
|
+
if (maxEntries === 0) {
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
return auditLog.slice(Math.max(0, auditLog.length - maxEntries));
|
|
1169
|
+
}
|
|
1170
|
+
function upsertSessionRecord(state, advertiserHost, primaryPolicyId, policies, now) {
|
|
1171
|
+
const key = normalizeHost(advertiserHost);
|
|
1172
|
+
const existing = state.sessions[key];
|
|
1173
|
+
const startedAt = existing?.startedAt ?? now;
|
|
1174
|
+
const lastActivityAt = now;
|
|
1175
|
+
const sessionRule = existing?.sessionRule === "session-or-min" || policies.some((policy) => policy.standdown.sessionRule === "session-or-min") ? "session-or-min" : "inactivity-window";
|
|
1176
|
+
const baseRecord = {
|
|
1177
|
+
advertiserHost: key,
|
|
1178
|
+
policyId: primaryPolicyId,
|
|
1179
|
+
startedAt,
|
|
1180
|
+
lastActivityAt,
|
|
1181
|
+
sessionRule,
|
|
1182
|
+
minDurationMs: Math.max(
|
|
1183
|
+
existing?.minDurationMs ?? 0,
|
|
1184
|
+
...policies.map((policy) => policy.standdown.minDurationMs)
|
|
1185
|
+
),
|
|
1186
|
+
behaviors: unionBehaviors([
|
|
1187
|
+
...existing?.behaviors ?? [],
|
|
1188
|
+
...policies.flatMap((policy) => policy.standdown.behaviors)
|
|
1189
|
+
])
|
|
1190
|
+
};
|
|
1191
|
+
if (sessionRule === "inactivity-window") {
|
|
1192
|
+
const inactivityMs = Math.max(
|
|
1193
|
+
existing?.inactivityMs ?? 0,
|
|
1194
|
+
...policies.flatMap(
|
|
1195
|
+
(policy) => policy.standdown.inactivityMs === void 0 ? [] : [policy.standdown.inactivityMs]
|
|
1196
|
+
)
|
|
1197
|
+
);
|
|
1198
|
+
if (inactivityMs > 0) {
|
|
1199
|
+
baseRecord.inactivityMs = inactivityMs;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const computedExpiresAt = computeExpiresAt(baseRecord);
|
|
1203
|
+
if (computedExpiresAt !== void 0) {
|
|
1204
|
+
baseRecord.expiresAt = Math.max(existing?.expiresAt ?? 0, computedExpiresAt);
|
|
1205
|
+
}
|
|
1206
|
+
state.sessions[key] = baseRecord;
|
|
1207
|
+
return baseRecord;
|
|
1208
|
+
}
|
|
1209
|
+
function policiesForDetection(policies, detection) {
|
|
1210
|
+
if (detection.strongest === void 0) {
|
|
1211
|
+
return [];
|
|
1212
|
+
}
|
|
1213
|
+
const matchingPolicyIds = new Set(
|
|
1214
|
+
detection.matched.filter(
|
|
1215
|
+
(match) => match.advertiserHost === detection.strongest?.advertiserHost
|
|
1216
|
+
).map((match) => match.policyId)
|
|
1217
|
+
);
|
|
1218
|
+
return policies.filter((policy) => matchingPolicyIds.has(policy.id));
|
|
1219
|
+
}
|
|
1220
|
+
function unionBehaviors(behaviors) {
|
|
1221
|
+
return [...new Set(behaviors)];
|
|
1222
|
+
}
|
|
1223
|
+
function activeDecisionForHost(state, advertiserHost, now) {
|
|
1224
|
+
const record = state.sessions[normalizeHost(advertiserHost)];
|
|
1225
|
+
if (!record || !isActive(record, now)) {
|
|
1226
|
+
return void 0;
|
|
1227
|
+
}
|
|
1228
|
+
return decisionFromRecord(record, now);
|
|
1229
|
+
}
|
|
1230
|
+
function decisionFromRecord(record, now, overrides) {
|
|
1231
|
+
const expiresAt = computeExpiresAt(record);
|
|
1232
|
+
const decision = {
|
|
1233
|
+
standDown: true,
|
|
1234
|
+
policyId: record.policyId,
|
|
1235
|
+
reason: overrides?.reason ?? `active-standdown:${record.policyId}`,
|
|
1236
|
+
behaviors: [...record.behaviors]
|
|
1237
|
+
};
|
|
1238
|
+
if (expiresAt !== void 0) {
|
|
1239
|
+
decision.expiresAt = expiresAt;
|
|
1240
|
+
}
|
|
1241
|
+
if (overrides?.referrerClass !== void 0) {
|
|
1242
|
+
decision.referrerClass = overrides.referrerClass;
|
|
1243
|
+
}
|
|
1244
|
+
if (!isActive({ ...record, ...expiresAt === void 0 ? {} : { expiresAt } }, now)) {
|
|
1245
|
+
return {
|
|
1246
|
+
standDown: false,
|
|
1247
|
+
reason: "standdown-expired",
|
|
1248
|
+
behaviors: []
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
return decision;
|
|
1252
|
+
}
|
|
1253
|
+
function computeExpiresAt(record) {
|
|
1254
|
+
if (record.sessionRule === "inactivity-window" && record.inactivityMs !== void 0) {
|
|
1255
|
+
return Math.max(
|
|
1256
|
+
record.startedAt + record.minDurationMs,
|
|
1257
|
+
record.lastActivityAt + record.inactivityMs
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return record.expiresAt;
|
|
1261
|
+
}
|
|
1262
|
+
function isActive(record, now) {
|
|
1263
|
+
if (record.sessionRule === "session-or-min") {
|
|
1264
|
+
return true;
|
|
1265
|
+
}
|
|
1266
|
+
const expiresAt = computeExpiresAt(record);
|
|
1267
|
+
return expiresAt === void 0 ? false : now < expiresAt;
|
|
1268
|
+
}
|
|
1269
|
+
function pruneExpiredSessions(state, now) {
|
|
1270
|
+
for (const [host, record] of Object.entries(state.sessions)) {
|
|
1271
|
+
if (!isActive(record, now)) {
|
|
1272
|
+
delete state.sessions[host];
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function failClosedDecision(reason) {
|
|
1277
|
+
return {
|
|
1278
|
+
standDown: true,
|
|
1279
|
+
reason,
|
|
1280
|
+
behaviors: [
|
|
1281
|
+
"suppress-prompts",
|
|
1282
|
+
"no-cookie-write",
|
|
1283
|
+
"no-redirect",
|
|
1284
|
+
"no-background-tracking"
|
|
1285
|
+
]
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function auditEntry(value) {
|
|
1289
|
+
const entry = {
|
|
1290
|
+
time: value.time,
|
|
1291
|
+
action: value.action
|
|
1292
|
+
};
|
|
1293
|
+
if (value.advertiserHost !== void 0) {
|
|
1294
|
+
entry.advertiserHost = value.advertiserHost;
|
|
1295
|
+
}
|
|
1296
|
+
if (value.detection !== void 0) {
|
|
1297
|
+
entry.detection = value.detection;
|
|
1298
|
+
}
|
|
1299
|
+
if (value.decision !== void 0) {
|
|
1300
|
+
entry.decision = value.decision;
|
|
1301
|
+
}
|
|
1302
|
+
return entry;
|
|
1303
|
+
}
|
|
1304
|
+
function cloneState(state) {
|
|
1305
|
+
return {
|
|
1306
|
+
sessions: Object.fromEntries(
|
|
1307
|
+
Object.entries(state.sessions).map(([host, record]) => [
|
|
1308
|
+
host,
|
|
1309
|
+
{
|
|
1310
|
+
...record,
|
|
1311
|
+
behaviors: [...record.behaviors]
|
|
1312
|
+
}
|
|
1313
|
+
])
|
|
1314
|
+
),
|
|
1315
|
+
auditLog: state.auditLog.map(cloneAuditEntry)
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
function cloneAuditEntry(entry) {
|
|
1319
|
+
const cloned = {
|
|
1320
|
+
time: entry.time,
|
|
1321
|
+
action: entry.action
|
|
1322
|
+
};
|
|
1323
|
+
if (entry.advertiserHost !== void 0) {
|
|
1324
|
+
cloned.advertiserHost = entry.advertiserHost;
|
|
1325
|
+
}
|
|
1326
|
+
if (entry.detection !== void 0) {
|
|
1327
|
+
const detection = {
|
|
1328
|
+
matched: entry.detection.matched.map((match) => ({ ...match })),
|
|
1329
|
+
selfMatch: entry.detection.selfMatch
|
|
1330
|
+
};
|
|
1331
|
+
if (entry.detection.strongest !== void 0) {
|
|
1332
|
+
detection.strongest = { ...entry.detection.strongest };
|
|
1333
|
+
}
|
|
1334
|
+
if (entry.detection.failClosedReason !== void 0) {
|
|
1335
|
+
detection.failClosedReason = entry.detection.failClosedReason;
|
|
1336
|
+
}
|
|
1337
|
+
cloned.detection = detection;
|
|
1338
|
+
}
|
|
1339
|
+
if (entry.decision !== void 0) {
|
|
1340
|
+
cloned.decision = {
|
|
1341
|
+
...entry.decision,
|
|
1342
|
+
behaviors: [...entry.decision.behaviors]
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
return cloned;
|
|
1346
|
+
}
|
|
1347
|
+
function normalizeHost(host) {
|
|
1348
|
+
return host.toLowerCase().replace(/\.$/, "");
|
|
1349
|
+
}
|
|
1350
|
+
function hostFromUrl2(value) {
|
|
1351
|
+
try {
|
|
1352
|
+
return normalizeHost(new URL(value).hostname);
|
|
1353
|
+
} catch {
|
|
1354
|
+
return void 0;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
function messageFromError3(error) {
|
|
1358
|
+
return error instanceof Error ? error.message : String(error);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
exports.MemoryStateStore = MemoryStateStore;
|
|
1362
|
+
exports.StanddownSession = StanddownSession;
|
|
1363
|
+
exports.canonicalJson = canonicalJson;
|
|
1364
|
+
exports.canonicalPolicyBundlePayload = canonicalPolicyBundlePayload;
|
|
1365
|
+
exports.classifyReferrer = classifyReferrer;
|
|
1366
|
+
exports.detect = detect;
|
|
1367
|
+
exports.domainRuleMatchesUrl = domainRuleMatchesUrl;
|
|
1368
|
+
exports.fromRakutenPolicy = fromRakutenPolicy;
|
|
1369
|
+
exports.guardActivation = guardActivation;
|
|
1370
|
+
exports.toRakutenPolicy = toRakutenPolicy;
|
|
1371
|
+
exports.validatePolicies = validatePolicies;
|
|
1372
|
+
exports.validatePolicy = validatePolicy;
|
|
1373
|
+
exports.verifyPolicyBundle = verifyPolicyBundle;
|
|
1374
|
+
//# sourceMappingURL=index.cjs.map
|
|
1375
|
+
//# sourceMappingURL=index.cjs.map
|