@tokenoftrust/storefront-runner 2.0.24 → 2.0.25

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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Read a tenant's BAKED managed installs off the materialize-only signed header.
3
+ *
4
+ * This is the read half of the wire documented in
5
+ * `packages/public-runtime/src/integration/baked-managed-installs.ts`. It exists because
6
+ * ADR 0013 gives a public page no per-request access to admin-installed state — the
7
+ * static bake has no D1 binding at all — so an active install can only reach a published
8
+ * bundle by being resolved and signed once, by the Worker, at go-live.
9
+ *
10
+ * Fail-safe at every step: not in materialize mode, no header, no secret, a signature
11
+ * that does not verify, or a payload that does not parse all yield an EMPTY list, and the
12
+ * render then resolves the environment-only plan exactly as it would for a tenant with no
13
+ * integrations. Nothing here can widen what a public request may inject, because gate one
14
+ * is a binding the deployed Worker never has.
15
+ */
16
+ import {
17
+ MANAGED_INSTALLS_INPUT_HEADER,
18
+ MANAGED_INSTALLS_SIG_HEADER,
19
+ parseManagedInstalls,
20
+ verifyManagedInstallsSig,
21
+ MATERIALIZE_MODE_BINDING,
22
+ CONSENT_SNAPSHOT_HMAC_SECRET_BINDING,
23
+ type IntegrationDeclarationInput,
24
+ } from "@tot/public-runtime";
25
+
26
+ /** Reads a runtime binding; injected so this is testable without a Worker env. */
27
+ export type ReadEnv = (key: string) => Promise<string | undefined>;
28
+
29
+ /**
30
+ * The signed, materialize-only managed installs for `tenantPublicId`, or `[]`.
31
+ *
32
+ * The signature is TENANT-BOUND, so a payload signed for one tenant cannot be replayed
33
+ * into another tenant's bundle even by something that legitimately holds a signed header
34
+ * — which matters because one publish job builds one tenant at a time from a shared
35
+ * pipeline.
36
+ */
37
+ export async function readBakedManagedInstalls(
38
+ request: Request,
39
+ tenantPublicId: string,
40
+ readEnv: ReadEnv,
41
+ ): Promise<IntegrationDeclarationInput[]> {
42
+ // Gate 1: the materialize-mode binding. The deployed Worker never declares it, so this
43
+ // header is unreadable on any public request and can never enter a cache key.
44
+ if ((await readEnv(MATERIALIZE_MODE_BINDING)) !== "1") return [];
45
+
46
+ const payload = request.headers.get(MANAGED_INSTALLS_INPUT_HEADER);
47
+ if (!payload) return [];
48
+
49
+ // Gate 2: the tenant-bound HMAC, verified constant-time against the go-live secret.
50
+ const secret = await readEnv(CONSENT_SNAPSHOT_HMAC_SECRET_BINDING);
51
+ if (!secret) return [];
52
+ const signature = request.headers.get(MANAGED_INSTALLS_SIG_HEADER);
53
+ if (!(await verifyManagedInstallsSig(secret, tenantPublicId, payload, signature))) return [];
54
+
55
+ return parseManagedInstalls(payload) ?? [];
56
+ }
@@ -6,11 +6,13 @@
6
6
  import {
7
7
  ENVIRONMENT_SCHEMA_VERSION,
8
8
  buildPlatformAdapterDeclaration,
9
+ getPlatformAdapter,
9
10
  validateIntegrationManifest,
10
11
  type IntegrationDeclarationInput,
11
12
  } from "@tot/public-runtime";
12
13
  import { AppVersionStore } from "../apps/appVersionStore.js";
13
14
  import type { Queryable } from "../d1/catalog.js";
15
+ import { parseStageDestinationIds } from "./stageDestinationIds.js";
14
16
 
15
17
  const MANAGED_KIND = "managed-analytics-v1";
16
18
 
