Break Glass

Column-policy / row-enforcement step-up auth — PHI / PCI columns require a fresh second-factor grant + operator-supplied reason; every unseal is audited row-by-row.

The operator declares which columns of which tables are GLASS-LOCKED. Reading a glass-locked column on any row requires the caller to (1) prove identity with a fresh second factor (TOTP or passkey), (2) supply a reason the audit chain captures, and (3) hold a short-lived scope-bounded grant. Each row read emits a per-row audit event; the default maxRowsPerGrant: 1 enforces row-by-row auth so every PHI / PCI access is its own discrete authenticated event.

Two crypto models ship side-by-side. Model A — the default — is a policy gate: glass-locked columns sit in the regular cryptoField sealed-row pipeline, and break-glass enforces the grant + audit contract on every read path. Model B (cryptographic: true on the policy) layers per-cell encryption on top: every (table, rowId, column) triple gets its own key derived K_cell = SHAKE256(DEK || table || rowId || column), AEAD-bound to AAD = SHA3-512(table || rowId || column) so swapping ciphertexts between rows fails closed. Operators opt into Model B per-policy, then run b.breakGlass.migrate(table) to convert existing rows.

Service-account bypass (policy.serviceAccountBypass) is opt-in per-table — both an apiKey-id allowlist and a required role must match. Admin tools (listActiveAll, revokeAll) cover security- team dashboards and incident-response offboarding.

b.breakGlass.encryptCell(plaintext, ctx) #

stable0.5.1

Encrypt a single glass-locked cell value with encryption-context binding. Operators running a policy in cryptographic: true mode call this at write time INSTEAD of letting cryptoField.sealRow seal the column. The framework derives a per-cell key from the policy's vault-sealed DEK plus (table, rowId, column), encrypts with XChaCha20-Poly1305, and sets AAD to SHA3-512(table || rowId || column) so a ciphertext literally cannot be decrypted under a different row identifier even with the same DEK.

Returns a string of the form bgcell:1: ready to write back to the column. Throws breakglass/policy-not-set when the table has no policy or the policy isn't in cryptographic mode, and breakglass/grant-column-mismatch when the column isn't glass- locked on the policy.

await b.breakGlass.policy.set("patients", {
  columns:       ["ssn"],
  factors:       ["totp"],
  cryptographic: true,
});
var sealed = await b.breakGlass.encryptCell("123-45-6789", {
  table:  "patients",
  rowId:  "patient-001",
  column: "ssn",
});
// → "bgcell:1:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="

b.breakGlass.decryptCell(ciphertext, ctx) #

stable0.5.1

Decrypt a Model-B bgcell:1: cell value. Internal — the caller must already hold a valid grant covering the (table, column); operator-facing reads route through b.breakGlass.unsealRow which gates this call. The encryption context (table, rowId, column) is fed into BOTH the per-cell key derivation AND the AEAD AAD, so a caller passing the wrong rowId trying to swap ciphertexts between rows fails closed at the AEAD verify step.

// unsealRow routes here automatically for cryptographic-mode
// policies; calling decryptCell directly is rare and only for
// operator tooling that's already enforced its own grant gate.
var plaintext = await b.breakGlass.decryptCell(sealed, {
  table:  "patients",
  rowId:  "patient-001",
  column: "ssn",
});
// → "123-45-6789"

b.breakGlass.migrate(table, opts) #

stable0.5.1
{
  batchSize:   number,   // _id-keyset page size (default 100)
  callerOpts:  object,   // forwarded to audit actor resolution
}

One-shot migration that converts every existing row of a glass- locked table from Model A (cryptoField.sealRow only) into Model B per-cell ciphertext. Iterates via _id-keyset paging so memory stays bounded; rows already in Model B (column starts with bgcell:) are skipped, making the migration idempotent and safe to re-run after a partial failure.

Emits a breakglass.migrate audit event on completion with totals and skipped counts. Refuses to run when the policy isn't in cryptographic mode — operator must policy.set({ cryptographic: true }) first.

await b.breakGlass.policy.set("patients", {
  columns:       ["ssn", "dob"],
  factors:       ["totp"],
  cryptographic: true,
});
var summary = await b.breakGlass.migrate("patients", { batchSize: 250 });
// → { table: "patients", totalRows: 1200, migratedRows: 1198, skippedRows: 2 }

b.breakGlass.init(opts) #

stable0.5.0
{
  trustedProxies:   string|string[],          // CIDRs of your reverse proxies — peer-gates X-Forwarded-For
  clientIpResolver: function(req): string|null,  // own grant-IP resolution
}

