🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@openparachute/scope-guard

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@openparachute/scope-guard - npm Package Compare versions

Comparing version
0.3.0
to
0.4.0-rc.1
+29
-0
dist/validate.d.ts

@@ -93,2 +93,31 @@ import { type JwksGetter, type JwksOptions } from "./jwks.js";

hubOrigin: string | (() => string);
/**
* Accept hub-signed JWTs that lack the `jti` claim entirely. **Default
* `false` — strict rejection** (per hub#218 hardening). Operators with
* pre-Phase-1 legacy tokens (issued before the token-registry contract
* stamped `jti` on every mint) can set this to `true` during a
* transition window; the validate path logs a warning via
* `missingJtiLogger` (when supplied) but accepts the token.
*
* Trust framing: rejection is the right default because revocation
* cannot be enforced on tokens we can't index. A jti-less hub-signed
* JWT is an anomaly — either a pre-registry mint that should have
* aged out, or a forged token from an attacker who got a signing key
* but skipped the registry path. Operators who explicitly know they
* still have legacy tokens in flight can opt out; everyone else gets
* the security floor.
*/
allowMissingJti?: boolean;
/**
* Logger called when a jti-less token is accepted under
* `allowMissingJti: true`. Lets operators monitor the legacy-token
* decay curve before flipping the opt-out back off. Defaults to a
* no-op when omitted — callers who want observability supply their
* service's structured logger here.
*/
missingJtiLogger?: (info: {
sub: string;
aud: string | undefined;
iat?: number;
}) => void;
/** Optional JWKS cache tuning. Defaults: 5min cacheMaxAge, 30s cooldown. */

@@ -95,0 +124,0 @@ jwks?: JwksOptions;

+41
-8

@@ -28,3 +28,3 @@ import { jwtVerify } from "jose";

export function createScopeGuard(opts) {
const { hubOrigin, jwks: jwksOpts, jwksGetter: injected } = opts;
const { hubOrigin, jwks: jwksOpts, jwksGetter: injected, allowMissingJti = false, missingJtiLogger, } = opts;
function pickGetter(origin) {

@@ -89,3 +89,34 @@ if (injected)

const scopes = typeof scopeRaw === "string" ? parseScopes(scopeRaw) : [];
const jti = typeof payload.jti === "string" ? payload.jti : undefined;
// jti presence is required by default (hub#218 hardening). The hub
// token-registry contract (introduced in hub#212 Phase 1) stamps `jti`
// on every issued JWT; a hub-signed token lacking the claim is an
// anomaly — either a pre-registry mint that should have aged out, or
// a forgery from an attacker who got a signing key but skipped the
// registry path. Revocation can't be enforced on tokens we can't
// index, so the strict default rejects them.
//
// Operators with legitimate pre-Phase-1 tokens still in flight can
// opt out via `allowMissingJti: true`, in which case the missing
// claim is logged (when `missingJtiLogger` is supplied) and the
// token is accepted. The opt-out is a transition aid, not a steady
// state.
//
// Placement: after signature + iss + sub + audience (so we don't
// surface a jti-shape error on a malformed/forged token whose
// signature already failed), but before the revocation lookup
// (which depends on jti existing).
const jtiRaw = payload.jti;
const jti = typeof jtiRaw === "string" && jtiRaw.length > 0 ? jtiRaw : undefined;
if (jti === undefined) {
if (!allowMissingJti) {
throw new HubJwtError("shape", "hub JWT missing required `jti` claim");
}
// Opt-out path: token is accepted but logged so operators can
// monitor the legacy-token decay curve.
missingJtiLogger?.({
sub: payload.sub,
aud,
iat: typeof payload.iat === "number" ? payload.iat : undefined,
});
}
const clientIdRaw = payload.client_id;

@@ -106,8 +137,10 @@ const clientId = typeof clientIdRaw === "string" ? clientIdRaw : undefined;

// Revocation enforcement runs LAST — only consulted if the JWT is
// otherwise valid. Cheaper checks (signature, iss, aud, expiry) reject
// first, so a bad signature never costs a network roundtrip. A token
// with no jti claim can't appear on any revocation list (lists are
// keyed by jti); we let it through. The hub always stamps jti on
// OAuth-issued tokens — only ad-hoc/legacy tokens lack one, and those
// are out of scope for revocation.
// otherwise valid. Cheaper checks (signature, iss, aud, expiry,
// jti-presence) reject first, so a bad signature never costs a
// network roundtrip. A token with no jti claim can't appear on any
// revocation list (lists are keyed by jti); under the strict default
// the jti-presence check above already rejected, so we only reach
// here with a defined jti. Under the `allowMissingJti: true` opt-out
// we may have undefined jti — those tokens skip the lookup entirely
// since revocation can't be enforced without an index key.
if (jti !== undefined) {

@@ -114,0 +147,0 @@ const cache = pickRevocationCache(origin);

+1
-1
{
"name": "@openparachute/scope-guard",
"version": "0.3.0",
"version": "0.4.0-rc.1",
"description": "Hub-issued JWT validation for Parachute resource servers (vault, scribe, parachute-agent, third-party modules).",

@@ -5,0 +5,0 @@ "license": "AGPL-3.0",

@@ -9,7 +9,7 @@ # @openparachute/scope-guard

- **`createScopeGuard({ hubOrigin, jwks?, jwksGetter? })`** — factory bound to a hub origin. Holds the JWKS getter so the cache lives across requests. `hubOrigin` may be a string or a resolver function (for layered env-var precedence).
- **`guard.validateHubJwt(token, { expectedAudience? })`** — JWKS-backed verify. Pins `iss` to the configured hub origin, strict-checks `aud` (RFC 7519 string-or-array) when supplied. Throws `HubJwtError` (with a `code`) on failure.
- **`createScopeGuard({ hubOrigin, jwks?, jwksGetter?, allowMissingJti?, missingJtiLogger? })`** — factory bound to a hub origin. Holds the JWKS getter so the cache lives across requests. `hubOrigin` may be a string or a resolver function (for layered env-var precedence). `allowMissingJti` (default `false` — strict) controls whether hub-signed JWTs lacking a `jti` claim are accepted (see [Versioning](#versioning) for the 0.4.0 rationale).
- **`guard.validateHubJwt(token, { expectedAudience? })`** — JWKS-backed verify. Pins `iss` to the configured hub origin, strict-checks `aud` (RFC 7519 string-or-array) when supplied. Rejects hub-signed tokens lacking `jti` by default (since 0.4.0; see CHANGELOG for the opt-out). Throws `HubJwtError` (with a `code`) on failure.
- **`parseScopes(raw)` / `extractBearer(authHeader)` / `looksLikeJwt(token)`** — string helpers every consumer reaches for.
- **`hasScope(granted, required)`** — generic `<resource>:<verb>` and `<resource>:<name>:<verb>` matcher with `admin ⊇ write ⊇ read` inheritance. The lib is the engine, not the dictionary; per-service vocabularies and cross-resource catch-alls stay in each service.
- **`HubJwtError.code`** — single error class with a coarse code: `signature | issuer | expired | kid | jwks | audience | shape`. Branch on `code` rather than catching subclasses.
- **`HubJwtError.code`** — single error class with a coarse code: `signature | issuer | expired | kid | jwks | audience | shape | revoked | revocation_unavailable`. Branch on `code` rather than catching subclasses.

@@ -16,0 +16,0 @@ ## Quick start