@@ -47,11 +49,17 @@ export async function loadActiveManagedInstallDeclarations(
47
49
  if (!manifest.ok || !current || current.tenantId !== tenantPublicId) continue;
48
50
  if (current.appVersion !== row.app_version || current.facets.appVersion !== row.app_version) continue;
49
51
  if (current.facets.digest !== manifest.value.app.digest || manifest.value.app.id !== row.app_id) continue;
50
- const destinationId = current.facets.publicConfig["destinationId"];
51
- if (typeof destinationId !== "string") continue;
52
+ // The stored per-stage map, re-parsed through the SAME validator the API used, so a
53
+ // row that no longer satisfies the contract disables its install rather than
54
+ // resolving a partial or stale destination set.
55
+ const ids = parseStageDestinationIds(
56
+ current.facets.publicConfig["destinationIds"],
57
+ getPlatformAdapter(row.app_id),
58
+ );
59
+ if (!ids.ok) continue;
52
60
  const built = buildPlatformAdapterDeclaration({
53
61
  adapterId: row.app_id,
54
- ids: { live: destinationId },
62
+ ids: ids.value,
55
63
  tenantPublicId,
56
64
  environment: {
57
65
  schemaVersion: ENVIRONMENT_SCHEMA_VERSION,
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The per-stage destination map, parsed once for both callers that need it: the
3
+ * managed-analytics API (validating what a merchant submits) and the render plane
4
+ * (rebuilding a stored install's declaration). One parser, so what the console accepts
5
+ * and what actually fires can never disagree.
6
+ *
7
+ * WHY PER STAGE. `resolveAdapterDestinationId` reads only the CURRENT stage's id, and
8
+ * `buildPlatformAdapterDeclaration` refuses a non-live stage that reuses the live one. So
9
+ * with a single id the runtime is already correct — a non-live stage resolves nothing and
10
+ * dispatches nothing — but the merchant has no way to measure anything off live either.
11
+ * A per-stage map is what makes non-live verification possible without pointing preview
12
+ * traffic at the property that carries real reporting.
13
+ */
14
+ import {
15
+ ADAPTER_CONFIG_STAGES,
16
+ type AdapterConfigStage,
17
+ type DestinationIdsByEnvironment,
18
+ type PlatformAdapterDefinition,
19
+ } from "@tot/public-runtime";
20
+
21
+ export interface StageDestinationIdsOk {
22
+ ok: true;
23
+ value: DestinationIdsByEnvironment;
24
+ }
25
+
26
+ export interface StageDestinationIdsError {
27
+ ok: false;
28
+ details: { path: string; code: string; message: string }[];
29
+ }
30
+
31
+ export type StageDestinationIdsResult = StageDestinationIdsOk | StageDestinationIdsError;
32
+
33
+ /**
34
+ * Parse and validate a submitted per-stage map fail-closed.
35
+ *
36
+ * `live` is required because it is the stage that publishes; a non-live stage is optional
37
+ * and simply resolves nothing when absent. Every id is checked against the ADAPTER's own
38
+ * format validator, so a Meta pixel id cannot be accepted where a GA4 measurement id
39
+ * belongs. Reusing the live id on a non-live stage is refused HERE with a field-level
40
+ * message rather than left to the declaration builder, so the console can point at the
41
+ * offending stage instead of failing the whole submission opaquely.
42
+ */
43
+ export function parseStageDestinationIds(
44
+ input: unknown,
45
+ platform: PlatformAdapterDefinition | undefined,
46
+ ): StageDestinationIdsResult {
47
+ const base = "body.configuration.destinationIds";
48
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
49
+ return {
50
+ ok: false,
51
+ details: [{ path: base, code: "type", message: "Expected a map of stage to destination identifier." }],
52
+ };
53
+ }
54
+ if (!platform) {
55
+ return { ok: false, details: [{ path: "body.adapterId", code: "unknown_adapter", message: "Unknown adapter." }] };
56
+ }
57
+
58
+ const entries = Object.entries(input as Record<string, unknown>);
59
+ const details: StageDestinationIdsError["details"] = [];
60
+ const value: DestinationIdsByEnvironment = {};
61
+
62
+ for (const [stage, id] of entries) {
63
+ const path = `${base}.${stage}`;
64
+ if (!(ADAPTER_CONFIG_STAGES as readonly string[]).includes(stage)) {
65
+ details.push({
66
+ path,
67
+ code: "unknown_enum",
68
+ message: `Stage must be one of: ${ADAPTER_CONFIG_STAGES.join(", ")}.`,
69
+ });
70
+ continue;
71
+ }
72
+ if (typeof id !== "string" || !platform.validateId(id)) {
73
+ details.push({ path, code: "format", message: "Destination identifier has an invalid format." });
74
+ continue;
75
+ }
76
+ value[stage as AdapterConfigStage] = id;
77
+ }
78
+
79
+ if (!value.live) {
80
+ details.push({
81
+ path: `${base}.live`,
82
+ code: "required",
83
+ message: "A live destination identifier is required.",
84
+ });
85
+ } else {
86
+ for (const stage of ADAPTER_CONFIG_STAGES) {
87
+ if (stage === "live") continue;
88
+ if (value[stage] === value.live) {
89
+ details.push({
90
+ path: `${base}.${stage}`,
91
+ code: "live_destination_reused",
92
+ message:
93
+ "A non-live stage must use its own TEST destination, never the live one — " +
94
+ "otherwise preview and candidate traffic lands in the property that carries " +
95
+ "real reporting.",
96
+ });
97
+ }
98
+ }
99
+ }
100
+
101
+ if (details.length > 0) return { ok: false, details };
102
+ return { ok: true, value };
103
+ }
@@ -62,6 +62,7 @@ import {
62
62
  MATERIALIZE_MODE_BINDING,
63
63
  verifyConsentSnapshotSig,
64
64
  } from "@tot/public-runtime";
65
+ import { readBakedManagedInstalls } from "@/lib/integration/bakedManagedInstalls";
65
66
  import type {
66
67
  CapabilityConfig,
67
68
  ConsentSnapshot,
@@ -219,7 +220,20 @@ export async function runRenderChain(input: RenderChainInput): Promise<Response>
219
220
  ? publicationSource.publicationTimestamp
220
221
  : exactPublicationTimestamp(await readEnv("TOT_PUBLICATION_TIMESTAMP"));
221
222
 
222
- const managedIntegrationDeclarations = input.managedIntegrationDeclarations;
223
+ // Managed installs reach this render one of two ways, never both. The control plane
224
+ // reads them from D1 per request (`controlPlane.ts`). A PUBLIC render supplies none by
225
+ // design (ADR 0013: "baked at publish, never read per request on a public page") — so
226
+ // on the materialize render, which is a public render with no D1 at all, they arrive
227
+ // BAKED: resolved and signed once by the go-live Worker, carried in a materialize-only
228
+ // signed header. Without this the bundle serves a plan whose `integrations` is empty
229
+ // for a tenant that has an active install, and nothing anywhere reports the difference.
230
+ //
231
+ // Read here, before declarations are assembled below, because the resolved plan and its
232
+ // compiled CSP both depend on the full declaration set.
233
+ const managedIntegrationDeclarations =
234
+ input.managedIntegrationDeclarations.length > 0
235
+ ? input.managedIntegrationDeclarations
236
+ : await readBakedManagedInstalls(request, tenant.tenant_id, readEnv);
223
237
 
224
238
  // Digest-addressed static-bundle serving. Deliberately before the edge page
225
239
  // cache/catalog/theme machinery. A selected publication bypasses this legacy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.0.24",
3
+ "version": "2.0.25",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",
@@ -202,7 +202,8 @@
202
202
  "enum": [
203
203
  "string",
204
204
  "boolean",
205
- "integer"
205
+ "integer",
206
+ "stageMap"
206
207
  ]
207
208
  },
208
209
  "required": {
@@ -147,6 +147,85 @@
147
147
  },
148
148
  {
149
149
  "type": "integer"
150
+ },
151
+ {
152
+ "type": "object",
153
+ "additionalProperties": false,
154
+ "minProperties": 1,
155
+ "properties": {
156
+ "candidate": {
157
+ "type": "string",
158
+ "not": {
159
+ "anyOf": [
160
+ {
161
+ "type": "string",
162
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
163
+ },
164
+ {
165
+ "type": "string",
166
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
167
+ },
168
+ {
169
+ "type": "string",
170
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
171
+ },
172
+ {
173
+ "type": "string",
174
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
175
+ }
176
+ ]
177
+ },
178
+ "minLength": 1
179
+ },
180
+ "staged": {
181
+ "type": "string",
182
+ "not": {
183
+ "anyOf": [
184
+ {
185
+ "type": "string",
186
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
187
+ },
188
+ {
189
+ "type": "string",
190
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
191
+ },
192
+ {
193
+ "type": "string",
194
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
195
+ },
196
+ {
197
+ "type": "string",
198
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
199
+ }
200
+ ]
201
+ },
202
+ "minLength": 1
203
+ },
204
+ "live": {
205
+ "type": "string",
206
+ "not": {
207
+ "anyOf": [
208
+ {
209
+ "type": "string",
210
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
211
+ },
212
+ {
213
+ "type": "string",
214
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
215
+ },
216
+ {
217
+ "type": "string",
218
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
219
+ },
220
+ {
221
+ "type": "string",
222
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
223
+ }
224
+ ]
225
+ },
226
+ "minLength": 1
227
+ }
228
+ }
150
229
  }
151
230
  ]
152
231
  }
@@ -120,6 +120,85 @@
120
120
  },
121
121
  {
122
122
  "type": "integer"
123
+ },
124
+ {
125
+ "type": "object",
126
+ "additionalProperties": false,
127
+ "minProperties": 1,
128
+ "properties": {
129
+ "candidate": {
130
+ "type": "string",
131
+ "not": {
132
+ "anyOf": [
133
+ {
134
+ "type": "string",
135
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
136
+ },
137
+ {
138
+ "type": "string",
139
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
140
+ },
141
+ {
142
+ "type": "string",
143
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
144
+ },
145
+ {
146
+ "type": "string",
147
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
148
+ }
149
+ ]
150
+ },
151
+ "minLength": 1
152
+ },
153
+ "staged": {
154
+ "type": "string",
155
+ "not": {
156
+ "anyOf": [
157
+ {
158
+ "type": "string",
159
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
160
+ },
161
+ {
162
+ "type": "string",
163
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
164
+ },
165
+ {
166
+ "type": "string",
167
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
168
+ },
169
+ {
170
+ "type": "string",
171
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
172
+ }
173
+ ]
174
+ },
175
+ "minLength": 1
176
+ },
177
+ "live": {
178
+ "type": "string",
179
+ "not": {
180
+ "anyOf": [
181
+ {
182
+ "type": "string",
183
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
184
+ },
185
+ {
186
+ "type": "string",
187
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
188
+ },
189
+ {
190
+ "type": "string",
191
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
192
+ },
193
+ {
194
+ "type": "string",
195
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
196
+ }
197
+ ]
198
+ },
199
+ "minLength": 1
200
+ }
201
+ }
123
202
  }
124
203
  ]