One-shot boot wiring. Clears the in-memory policy cache, resets the factor-lockout counter, and records how the grant row's ip field is resolved. That IP is a security binding — the grant pins to it at mint and re-checks it on redeem — so resolution is peer-gated: declare your reverse proxies via trustedProxies (CIDRs; X-Forwarded-For honored only from a trusted peer) or own resolution via clientIpResolver. A bare trustProxy is refused — a forgeable pin is no pin. Operators call this once at boot, before any policy / grant / unseal call — every other primitive throws breakglass/not-initialized until init has run.

b.breakGlass.init({ trustedProxies: ["10.0.0.0/8"] });
// → undefined  (init returns nothing; throws on bad opts)

b.breakGlass.policy.set(table, opts, callerOpts) #

stable0.5.0hipaapci-dssgdprsoc2
{
  columns:              Array,        // glass-locked column names (required, ≥1)
  factors:              Array,        // allowed second factors: "totp" / "passkey"
  cryptographic:        boolean,              // opt into Model B per-cell encryption (default false)
  grantTtl:             number,               // grant lifetime in ms (default 15 minutes)
  maxRowsPerGrant:      number,               // rows a grant may unseal (default 1 — row-by-row)
  reasonRequired:       boolean,              // require operator reason on grant (default true)
  reasonMinLength:      number,               // minimum reason length in chars (default 12)
  pinIp:                boolean,              // bind grant to issuing IP (default true)
  sessionPin:           boolean,              // bind grant to issuing session (default true)
  onLockedAccess:       string,               // "throw" | "redact" on unauthorized read (default "throw")
  requireScope:         string,               // actor scope required before grant mints (e.g. "phi:admin")
  serviceAccountBypass: object,               // { enabled, apiKeyIds, requireRole } — opt-in machine bypass
  auditReasonStorage:   string,               // "cleartext" | "hmac" | "both" (default "cleartext")
}

Declare the column-policy that gates step-up auth on the named table. The listed columns become GLASS-LOCKED — every read of one of those columns on any row requires the caller to hold a fresh second-factor grant whose scope covers the column. Stored cluster-wide in _blamejs_break_glass_policies (sealed via cryptoField) so every node honors the same gate. Re-runs UPSERT; the policy cache flushes for the table.

await b.breakGlass.policy.set("patients", {
  columns:         ["ssn", "dob"],
  factors:         ["totp"],
  grantTtl:        600000,
  maxRowsPerGrant: 1,
  requireScope:    "phi:admin",
});
// → { applied: true, table: "patients" }

b.breakGlass.policy.get(table) #

stable0.5.0

Read the current break-glass policy for table from the cluster- shared policies table, with an in-process cache that short-circuits the DB roundtrip on the unsealRow hot path. Returns null when the table has no policy declared (a non-glass-locked table). The cache invalidates on policy.set / policy.delete.

var policy = await b.breakGlass.policy.get("patients");
// → { table: "patients", columns: ["ssn", "dob"], factors: ["totp"], ... }

var none = await b.breakGlass.policy.get("posts");
// → null

b.breakGlass.policy.list() #

stable0.5.0

Enumerate every glass-locked table the cluster knows about. Used by compliance dashboards (which tables hold PHI / PCI?) and migration tooling that needs to walk the full set. Returns hydrated policy objects in tableName order — no abbreviated row form.

var policies = await b.breakGlass.policy.list();
// → [{ table: "patients", columns: ["ssn", "dob"], ... }, { table: "cards", ... }]
policies.length;
// → 2

b.breakGlass.policy.delete(table, callerOpts) #

stable0.5.0

Remove the break-glass policy for table. Subsequent reads of the previously glass-locked columns no longer require a grant — operators call this only when a column genuinely stops being PHI / PCI (rare; almost always the operator wants policy.set with a revised column list instead). Emits a breakglass.policy.delete audit event.

await b.breakGlass.policy.delete("legacy_patients");
// → { deleted: true, table: "legacy_patients" }

b.breakGlass.grant(opts) #

stable0.5.0hipaapci-dsssoc2
{
  req:      object,        // the active request (carries actor identity, ip, session)
  table:    string,        // glass-locked table the grant scopes to
  columns:  Array, // optional subset of policy.columns (default = full policy)
  reason:   string,        // operator-supplied reason (length-gated by policy.reasonMinLength)
  factor:   object,        // { type: "totp", secret, code } or { type: "passkey", response, ... }
}

Mint a short-lived, scope-bounded break-glass grant. The framework verifies the operator's second factor (TOTP code or passkey assertion), records the operator-supplied reason into the audit chain, and issues a grant whose scope covers the named columns of the named table for policy.grantTtl ms or policy.maxRowsPerGrant row reads — whichever ends first. Failures emit a denied-grant audit row; repeated factor failures trigger the lockout primitive.

var handle = await b.breakGlass.grant({
  req:     req,
  table:   "patients",
  columns: ["ssn"],
  reason:  "ER admit verifying identity for patient-001",
  factor:  { type: "totp", secret: req.user.totpSecret, code: "123456" },
});
// → { id: "bg-...", expiresAt: 1735000000000, rowsRemaining: 1, scopeTable: "patients", scopeColumns: ["ssn"] }

