@zapier/policy-context 1.3.0 → 3.0.0-mr.39.bc800a62

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/README.md CHANGED
@@ -16,7 +16,7 @@ npm install @zapier/policy-context zod
16
16
 
17
17
  ## What it provides
18
18
 
19
- Three layers, each building on the previous:
19
+ Four layers, each building on the previous:
20
20
 
21
21
  1. **Types** — Zod schemas for `HttpRequestContext`, `ActionRunContext`, and the
22
22
  `RequestContext` discriminated union. The canonical shapes stored in
@@ -25,7 +25,11 @@ Three layers, each building on the previous:
25
25
  body coercion) into validated contexts.
26
26
  3. **Conditions / Policies** — Convert stored contexts into
27
27
  [`@zapier/policy-schema`](https://www.npmjs.com/package/@zapier/policy-schema)
28
- `Condition[]` and full `Policy` documents.
28
+ `Condition[]` and full policy documents. Canonical builders emit v4 with one
29
+ rule tier (`rules: [[...]]`) and randomly generated `r_<uuid>` rule IDs;
30
+ `toExactPolicyV3` is an explicit legacy exact-request escape hatch.
31
+ 4. **Zapier policy validation** — `ZapierPolicySchema` composes the generic
32
+ `PolicySchema` with Zapier resource grammar and permission/resource rules.
29
33
 
30
34
  ## Usage
31
35
 
@@ -65,6 +69,27 @@ import { toExactPolicy } from "@zapier/policy-context";
65
69
  const policy = toExactPolicy(context);
66
70
  ```
67
71
 
72
+ `toExactPolicy` is the canonical exact builder and returns
73
+ `Extract<Policy, { version: 4 }>`, with a single rule tier (`rules: [[...]]`)
74
+ and `r_<uuid>` rule IDs. Rule content and ordering are unchanged. Migrate
75
+ `policy.statements[0][i]` to `policy.rules[0][i]` and any statement-ID prefix
76
+ handling from `s_` to `r_`.
77
+
78
+ For a legacy consumer that still requires an exact v3 document, use the explicit
79
+ factory instead:
80
+
81
+ ```ts
82
+ import { toExactPolicyV3 } from "@zapier/policy-context";
83
+
84
+ const legacyPolicy = toExactPolicyV3(context);
85
+ ```
86
+
87
+ `toExactPolicyV3` returns `Extract<Policy, { version: 3 }>`, with `version: 3`,
88
+ one statement tier (`statements: [[...]]`), and `s_<uuid>` statement IDs. It
89
+ preserves the canonical exact builder's conditions, content, and ordering.
90
+ `buildConnectionPolicy` and `buildConnectionlessPolicy` remain v4; this escape
91
+ hatch is only for exact-request policies.
92
+
68
93
  ### Connection-scoped policies
69
94
 
70
95
  ```ts
@@ -76,6 +101,24 @@ const policy = buildConnectionPolicy({
76
101
  });
77
102
  ```
78
103
 
104
+ ### Validate a Zapier policy
105
+
106
+ ```ts
107
+ import { ZapierPolicySchema } from "@zapier/policy-context";
108
+
109
+ const policy = ZapierPolicySchema.parse(input);
110
+ ```
111
+
112
+ `ZapierPolicySchema` output includes v4 documents as well as earlier versions.
113
+ This accepts `http`, `connection/{id|*}`, canonical
114
+ `action/{selected_api}/{action_type|*}/{action_key}` patterns, and the lone `*`
115
+ god-mode resource. It validates structure only; app, API version, and action-key
116
+ existence remain the caller's responsibility. It also rejects a statement or rule
117
+ when one higher-precedence statement or rule (`deny` > `ask` > `allow`) fully
118
+ subsumes it. Validation traverses each ordered tier of v3 `statements` and v4
119
+ `rules`, checking masking only within that tier. Same-effect redundancy and
120
+ coverage assembled from multiple statements or rules remain valid.
121
+
79
122
  ## License
80
123
 
81
124
  Published as part of Zapier's services. Use is governed by the
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AccessLevelSchema: () => AccessLevelSchema,
24
+ ActionResourcePatternSchema: () => ActionResourcePatternSchema,
24
25
  ActionRunContextInputSchema: () => ActionRunContextInputSchema,
25
26
  ActionRunContextSchema: () => ActionRunContextSchema,
26
27
  ActionTypeSchema: () => ActionTypeSchema,
@@ -33,6 +34,7 @@ __export(index_exports, {
33
34
  READ_ACTION_TYPES: () => READ_ACTION_TYPES,
34
35
  READ_HTTP_METHODS: () => READ_HTTP_METHODS,
35
36
  RequestContextSchema: () => RequestContextSchema,
37
+ ZapierPolicySchema: () => ZapierPolicySchema,
36
38
  buildActionResource: () => buildActionResource,
37
39
  buildActionRunContext: () => buildActionRunContext,
38
40
  buildBroadActionResource: () => buildBroadActionResource,
@@ -44,16 +46,33 @@ __export(index_exports, {
44
46
  buildHttpRequestContext: () => buildHttpRequestContext,
45
47
  buildUrlString: () => buildUrlString,
46
48
  globSelectedApiVersion: () => globSelectedApiVersion,
49
+ isIgnoredHeader: () => isIgnoredHeader,
47
50
  isWriteRequest: () => isWriteRequest,
48
51
  parseActionResource: () => parseActionResource,
49
52
  parseUrl: () => parseUrl,
50
53
  toAppKey: () => toAppKey,
51
- toExactPolicy: () => toExactPolicy
54
+ toExactPolicy: () => toExactPolicy,
55
+ toExactPolicyV3: () => toExactPolicyV3
52
56
  });
53
57
  module.exports = __toCommonJS(index_exports);
54
58
 
55
59
  // src/types.ts
56
60
  var import_zod = require("zod");
61
+ function containsUnsafeInteger(value) {
62
+ if (typeof value === "number") {
63
+ return Number.isInteger(value) && !Number.isSafeInteger(value);
64
+ }
65
+ if (Array.isArray(value)) {
66
+ return value.some(containsUnsafeInteger);
67
+ }
68
+ if (value !== null && typeof value === "object") {
69
+ return Object.values(value).some(containsUnsafeInteger);
70
+ }
71
+ return false;
72
+ }
73
+ var SafeJsonValueSchema = import_zod.z.json().refine((value) => !containsUnsafeInteger(value), {
74
+ message: "JSON integers must be within the IEEE-754 safe range; encode larger identifiers as strings"
75
+ });
57
76
  var ParsedUrlSchema = import_zod.z.object({
58
77
  scheme: import_zod.z.string().describe('URL scheme, e.g. "https"'),
59
78
  host: import_zod.z.string().describe('Hostname, e.g. "api.example.com"'),
@@ -112,7 +131,7 @@ var HttpRequestContextSchema = import_zod.z.object({
112
131
  * `z.json()` still rejects non-JSON values (`Date`, `undefined`,
113
132
  * functions) at parse time. Same inferred `JsonValue` type either way.
114
133
  */
115
- body_json: import_zod.z.unknown().pipe(import_zod.z.json()).nullable().describe("Parsed JSON body (if applicable)"),
134
+ body_json: import_zod.z.unknown().pipe(SafeJsonValueSchema).nullable().describe("Parsed JSON body (if applicable)"),
116
135
  /** Zapier connection ID used for this request */
117
136
  connection_id: import_zod.z.string().max(255).optional().describe("Zapier connection ID used for this request"),
118
137
  /** Optional human-readable reason for the request */
@@ -130,6 +149,11 @@ var ActionTypeSchema = import_zod.z.enum([
130
149
  ]).meta({ id: "action_type", description: "Canonical Zapier action type." });
131
150
  var ACTION_RESOURCE_SEGMENT_PATTERN = /^[A-Za-z0-9._+-]+(?:@[A-Za-z0-9._+-]+)?$/;
132
151
  var ACTION_KEY_SEGMENT_PATTERN = /^[A-Za-z0-9._+:-]+(?:@[A-Za-z0-9._+:-]+)?$/;
152
+ var CONNECTION_ID_CHARS = "[^/*\\x00-\\x20\\x7f-\\xa0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]";
153
+ var CONNECTION_ID_PATTERN = new RegExp(`^${CONNECTION_ID_CHARS}+$`);
154
+ var CONNECTION_RESOURCE_PATTERN = new RegExp(
155
+ `^connection/(?:\\*|${CONNECTION_ID_CHARS}+)$`
156
+ );
133
157
  function parseActionResource(resource) {
134
158
  const segments = resource.split("/");
135
159
  if (segments.length !== 4 || segments[0] !== "action") {
@@ -190,7 +214,7 @@ var ActionRunContextInputSchema = import_zod.z.object({
190
214
  }),
191
215
  connection_id: import_zod.z.string().max(255).optional().describe("Zapier connection ID used for this request"),
192
216
  /** Action input parameters. See body_json above for the pipe rationale. */
193
- inputs: import_zod.z.unknown().pipe(import_zod.z.json()).optional().describe("Action input parameters"),
217
+ inputs: import_zod.z.unknown().pipe(SafeJsonValueSchema).optional().describe("Action input parameters"),
194
218
  reason: import_zod.z.string().max(1024).optional().describe("Optional human-readable reason for the request")
195
219
  }).meta({
196
220
  id: "ActionRunContext",
@@ -221,7 +245,7 @@ var ActionRunContextSchema = ActionRunContextInputSchema.transform(
221
245
  ...input.connection_id !== void 0 && {
222
246
  connection_id: input.connection_id
223
247
  },
224
- ...input.inputs !== void 0 && { inputs: input.inputs },
248
+ ...input.inputs !== void 0 && input.inputs !== null && { inputs: input.inputs },
225
249
  ...input.reason !== void 0 && { reason: input.reason }
226
250
  };
227
251
  }
@@ -265,8 +289,15 @@ var IGNORED_HEADERS = /* @__PURE__ */ new Set([
265
289
  "x-real-ip",
266
290
  // Proxy internal — sdkapi-specific headers
267
291
  "x-is-interactive-session",
268
- "x-relay-callback-url"
292
+ "x-relay-callback-url",
293
+ // Subsumed by isIgnoredHeader's x-zapier- prefix rule; kept for byte-parity
294
+ // with the approvalsapi source and for consumers reading the set directly.
295
+ "x-zapier-approval-token"
269
296
  ]);
297
+ function isIgnoredHeader(name) {
298
+ const normalized = name.toLowerCase();
299
+ return IGNORED_HEADERS.has(normalized) || normalized.startsWith("zapier-") || normalized.startsWith("x-zapier-");
300
+ }
270
301
 
271
302
  // src/builders.ts
272
303
  var import_zod2 = require("zod");
@@ -289,14 +320,14 @@ function buildExactHttpConditions(context) {
289
320
  conditions.push({
290
321
  path: ["url", "query", key],
291
322
  operator: "equals",
292
- value: values
323
+ value: structuredClone(values)
293
324
  });
294
325
  }
295
326
  if (context.body_json !== null) {
296
327
  conditions.push({
297
328
  path: ["body_json"],
298
329
  operator: "equals",
299
- value: context.body_json
330
+ value: structuredClone(context.body_json)
300
331
  });
301
332
  } else if (context.body !== null) {
302
333
  conditions.push({
@@ -306,7 +337,7 @@ function buildExactHttpConditions(context) {
306
337
  });
307
338
  }
308
339
  for (const [key, value] of Object.entries(context.headers)) {
309
- if (!IGNORED_HEADERS.has(key.toLowerCase()) && typeof value === "string") {
340
+ if (!isIgnoredHeader(key) && typeof value === "string") {
310
341
  conditions.push({
311
342
  path: ["headers", key.toLowerCase()],
312
343
  operator: "equals",
@@ -331,7 +362,7 @@ function buildExactActionConditions(context) {
331
362
  conditions.push({
332
363
  path: ["inputs"],
333
364
  operator: "equals",
334
- value: context.inputs
365
+ value: structuredClone(context.inputs)
335
366
  });
336
367
  }
337
368
  return conditions;
@@ -348,7 +379,7 @@ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
348
379
  method: import_zod2.z.string(),
349
380
  /** Raw URL string. Will be normalized with https:// if no protocol present. */
350
381
  url: import_zod2.z.string(),
351
- /** Raw headers from the request. Will be filtered through IGNORED_HEADERS. */
382
+ /** Raw headers from the request. Will be filtered through isIgnoredHeader. */
352
383
  headers: import_zod2.z.record(
353
384
  import_zod2.z.string(),
354
385
  import_zod2.z.union([import_zod2.z.string(), import_zod2.z.array(import_zod2.z.string()), import_zod2.z.undefined()])
@@ -356,7 +387,7 @@ var BuildHttpRequestContextOptionsSchema = import_zod2.z.object({
356
387
  /** Raw body string */
357
388
  body: import_zod2.z.string().nullable().optional(),
358
389
  /** Pre-parsed JSON body (e.g. from Fastify's automatic JSON parsing) */
359
- body_json: import_zod2.z.json().optional(),
390
+ body_json: SafeJsonValueSchema.optional(),
360
391
  /** Connection ID associated with this request */
361
392
  connection_id: import_zod2.z.string().optional()
362
393
  });
@@ -368,7 +399,7 @@ function buildHttpRequestContext(options) {
368
399
  if (parsed.headers) {
369
400
  for (const [key, value] of Object.entries(parsed.headers)) {
370
401
  const lowerKey = key.toLowerCase();
371
- if (!IGNORED_HEADERS.has(lowerKey) && typeof value === "string") {
402
+ if (!isIgnoredHeader(lowerKey) && typeof value === "string") {
372
403
  headers[lowerKey] = value;
373
404
  }
374
405
  }
@@ -391,7 +422,7 @@ function buildHttpRequestContext(options) {
391
422
  method: parsed.method.toUpperCase(),
392
423
  url,
393
424
  headers,
394
- body: parsed.body ?? null,
425
+ body: body_json === null ? parsed.body ?? null : null,
395
426
  body_json,
396
427
  connection_id: parsed.connection_id
397
428
  });
@@ -405,7 +436,7 @@ var BuildActionRunContextOptionsSchema = import_zod2.z.object({
405
436
  message: "action_key must contain only [A-Za-z0-9._@:+-] (no globs, slashes, or whitespace)"
406
437
  }),
407
438
  connection_id: import_zod2.z.string().optional(),
408
- inputs: import_zod2.z.json().optional(),
439
+ inputs: SafeJsonValueSchema.optional(),
409
440
  reason: import_zod2.z.string().optional()
410
441
  });
411
442
  function buildActionRunContext(options) {
@@ -424,56 +455,212 @@ function buildActionRunContext(options) {
424
455
  }
425
456
 
426
457
  // src/policies.ts
427
- function buildAuthStatement(authenticationId) {
458
+ function buildAuthStatement(authenticationId, idPrefix) {
428
459
  return {
460
+ id: `${idPrefix}_${crypto.randomUUID()}`,
429
461
  effect: "allow",
430
462
  permissions: ["can_use"],
431
463
  resources: [`connection/${authenticationId}`],
432
464
  conditions: []
433
465
  };
434
466
  }
435
- function httpContextToPolicy(context) {
436
- const statements = [];
437
- if (context.connection_id) {
438
- statements.push(buildAuthStatement(context.connection_id));
439
- }
467
+ function httpContextToTier(context, idPrefix) {
440
468
  const conditions = buildExactHttpConditions(context);
441
469
  conditions.push({
442
470
  path: ["url", "query"],
443
471
  operator: "keys_in",
444
472
  value: Object.keys(context.url.query)
445
473
  });
446
- statements.push({
474
+ const statement = {
475
+ id: `${idPrefix}_${crypto.randomUUID()}`,
447
476
  effect: "allow",
448
477
  permissions: ["can_execute"],
449
478
  resources: ["http"],
450
479
  conditions
451
- });
452
- return { version: 2, statements };
480
+ };
481
+ return context.connection_id ? [buildAuthStatement(context.connection_id, idPrefix), statement] : [statement];
453
482
  }
454
- function actionContextToPolicy(context) {
455
- const statements = [];
456
- if (context.connection_id) {
457
- statements.push(buildAuthStatement(context.connection_id));
458
- }
459
- statements.push({
483
+ function actionContextToTier(context, idPrefix) {
484
+ const statement = {
485
+ id: `${idPrefix}_${crypto.randomUUID()}`,
460
486
  effect: "allow",
461
487
  permissions: ["can_execute"],
462
488
  resources: [context.resource],
463
489
  conditions: buildExactActionConditions(context)
464
- });
465
- return { version: 2, statements };
490
+ };
491
+ return context.connection_id ? [buildAuthStatement(context.connection_id, idPrefix), statement] : [statement];
466
492
  }
467
493
  function toExactPolicy(context) {
468
- if (context.request_type === "http_request") {
469
- return httpContextToPolicy(context);
494
+ return {
495
+ version: 4,
496
+ rules: [
497
+ context.request_type === "http_request" ? httpContextToTier(context, "r") : actionContextToTier(context, "r")
498
+ ]
499
+ };
500
+ }
501
+ function toExactPolicyV3(context) {
502
+ return {
503
+ version: 3,
504
+ statements: [
505
+ context.request_type === "http_request" ? httpContextToTier(context, "s") : actionContextToTier(context, "s")
506
+ ]
507
+ };
508
+ }
509
+
510
+ // src/zapier-policy.ts
511
+ var import_policy = require("@zapier/policy");
512
+ var import_policy_schema = require("@zapier/policy-schema");
513
+ var import_zod3 = require("zod");
514
+ var SELECTED_API_GLOB_PART_PATTERN = /^[A-Za-z0-9._+*-]+$/;
515
+ var ACTION_KEY_GLOB_PART_PATTERN = /^[A-Za-z0-9._+:*-]+$/;
516
+ var PERMISSIONS = /* @__PURE__ */ new Set([
517
+ "can_execute",
518
+ "can_use",
519
+ "can_view",
520
+ "can_edit",
521
+ "*"
522
+ ]);
523
+ var EFFECT_PRECEDENCE = { allow: 0, ask: 1, deny: 2 };
524
+ function isSelectedApiPattern(value) {
525
+ return isGlobSegment(
526
+ value,
527
+ ACTION_RESOURCE_SEGMENT_PATTERN,
528
+ SELECTED_API_GLOB_PART_PATTERN
529
+ );
530
+ }
531
+ function isActionKeyPattern(value) {
532
+ return isGlobSegment(
533
+ value,
534
+ ACTION_KEY_SEGMENT_PATTERN,
535
+ ACTION_KEY_GLOB_PART_PATTERN
536
+ );
537
+ }
538
+ function isGlobSegment(value, exactPattern, globPartPattern) {
539
+ if (exactPattern.test(value)) {
540
+ return true;
541
+ }
542
+ if (!value.includes("*") || value.includes("**")) {
543
+ return false;
544
+ }
545
+ const parts = value.split("@");
546
+ return parts.length <= 2 && parts.every((part) => globPartPattern.test(part));
547
+ }
548
+ function isActionResourcePattern(value) {
549
+ const segments = value.split("/");
550
+ if (segments.length !== 4 || segments[0] !== "action") {
551
+ return false;
470
552
  }
471
- return actionContextToPolicy(context);
553
+ const [, selectedApi, actionType, actionKey] = segments;
554
+ return selectedApi !== void 0 && actionType !== void 0 && actionKey !== void 0 && isSelectedApiPattern(selectedApi) && (actionType === "*" || ActionTypeSchema.safeParse(actionType).success) && isActionKeyPattern(actionKey);
472
555
  }
556
+ var ActionResourcePatternSchema = import_zod3.z.string().refine(isActionResourcePattern, {
557
+ message: "Expected action/{selected_api}/{canonical_action_type|*}/{action_key_pattern}"
558
+ });
559
+ function resourceFamily(resource) {
560
+ if (resource === "*" || resource === "http") {
561
+ return resource;
562
+ }
563
+ if (ActionResourcePatternSchema.safeParse(resource).success) {
564
+ return "action";
565
+ }
566
+ if (CONNECTION_RESOURCE_PATTERN.test(resource)) {
567
+ return "connection";
568
+ }
569
+ return void 0;
570
+ }
571
+ function permissionAppliesTo(permission, family) {
572
+ if (permission === "*" || family === "*") {
573
+ return true;
574
+ }
575
+ return permission === "can_execute" ? family === "action" || family === "http" : family === "connection";
576
+ }
577
+ function statementGroups(policy) {
578
+ if (policy.version === 4) {
579
+ return policy.rules.map((rules, tierIndex) => ({
580
+ statements: rules,
581
+ path: ["rules", tierIndex],
582
+ entry: "rule"
583
+ }));
584
+ }
585
+ if (policy.version === 3) {
586
+ return policy.statements.map((statements, tierIndex) => ({
587
+ statements,
588
+ path: ["statements", tierIndex],
589
+ entry: "statement"
590
+ }));
591
+ }
592
+ return [
593
+ {
594
+ statements: policy.statements,
595
+ path: ["statements"],
596
+ entry: "statement"
597
+ }
598
+ ];
599
+ }
600
+ var ZapierPolicySchema = import_policy_schema.PolicySchema.superRefine((policy, ctx) => {
601
+ statementGroups(policy).forEach(({ statements, path, entry }) => {
602
+ const Entry = entry === "rule" ? "Rule" : "Statement";
603
+ statements.forEach((statement, statementIndex) => {
604
+ const families = statement.resources.map((resource, resourceIndex) => {
605
+ const family = resourceFamily(resource);
606
+ if (family === void 0) {
607
+ ctx.addIssue({
608
+ code: "custom",
609
+ message: 'Expected "http", "connection/{id|*}", a valid action pattern, or "*"',
610
+ path: [...path, statementIndex, "resources", resourceIndex]
611
+ });
612
+ }
613
+ return family;
614
+ });
615
+ statement.permissions.forEach((permission, permissionIndex) => {
616
+ if (!PERMISSIONS.has(permission)) {
617
+ ctx.addIssue({
618
+ code: "custom",
619
+ message: `Unknown Zapier permission "${permission}"`,
620
+ path: [...path, statementIndex, "permissions", permissionIndex]
621
+ });
622
+ return;
623
+ }
624
+ const incompatibleFamily = families.find(
625
+ (family) => family !== void 0 && !permissionAppliesTo(permission, family)
626
+ );
627
+ if (incompatibleFamily !== void 0) {
628
+ ctx.addIssue({
629
+ code: "custom",
630
+ message: `Permission "${permission}" cannot apply to ${incompatibleFamily} resources`,
631
+ path: [...path, statementIndex, "permissions", permissionIndex]
632
+ });
633
+ }
634
+ });
635
+ const maskingStatementIndex = statements.findIndex(
636
+ (maskingStatement, maskingIndex) => maskingIndex !== statementIndex && EFFECT_PRECEDENCE[maskingStatement.effect] > EFFECT_PRECEDENCE[statement.effect] && (0, import_policy.isStatementSubsumedBy)(statement, {
637
+ ...maskingStatement,
638
+ effect: statement.effect
639
+ })
640
+ );
641
+ if (maskingStatementIndex !== -1) {
642
+ const maskingStatement = statements[maskingStatementIndex];
643
+ const maskingId = maskingStatement.id ? ` (id "${maskingStatement.id}")` : "";
644
+ ctx.addIssue({
645
+ code: "custom",
646
+ message: `${Entry} is entirely masked by higher-precedence ${entry} at index ${maskingStatementIndex}${maskingId}`,
647
+ path: [...path, statementIndex]
648
+ });
649
+ }
650
+ });
651
+ });
652
+ });
473
653
 
474
654
  // src/connection-policy.ts
475
- var import_zod3 = require("zod");
476
- var AccessLevelSchema = import_zod3.z.enum(["read", "read_write"]);
655
+ var import_zod4 = require("zod");
656
+ var AccessLevelSchema = import_zod4.z.enum(["read", "read_write"]);
657
+ function requireAccessLevel(accessLevel) {
658
+ if (!AccessLevelSchema.safeParse(accessLevel).success) {
659
+ throw new Error(
660
+ `invalid access_level "${accessLevel}": expected "read" or "read_write"`
661
+ );
662
+ }
663
+ }
477
664
  var READ_HTTP_METHODS = ["GET", "HEAD", "OPTIONS"];
478
665
  var READ_ACTION_TYPES = [
479
666
  "read",
@@ -493,7 +680,19 @@ function isWriteRequest(context) {
493
680
  }
494
681
  function buildConnectionPolicy(options) {
495
682
  const { connectionId, selectedApi, accessLevel } = options;
683
+ requireAccessLevel(accessLevel);
684
+ if (!CONNECTION_ID_PATTERN.test(connectionId)) {
685
+ throw new Error(
686
+ `invalid connection_id "${connectionId}": must not contain "/", "*", or whitespace`
687
+ );
688
+ }
689
+ if (!ACTION_RESOURCE_SEGMENT_PATTERN.test(selectedApi)) {
690
+ throw new Error(
691
+ `invalid selected_api "${selectedApi}": must not contain "/", "*", or whitespace`
692
+ );
693
+ }
496
694
  const authStatement = {
695
+ id: `r_${crypto.randomUUID()}`,
497
696
  effect: "allow",
498
697
  permissions: ["can_use"],
499
698
  resources: [`connection/${connectionId}`],
@@ -513,6 +712,7 @@ function buildConnectionPolicy(options) {
513
712
  });
514
713
  }
515
714
  const httpStatement = {
715
+ id: `r_${crypto.randomUUID()}`,
516
716
  effect: "allow",
517
717
  permissions: ["can_execute"],
518
718
  resources: ["http"],
@@ -521,20 +721,22 @@ function buildConnectionPolicy(options) {
521
721
  const apiPattern = globSelectedApiVersion(selectedApi);
522
722
  const actionResources = accessLevel === "read" ? READ_ACTION_TYPES.map((t) => `action/${apiPattern}/${t}/*`) : [`action/${apiPattern}/*/*`];
523
723
  const actionStatement = {
724
+ id: `r_${crypto.randomUUID()}`,
524
725
  effect: "allow",
525
726
  permissions: ["can_execute"],
526
727
  resources: actionResources,
527
728
  conditions: [connectionCondition]
528
729
  };
529
730
  return {
530
- version: 2,
531
- statements: [authStatement, httpStatement, actionStatement]
731
+ version: 4,
732
+ rules: [[authStatement, httpStatement, actionStatement]]
532
733
  };
533
734
  }
534
735
 
535
736
  // src/connectionless-policy.ts
536
737
  function buildConnectionlessPolicy(options) {
537
738
  const { context, accessLevel } = options;
739
+ requireAccessLevel(accessLevel);
538
740
  if (context.request_type === "http_request") {
539
741
  const conditions = [
540
742
  {
@@ -551,27 +753,36 @@ function buildConnectionlessPolicy(options) {
551
753
  });
552
754
  }
553
755
  const statement2 = {
756
+ id: `r_${crypto.randomUUID()}`,
554
757
  effect: "allow",
555
758
  permissions: ["can_execute"],
556
759
  resources: ["http"],
557
760
  conditions
558
761
  };
559
- return { version: 2, statements: [statement2] };
762
+ return {
763
+ version: 4,
764
+ rules: [[statement2]]
765
+ };
560
766
  }
561
767
  const { selected_api } = parseActionResource(context.resource);
562
768
  const apiPattern = globSelectedApiVersion(selected_api);
563
769
  const resources = accessLevel === "read" ? READ_ACTION_TYPES.map((t) => `action/${apiPattern}/${t}/*`) : [`action/${apiPattern}/*/*`];
564
770
  const statement = {
771
+ id: `r_${crypto.randomUUID()}`,
565
772
  effect: "allow",
566
773
  permissions: ["can_execute"],
567
774
  resources,
568
775
  conditions: []
569
776
  };
570
- return { version: 2, statements: [statement] };
777
+ return {
778
+ version: 4,
779
+ rules: [[statement]]
780
+ };
571
781
  }
572
782
  // Annotate the CommonJS export names for ESM import in node:
573
783
  0 && (module.exports = {
574
784
  AccessLevelSchema,
785
+ ActionResourcePatternSchema,
575
786
  ActionRunContextInputSchema,
576
787
  ActionRunContextSchema,
577
788
  ActionTypeSchema,
@@ -584,6 +795,7 @@ function buildConnectionlessPolicy(options) {
584
795
  READ_ACTION_TYPES,
585
796
  READ_HTTP_METHODS,
586
797
  RequestContextSchema,
798
+ ZapierPolicySchema,
587
799
  buildActionResource,
588
800
  buildActionRunContext,
589
801
  buildBroadActionResource,
@@ -595,10 +807,12 @@ function buildConnectionlessPolicy(options) {
595
807
  buildHttpRequestContext,
596
808
  buildUrlString,
597
809
  globSelectedApiVersion,
810
+ isIgnoredHeader,
598
811
  isWriteRequest,
599
812
  parseActionResource,
600
813
  parseUrl,
601
814
  toAppKey,
602
- toExactPolicy
815
+ toExactPolicy,
816
+ toExactPolicyV3
603
817
  });
604
818
  //# sourceMappingURL=index.cjs.map