125
204
  }
@@ -176,6 +176,85 @@
176
176
  },
177
177
  {
178
178
  "type": "integer"
179
+ },
180
+ {
181
+ "type": "object",
182
+ "additionalProperties": false,
183
+ "minProperties": 1,
184
+ "properties": {
185
+ "candidate": {
186
+ "type": "string",
187
+ "not": {
188
+ "anyOf": [
189
+ {
190
+ "type": "string",
191
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
192
+ },
193
+ {
194
+ "type": "string",
195
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
196
+ },
197
+ {
198
+ "type": "string",
199
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
200
+ },
201
+ {
202
+ "type": "string",
203
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
204
+ }
205
+ ]
206
+ },
207
+ "minLength": 1
208
+ },
209
+ "staged": {
210
+ "type": "string",
211
+ "not": {
212
+ "anyOf": [
213
+ {
214
+ "type": "string",
215
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
216
+ },
217
+ {
218
+ "type": "string",
219
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
220
+ },
221
+ {
222
+ "type": "string",
223
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
224
+ },
225
+ {
226
+ "type": "string",
227
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
228
+ }
229
+ ]
230
+ },
231
+ "minLength": 1
232
+ },
233
+ "live": {
234
+ "type": "string",
235
+ "not": {
236
+ "anyOf": [
237
+ {
238
+ "type": "string",
239
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
240
+ },
241
+ {
242
+ "type": "string",
243
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
244
+ },
245
+ {
246
+ "type": "string",
247
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
248
+ },
249
+ {
250
+ "type": "string",
251
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
252
+ }
253
+ ]
254
+ },
255
+ "minLength": 1
256
+ }
257
+ }
179
258
  }
