@rayspec/auth-core 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +105 -0
  2. package/dist/api-key.d.ts +28 -0
  3. package/dist/api-key.d.ts.map +1 -0
  4. package/dist/api-key.js +56 -0
  5. package/dist/api-key.js.map +1 -0
  6. package/dist/audit.d.ts +26 -0
  7. package/dist/audit.d.ts.map +1 -0
  8. package/dist/audit.js +11 -0
  9. package/dist/audit.js.map +1 -0
  10. package/dist/authz.d.ts +66 -0
  11. package/dist/authz.d.ts.map +1 -0
  12. package/dist/authz.js +112 -0
  13. package/dist/authz.js.map +1 -0
  14. package/dist/config.d.ts +23 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +42 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/deferrals.d.ts +33 -0
  19. package/dist/deferrals.d.ts.map +1 -0
  20. package/dist/deferrals.js +38 -0
  21. package/dist/deferrals.js.map +1 -0
  22. package/dist/dto.d.ts +337 -0
  23. package/dist/dto.d.ts.map +1 -0
  24. package/dist/dto.js +221 -0
  25. package/dist/dto.js.map +1 -0
  26. package/dist/email.d.ts +24 -0
  27. package/dist/email.d.ts.map +1 -0
  28. package/dist/email.js +46 -0
  29. package/dist/email.js.map +1 -0
  30. package/dist/errors.d.ts +87 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +104 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/index.d.ts +14 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +14 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/invite.d.ts +18 -0
  39. package/dist/invite.d.ts.map +1 -0
  40. package/dist/invite.js +33 -0
  41. package/dist/invite.js.map +1 -0
  42. package/dist/password.d.ts +17 -0
  43. package/dist/password.d.ts.map +1 -0
  44. package/dist/password.js +46 -0
  45. package/dist/password.js.map +1 -0
  46. package/dist/rate-limit.d.ts +93 -0
  47. package/dist/rate-limit.d.ts.map +1 -0
  48. package/dist/rate-limit.js +156 -0
  49. package/dist/rate-limit.js.map +1 -0
  50. package/dist/session.d.ts +18 -0
  51. package/dist/session.d.ts.map +1 -0
  52. package/dist/session.js +41 -0
  53. package/dist/session.js.map +1 -0
  54. package/dist/tokens.d.ts +117 -0
  55. package/dist/tokens.d.ts.map +1 -0
  56. package/dist/tokens.js +150 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Socialinsiders UG (haftungsbeschränkt)
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Read the api-key pepper from the environment. THROWS if missing/blank — the boot-required
3
+ * contract (the pepper is in the boot-fails-closed set alongside the JWT signing key).
4
+ */
5
+ export declare function getApiKeyPepper(): string;
6
+ /** HMAC-SHA256 the secret with the boot-required pepper; returns a lowercase hex digest. */
7
+ export declare function hashApiKey(secret: string, pepper?: string): string;
8
+ /**
9
+ * Constant-time verify of a presented secret against a stored HMAC hash. Recomputes the HMAC
10
+ * under the pepper and compares with timingSafeEqual (length-guarded so a wrong-length hash
11
+ * does not throw). Never branches observably on the hash contents.
12
+ */
13
+ export declare function verifyApiKey(storedHash: string, secret: string, pepper?: string): boolean;
14
+ export interface MintedApiKey {
15
+ /** The full plaintext shown to the user ONCE: `${prefix}.${secret}`. Never stored. */
16
+ plaintext: string;
17
+ /** The public, indexed lookup handle (safe to store + query). */
18
+ prefix: string;
19
+ /** The HMAC hash to persist (key_hash). */
20
+ hash: string;
21
+ }
22
+ /**
23
+ * Mint a new api-key: a short public prefix + a ≥128-bit secret. Returns the one-time
24
+ * plaintext, the prefix to index, and the HMAC hash to store. The secret is 32 bytes
25
+ * (256-bit) of CSPRNG, base64url-encoded.
26
+ */
27
+ export declare function mintApiKey(pepper?: string): MintedApiKey;
28
+ //# sourceMappingURL=api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAQxC;AAED,4FAA4F;AAC5F,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAA0B,GAAG,MAAM,CAErF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAA0B,GACjC,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,MAA0B,GAAG,YAAY,CAK3E"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * API-key / client-secret hashing primitive — HMAC-SHA256 with a server PEPPER (a deliberate
3
+ * choice: fast on the hot per-request auth path, sound for ≥128-bit machine secrets;
4
+ * argon2id is reserved for low-entropy passwords). Uses node:crypto (stdlib) only.
5
+ *
6
+ * The pepper is REQUIRED-AT-BOOT: `getApiKeyPepper()` throws if RAYSPEC_API_KEY_PEPPER is
7
+ * unset/blank, so a misconfigured deploy fails closed instead of silently hashing keys with
8
+ * an empty pepper. (RAYSPEC_API_KEY_PEPPER is wired into ci.yml + the boot-fails-closed
9
+ * test; here the primitive is the boot-required seam.)
10
+ *
11
+ * Storage model: plaintext shown ONCE at mint; only the public `keyPrefix` (indexed for O(1)
12
+ * lookup) + the HMAC `keyHash` are persisted. Verification recomputes the HMAC and compares
13
+ * in constant time (timingSafeEqual) so a stored-hash comparison cannot be timed.
14
+ */
15
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
16
+ const PEPPER_ENV = 'RAYSPEC_API_KEY_PEPPER';
17
+ /**
18
+ * Read the api-key pepper from the environment. THROWS if missing/blank — the boot-required
19
+ * contract (the pepper is in the boot-fails-closed set alongside the JWT signing key).
20
+ */
21
+ export function getApiKeyPepper() {
22
+ const pepper = process.env[PEPPER_ENV];
23
+ if (!pepper || pepper.trim().length === 0) {
24
+ throw new Error(`${PEPPER_ENV} is required at boot (the api-key HMAC pepper). Refusing to start without it.`);
25
+ }
26
+ return pepper;
27
+ }
28
+ /** HMAC-SHA256 the secret with the boot-required pepper; returns a lowercase hex digest. */
29
+ export function hashApiKey(secret, pepper = getApiKeyPepper()) {
30
+ return createHmac('sha256', pepper).update(secret).digest('hex');
31
+ }
32
+ /**
33
+ * Constant-time verify of a presented secret against a stored HMAC hash. Recomputes the HMAC
34
+ * under the pepper and compares with timingSafeEqual (length-guarded so a wrong-length hash
35
+ * does not throw). Never branches observably on the hash contents.
36
+ */
37
+ export function verifyApiKey(storedHash, secret, pepper = getApiKeyPepper()) {
38
+ const computed = hashApiKey(secret, pepper);
39
+ const a = Buffer.from(computed, 'utf8');
40
+ const b = Buffer.from(storedHash, 'utf8');
41
+ if (a.length !== b.length)
42
+ return false;
43
+ return timingSafeEqual(a, b);
44
+ }
45
+ /**
46
+ * Mint a new api-key: a short public prefix + a ≥128-bit secret. Returns the one-time
47
+ * plaintext, the prefix to index, and the HMAC hash to store. The secret is 32 bytes
48
+ * (256-bit) of CSPRNG, base64url-encoded.
49
+ */
50
+ export function mintApiKey(pepper = getApiKeyPepper()) {
51
+ const prefix = `rk_${randomBytes(6).toString('base64url')}`;
52
+ const secret = randomBytes(32).toString('base64url'); // 256-bit
53
+ const plaintext = `${prefix}.${secret}`;
54
+ return { plaintext, prefix, hash: hashApiKey(secret, pepper) };
55
+ }
56
+ //# sourceMappingURL=api-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-key.js","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,GAAG,UAAU,+EAA+E,CAC7F,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,SAAiB,eAAe,EAAE;IAC3E,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,MAAc,EACd,SAAiB,eAAe,EAAE;IAElC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,SAAiB,eAAe,EAAE;IAC3D,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;IAC5D,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;IAChE,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;IACxC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AACjE,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Neutral auth-audit EVENT shape (the DB write is api-auth's audit-store).
3
+ *
4
+ * Services return a list of AuditEvents describing what happened; the route layer commits them
5
+ * OUT-OF-BAND (their own unit of work) so a 404/rollback of the main request never drops a
6
+ * security event. Cross-tenant-denial events record the ACTOR's resolved tenant
7
+ * authoritatively + the attempted target as an opaque `targetHash` (never a target-org FK the
8
+ * actor does not own). Hashes/metadata only — no raw secrets/PII.
9
+ */
10
+ /** The closed set of audited auth events. */
11
+ export type AuthEventName = 'register' | 'login' | 'login_failed' | 'refresh' | 'refresh_reuse_detected' | 'logout' | 'org_create' | 'org_switch' | 'org_member_add' | 'org_member_change' | 'org_invite_issued' | 'apikey_mint' | 'apikey_revoke' | 'apikey_auth_failed' | 'cross_tenant_denied' | 'authz_denied';
12
+ /** One audit event a service emits for the route to commit out-of-band. */
13
+ export interface AuditEvent {
14
+ event: AuthEventName;
15
+ /** The actor's resolved tenant (authoritative; null when pre-org). */
16
+ actorOrgId?: string | null;
17
+ /** The actor's user id (null for fully anonymous failures). */
18
+ actorUserId?: string | null;
19
+ /** Refresh family id, when relevant. */
20
+ familyId?: string | null;
21
+ /** Opaque hash of an attempted cross-tenant target (NEVER a target-org FK). */
22
+ targetHash?: string | null;
23
+ /** Extra non-PII metadata (hashes/flags only). */
24
+ meta?: Record<string, unknown>;
25
+ }
26
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,aAAa,GACrB,UAAU,GACV,OAAO,GACP,cAAc,GACd,SAAS,GACT,wBAAwB,GACxB,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,gBAAgB,GAChB,mBAAmB,GACnB,mBAAmB,GACnB,aAAa,GACb,eAAe,GACf,oBAAoB,GACpB,qBAAqB,GACrB,cAAc,CAAC;AAEnB,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC"}
package/dist/audit.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Neutral auth-audit EVENT shape (the DB write is api-auth's audit-store).
3
+ *
4
+ * Services return a list of AuditEvents describing what happened; the route layer commits them
5
+ * OUT-OF-BAND (their own unit of work) so a 404/rollback of the main request never drops a
6
+ * security event. Cross-tenant-denial events record the ACTOR's resolved tenant
7
+ * authoritatively + the attempted target as an opaque `targetHash` (never a target-org FK the
8
+ * actor does not own). Hashes/metadata only — no raw secrets/PII.
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Authorization — the static ROLE_PERMISSIONS table + the authorize() contract.
3
+ * DB-free here; the LIVE membership lookup is injected by the caller (api-auth holds the
4
+ * IdentityStore). A general policy engine is overkill here — a static code constant.
5
+ *
6
+ * THE CRITICAL SPLIT: for SENSITIVE/MUTATING permissions the caller MUST resolve the role from a
7
+ * LIVE membership lookup (TenantDb/IdentityStore), NOT the JWT claim — so a revoked/demoted
8
+ * principal is denied within the ~8-min token TTL (the TTL is a bounded READ-staleness risk, not
9
+ * a write-privilege bypass). `isSensitive(permission)` tells the caller which path to take.
10
+ */
11
+ /**
12
+ * The closed set of permissions.
13
+ *
14
+ * `store:read`/`store:write` are the GENERIC, product-agnostic CRUD permissions a
15
+ * declared `api` route over a materialized store is gated by — read ops (list/get) require
16
+ * `store:read`, mutating ops (create/update/delete) require `store:write`. They are NOT
17
+ * store-specific (no `notebooks:*`): the platform stays product-free and one pair gates every
18
+ * declared store route. `store:write` is SENSITIVE (a live-membership recheck on every mutation,
19
+ * mirroring why a write must never trust a stale JWT claim — see the SENSITIVE set below).
20
+ */
21
+ export type Permission = 'agent:run' | 'agent:read' | 'store:read' | 'store:write' | 'org:read' | 'org:member:add' | 'org:member:change' | 'org:switch' | 'apikey:read' | 'apikey:mint' | 'apikey:revoke';
22
+ /** Roles → the permissions they grant (static; owner ⊇ admin ⊇ member). */
23
+ export declare const ROLE_PERMISSIONS: Record<string, Permission[]>;
24
+ /** True if `permission` requires a live membership check (never trust the JWT claim). */
25
+ export declare function isSensitive(permission: Permission): boolean;
26
+ /**
27
+ * The CLOSED set of permissions an api-key principal may EVER exercise — its scopes ∩ this set. This
28
+ * is the SINGLE source of truth for both `authorize()` (the runtime gate) and the `ApiKeyScope` DTO
29
+ * enum (the mint-time grammar; dto.ts derives its enum from this so the two can never silently
30
+ * desync). It is purely scope-driven (an api-key is a machine principal with NO membership/role).
31
+ *
32
+ * What is DELIBERATELY ABSENT is the security boundary: the org-MANAGEMENT sensitive ops
33
+ * (`apikey:mint`, `apikey:revoke`, `org:member:change`, `org:switch`) are NOT here, so an api-key can
34
+ * never perform them regardless of scope. `store:write` IS here (the programmatic/agency consumer
35
+ * model — a desktop app / automation POSTing rows authenticates via an org-scoped key); it is also
36
+ * SENSITIVE, but for an api-key the KEY is the live credential, so requirePermission falls api-keys
37
+ * through to authorize(), where this set ∩ scopes is the gate (no stale-claim recheck applies).
38
+ *
39
+ * Frozen so a caller cannot mutate the shared authority; `authorize()` reads it via `.includes`.
40
+ */
41
+ export declare const API_KEY_GRANTABLE: readonly Permission[];
42
+ /** Does `role` grant `permission` per the static table? */
43
+ export declare function roleGrants(role: string | undefined, permission: Permission): boolean;
44
+ /**
45
+ * The principal an authorize() call evaluates. For sensitive permissions the caller supplies the
46
+ * LIVE role (resolved via the membership store); for read-mostly permissions the JWT-claim role
47
+ * is acceptable.
48
+ */
49
+ export interface AuthzPrincipal {
50
+ userId?: string;
51
+ /** The role to evaluate against — LIVE for sensitive ops, claim-derived otherwise. */
52
+ role?: string;
53
+ /** API-key scopes (an api-key principal is also gated by its granted scopes). */
54
+ scopes?: string[];
55
+ }
56
+ /**
57
+ * Decide whether `principal` may perform `permission` in the (already tenant-resolved) org.
58
+ *
59
+ * Pure decision: the caller has ALREADY (a) asserted the principal belongs to the tenant and
60
+ * (b) for a sensitive permission, set `principal.role` from a LIVE membership lookup. For an
61
+ * api-key principal, the permission must ALSO be within its granted scopes (scope ∩ role).
62
+ */
63
+ export declare function authorize(principal: AuthzPrincipal, permission: Permission, opts?: {
64
+ isApiKey?: boolean;
65
+ }): boolean;
66
+ //# sourceMappingURL=authz.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz.d.ts","sourceRoot":"","sources":["../src/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;;;GASG;AACH,MAAM,MAAM,UAAU,GAClB,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,aAAa,GACb,UAAU,GACV,gBAAgB,GAChB,mBAAmB,GACnB,YAAY,GACZ,aAAa,GACb,aAAa,GACb,eAAe,CAAC;AAEpB,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CA0BzD,CAAC;AAmBF,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAE3D;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,UAAU,EAOjD,CAAC;AAEH,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAGpF;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,UAAU,EACtB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAChC,OAAO,CAeT"}
package/dist/authz.js ADDED
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Authorization — the static ROLE_PERMISSIONS table + the authorize() contract.
3
+ * DB-free here; the LIVE membership lookup is injected by the caller (api-auth holds the
4
+ * IdentityStore). A general policy engine is overkill here — a static code constant.
5
+ *
6
+ * THE CRITICAL SPLIT: for SENSITIVE/MUTATING permissions the caller MUST resolve the role from a
7
+ * LIVE membership lookup (TenantDb/IdentityStore), NOT the JWT claim — so a revoked/demoted
8
+ * principal is denied within the ~8-min token TTL (the TTL is a bounded READ-staleness risk, not
9
+ * a write-privilege bypass). `isSensitive(permission)` tells the caller which path to take.
10
+ */
11
+ /** Roles → the permissions they grant (static; owner ⊇ admin ⊇ member). */
12
+ export const ROLE_PERMISSIONS = {
13
+ owner: [
14
+ 'agent:run',
15
+ 'agent:read',
16
+ 'store:read',
17
+ 'store:write',
18
+ 'org:read',
19
+ 'org:member:add',
20
+ 'org:member:change',
21
+ 'org:switch',
22
+ 'apikey:read',
23
+ 'apikey:mint',
24
+ 'apikey:revoke',
25
+ ],
26
+ admin: [
27
+ 'agent:run',
28
+ 'agent:read',
29
+ 'store:read',
30
+ 'store:write',
31
+ 'org:read',
32
+ 'org:switch',
33
+ 'apikey:read',
34
+ 'apikey:mint',
35
+ 'apikey:revoke',
36
+ ],
37
+ member: ['agent:run', 'agent:read', 'store:read', 'store:write', 'org:read', 'org:switch'],
38
+ };
39
+ /**
40
+ * SENSITIVE/MUTATING permissions — for these the caller MUST do a LIVE membership+role lookup and
41
+ * MUST NOT trust the JWT claim. (org switch is included: it re-mints a privileged token, so it
42
+ * re-checks live.)
43
+ */
44
+ const SENSITIVE = new Set([
45
+ 'apikey:mint',
46
+ 'apikey:revoke',
47
+ 'org:member:add',
48
+ 'org:member:change',
49
+ 'org:switch',
50
+ // A declared-store MUTATION (create/update/delete) must re-check live membership — a revoked/
51
+ // demoted principal must not write product data on a stale JWT claim (the same write-bypass
52
+ // reasoning that makes the api-key/org-management ops sensitive). store:read stays claim-trusted.
53
+ 'store:write',
54
+ ]);
55
+ /** True if `permission` requires a live membership check (never trust the JWT claim). */
56
+ export function isSensitive(permission) {
57
+ return SENSITIVE.has(permission);
58
+ }
59
+ /**
60
+ * The CLOSED set of permissions an api-key principal may EVER exercise — its scopes ∩ this set. This
61
+ * is the SINGLE source of truth for both `authorize()` (the runtime gate) and the `ApiKeyScope` DTO
62
+ * enum (the mint-time grammar; dto.ts derives its enum from this so the two can never silently
63
+ * desync). It is purely scope-driven (an api-key is a machine principal with NO membership/role).
64
+ *
65
+ * What is DELIBERATELY ABSENT is the security boundary: the org-MANAGEMENT sensitive ops
66
+ * (`apikey:mint`, `apikey:revoke`, `org:member:change`, `org:switch`) are NOT here, so an api-key can
67
+ * never perform them regardless of scope. `store:write` IS here (the programmatic/agency consumer
68
+ * model — a desktop app / automation POSTing rows authenticates via an org-scoped key); it is also
69
+ * SENSITIVE, but for an api-key the KEY is the live credential, so requirePermission falls api-keys
70
+ * through to authorize(), where this set ∩ scopes is the gate (no stale-claim recheck applies).
71
+ *
72
+ * Frozen so a caller cannot mutate the shared authority; `authorize()` reads it via `.includes`.
73
+ */
74
+ export const API_KEY_GRANTABLE = Object.freeze([
75
+ 'agent:run',
76
+ 'agent:read',
77
+ 'store:read',
78
+ 'store:write',
79
+ 'org:read',
80
+ 'apikey:read',
81
+ ]);
82
+ /** Does `role` grant `permission` per the static table? */
83
+ export function roleGrants(role, permission) {
84
+ if (!role)
85
+ return false;
86
+ return (ROLE_PERMISSIONS[role] ?? []).includes(permission);
87
+ }
88
+ /**
89
+ * Decide whether `principal` may perform `permission` in the (already tenant-resolved) org.
90
+ *
91
+ * Pure decision: the caller has ALREADY (a) asserted the principal belongs to the tenant and
92
+ * (b) for a sensitive permission, set `principal.role` from a LIVE membership lookup. For an
93
+ * api-key principal, the permission must ALSO be within its granted scopes (scope ∩ role).
94
+ */
95
+ export function authorize(principal, permission, opts = {}) {
96
+ // The permissions an api-key may EVER exercise — purely its scopes, NO role (an api-key is a
97
+ // machine principal with no membership). Sensitive org-management ops are NOT in this set, so
98
+ // an api-key can never mint/revoke keys or change membership/switch org regardless of scope.
99
+ if (opts.isApiKey) {
100
+ // The api-key-grantable SET is the single shared authority (API_KEY_GRANTABLE above): an api-key
101
+ // may exercise ONLY a permission that is BOTH in that set AND in its scopes. The org-MANAGEMENT
102
+ // sensitive ops (apikey:mint/revoke, org:member:change, org:switch) are absent from the set, so
103
+ // an api-key can never perform them regardless of scope. (store:write IS in the set — the
104
+ // programmatic/agency consumer model — see API_KEY_GRANTABLE's rationale.)
105
+ if (!API_KEY_GRANTABLE.includes(permission))
106
+ return false;
107
+ return (principal.scopes ?? []).includes(permission);
108
+ }
109
+ // User principals: the role (LIVE for sensitive ops, claim-derived otherwise) must grant it.
110
+ return roleGrants(principal.role, permission);
111
+ }
112
+ //# sourceMappingURL=authz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authz.js","sourceRoot":"","sources":["../src/authz.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAyBH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAiC;IAC5D,KAAK,EAAE;QACL,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,UAAU;QACV,gBAAgB;QAChB,mBAAmB;QACnB,YAAY;QACZ,aAAa;QACb,aAAa;QACb,eAAe;KAChB;IACD,KAAK,EAAE;QACL,WAAW;QACX,YAAY;QACZ,YAAY;QACZ,aAAa;QACb,UAAU;QACV,YAAY;QACZ,aAAa;QACb,aAAa;QACb,eAAe;KAChB;IACD,MAAM,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,CAAC;CAC3F,CAAC;AAEF;;;;GAIG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,CAAa;IACpC,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,mBAAmB;IACnB,YAAY;IACZ,8FAA8F;IAC9F,4FAA4F;IAC5F,kGAAkG;IAClG,aAAa;CACd,CAAC,CAAC;AAEH,yFAAyF;AACzF,MAAM,UAAU,WAAW,CAAC,UAAsB;IAChD,OAAO,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA0B,MAAM,CAAC,MAAM,CAAC;IACpE,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,aAAa;IACb,UAAU;IACV,aAAa;CACd,CAAC,CAAC;AAEH,2DAA2D;AAC3D,MAAM,UAAU,UAAU,CAAC,IAAwB,EAAE,UAAsB;IACzE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AAC7D,CAAC;AAeD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,SAAyB,EACzB,UAAsB,EACtB,OAA+B,EAAE;IAEjC,6FAA6F;IAC7F,8FAA8F;IAC9F,6FAA6F;IAC7F,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,iGAAiG;QACjG,gGAAgG;QAChG,gGAAgG;QAChG,0FAA0F;QAC1F,2EAA2E;QAC3E,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1D,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvD,CAAC;IACD,6FAA6F;IAC7F,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Boot-required secret configuration (no plaintext secrets, fail-closed at boot).
3
+ *
4
+ * Two secrets are REQUIRED-AT-BOOT and live ONLY in env/secret-manager, never in the DB or git:
5
+ * - RAYSPEC_JWT_SIGNING_KEY — the PKCS#8 PEM private key the access-token signer uses.
6
+ * - RAYSPEC_API_KEY_PEPPER — the HMAC pepper for api-key/client-secret hashing (api-key.ts).
7
+ *
8
+ * `assertBootSecrets()` throws if either is missing/blank, so a misconfigured deploy fails to
9
+ * start rather than silently minting unsignable tokens or hashing keys with an empty pepper. The
10
+ * api-auth app calls it at construction; the boot-fails-closed test asserts it throws when a
11
+ * secret is absent.
12
+ */
13
+ export declare const JWT_SIGNING_KEY_ENV = "RAYSPEC_JWT_SIGNING_KEY";
14
+ export declare const API_KEY_PEPPER_ENV = "RAYSPEC_API_KEY_PEPPER";
15
+ /** Read the JWT signing key PEM; THROWS if unset/blank (boot-required). */
16
+ export declare function getJwtSigningKeyPem(): string;
17
+ /**
18
+ * Assert BOTH boot-required secrets are present. Throws a single combined error listing every
19
+ * missing one. Call this once at app construction (fail-closed boot). Does NOT validate the key
20
+ * material itself — the token module does that lazily on first sign (which also fails closed).
21
+ */
22
+ export declare function assertBootSecrets(env?: NodeJS.ProcessEnv): void;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAE3D,2EAA2E;AAC3E,wBAAgB,mBAAmB,IAAI,MAAM,CAS5C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAc5E"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Boot-required secret configuration (no plaintext secrets, fail-closed at boot).
3
+ *
4
+ * Two secrets are REQUIRED-AT-BOOT and live ONLY in env/secret-manager, never in the DB or git:
5
+ * - RAYSPEC_JWT_SIGNING_KEY — the PKCS#8 PEM private key the access-token signer uses.
6
+ * - RAYSPEC_API_KEY_PEPPER — the HMAC pepper for api-key/client-secret hashing (api-key.ts).
7
+ *
8
+ * `assertBootSecrets()` throws if either is missing/blank, so a misconfigured deploy fails to
9
+ * start rather than silently minting unsignable tokens or hashing keys with an empty pepper. The
10
+ * api-auth app calls it at construction; the boot-fails-closed test asserts it throws when a
11
+ * secret is absent.
12
+ */
13
+ export const JWT_SIGNING_KEY_ENV = 'RAYSPEC_JWT_SIGNING_KEY';
14
+ export const API_KEY_PEPPER_ENV = 'RAYSPEC_API_KEY_PEPPER';
15
+ /** Read the JWT signing key PEM; THROWS if unset/blank (boot-required). */
16
+ export function getJwtSigningKeyPem() {
17
+ const pem = process.env[JWT_SIGNING_KEY_ENV];
18
+ if (!pem || pem.trim().length === 0) {
19
+ throw new Error(`${JWT_SIGNING_KEY_ENV} is required at boot (the JWT signing key, PKCS#8 PEM). ` +
20
+ 'Refusing to start without it.');
21
+ }
22
+ return pem;
23
+ }
24
+ /**
25
+ * Assert BOTH boot-required secrets are present. Throws a single combined error listing every
26
+ * missing one. Call this once at app construction (fail-closed boot). Does NOT validate the key
27
+ * material itself — the token module does that lazily on first sign (which also fails closed).
28
+ */
29
+ export function assertBootSecrets(env = process.env) {
30
+ const missing = [];
31
+ if (!env[JWT_SIGNING_KEY_ENV] || env[JWT_SIGNING_KEY_ENV]?.trim().length === 0) {
32
+ missing.push(JWT_SIGNING_KEY_ENV);
33
+ }
34
+ if (!env[API_KEY_PEPPER_ENV] || env[API_KEY_PEPPER_ENV]?.trim().length === 0) {
35
+ missing.push(API_KEY_PEPPER_ENV);
36
+ }
37
+ if (missing.length > 0) {
38
+ throw new Error(`Boot-required secret(s) missing: ${missing.join(', ')}. ` +
39
+ 'These live in env/secret-manager only (never DB/git). Refusing to start (fail-closed).');
40
+ }
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,yBAAyB,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAAwB,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,GAAG,mBAAmB,0DAA0D;YAC9E,+BAA+B,CAClC,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACpE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,mBAAmB,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/E,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,GAAG,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,oCAAoC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YACxD,wFAAwF,CAC3F,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The before-external-exposure deferral line — EXPLICIT, in code, so the boundary is auditable.
3
+ *
4
+ * The following are NOT shipped yet (they are gated to BEFORE external/SaaS exposure). The
5
+ * platform deliberately ships the forward-compatible HOOKS for each so enabling them is a
6
+ * config/policy change, not a re-architecture:
7
+ *
8
+ * - KMS per-tenant DEK + crypto-shred → hook: orgs.region + ON DELETE CASCADE purge of
9
+ * the at-rest raw-PII columns: the jsonb
10
+ * payloads conversation_items.payload, runs.output,
11
+ * journal_steps.output, run_events.data + the text
12
+ * runs.final_text. (NOT conversation_items.content
13
+ * — deprecated legacy column, part data now lives in
14
+ * payload; see schema.ts.)
15
+ * - Postgres Row-Level Security (RLS) → hook: the TenantDb.transaction() set_config
16
+ * app.current_tenant GUC seam (TENANT_GUC) — RLS
17
+ * policies bind to an already-populated GUC with
18
+ * zero call-site churn.
19
+ * - DPoP-bound access tokens → hook: the jose access-token module (add a cnf
20
+ * claim + DPoP proof verification).
21
+ * - Per-tenant tool/handler sandbox → hook: the neutral Backend boundary + per-tenant
22
+ * CLAUDE_CONFIG_DIR isolation already in the
23
+ * Anthropic adapter.
24
+ * - jti denylist (immediate token revocation) → hook: the jti claim is minted on every token;
25
+ * the opaque session is the revocation point
26
+ * (the ~8-min TTL bounds stateless-read staleness).
27
+ *
28
+ * This constant is exported so a grep for `SECURITY_DEFERRALS_10B` finds the single source of the
29
+ * deferral record; it is referenced by the docs and intentionally never used as runtime logic.
30
+ */
31
+ export declare const SECURITY_DEFERRALS_10B: readonly ["kms-per-tenant-dek-crypto-shred", "postgres-row-level-security", "dpop-bound-tokens", "per-tenant-tool-sandbox", "jti-denylist"];
32
+ export type SecurityDeferral10B = (typeof SECURITY_DEFERRALS_10B)[number];
33
+ //# sourceMappingURL=deferrals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferrals.d.ts","sourceRoot":"","sources":["../src/deferrals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,sBAAsB,6IAMzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The before-external-exposure deferral line — EXPLICIT, in code, so the boundary is auditable.
3
+ *
4
+ * The following are NOT shipped yet (they are gated to BEFORE external/SaaS exposure). The
5
+ * platform deliberately ships the forward-compatible HOOKS for each so enabling them is a
6
+ * config/policy change, not a re-architecture:
7
+ *
8
+ * - KMS per-tenant DEK + crypto-shred → hook: orgs.region + ON DELETE CASCADE purge of
9
+ * the at-rest raw-PII columns: the jsonb
10
+ * payloads conversation_items.payload, runs.output,
11
+ * journal_steps.output, run_events.data + the text
12
+ * runs.final_text. (NOT conversation_items.content
13
+ * — deprecated legacy column, part data now lives in
14
+ * payload; see schema.ts.)
15
+ * - Postgres Row-Level Security (RLS) → hook: the TenantDb.transaction() set_config
16
+ * app.current_tenant GUC seam (TENANT_GUC) — RLS
17
+ * policies bind to an already-populated GUC with
18
+ * zero call-site churn.
19
+ * - DPoP-bound access tokens → hook: the jose access-token module (add a cnf
20
+ * claim + DPoP proof verification).
21
+ * - Per-tenant tool/handler sandbox → hook: the neutral Backend boundary + per-tenant
22
+ * CLAUDE_CONFIG_DIR isolation already in the
23
+ * Anthropic adapter.
24
+ * - jti denylist (immediate token revocation) → hook: the jti claim is minted on every token;
25
+ * the opaque session is the revocation point
26
+ * (the ~8-min TTL bounds stateless-read staleness).
27
+ *
28
+ * This constant is exported so a grep for `SECURITY_DEFERRALS_10B` finds the single source of the
29
+ * deferral record; it is referenced by the docs and intentionally never used as runtime logic.
30
+ */
31
+ export const SECURITY_DEFERRALS_10B = [
32
+ 'kms-per-tenant-dek-crypto-shred',
33
+ 'postgres-row-level-security',
34
+ 'dpop-bound-tokens',
35
+ 'per-tenant-tool-sandbox',
36
+ 'jti-denylist',
37
+ ];
38
+ //# sourceMappingURL=deferrals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deferrals.js","sourceRoot":"","sources":["../src/deferrals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,iCAAiC;IACjC,6BAA6B;IAC7B,mBAAmB;IACnB,yBAAyB;IACzB,cAAc;CACN,CAAC"}