b.breakGlass.unsealRow(grantHandle, table, rowId, opts) #

stable0.5.0hipaapci-dsssoc2
{
  req: object,   // optional originating request — populates ip / userAgent / sessionId / requestId on the audit row
}

Read one row's glass-locked columns under an active grant. The framework validates the grant (not revoked, not expired, not exhausted, scope matches the table), atomically increments rowsConsumed, fetches and unseals the row, and emits a per-row breakglass.unsealrow audit event carrying the reason + actor + remaining rows. For Model B (cryptographic) policies, glass-locked columns route through decryptCell with encryption-context binding — a swapped ciphertext from another row fails closed at AEAD verify.

var row = await b.breakGlass.unsealRow(handle, "patients", "patient-001", { req: req });
// → { _id: "patient-001", name: "Alice", ssn: "123-45-6789", dob: "1980-04-12", ... }

b.breakGlass.revoke(grantId, opts) #

stable0.5.0
{
  reason:     string,   // operator note recorded into the audit row
  callerOpts: object,   // forwarded to audit actor resolution
}

Mark a single grant revoked. Subsequent unsealRow calls against the grant id throw breakglass/grant-revoked. Idempotent — already- revoked grants stay at their original revokedAt timestamp because the UPDATE clause is gated on revokedAt IS NULL.

await b.breakGlass.revoke("bg-abc123", { reason: "operator finished read; releasing" });
// → { revoked: true, grantId: "bg-abc123" }

b.breakGlass.listActive(opts) #

stable0.5.0
{
  req: object,   // request carrying the actor identity (req.user.id or req.apiKey.id)
}

Enumerate the active (not revoked, not expired, rows remaining) grants the caller currently holds. Lookup is keyed via cryptoField's computeDerived so the actor's id never appears in cleartext on the grants table — the framework hashes via the table's namespaced derivation. Unauthenticated callers (no actorId on req) get an empty array.

var grants = await b.breakGlass.listActive({ req: req });
// → [{ id: "bg-...", scopeTable: "patients", scopeColumns: ["ssn"], expiresAt: ..., rowsRemaining: 1, factorType: "totp" }]

b.breakGlass.unsealRowAsService(req, table, rowId, opts) #

stable0.5.0hipaapci-dsssoc2
{
  reason: string,   // operator-supplied reason recorded into the audit row
}

Machine-account read of a glass-locked row. Bypass is gated by the policy's serviceAccountBypass block — both the verified req.apiKey.id must be on the operator-declared allowlist AND the apiKey must carry the operator-declared role. Both checks must pass; either failure emits a denied bypass audit row and throws breakglass/bypass-unauthorized. Each successful bypass emits a distinct breakglass.grant.bypass audit row so post-incident review separates operator-initiated reads from scheduled-job reads.

This path is service-to-service: it consumes NO grant row, so the pinIp / sessionPin grant bindings enforced by unsealRow do not apply here. A grant that was minted with those pins is not redeemable through this surface — the bypass is gated solely by the serviceAccountBypass allowlist + required-role check.

var row = await b.breakGlass.unsealRowAsService(req, "patients", "patient-001", {
  reason: "nightly de-identification job",
});
// → { _id: "patient-001", name: "Alice", ssn: "123-45-6789", ... }

b.breakGlass.listActiveAll(opts) #

stable0.5.0
{
  table:      string,   // optional filter — only grants scoped to this table
  since:      number,   // optional issuedAt floor (ms epoch)
  callerOpts: object,   // forwarded to audit actor resolution
}

Admin variant of listActive — returns every active grant across every actor. Used by security-team dashboards and offboarding workflows; operators wire their own gate (requireScope or a middleware) on the calling route so non-admins can't enumerate the full grant pool. Each call emits a breakglass.admin.listactiveall audit row.

var all = await b.breakGlass.listActiveAll({ table: "patients" });
// → [{ id: "bg-...", issuedToActorId: "user-42", scopeTable: "patients", ... }]

b.breakGlass.revokeAll(criteria, opts) #

stable0.5.0
{
  callerOpts: object,   // forwarded to audit actor resolution
}

Mass-revoke grants matching a scope predicate. Refuses to run with empty criteria — IR teams must name at least one of actorId or table so the framework never silently revokes every grant in the cluster. The to-be-revoked grant ids are snapshotted into the audit row before the UPDATE, so post-incident timelines have the exact list. Common shape: revoke every active grant held by a suspected- compromised account.

var result = await b.breakGlass.revokeAll(
  { actorId: "user-42", reason: "account compromise — IR-2026-0042" },
  { callerOpts: { actor: { userId: "soc-on-call" } } }
);
// → { revokedCount: 3 }

Last updated 2026-08-08T16:39:49.652Z by seeder.