180
259
  ]
181
260
  }
@@ -135,6 +135,85 @@
135
135
  },
136
136
  {
137
137
  "type": "integer"
138
+ },
139
+ {
140
+ "type": "object",
141
+ "additionalProperties": false,
142
+ "minProperties": 1,
143
+ "properties": {
144
+ "candidate": {
145
+ "type": "string",
146
+ "not": {
147
+ "anyOf": [
148
+ {
149
+ "type": "string",
150
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
151
+ },
152
+ {
153
+ "type": "string",
154
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
155
+ },
156
+ {
157
+ "type": "string",
158
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
159
+ },
160
+ {
161
+ "type": "string",
162
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
163
+ }
164
+ ]
165
+ },
166
+ "minLength": 1
167
+ },
168
+ "staged": {
169
+ "type": "string",
170
+ "not": {
171
+ "anyOf": [
172
+ {
173
+ "type": "string",
174
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
175
+ },
176
+ {
177
+ "type": "string",
178
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
179
+ },
180
+ {
181
+ "type": "string",
182
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
183
+ },
184
+ {
185
+ "type": "string",
186
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
187
+ }
188
+ ]
189
+ },
190
+ "minLength": 1
191
+ },
192
+ "live": {
193
+ "type": "string",
194
+ "not": {
195
+ "anyOf": [
196
+ {
197
+ "type": "string",
198
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
199
+ },
200
+ {
201
+ "type": "string",
202
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
203
+ },
204
+ {
205
+ "type": "string",
206
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
207
+ },
208
+ {
209
+ "type": "string",
210
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
211
+ }
212
+ ]
213
+ },
214
+ "minLength": 1
215
+ }
216
+ }
138
217
  }
139
218
  ]
140
219
  }
@@ -146,6 +146,85 @@
146
146
  },
147
147
  {
148
148
  "type": "integer"
149
+ },
150
+ {
151
+ "type": "object",
152
+ "additionalProperties": false,
153
+ "minProperties": 1,
154
+ "properties": {
155
+ "candidate": {
156
+ "type": "string",
157
+ "not": {
158
+ "anyOf": [
159
+ {
160
+ "type": "string",
161
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
162
+ },
163
+ {
164
+ "type": "string",
165
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
166
+ },
167
+ {
168
+ "type": "string",
169
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
170
+ },
171
+ {
172
+ "type": "string",
173
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
174
+ }
175
+ ]
176
+ },
177
+ "minLength": 1
178
+ },
179
+ "staged": {
180
+ "type": "string",
181
+ "not": {
182
+ "anyOf": [
183
+ {
184
+ "type": "string",
185
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
186
+ },
187
+ {
188
+ "type": "string",
189
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
190
+ },
191
+ {
192
+ "type": "string",
193
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
194
+ },
195
+ {
196
+ "type": "string",
197
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
198
+ }
199
+ ]
200
+ },
201
+ "minLength": 1
202
+ },
203
+ "live": {
204
+ "type": "string",
205
+ "not": {
206
+ "anyOf": [
207
+ {
208
+ "type": "string",
209
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
210
+ },
211
+ {
212
+ "type": "string",
213
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
214
+ },
215
+ {
216
+ "type": "string",
217
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
218
+ },
219
+ {
220
+ "type": "string",
221
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
222
+ }
223
+ ]
224
+ },
225
+ "minLength": 1
226
+ }
227
+ }
149
228
  }
150
229
  ]
151
230
  }
@@ -147,6 +147,85 @@
147
147
  },
