@coffre/core 0.6.2 → 0.7.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.
@@ -370,6 +370,24 @@ function grantKind(grant) {
370
370
  /** The most places a scope asked for names: far more than any credential needs, and a bound on what a request makes the app check. */
371
371
  const MAX_SCOPE_PLACES = 1e3;
372
372
  /**
373
+ * Whether a personal token's scope may name `role`: one that manages no
374
+ * access. A token acting as its person reaches their secrets and places,
375
+ * never who holds them, so it cannot hand anyone, itself included, more
376
+ * than it was given. A project's Owner who wants a token for all of it
377
+ * names `maintainer`.
378
+ */
379
+ function delegable(role) {
380
+ return !roleGrants(role, "grant.manage");
381
+ }
382
+ /** The roles a personal token's scope may name, in catalogue order. */
383
+ const DELEGABLE_ROLES = ROLE_NAMES.filter(delegable);
384
+ /**
385
+ * The longest a personal token lasts: a week. It is for an afternoon's
386
+ * script or agent, and whoever needs it longer makes another, as a person,
387
+ * rather than keep one around.
388
+ */
389
+ const MAX_PERSONAL_TOKEN_MINUTES = 10080;
390
+ /**
373
391
  * A credential's scope, by path as the API names it, in words, each role
374
392
  * with its places: `developer on market/dev, api/dev; viewer on market/prod`.
375
393
  */
@@ -426,6 +444,16 @@ function allows(holder, permission, place) {
426
444
  if (instanceRoleGrants(holder.role, permission) && inScope(holder.scope, place)) return true;
427
445
  return holdsAt(holder.grants, permission, place);
428
446
  }
447
+ /**
448
+ * Whether the credential a request came in on, a scoped token or binding,
449
+ * leaves `permission` at `place` to its holder: always, for a session. A
450
+ * personal config's owner holds its every permission by reading its parent
451
+ * (docs/design/forking.md), and a token of theirs is still held to the
452
+ * role its scope names there: a viewer's token reads it, and writes nothing.
453
+ */
454
+ function withinCredential(holder, permission, place) {
455
+ return holder.credentialScope == null || holdsAt(holder.credentialScope, permission, place);
456
+ }
429
457
  /** Whether a grant among `grants` covers `place` with a role that holds `permission`. */
430
458
  function holdsAt(grants, permission, place) {
431
459
  return grants.some((grant) => covers(grant, place) && roleGrants(grant.role, permission));
@@ -447,7 +475,7 @@ function mayManageAccess(actor, place) {
447
475
  * does none of these.
448
476
  */
449
477
  function runsInstance(holder) {
450
- return administers(holder.role) && unscoped(holder.scope);
478
+ return holder.credentialScope == null && administers(holder.role) && unscoped(holder.scope);
451
479
  }
452
480
  /**
453
481
  * Whether `holder` may give a service account `role` at `place`: where
@@ -455,11 +483,12 @@ function runsInstance(holder) {
455
483
  * everything `role` does. A Developer on `market/dev` gives viewer or
456
484
  * developer there, never maintainer; a grant on `market` takes holding it
457
485
  * on all of `market`. `setting` is null for a service account, which sets
458
- * up none.
486
+ * up none; a request on a credential with a scope sets none up either,
487
+ * since what it set up would outlive it.
459
488
  */
460
489
  function givesService(holder, setting, role, place) {
461
490
  if (mayManageAccess(holder, place)) return true;
462
- return setting !== null && inScope(setting, place) && ROLES[role].permissions.every((permission) => allows(holder, permission, place));
491
+ return setting !== null && holder.credentialScope == null && inScope(setting, place) && ROLES[role].permissions.every((permission) => allows(holder, permission, place));
463
492
  }
464
493
  /** Whether `holder` gives a service account anything anywhere among `places`: whether they set any up. */
465
494
  function setsUpServices(holder, setting, places) {
@@ -511,7 +540,7 @@ function issuesCredential(holder, setting, actor, account, scope) {
511
540
  * Anyone else reads the places they hold `audit.read` in.
512
541
  */
513
542
  function readsWholeLog(holder) {
514
- return instanceRoleGrants(holder.role, "audit.read") && unscoped(holder.scope);
543
+ return holder.credentialScope == null && instanceRoleGrants(holder.role, "audit.read") && unscoped(holder.scope);
515
544
  }
516
545
  /**
517
546
  * Whether the holder may make a project: an Admin or Owner whose scope
@@ -521,7 +550,7 @@ function readsWholeLog(holder) {
521
550
  */
522
551
  function makesProjects(holder) {
523
552
  const { projects, environments } = holder.scope;
524
- return administers(holder.role) && environments === "all" && (projects === "all" || "except" in projects);
553
+ return holder.credentialScope == null && administers(holder.role) && environments === "all" && (projects === "all" || "except" in projects);
525
554
  }
526
555
  //#endregion
527
- export { readsWholeLog as A, isScope as C, mayManageAccess as D, managesService as E, unscoped as F, convertEveryProjectGrants as I, runsInstance as M, scopeInWords as N, normalScope as O, setsUpServices as P, isRole as S, makesProjects as T, grantKind as _, NOTHING as a, isCredentialScope as b, ROLES as c, admits as d, allows as f, givesService as g, credentialScopeInWords as h, MAX_SCOPE_PLACES as i, roleGrants as j, ownsInstance as k, ROLE_NAMES as l, covers as m, INSTANCE_ROLES as n, PERMISSIONS as o, assignableToEnvironment as p, INSTANCE_ROLE_NAMES as r, PROJECT_ONLY_PERMISSIONS as s, EVERYWHERE as t, administers as u, inScope as v, issuesCredential as w, isInstanceRole as x, instanceRoleGrants as y };
556
+ export { mayManageAccess as A, convertEveryProjectGrants as B, isCredentialScope as C, issuesCredential as D, isScope as E, runsInstance as F, scopeInWords as I, setsUpServices as L, ownsInstance as M, readsWholeLog as N, makesProjects as O, roleGrants as P, unscoped as R, instanceRoleGrants as S, isRole as T, credentialScopeInWords as _, MAX_PERSONAL_TOKEN_MINUTES as a, grantKind as b, PERMISSIONS as c, ROLE_NAMES as d, administers as f, covers as g, assignableToEnvironment as h, INSTANCE_ROLE_NAMES as i, normalScope as j, managesService as k, PROJECT_ONLY_PERMISSIONS as l, allows as m, EVERYWHERE as n, MAX_SCOPE_PLACES as o, admits as p, INSTANCE_ROLES as r, NOTHING as s, DELEGABLE_ROLES as t, ROLES as u, delegable as v, isInstanceRole as w, inScope as x, givesService as y, withinCredential as z };
@@ -293,12 +293,32 @@ declare function grantKind(grant: GrantPlace): 'project' | 'environment' | null;
293
293
  * account developer on market/dev, then viewer on market/prod too
294
294
  * token developer on market/dev, issued before market/prod
295
295
  * request reads and writes market/dev, never market/prod
296
+ *
297
+ * A person's personal token carries one too, each grant one they held when
298
+ * they made it (`delegable` roles only), and is weighed the same way
299
+ * against what they hold now, by their instance role or their grants.
296
300
  */
297
301
  type CredentialScope = readonly (GrantPlace & {
298
302
  role: Role;
299
303
  })[];
300
304
  /** The most places a scope asked for names: far more than any credential needs, and a bound on what a request makes the app check. */
301
305
  declare const MAX_SCOPE_PLACES = 1000;
306
+ /**
307
+ * Whether a personal token's scope may name `role`: one that manages no
308
+ * access. A token acting as its person reaches their secrets and places,
309
+ * never who holds them, so it cannot hand anyone, itself included, more
310
+ * than it was given. A project's Owner who wants a token for all of it
311
+ * names `maintainer`.
312
+ */
313
+ declare function delegable(role: Role): boolean;
314
+ /** The roles a personal token's scope may name, in catalogue order. */
315
+ declare const DELEGABLE_ROLES: readonly Role[];
316
+ /**
317
+ * The longest a personal token lasts: a week. It is for an afternoon's
318
+ * script or agent, and whoever needs it longer makes another, as a person,
319
+ * rather than keep one around.
320
+ */
321
+ declare const MAX_PERSONAL_TOKEN_MINUTES: number;
302
322
  /**
303
323
  * A credential's scope, by path as the API names it, in words, each role
304
324
  * with its places: `developer on market/dev, api/dev; viewer on market/prod`.
@@ -320,7 +340,13 @@ type Holdings = {
320
340
  grants: readonly (GrantPlace & {
321
341
  role: Role;
322
342
  })[];
323
- /** The scope of the credential a request came in on, past which nothing reaches; absent or null for a session's. */
343
+ /**
344
+ * The scope of the credential a request came in on, past which nothing
345
+ * reaches; absent or null for a session's. A scope names places, so a
346
+ * request made with one holds nothing of the instance's own: it does not
347
+ * run the instance, read its whole log or make projects, whatever its
348
+ * holder's instance role.
349
+ */
324
350
  credentialScope?: CredentialScope | null;
325
351
  };
326
352
  /** Someone who holds nothing: a stranger, a removed member, a tampered row. */
@@ -342,6 +368,14 @@ declare function covers(grant: GrantPlace, place: Place): boolean;
342
368
  * `billing`.
343
369
  */
344
370
  declare function allows(holder: Holdings, permission: Permission, place: Place): boolean;
371
+ /**
372
+ * Whether the credential a request came in on, a scoped token or binding,
373
+ * leaves `permission` at `place` to its holder: always, for a session. A
374
+ * personal config's owner holds its every permission by reading its parent
375
+ * (docs/design/forking.md), and a token of theirs is still held to the
376
+ * role its scope names there: a viewer's token reads it, and writes nothing.
377
+ */
378
+ declare function withinCredential(holder: Holdings, permission: Permission, place: Place): boolean;
345
379
  /**
346
380
  * Whether the actor may give or take grants at this place: with
347
381
  * `grant.manage` there, from a project grant or an instance role whose
@@ -373,7 +407,8 @@ type ServiceSetting = Scope;
373
407
  * everything `role` does. A Developer on `market/dev` gives viewer or
374
408
  * developer there, never maintainer; a grant on `market` takes holding it
375
409
  * on all of `market`. `setting` is null for a service account, which sets
376
- * up none.
410
+ * up none; a request on a credential with a scope sets none up either,
411
+ * since what it set up would outlive it.
377
412
  */
378
413
  declare function givesService(holder: Holdings, setting: ServiceSetting | null, role: Role, place: Place): boolean;
379
414
  /** Whether `holder` gives a service account anything anywhere among `places`: whether they set any up. */
@@ -425,4 +460,4 @@ declare function readsWholeLog(holder: Holdings): boolean;
425
460
  */
426
461
  declare function makesProjects(holder: Holdings): boolean;
427
462
  //#endregion
428
- export { instanceRoleGrants as A, ownsInstance as B, allows as C, givesService as D, credentialScopeInWords as E, issuesCredential as F, setsUpServices as G, roleGrants as H, makesProjects as I, ConversionInput as J, unscoped as K, managesService as L, isInstanceRole as M, isRole as N, grantKind as O, isScope as P, convertEveryProjectGrants as Q, mayManageAccess as R, admits as S, covers as T, runsInstance as U, readsWholeLog as V, scopeInWords as W, EveryProjectGrant as X, ConvertedGrant as Y, Narrowed as Z, Role as _, Holdings as a, ServiceSetting as b, InstanceRole as c, PERMISSIONS as d, PROJECT_ONLY_PERMISSIONS as f, ROLE_NAMES as g, ROLES as h, GrantPlace as i, isCredentialScope as j, inScope as k, MAX_SCOPE_PLACES as l, Place as m, EVERYWHERE as n, INSTANCE_ROLES as o, Permission as p, Conversion as q, Filter as r, INSTANCE_ROLE_NAMES as s, CredentialScope as t, NOTHING as u, Scope as v, assignableToEnvironment as w, administers as x, ServiceAccount as y, normalScope as z };
463
+ export { ConvertedGrant as $, givesService as A, managesService as B, administers as C, covers as D, assignableToEnvironment as E, isInstanceRole as F, roleGrants as G, normalScope as H, isRole as I, setsUpServices as J, runsInstance as K, isScope as L, inScope as M, instanceRoleGrants as N, credentialScopeInWords as O, isCredentialScope as P, ConversionInput as Q, issuesCredential as R, ServiceSetting as S, allows as T, ownsInstance as U, mayManageAccess as V, readsWholeLog as W, withinCredential as X, unscoped as Y, Conversion as Z, ROLES as _, GrantPlace as a, Scope as b, INSTANCE_ROLE_NAMES as c, MAX_SCOPE_PLACES as d, EveryProjectGrant as et, NOTHING as f, Place as g, Permission as h, Filter as i, grantKind as j, delegable as k, InstanceRole as l, PROJECT_ONLY_PERMISSIONS as m, DELEGABLE_ROLES as n, convertEveryProjectGrants as nt, Holdings as o, PERMISSIONS as p, scopeInWords as q, EVERYWHERE as r, INSTANCE_ROLES as s, CredentialScope as t, Narrowed as tt, MAX_PERSONAL_TOKEN_MINUTES as u, ROLE_NAMES as v, admits as w, ServiceAccount as x, Role as y, makesProjects as z };
package/dist/access.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as instanceRoleGrants, B as ownsInstance, C as allows, D as givesService, E as credentialScopeInWords, F as issuesCredential, G as setsUpServices, H as roleGrants, I as makesProjects, J as ConversionInput, K as unscoped, L as managesService, M as isInstanceRole, N as isRole, O as grantKind, P as isScope, Q as convertEveryProjectGrants, R as mayManageAccess, S as admits, T as covers, U as runsInstance, V as readsWholeLog, W as scopeInWords, X as EveryProjectGrant, Y as ConvertedGrant, Z as Narrowed, _ as Role, a as Holdings, b as ServiceSetting, c as InstanceRole, d as PERMISSIONS, f as PROJECT_ONLY_PERMISSIONS, g as ROLE_NAMES, h as ROLES, i as GrantPlace, j as isCredentialScope, k as inScope, l as MAX_SCOPE_PLACES, m as Place, n as EVERYWHERE, o as INSTANCE_ROLES, p as Permission, q as Conversion, r as Filter, s as INSTANCE_ROLE_NAMES, t as CredentialScope, u as NOTHING, v as Scope, w as assignableToEnvironment, x as administers, y as ServiceAccount, z as normalScope } from "./access-BpcBLDW9.js";
2
- export { type Conversion, type ConversionInput, type ConvertedGrant, CredentialScope, EVERYWHERE, type EveryProjectGrant, Filter, GrantPlace, Holdings, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, InstanceRole, MAX_SCOPE_PLACES, NOTHING, type Narrowed, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, Permission, Place, ROLES, ROLE_NAMES, Role, Scope, ServiceAccount, ServiceSetting, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped };
1
+ import { $ as ConvertedGrant, A as givesService, B as managesService, C as administers, D as covers, E as assignableToEnvironment, F as isInstanceRole, G as roleGrants, H as normalScope, I as isRole, J as setsUpServices, K as runsInstance, L as isScope, M as inScope, N as instanceRoleGrants, O as credentialScopeInWords, P as isCredentialScope, Q as ConversionInput, R as issuesCredential, S as ServiceSetting, T as allows, U as ownsInstance, V as mayManageAccess, W as readsWholeLog, X as withinCredential, Y as unscoped, Z as Conversion, _ as ROLES, a as GrantPlace, b as Scope, c as INSTANCE_ROLE_NAMES, d as MAX_SCOPE_PLACES, et as EveryProjectGrant, f as NOTHING, g as Place, h as Permission, i as Filter, j as grantKind, k as delegable, l as InstanceRole, m as PROJECT_ONLY_PERMISSIONS, n as DELEGABLE_ROLES, nt as convertEveryProjectGrants, o as Holdings, p as PERMISSIONS, q as scopeInWords, r as EVERYWHERE, s as INSTANCE_ROLES, t as CredentialScope, tt as Narrowed, u as MAX_PERSONAL_TOKEN_MINUTES, v as ROLE_NAMES, w as admits, x as ServiceAccount, y as Role, z as makesProjects } from "./access-CONhKG2P.js";
2
+ export { type Conversion, type ConversionInput, type ConvertedGrant, CredentialScope, DELEGABLE_ROLES, EVERYWHERE, type EveryProjectGrant, Filter, GrantPlace, Holdings, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, InstanceRole, MAX_PERSONAL_TOKEN_MINUTES, MAX_SCOPE_PLACES, NOTHING, type Narrowed, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, Permission, Place, ROLES, ROLE_NAMES, Role, Scope, ServiceAccount, ServiceSetting, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, delegable, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped, withinCredential };
package/dist/access.js CHANGED
@@ -1,2 +1,2 @@
1
- import { A as readsWholeLog, C as isScope, D as mayManageAccess, E as managesService, F as unscoped, I as convertEveryProjectGrants, M as runsInstance, N as scopeInWords, O as normalScope, P as setsUpServices, S as isRole, T as makesProjects, _ as grantKind, a as NOTHING, b as isCredentialScope, c as ROLES, d as admits, f as allows, g as givesService, h as credentialScopeInWords, i as MAX_SCOPE_PLACES, j as roleGrants, k as ownsInstance, l as ROLE_NAMES, m as covers, n as INSTANCE_ROLES, o as PERMISSIONS, p as assignableToEnvironment, r as INSTANCE_ROLE_NAMES, s as PROJECT_ONLY_PERMISSIONS, t as EVERYWHERE, u as administers, v as inScope, w as issuesCredential, x as isInstanceRole, y as instanceRoleGrants } from "./access-DvwZzqRR.js";
2
- export { EVERYWHERE, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, MAX_SCOPE_PLACES, NOTHING, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped };
1
+ import { A as mayManageAccess, B as convertEveryProjectGrants, C as isCredentialScope, D as issuesCredential, E as isScope, F as runsInstance, I as scopeInWords, L as setsUpServices, M as ownsInstance, N as readsWholeLog, O as makesProjects, P as roleGrants, R as unscoped, S as instanceRoleGrants, T as isRole, _ as credentialScopeInWords, a as MAX_PERSONAL_TOKEN_MINUTES, b as grantKind, c as PERMISSIONS, d as ROLE_NAMES, f as administers, g as covers, h as assignableToEnvironment, i as INSTANCE_ROLE_NAMES, j as normalScope, k as managesService, l as PROJECT_ONLY_PERMISSIONS, m as allows, n as EVERYWHERE, o as MAX_SCOPE_PLACES, p as admits, r as INSTANCE_ROLES, s as NOTHING, t as DELEGABLE_ROLES, u as ROLES, v as delegable, w as isInstanceRole, x as inScope, y as givesService, z as withinCredential } from "./access-B1wpMzEB.js";
2
+ export { DELEGABLE_ROLES, EVERYWHERE, INSTANCE_ROLES, INSTANCE_ROLE_NAMES, MAX_PERSONAL_TOKEN_MINUTES, MAX_SCOPE_PLACES, NOTHING, PERMISSIONS, PROJECT_ONLY_PERMISSIONS, ROLES, ROLE_NAMES, administers, admits, allows, assignableToEnvironment, convertEveryProjectGrants, covers, credentialScopeInWords, delegable, givesService, grantKind, inScope, instanceRoleGrants, isCredentialScope, isInstanceRole, isRole, isScope, issuesCredential, makesProjects, managesService, mayManageAccess, normalScope, ownsInstance, readsWholeLog, roleGrants, runsInstance, scopeInWords, setsUpServices, unscoped, withinCredential };
@@ -1,4 +1,4 @@
1
- import { d as checkContext, f as encodeAad, l as AAD_VERSION, t as KekRegistry, u as SecretContext } from "./registry-C0NGpxcc.js";
1
+ import { d as checkContext, f as encodeAad, l as AAD_VERSION, p as ownerKey, t as KekRegistry, u as SecretContext } from "./registry-Bd7hPoY4.js";
2
2
  //#region src/envelope.d.ts
3
3
  /**
4
4
  * Current envelope format version. Stored on every row so the format can change
@@ -47,4 +47,4 @@ export declare function seal(plaintext: Buffer, ctx: SecretContext, keks: KekReg
47
47
  /** Unwrap the envelope's DEK under the registry and decrypt: `decrypt` and the unwrap in one. */
48
48
  export declare function open(envelope: Envelope, ctx: SecretContext, keks: KekRegistry): Promise<Buffer>;
49
49
  //#endregion
50
- export { AAD_VERSION, SecretContext, checkContext, encodeAad };
50
+ export { AAD_VERSION, SecretContext, checkContext, encodeAad, ownerKey };
package/dist/envelope.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as AAD_VERSION, o as checkContext, s as encodeAad } from "./types-Mi7-Tzj9.js";
1
+ import { a as AAD_VERSION, c as ownerKey, o as checkContext, s as encodeAad } from "./types-E1Sz50n6.js";
2
2
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
3
3
  //#region src/envelope.ts
4
4
  const IV_BYTES = 12;
@@ -80,4 +80,4 @@ async function open(envelope, ctx, keks) {
80
80
  }
81
81
  }
82
82
  //#endregion
83
- export { AAD_VERSION, ENVELOPE_VERSION, checkContext, decrypt, encodeAad, encrypt, freshDek, open, seal };
83
+ export { AAD_VERSION, ENVELOPE_VERSION, checkContext, decrypt, encodeAad, encrypt, freshDek, open, ownerKey, seal };
@@ -1,4 +1,4 @@
1
- import { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
1
+ import { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
2
2
  import { JSONWebKeySet, JWTPayload } from "jose";
3
3
  //#region src/identity/github-app.d.ts
4
4
  /**
@@ -604,8 +604,9 @@ export declare function unseal<T>(key: Uint8Array, sealed: string | undefined |
604
604
  //#endregion
605
605
  //#region src/identity/tokens.d.ts
606
606
  /**
607
- * Bearer credentials coffre issues itself: browser sessions, CLI sessions and
608
- * service tokens.
607
+ * Bearer credentials coffre issues itself: browser sessions, CLI sessions,
608
+ * service tokens, and personal tokens, which act as the person who made
609
+ * them within the scope they named.
609
610
  *
610
611
  * A token is a kind prefix and 32 random bytes, e.g.
611
612
  * `coffre_cli_Qm9Y...`. The prefix tells a person (or a secret scanner) what a
@@ -614,7 +615,7 @@ export declare function unseal<T>(key: Uint8Array, sealed: string | undefined |
614
615
  * right tool here, unlike for passwords: 256 bits of randomness leave nothing
615
616
  * to brute-force.
616
617
  */
617
- export type CredentialKind = 'browser' | 'cli' | 'service';
618
+ export type CredentialKind = 'browser' | 'cli' | 'service' | 'personal';
618
619
  export declare function generateToken(kind: CredentialKind): string;
619
620
  /** Whether a string has the shape of a coffre token, before any lookup. */
620
621
  export declare function isCoffreToken(value: string): boolean;
@@ -683,6 +684,8 @@ export type WorkloadRefusal =
683
684
  'expired' |
684
685
  /** Its `iat` is more than an hour ago, or not yet. */
685
686
  'too_old' |
687
+ /** It lives longer than an hour, `exp` − `iat`, where its binding carries its keys. */
688
+ 'too_long' |
686
689
  /** Its `nbf` is still to come. */
687
690
  'not_yet_valid' |
688
691
  /** Its audience is not this instance alone. */
@@ -715,12 +718,14 @@ export declare function decodeWorkloadToken(token: string): DecodedToken;
715
718
  * algorithms fixed and nothing taken from the header but `alg` and `kid`;
716
719
  * the issuer exactly; `exp`, `nbf` and `iat` within the tolerance, `iat` at
717
720
  * most an hour old; and the audience, this instance's public URL alone.
718
- * Returns the verified claims.
721
+ * With `maxLifetime`, in seconds, a token whose `exp` is further than that
722
+ * from its `iat` is refused too. Returns the verified claims.
719
723
  */
720
724
  export declare function verifyWorkloadToken(token: string, keys: JSONWebKeySet, expected: {
721
725
  issuer: string;
722
726
  audience: string;
723
727
  now: Date;
728
+ maxLifetime?: number;
724
729
  }): Promise<JWTPayload & DecodedToken['claims']>;
725
730
  /**
726
731
  * Why a token issued at `iat` may not be exchanged at `now`, both in
@@ -729,5 +734,42 @@ export declare function verifyWorkloadToken(token: string, keys: JSONWebKeySet,
729
734
  * both ask it.
730
735
  */
731
736
  export declare function issuedRefusal(iat: number, now: number): WorkloadTokenRefused | null;
737
+ /** At most this many keys in a set, fetched or uploaded. */
738
+ export declare const MAX_KEYS = 32;
739
+ /** One key of an uploaded set, as an owner sees it: its `kid`, its algorithm, and its RFC 7638 thumbprint. */
740
+ export type KeySummary = {
741
+ kid: string | null;
742
+ alg: (typeof WORKLOAD_ALGORITHMS)[number];
743
+ thumbprint: string;
744
+ };
745
+ /**
746
+ * A key set uploaded with a binding, as the binding keeps it: its public
747
+ * keys alone, as canonical JSON, and that JSON's SHA-256, in hex, which
748
+ * stands in the binding where a URL would and so is under its MAC.
749
+ */
750
+ export type UploadedKeys = {
751
+ jwks: string;
752
+ digest: string;
753
+ keys: KeySummary[];
754
+ };
755
+ /** A key set that cannot be uploaded, said so an owner can fix it. */
756
+ export declare class KeysInvalid extends Error {
757
+ constructor(message: string);
758
+ }
759
+ /**
760
+ * An issuer's public keys, as uploaded for a binding: a JWKS, `{ "keys": [
761
+ * … ] }`, of RSA keys of 2048 bits or more, for RS256, and P-256 keys, for
762
+ * ES256, each kept with only the members that verify (`kty`, `kid`, `use`,
763
+ * `alg` and the key), in the order of their thumbprints, so that one set
764
+ * however listed has one digest. An RSA key's parameters are checked
765
+ * before jose sees them, since it takes some that verify anything: an
766
+ * exponent of 1 makes every signature its message. Keys for encryption are
767
+ * left out, as from a fetched set; a private or a symmetric key refuses the
768
+ * whole set, since coffre keeps no secret of an issuer's. Throws
769
+ * `KeysInvalid`.
770
+ */
771
+ export declare function uploadedKeys(value: unknown): Promise<UploadedKeys>;
772
+ /** The keys of a set `uploadedKeys` kept, as an owner sees them. */
773
+ export declare function keySummaries(jwks: string): KeySummary[];
732
774
  //#endregion
733
- export { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, type JSONWebKeySet, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef };
775
+ export { BindingClaims, BindingInvalid, BindingPolicy, ClaimValue, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, type JSONWebKeySet, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, Starts, WORKLOAD_PROFILES, WorkloadProfile, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef };
package/dist/identity.js CHANGED
@@ -1,7 +1,7 @@
1
- import { BindingInvalid, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, REQUIRED_CLAIMS, WORKLOAD_PROFILES, canonicalClaims, checkBinding, checkFetchUrl, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
1
+ import { BindingInvalid, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, REQUIRED_CLAIMS, WORKLOAD_PROFILES, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, defaultIssuer, differingClaims, fullRef, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, workflowRef } from "./workloads.js";
2
2
  import { createCipheriv, createDecipheriv, createHash, hkdfSync, randomBytes } from "node:crypto";
3
3
  import * as oauth from "oauth4webapi";
4
- import { createLocalJWKSet, createRemoteJWKSet, errors, jwtVerify } from "jose";
4
+ import { createLocalJWKSet, createRemoteJWKSet, errors, importJWK, jwtVerify } from "jose";
5
5
  //#region src/identity/github-app.ts
6
6
  /** GitHub could not be asked, or refused the app: the message says which, for a person, and carries no credential. */
7
7
  var GitHubAppUnavailable = class extends Error {
@@ -996,9 +996,10 @@ function unseal(key, sealed, now = Date.now()) {
996
996
  const PREFIX = {
997
997
  browser: "coffre_web_",
998
998
  cli: "coffre_cli_",
999
- service: "coffre_svc_"
999
+ service: "coffre_svc_",
1000
+ personal: "coffre_pat_"
1000
1001
  };
1001
- const TOKEN = /^coffre_(web|cli|svc)_[A-Za-z0-9_-]{43}$/;
1002
+ const TOKEN = /^coffre_(web|cli|svc|pat)_[A-Za-z0-9_-]{43}$/;
1002
1003
  function generateToken(kind) {
1003
1004
  return PREFIX[kind] + randomBytes(32).toString("base64url");
1004
1005
  }
@@ -1154,28 +1155,32 @@ function decodeWorkloadToken(token) {
1154
1155
  * algorithms fixed and nothing taken from the header but `alg` and `kid`;
1155
1156
  * the issuer exactly; `exp`, `nbf` and `iat` within the tolerance, `iat` at
1156
1157
  * most an hour old; and the audience, this instance's public URL alone.
1157
- * Returns the verified claims.
1158
+ * With `maxLifetime`, in seconds, a token whose `exp` is further than that
1159
+ * from its `iat` is refused too. Returns the verified claims.
1158
1160
  */
1159
1161
  async function verifyWorkloadToken(token, keys, expected) {
1162
+ const options = {
1163
+ algorithms: [...WORKLOAD_ALGORITHMS],
1164
+ issuer: expected.issuer,
1165
+ clockTolerance: 30,
1166
+ requiredClaims: [
1167
+ "iss",
1168
+ "sub",
1169
+ "exp",
1170
+ "iat"
1171
+ ],
1172
+ currentDate: expected.now
1173
+ };
1160
1174
  let payload;
1161
1175
  try {
1162
- ({payload} = await jwtVerify(token, createLocalJWKSet(keys), {
1163
- algorithms: [...WORKLOAD_ALGORITHMS],
1164
- issuer: expected.issuer,
1165
- clockTolerance: 30,
1166
- requiredClaims: [
1167
- "iss",
1168
- "sub",
1169
- "exp",
1170
- "iat"
1171
- ],
1172
- currentDate: expected.now
1173
- }));
1176
+ ({payload} = await jwtVerify(token, createLocalJWKSet(keys), options));
1174
1177
  } catch (error) {
1175
- throw refusalOf(error);
1178
+ if (!(error instanceof errors.JWKSMultipleMatchingKeys)) throw refusalOf(error);
1179
+ payload = await anyKey(token, error, options);
1176
1180
  }
1177
1181
  const issued = issuedRefusal(payload.iat, expected.now.getTime() / 1e3);
1178
1182
  if (issued !== null) throw issued;
1183
+ if (expected.maxLifetime !== void 0 && payload.exp - payload.iat > expected.maxLifetime) throw new WorkloadTokenRefused("too_long", `the token lives longer than ${expected.maxLifetime / 60} minutes, from its iat to its exp: ask for a shorter one`);
1179
1184
  const { aud } = payload;
1180
1185
  if (!(aud === expected.audience || Array.isArray(aud) && aud.length === 1 && aud[0] === expected.audience)) throw new WorkloadTokenRefused("audience", `the token is for ${JSON.stringify(aud ?? null)}, not ${expected.audience} alone`);
1181
1186
  return payload;
@@ -1191,6 +1196,19 @@ function issuedRefusal(iat, now) {
1191
1196
  if (iat - now > 30) return new WorkloadTokenRefused("too_old", "the token says it was issued in the future");
1192
1197
  return null;
1193
1198
  }
1199
+ /**
1200
+ * A token that names no key, or one several keys share, verified by
1201
+ * whichever of them signed it: a set may hold several, as while its issuer
1202
+ * rotates. At most `MAX_KEYS` tries, each a signature check alone.
1203
+ */
1204
+ async function anyKey(token, matching, options) {
1205
+ for await (const key of matching) try {
1206
+ return (await jwtVerify(token, key, options)).payload;
1207
+ } catch (error) {
1208
+ if (!(error instanceof errors.JWSSignatureVerificationFailed)) throw refusalOf(error);
1209
+ }
1210
+ throw new WorkloadTokenRefused("signature", "the token's signature does not verify");
1211
+ }
1194
1212
  function refusalOf(error) {
1195
1213
  if (error instanceof errors.JWKSNoMatchingKey) return new WorkloadTokenRefused("unknown_key", "the token's key is not among the issuer's");
1196
1214
  if (error instanceof errors.JWTExpired) return error.claim === "iat" ? new WorkloadTokenRefused("too_old", `the token was issued more than ${MAX_TOKEN_AGE_SECONDS / 60} minutes ago`) : new WorkloadTokenRefused("expired", "the token has expired");
@@ -1205,5 +1223,137 @@ function refusalOf(error) {
1205
1223
  if (error instanceof errors.JOSEError) return new WorkloadTokenRefused("signature", `the token does not verify: ${error.code}`);
1206
1224
  return new WorkloadTokenRefused("signature", "the token does not verify");
1207
1225
  }
1226
+ /** At most this many keys in a set, fetched or uploaded. */
1227
+ const MAX_KEYS = 32;
1228
+ /** A key set that cannot be uploaded, said so an owner can fix it. */
1229
+ var KeysInvalid = class extends Error {
1230
+ constructor(message) {
1231
+ super(message);
1232
+ this.name = "KeysInvalid";
1233
+ }
1234
+ };
1235
+ /** Members that only a private or a symmetric key has. */
1236
+ const PRIVATE_MEMBERS = [
1237
+ "d",
1238
+ "p",
1239
+ "q",
1240
+ "dp",
1241
+ "dq",
1242
+ "qi",
1243
+ "oth",
1244
+ "k"
1245
+ ];
1246
+ /**
1247
+ * An issuer's public keys, as uploaded for a binding: a JWKS, `{ "keys": [
1248
+ * … ] }`, of RSA keys of 2048 bits or more, for RS256, and P-256 keys, for
1249
+ * ES256, each kept with only the members that verify (`kty`, `kid`, `use`,
1250
+ * `alg` and the key), in the order of their thumbprints, so that one set
1251
+ * however listed has one digest. An RSA key's parameters are checked
1252
+ * before jose sees them, since it takes some that verify anything: an
1253
+ * exponent of 1 makes every signature its message. Keys for encryption are
1254
+ * left out, as from a fetched set; a private or a symmetric key refuses the
1255
+ * whole set, since coffre keeps no secret of an issuer's. Throws
1256
+ * `KeysInvalid`.
1257
+ */
1258
+ async function uploadedKeys(value) {
1259
+ const listed = value?.keys;
1260
+ if (typeof value !== "object" || value === null || !Array.isArray(listed)) throw new KeysInvalid("the keys are a JWKS: { \"keys\": [ … ] }");
1261
+ if (listed.length > 32) throw new KeysInvalid(`a key set holds at most 32 keys`);
1262
+ const keys = [];
1263
+ for (const [i, given] of listed.entries()) {
1264
+ if (typeof given !== "object" || given === null || Array.isArray(given)) throw new KeysInvalid(`key ${i + 1} is not a JWK`);
1265
+ const jwk = given;
1266
+ const named = typeof jwk.kid === "string" ? `key ${jwk.kid}` : `key ${i + 1}`;
1267
+ if (PRIVATE_MEMBERS.some((member) => member in jwk)) throw new KeysInvalid(`${named} is a private or a secret key: upload the issuer's public keys only`);
1268
+ if (jwk.use === "enc") continue;
1269
+ if (jwk.use !== void 0 && jwk.use !== "sig") throw new KeysInvalid(`${named} has use ${JSON.stringify(jwk.use)}: sig, or none`);
1270
+ if (jwk.kid !== void 0 && (typeof jwk.kid !== "string" || jwk.kid === "" || jwk.kid.length > 256)) throw new KeysInvalid(`key ${i + 1}'s kid is not a string`);
1271
+ if (jwk.kid !== void 0 && keys.some((key) => key.kid === jwk.kid)) throw new KeysInvalid(`two keys are ${named}`);
1272
+ let alg;
1273
+ let key;
1274
+ if (jwk.kty === "RSA" && (jwk.alg === void 0 || jwk.alg === "RS256")) {
1275
+ alg = "RS256";
1276
+ const [n, e] = [integer(jwk.n), integer(jwk.e)];
1277
+ if (n === null || e === null) throw new KeysInvalid(`${named} is an RSA key without n and e, each a base64url integer in its fewest bytes`);
1278
+ if (n.toString(2).length < 2048) throw new KeysInvalid(`${named} is an RSA key under 2048 bits`);
1279
+ if (n % 2n === 0n) throw new KeysInvalid(`${named} is not a valid RSA key`);
1280
+ if (e % 2n === 0n || e <= 65536n || e >= 2n ** 256n) throw new KeysInvalid(`${named}'s exponent is not one an RSA key signs with: odd, from 65537`);
1281
+ key = {
1282
+ kty: "RSA",
1283
+ ...kidAndUse(jwk),
1284
+ alg,
1285
+ n: jwk.n,
1286
+ e: jwk.e
1287
+ };
1288
+ } else if (jwk.kty === "EC" && jwk.crv === "P-256" && (jwk.alg === void 0 || jwk.alg === "ES256")) {
1289
+ alg = "ES256";
1290
+ if (!coordinate(jwk.x) || !coordinate(jwk.y)) throw new KeysInvalid(`${named} is an EC key without x and y, each 32 bytes in base64url`);
1291
+ key = {
1292
+ kty: "EC",
1293
+ ...kidAndUse(jwk),
1294
+ alg,
1295
+ crv: "P-256",
1296
+ x: jwk.x,
1297
+ y: jwk.y
1298
+ };
1299
+ } else throw new KeysInvalid(`${named} is not an RSA key for RS256 or a P-256 key for ES256, the two coffre accepts`);
1300
+ try {
1301
+ await importJWK(key, alg);
1302
+ } catch {
1303
+ throw new KeysInvalid(`${named} is not a valid ${key.kty} key`);
1304
+ }
1305
+ keys.push(key);
1306
+ }
1307
+ if (keys.length === 0) throw new KeysInvalid("the key set holds no key that signs");
1308
+ const sorted = keys.map((key) => [`${thumbprint(key)} ${JSON.stringify(key)}`, key]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1309
+ const jwks = JSON.stringify({ keys: sorted.map(([, key]) => key) });
1310
+ return {
1311
+ jwks,
1312
+ digest: createHash("sha256").update(jwks, "utf8").digest("hex"),
1313
+ keys: keySummaries(jwks)
1314
+ };
1315
+ }
1316
+ /** The keys of a set `uploadedKeys` kept, as an owner sees them. */
1317
+ function keySummaries(jwks) {
1318
+ return JSON.parse(jwks).keys.map((key) => ({
1319
+ kid: key.kid ?? null,
1320
+ alg: key.kty === "RSA" ? "RS256" : "ES256",
1321
+ thumbprint: thumbprint(key)
1322
+ }));
1323
+ }
1324
+ /** A key's RFC 7638 thumbprint: the SHA-256 of its required members alone, in lexicographic order. */
1325
+ function thumbprint(key) {
1326
+ const required = key.kty === "RSA" ? {
1327
+ e: key.e,
1328
+ kty: key.kty,
1329
+ n: key.n
1330
+ } : {
1331
+ crv: key.crv,
1332
+ kty: key.kty,
1333
+ x: key.x,
1334
+ y: key.y
1335
+ };
1336
+ return createHash("sha256").update(JSON.stringify(required), "utf8").digest("base64url");
1337
+ }
1338
+ const BASE64URL = /^[A-Za-z0-9_-]+$/;
1339
+ /** A P-256 coordinate as JWK writes one: 32 bytes, in base64url exactly, so that one key has one spelling. */
1340
+ function coordinate(value) {
1341
+ if (typeof value !== "string" || !BASE64URL.test(value)) return false;
1342
+ const bytes = Buffer.from(value, "base64url");
1343
+ return bytes.length === 32 && bytes.toString("base64url") === value;
1344
+ }
1345
+ /** An unsigned integer as JWK writes one, base64url in its fewest bytes; null for anything else. */
1346
+ function integer(value) {
1347
+ if (typeof value !== "string" || !BASE64URL.test(value)) return null;
1348
+ const bytes = Buffer.from(value, "base64url");
1349
+ if (bytes.length === 0 || bytes[0] === 0 || bytes.toString("base64url") !== value) return null;
1350
+ return BigInt(`0x${bytes.toString("hex")}`);
1351
+ }
1352
+ function kidAndUse(jwk) {
1353
+ return {
1354
+ ...jwk.kid === void 0 ? {} : { kid: jwk.kid },
1355
+ ...jwk.use === void 0 ? {} : { use: "sig" }
1356
+ };
1357
+ }
1208
1358
  //#endregion
1209
- export { ACCESS_JWT_HEADER, AccessIdentityVerifier, BindingInvalid, CLOCK_TOLERANCE_SECONDS, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, GitHubApp, GitHubAppUnavailable, GitHubSigninProvider, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_LISTING_REQUESTS, MAX_TOKEN_AGE_SECONDS, MAX_TOKEN_BYTES, OidcSigninProvider, REQUIRED_CLAIMS, SigninError, TOKEN_MARGIN_MS, WORKLOAD_ALGORITHMS, WORKLOAD_PROFILES, WorkloadTokenRefused, canonicalClaims, checkBinding, checkFetchUrl, claimValues, cloudflareAccess, decodeWorkloadToken, defaultIssuer, defineSignin, deriveKey, differingClaims, fullRef, generateToken, github, githubApp, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, google, hashToken, isCoffreToken, issuedRefusal, microsoft, oidc, pkcs8Of, publicOrigin, seal, signin, toPrincipal, tokenHint, tokenKind, unseal, verifyWorkloadToken, workflowRef };
1359
+ export { ACCESS_JWT_HEADER, AccessIdentityVerifier, BindingInvalid, CLOCK_TOLERANCE_SECONDS, EVENT_EXPOSURE, GITHUB_EVENTS, GITHUB_ISSUER, GITLAB_ISSUER, GITLAB_PIPELINE_SOURCES, GitHubApp, GitHubAppUnavailable, GitHubSigninProvider, KeysInvalid, MAX_BINDINGS, MAX_CLAIMS, MAX_CLAIM_BYTES, MAX_ISSUER_BYTES, MAX_KEYS, MAX_LISTING_REQUESTS, MAX_TOKEN_AGE_SECONDS, MAX_TOKEN_BYTES, OidcSigninProvider, REQUIRED_CLAIMS, SigninError, TOKEN_MARGIN_MS, WORKLOAD_ALGORITHMS, WORKLOAD_PROFILES, WorkloadTokenRefused, canonicalClaims, checkBinding, checkFetchUrl, claimAt, claimValues, cloudflareAccess, decodeWorkloadToken, defaultIssuer, defineSignin, deriveKey, differingClaims, fullRef, generateToken, github, githubApp, githubEventsAt, githubReusable, githubWorkflow, gitlabProject, google, hashToken, isCoffreToken, issuedRefusal, keySummaries, microsoft, oidc, pkcs8Of, publicOrigin, seal, signin, toPrincipal, tokenHint, tokenKind, unseal, uploadedKeys, verifyWorkloadToken, workflowRef };
package/dist/kek.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as KekProvider, c as WrappedDek, i as KekCancelledError, n as DEK_BYTES, o as KekUnavailableError, r as KekBadClaimError, s as KeyOperation, t as KekRegistry, u as SecretContext } from "./registry-C0NGpxcc.js";
1
+ import { a as KekProvider, c as WrappedDek, i as KekCancelledError, n as DEK_BYTES, o as KekUnavailableError, r as KekBadClaimError, s as KeyOperation, t as KekRegistry, u as SecretContext } from "./registry-Bd7hPoY4.js";
2
2
  //#region src/kek/local.d.ts
3
3
  /**
4
4
  * A KEK held in local process memory, loaded from configuration.
package/dist/kek.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { deriveLogKey } from "./audit.js";
2
- import { i as KekUnavailableError, n as KekBadClaimError, o as checkContext, r as KekCancelledError, s as encodeAad, t as DEK_BYTES } from "./types-Mi7-Tzj9.js";
2
+ import { i as KekUnavailableError, n as KekBadClaimError, o as checkContext, r as KekCancelledError, s as encodeAad, t as DEK_BYTES } from "./types-E1Sz50n6.js";
3
3
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
4
4
  //#region src/kek/cancellation.ts
5
5
  function checkOperation(operation, uncertain = false) {
@@ -425,7 +425,8 @@ function encryptionContext(ctx) {
425
425
  return {
426
426
  "coffre:project": ctx.projectId,
427
427
  "coffre:environment": ctx.environmentId,
428
- "coffre:secret": ctx.secretId
428
+ "coffre:secret": ctx.secretId,
429
+ ...ctx.owner === void 0 ? {} : { "coffre:owner": ctx.owner }
429
430
  };
430
431
  }
431
432
  /**