148
148
  {
149
149
  "type": "integer"
150
+ },
151
+ {
152
+ "type": "object",
153
+ "additionalProperties": false,
154
+ "minProperties": 1,
155
+ "properties": {
156
+ "candidate": {
157
+ "type": "string",
158
+ "not": {
159
+ "anyOf": [
160
+ {
161
+ "type": "string",
162
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
163
+ },
164
+ {
165
+ "type": "string",
166
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
167
+ },
168
+ {
169
+ "type": "string",
170
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
171
+ },
172
+ {
173
+ "type": "string",
174
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
175
+ }
176
+ ]
177
+ },
178
+ "minLength": 1
179
+ },
180
+ "staged": {
181
+ "type": "string",
182
+ "not": {
183
+ "anyOf": [
184
+ {
185
+ "type": "string",
186
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
187
+ },
188
+ {
189
+ "type": "string",
190
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
191
+ },
192
+ {
193
+ "type": "string",
194
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
195
+ },
196
+ {
197
+ "type": "string",
198
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
199
+ }
200
+ ]
201
+ },
202
+ "minLength": 1
203
+ },
204
+ "live": {
205
+ "type": "string",
206
+ "not": {
207
+ "anyOf": [
208
+ {
209
+ "type": "string",
210
+ "pattern": "(?:[Gg][Tt][Mm]-[A-Za-z0-9]+|[Uu][Aa]-[0-9]+-[0-9]+)"
211
+ },
212
+ {
213
+ "type": "string",
214
+ "pattern": "(?:-----BEGIN(?: [A-Z]+)? PRIVATE KEY-----|\\bAKIA[0-9A-Z]{16}\\b|\\bsk-proj-[A-Za-z0-9_-]{8,}\\b|\\bsk-[A-Za-z0-9_-]{20,}\\b|\\b(?:sk|rk)_(?:live|test|prod)_[A-Za-z0-9_-]{8,}\\b|\\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\\b|\\b(?:npm_|glpat-)[A-Za-z0-9_.-]{8,}\\b|\\bxox[baprs]-[A-Za-z0-9-]{10,}\\b|\\bAIza[A-Za-z0-9_-]{20,}\\b|\\bSG\\.[A-Za-z0-9_.-]{8,}\\b|\\b(?:[Ss]ecret|[Pp]assword|[Cc]redential)[_-][A-Za-z0-9_-]{4,}\\b|\\b[Bb]earer\\s+[A-Za-z0-9._~+/-]{12,}={0,2}\\b|\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]+\\b|\\b(?:api[_ -]?[Kk]ey|client[_ -]?[Ss]ecret|[Pp]assword|[Cc]redential|private[_ -]?[Kk]ey)\\s*[:=]\\s*\\S+)"
215
+ },
216
+ {
217
+ "type": "string",
218
+ "pattern": "(?:[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}|\\b[0-9]{3}-[0-9]{2}-[0-9]{4}\\b|\\b(?:[0-9][ -]?){13,19}\\b|(?:^|[^0-9])(?:\\+?1[ .-]?)?(?:\\([2-9][0-9]{2}\\)|[2-9][0-9]{2})[ .-]?[0-9]{3}[ .-]?[0-9]{4}(?=$|[^0-9]))"
219
+ },
220
+ {
221
+ "type": "string",
222
+ "pattern": "(?:</?[A-Za-z][^>]*>|[Jj][Aa][Vv][Aa][Ss][Cc][Rr][Ii][Pp][Tt]\\s*:|[Ee][Vv][Aa][Ll]\\s*\\(|(?:[Aa][Ll][Ee][Rr][Tt]|[Ff][Ee][Tt][Cc][Hh])\\s*\\(|\\{\\{|\\}\\}|\\$\\{|#[A-Za-z_][A-Za-z0-9_-]*|(?:^|[\\s,>+~])\\.[A-Za-z_-][A-Za-z0-9_-]*(?:$|[\\s,>+~.#[\\:])|\\[[A-Za-z_:][-A-Za-z0-9_:.]*(?:[~|^$*]?=[^\\]]+)?\\]|(?:[Ll][Oo][Cc][Aa][Ll]|[Ss][Ee][Ss][Ss][Ii][Oo][Nn])[Ss][Tt][Oo][Rr][Aa][Gg][Ee]|[Ii][Nn][Dd][Ee][Xx][Ee][Dd][Dd][Bb]|[Qq][Uu][Ee][Rr][Yy][Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|(?:[Ww][Ii][Nn][Dd][Oo][Ww]|[Dd][Oo][Cc][Uu][Mm][Ee][Nn][Tt]|[Gg][Ll][Oo][Bb][Aa][Ll][Tt][Hh][Ii][Ss]|[Cc][Oo][Nn][Ss][Oo][Ll][Ee])\\s*(?:\\.|\\[)|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\.\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*\\(|[A-Za-z_$][A-Za-z0-9_$]*\\s*\\([^)]*\\)\\s*;|[Rr][Ee][Tt][Uu][Rr][Nn][^;{}]*;|[Ff][Uu][Nn][Cc][Tt][Ii][Oo][Nn]|=>|(?:[Vv][Aa][Rr]|[Ll][Ee][Tt]|[Cc][Oo][Nn][Ss][Tt])\\s+[A-Za-z_$])"
223
+ }
224
+ ]
225
+ },
226
+ "minLength": 1
227
+ }
228
+ }
150
229
  }
151
230
  ]
152
231
  }
@@ -58,7 +58,7 @@ export const WS1_GOOGLE_MEASUREMENT_ADAPTER_ID: OpaquePublicId = "platform.googl
58
58
  * The canonical WS1 config field key the google-measurement adapter declares for
59
59
  * its destination id. Used as the DEFAULT config key when no catalog is supplied.
60
60
  */
61
- export const WS1_GOOGLE_MEASUREMENT_DESTINATION_KEY = "destinationId";
61
+ export const WS1_GOOGLE_MEASUREMENT_DESTINATION_KEY = "destinationIds";
62
62
 
63
63
  /** The opaque adapter handle used when a container has no supported GA4 concept. */
64
64
  export const NO_ADAPTER_SELECTED_ID: OpaquePublicId = "none-selected";
@@ -343,7 +343,10 @@ export function generateDisabledMigrationDraft(
343
343
  break; // deterministic: first config carrying a literal id wins.
344
344
  }
345
345
  }
346
- if (destinationId !== null) proposedConfiguration[destinationKey] = destinationId;
346
+ // A container's GA4 id is the merchant's LIVE destination, so the draft proposes it
347
+ // at that stage only. A non-live stage needs its own TEST id, which the merchant adds
348
+ // before verifying — the contract refuses reusing the live one there.
349
+ if (destinationId !== null) proposedConfiguration[destinationKey] = { live: destinationId };
347
350
  }
348
351
 
349
352
  // --- Redacted finding codes (no source text; stable, sorted-by-construction). -
@@ -0,0 +1,188 @@
1
+ /**
2
+ * The baked managed-install wire (ADR 0013).
3
+ *
4
+ * ADR 0013 already specifies what a public page does about admin-installed state:
5
+ * managed installs are "baked at publish, never read per request on a public page"
6
+ * (`publicRender.ts`). The read side honored that by supplying no declarations; this is
7
+ * the bake side that gives it something to honor. Without it a tenant whose public site
8
+ * serves CDN → content-addressed storage can never measure anything, however correctly
9
+ * its install is configured — the served page carries a complete-looking integration
10
+ * plan with an empty `integrations` list, which is indistinguishable from "this tenant
11
+ * has no integrations" on every surface that reports configuration rather than delivery.
12
+ *
13
+ * WHAT IS SIGNED: the resolved DECLARATIONS, not the resolved plan. A plan is
14
+ * route-dependent — `deriveSiteSurface` varies `siteSurface` by pathname, and the stage
15
+ * comes from the request — so one signed plan cannot serve every route of a bundle.
16
+ * Declarations are route-independent, so the render resolves each route's own plan from
17
+ * them exactly as the Worker does, through the same resolver.
18
+ *
19
+ * The trust model mirrors the consent snapshot's, because the constraint is the same: the
20
+ * go-live Worker holds D1 and the signing secret, the bake holds neither. Two gates make
21
+ * the header un-forgeable, and BOTH must hold:
22
+ * 1. MATERIALIZE-MODE binding — honored only when `TOT_MATERIALIZE_MODE` is set, which
23
+ * the materializer's wrangler dev injects and the DEPLOYED Worker never has. So a
24
+ * client cannot inject installs on the live Worker, and no cacheable response can
25
+ * vary on the header, because it is never read there.
26
+ * 2. HMAC-SHA256 over a length-prefixed, DOMAIN-SEPARATED framing of
27
+ * (purpose, tenantId, serialized) — verified constant-time. The purpose label is
28
+ * what keeps this envelope and the consent envelope from being interchangeable
29
+ * under one secret: a signature minted for one can never be replayed as the other,
30
+ * independently of whether the payload would happen to parse.
31
+ *
32
+ * Either gate failing ⇒ no baked installs, and the render falls through to the
33
+ * environment-only plan (nothing loads). Fail-safe, never fail-open.
34
+ *
35
+ * Web Crypto only (`crypto.subtle`), so it runs unchanged on workerd.
36
+ */
37
+ import type { IntegrationDeclarationInput } from "./resolve.js";
38
+ import { validateIntegrationManifest } from "./manifest.js";
39
+ import { findForbiddenFields } from "./secrets.js";
40
+
41
+ /** The materialize-only request header carrying a tenant's resolved managed installs. */
42
+ export const MANAGED_INSTALLS_INPUT_HEADER = "x-tot-managed-installs";
43
+
44
+ /** The hex HMAC-SHA256 signature over {@link MANAGED_INSTALLS_INPUT_HEADER}. */
45
+ export const MANAGED_INSTALLS_SIG_HEADER = "x-tot-managed-installs-sig";
46
+
47
+ /**
48
+ * The domain-separation label for this envelope's signatures. It is part of the signed
49
+ * bytes, so a signature is bound to this purpose and cannot be reused for another.
50
+ */
51
+ const MANAGED_INSTALLS_SIG_PURPOSE = "managed-installs.v1";
52
+
53
+ const encoder = new TextEncoder();
54
+
55
+ /**
56
+ * The exact string a signature covers: purpose, tenant and payload each prefixed by
57
+ * their own UTF-8 byte length, so no value can be read as a field separator and no
58
+ * field's content can shift a byte across a boundary.
59
+ */
60
+ function signaturePayload(tenantId: string, serialized: string): string {
61
+ const purposeBytes = encoder.encode(MANAGED_INSTALLS_SIG_PURPOSE).length;
62
+ const tenantBytes = encoder.encode(tenantId).length;
63
+ const payloadBytes = encoder.encode(serialized).length;
64
+ return (
65
+ `${purposeBytes}:${MANAGED_INSTALLS_SIG_PURPOSE}` +
66
+ `${tenantBytes}:${tenantId}` +
67
+ `${payloadBytes}:${serialized}`
68
+ );
69
+ }
70
+
71
+ async function hmacKey(secret: string): Promise<CryptoKey> {
72
+ return crypto.subtle.importKey(
73
+ "raw",
74
+ encoder.encode(secret) as unknown as BufferSource,
75
+ { name: "HMAC", hash: "SHA-256" },
76
+ false,
77
+ ["sign", "verify"],
78
+ );
79
+ }
80
+
81
+ function hexToBytes(hex: string): Uint8Array | null {
82
+ if (hex.length === 0 || hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
83
+ const out = new Uint8Array(hex.length / 2);
84
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
85
+ return out;
86
+ }
87
+
88
+ /** Serialize resolved declarations for transport. The inverse of {@link parseManagedInstalls}. */
89
+ export function serializeManagedInstalls(
90
+ declarations: readonly IntegrationDeclarationInput[],
91
+ ): string {
92
+ return JSON.stringify(declarations);
93
+ }
94
+
95
+ /**
96
+ * Parse a serialized managed-install list fail-closed. Anything unexpected — malformed
97
+ * JSON, a non-array, an entry missing its fields, a manifest that does not validate, or
98
+ * any secret-bearing public config — yields `null` for the WHOLE list rather than a
99
+ * partial one, so a tampered payload can never contribute a subset of declarations.
100
+ *
101
+ * The resolver re-validates every manifest again downstream; this is the outer layer, so
102
+ * a bad payload is rejected before it can reach plan resolution at all.
103
+ */
104
+ export function parseManagedInstalls(
105
+ raw: unknown,
106
+ ): IntegrationDeclarationInput[] | null {
107
+ let parsed: unknown = raw;
108
+ if (typeof raw === "string") {
109
+ try {
110
+ parsed = JSON.parse(raw);
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+ if (!Array.isArray(parsed)) return null;
116
+
117
+ const out: IntegrationDeclarationInput[] = [];
118
+ for (const entry of parsed) {
119
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return null;
120
+ const record = entry as Record<string, unknown>;
121
+ if (typeof record["origin"] !== "string" || typeof record["ref"] !== "string") return null;
122
+
123
+ const publicConfig = record["publicConfig"];
124
+ if (typeof publicConfig !== "object" || publicConfig === null || Array.isArray(publicConfig)) {
125
+ return null;
126
+ }
127
+ if (findForbiddenFields(publicConfig, "publicConfig").length > 0) return null;
128
+
129
+ const assets = record["materializedAssets"];
130
+ if (!Array.isArray(assets) || assets.some((a) => typeof a !== "string")) return null;
131
+
132
+ const manifest = validateIntegrationManifest(record["manifest"]);
133
+ if (!manifest.ok) return null;
134
+
135
+ out.push({
136
+ origin: record["origin"] as IntegrationDeclarationInput["origin"],
137
+ ref: record["ref"],
138
+ manifest: manifest.value,
139
+ publicConfig: publicConfig as Record<string, unknown>,
140
+ materializedAssets: assets as string[],
141
+ });
142
+ }
143
+ return out;
144
+ }
145
+
146
+ /**
147
+ * Sign a tenant's serialized managed installs: hex HMAC-SHA256 over the length-prefixed,
148
+ * domain-separated {@link signaturePayload}. The go-live Worker calls this; the
149
+ * materialize render verifies with the same secret via {@link verifyManagedInstallsSig}.
150
+ */
151
+ export async function signManagedInstalls(
152
+ secret: string,
153
+ tenantId: string,
154
+ serialized: string,
155
+ ): Promise<string> {
156
+ const key = await hmacKey(secret);
157
+ const sig = await crypto.subtle.sign(
158
+ "HMAC",
159
+ key,
160
+ encoder.encode(signaturePayload(tenantId, serialized)) as unknown as BufferSource,
161
+ );
162
+ let hex = "";
163
+ for (const b of new Uint8Array(sig)) hex += b.toString(16).padStart(2, "0");
164
+ return hex;
165
+ }
166
+
167
+ /**
168
+ * Verify a managed-install signature (constant-time via `crypto.subtle.verify`). A
169
+ * malformed, short, or absent signature returns `false` rather than throwing — fail
170
+ * closed, so no caller can smuggle in unsigned or forged installs.
171
+ */
172
+ export async function verifyManagedInstallsSig(
173
+ secret: string,
174
+ tenantId: string,
175
+ serialized: string,
176
+ signatureHex: string | null | undefined,
177
+ ): Promise<boolean> {
178
+ if (!signatureHex) return false;
179
+ const sig = hexToBytes(signatureHex.trim());
180
+ if (!sig) return false;
181
+ const key = await hmacKey(secret);
182
+ return crypto.subtle.verify(
183
+ "HMAC",
184
+ key,
185
+ sig as unknown as BufferSource,
186
+ encoder.encode(signaturePayload(tenantId, serialized)) as unknown as BufferSource,
187
+ );
188
+ }
@@ -62,6 +62,7 @@ export * from "./platform-adapter-runtime.js";
62
62
  export * from "./render-phases.js";
63
63
  export * from "./interim-bridge.js";
64
64
  export * from "./consent-broker.js";
65
+ export * from "./baked-managed-installs.js";
65
66
  export * from "./consent-cmp-demand.js";
66
67
  export * from "./consent-config.js";
67
68
  export * from "./consent-preview.js";
@@ -28,6 +28,22 @@ const PUBLIC_IDENTIFIER = { ...SAFE_CONFIG_STRING, minLength: 1, maxLength: 128,
28
28
  const FINDING_CODE = { ...SAFE_CONFIG_STRING, pattern: "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$" } as const;
29
29
  const REDACTED_PATH = { ...SAFE_CONFIG_STRING, pattern: "^(?:\\$|[A-Za-z][A-Za-z0-9]*)(?:\\.[A-Za-z][A-Za-z0-9]*|\\[[0-9]+\\])*$" } as const;
30
30
 
31
+ /**
32
+ * A per-stage destination map — the one non-primitive configuration value, and closed:
33
+ * only the declared stages, only safe strings. `local` is absent because the local dev
34
+ * loop ships no integrations, so a value there could never fire.
35
+ */
36
+ const stageDestinationMap = {
37
+ type: "object",
38
+ additionalProperties: false,
39
+ minProperties: 1,
40
+ properties: {
41
+ candidate: SAFE_NON_EMPTY_STRING,
42
+ staged: SAFE_NON_EMPTY_STRING,
43
+ live: SAFE_NON_EMPTY_STRING,
44
+ },
45
+ } as const;
46
+
31
47
  const primitiveConfiguration = {
32
48
  type: "object",
33
49
  propertyNames: SAFE_CONFIG_KEY,
@@ -36,6 +52,7 @@ const primitiveConfiguration = {
36
52
  SAFE_CONFIG_STRING,
37
53
  { type: "boolean" },
38
54
  { type: "integer" },
55
+ stageDestinationMap,
39
56
  ],
40
57
  },
41
58
  } as const;
@@ -64,7 +81,7 @@ export const ADAPTER_DEFINITION_V1_JSON_SCHEMA = {
64
81
  key: SAFE_CONFIG_KEY,
65
82
  label: SAFE_NON_EMPTY_STRING,
66
83
  description: SAFE_NON_EMPTY_STRING,
67
- kind: { type: "string", enum: ["string", "boolean", "integer"] },
84
+ kind: { type: "string", enum: ["string", "boolean", "integer", "stageMap"] },
68
85
  required: { type: "boolean" },
69
86
  },
70
87
  },
@@ -28,9 +28,30 @@ export type IntegrationRevisionStatus = (typeof INTEGRATION_REVISION_STATUSES)[n
28
28
  export const ADAPTER_AVAILABILITY = ["available", "deprecated"] as const;
29
29
  export type AdapterAvailability = (typeof ADAPTER_AVAILABILITY)[number];
30
30
 
31
- export const ADAPTER_CONFIG_VALUE_KINDS = ["string", "boolean", "integer"] as const;
31
+ export const ADAPTER_CONFIG_VALUE_KINDS = ["string", "boolean", "integer", "stageMap"] as const;
32
32
  export type AdapterConfigValueKind = (typeof ADAPTER_CONFIG_VALUE_KINDS)[number];
33
- export type AdapterConfigValue = string | boolean | number;
33
+
34
+ /**
35
+ * The stages a merchant may address a destination at. `local` is deliberately absent:
36
+ * the local dev loop ships no integrations and grants only essential consent, so a value
37
+ * there could never fire and offering one would imply otherwise.
38
+ */
39
+ export const ADAPTER_CONFIG_STAGES = ["candidate", "staged", "live"] as const;
40
+ export type AdapterConfigStage = (typeof ADAPTER_CONFIG_STAGES)[number];
41
+
42
+ /**
43
+ * A per-stage destination map — the one non-primitive configuration value.
44
+ *
45
+ * Its shape is CLOSED: keys come from {@link ADAPTER_CONFIG_STAGES} and every value is a
46
+ * plain string held to the same executable-content and PII checks as any other
47
+ * configuration value. So the property that makes this contract safe is intact — a
48
+ * merchant still cannot supply markup, selectors, secrets, or arbitrary structure — while
49
+ * a destination can differ per stage, which is what makes the devbook's own rule (a
50
+ * non-live stage must not reuse the live property) satisfiable through the console at all.
51
+ */
52
+ export type AdapterConfigStageMap = Partial<Record<AdapterConfigStage, string>>;
53
+
54
+ export type AdapterConfigValue = string | boolean | number | AdapterConfigStageMap;
34
55
  /** Public identifiers are opaque handles: never emails, names, or user-provided labels. */
35
56
  export type OpaquePublicId = string;
36
57
  /** Stable, redacted machine code; migration findings never contain source text or PII. */
@@ -535,12 +556,36 @@ export function validateManagedAnalyticsSafePayload(input: unknown): ContractRes
535
556
  return v.ok ? prepared : { ok: false, violations: v.items };
536
557
  }
537
558
 
559
+ /** A plain object, i.e. a candidate per-stage map rather than a primitive. */
560
+ function isStageMap(value: unknown): boolean {
561
+ return typeof value === "object" && value !== null && !Array.isArray(value);
562
+ }
563
+
538
564
  function validateConfig(input: unknown, v: Violations, path: string): void {
539
565
  const obj = object(input, v, path);
540
566
  if (!obj) return;
541
567
  for (const [key, value] of Object.entries(obj)) {
542
568
  if (!/^[a-z][a-zA-Z0-9]{0,63}$/.test(key)) v.add(pathAt(path, key), "format", "Configuration keys must be lower camel-case identifiers.");
543
- if (!(typeof value === "string" || typeof value === "boolean" || (typeof value === "number" && Number.isInteger(value)))) v.add(pathAt(path, key), "type", "Configuration values must be strings, booleans, or integers.");
569
+ // A per-stage map is the one permitted non-primitive, and it is CLOSED: stage keys
570
+ // only, string values only, each held to the same executable-content check below. So
571
+ // no arbitrary structure or depth can enter configuration through it.
572
+ if (isStageMap(value)) {
573
+ const entries = Object.entries(value as Record<string, unknown>);
574
+ if (entries.length === 0) v.add(pathAt(path, key), "empty", "A per-stage map must name at least one stage.");
575
+ for (const [stage, id] of entries) {
576
+ const stagePath = pathAt(pathAt(path, key), stage);
577
+ if (!(ADAPTER_CONFIG_STAGES as readonly string[]).includes(stage)) {
578
+ v.add(stagePath, "unknown_enum", `Stage must be one of: ${ADAPTER_CONFIG_STAGES.join(", ")}.`);
579
+ }
580
+ if (typeof id !== "string") {
581
+ v.add(stagePath, "type", "A per-stage value must be a string.");
582
+ } else if (EXECUTABLE_OR_INSTRUCTION_VALUE.test(id)) {
583
+ v.add(stagePath, "executable_value", "Configuration cannot contain markup, JavaScript, templates, selectors, or storage instructions.");
584
+ }
585
+ }
586
+ continue;
587
+ }
588
+ if (!(typeof value === "string" || typeof value === "boolean" || (typeof value === "number" && Number.isInteger(value)))) v.add(pathAt(path, key), "type", "Configuration values must be strings, booleans, integers, or a per-stage map of strings.");
544
589
  if (typeof value === "string" && EXECUTABLE_OR_INSTRUCTION_VALUE.test(value)) v.add(pathAt(path, key), "executable_value", "Configuration cannot contain markup, JavaScript, templates, selectors, or storage instructions.");
545
590
  }
546
591
  const safe = validateManagedAnalyticsSafePayload(obj);