Repository navigation
Releases: provin-line/auth
Release list
v0.5.0 — auth.provider 0.18.4 provider (BREAKING: generator, settings, image variables); 0.15 packages removed; policy-verifier 0.17.0 baseline; kid-match under every contract; token-control
A minor number, not a patch: the provider moves to auth.provider 0.18.4.
Its generator now calls upstream's, and the DID grant, the registry
resolver and the nonce store are 0.18 modules. Every provider deployment
changes its settings, its image's variables and its records, and the 0.15
packages are removed. The policy-verifier baseline moves to 0.17.0, which
changes the AppConfig type. Consumers that track the moving v0.4 image
tag are unaffected; provin.oss's quickstart pins v0.3 and moves to v0.5
on its own schedule.
Tokens from the DID grant carry the six signed claims of
auth.token.signed-claims, the generated provider answers revocation and
introspection, and rule auth.grant.kid-match holds under every contract.
docs/upstream-compatibility.md lists what
an existing provider or verifier instance changes.
Added
@provin-line/auth-dplaax-registry: provides the DID grant's
didDocuments(packages/dplaax-registry).- It resolves did:dplaax owner DIDs at
<url>/did/<accountType>/<accountId>/did.jsonthrough auth.provider's
outbound, as the operator's own URL. The operator's destination policy
decides between https (outbound.internal-hosts) and plain http to a
private address (outbound.plain-internal). - Its section
dplaax-registryreplaces 0.15'sdplaax.registry.baseUrl
/allowedRegistriesandoauth.grants.did.resolver.*:url(now
required; 0.15 defaulted tohttps://registry.dplaax.dev),
allowed-registries,timeout(5s) andmax-document-size(64KiB,
withinoutbound.max-response-size; 0.15 took 1 MiB). - Concurrency is outbound's
max-concurrent(16, shared with every
outbound caller): a request past it is refused at once (503), where 0.15
queued it behind its own limit of 8. - Outcomes: rejected for a DID the registry does not hold (404 or another
4xx) or bytes that are no document for it; unavailable for 5xx, no
answer in time, a refused destination (a redirect included) and an
answer over the size limit. outbound stops a large answer before its
status is read, so an oversized document is now unavailable (503), where
0.15 rejected it (400). - Decoding meets rule
canon.strict-decode: invalid UTF-8 and unpaired
surrogate escapes are refused, and integers beyond 2^53 are kept
exactly.strictJsonParseis internal to this package.
- It resolves did:dplaax owner DIDs at
@provin-line/auth-did-grant-store: keeps the DID grant's spent nonces
onrecordsover the kernel'sstore(packages/did-grant-store), with
the guaranteesspentNoncesNeedsasks, as upstream'sjwt-bearer-store
does.@provin-line/auth-did-grant: the DID grant as an auth.provider 0.18
module (packages/did-grant), replacing the 0.15auth-provider-did.- Its tokens carry the six claims of rule
auth.token.signed-claims
(auth_contract_id,verification_method,did_document_snapshot,
lifecycle_state_ref,lifecycle_freshness_ref,
authorization_scope) through auth.provider's added claims, signed
into the JWT.client_idnames the client;azpis no longer written. - The package exports the contract at
., the module at./moduleand
spentNoncesContractat./testing. auth.provider's packages and zod
are peers. - It takes two contracts it owns:
didDocuments(the resolver) and
spentNonces(the nonces already used). Custom signature algorithms
are contributed atsignatureAlgorithmsinstead of an injected
VerifierRegistry, and say whether they carry a JWS header; the grant
issues only when a verifier's subject and message are the requested
DID's. Verifiers are made at boot, so a missing@noble/ed25519
refuses boot instead of failing every request. es256k_jwsis no longer offered: jose does not verify ES256K on Node,
so it never worked. A deployment that needs it contributes a verifier.- Its section
did-grantreplacesoauth.grants.did: lifetimes carry a
unit, andaccess-token-lifetimeis the grant's own. Audiences and
scopes come from the client'sclient-allowance(allowedAudiencesis
gone), and the OWNER token endpoint comes from auth.provider's
tokenEndpointLocation(tokenEndpointis gone). - A failed signature is
400 invalid_grant, never401. A resolver or
nonce store that cannot tell answers503. - A message's nonce is kept until the message's own window closes, plus a
minute for clock differences. It was kept formessage-max-agefrom
the request, which left a message stamped ahead of the clock usable
once more.
- Its tokens carry the six claims of rule
Changed
- BREAKING (
@provin-line/create-auth-provider0.4.0): the generated provider is an
auth.provider 0.18.4 app. The generator calls auth.provider's own
(@o3co/auth-provider-scaffold, pinned at exactly 0.18.4) for a listed app
of@mod/token-endpoint,@mod/client-allowance,@mod/outbound,
@mod/discoveryand@mod/token-control, then composes@provin-line/auth-did-grant,
auth-dplaax-registryandauth-did-grant-storeinto it
(packages/create-provider/src/overlay/, one function per patched file).- The 0.15 template is gone. The app runs its sources
(node src/main.ts), with no build, notsconfig.jsonand nodist/;
its settings are env variables read byconfig/application.conf, a local
layer (APP_CONFIG) for lists, and a refused boot exits 78. Existing
0.15 instances do not carry over: auth.provider 0.18 reads different
keys and records. - Revocation (
POST /oauth/revoke) and introspection
(POST /oauth/introspect) are@mod/token-control's.- Introspection takes client authentication only: a
confidential
client with aclient-secret.hashwhoseclient-allowance.audiences
covers the token's audience (the issuer URL for a DID client with no
audiences). 0.15's self-introspection, with the caller's own access
token as its credential, is gone, and a public client gets401. - With
tokens.revocation = denylist, introspection answers a revoked
token inactive. Withunsupported,/oauth/revokeanswers
400 unsupported_token_typefor access tokens and the metadata names
norevocation_endpoint; 0.15 always kept a denylist. - The provin policy-verifier reads the JWT alone and does not see a
revocation: a revoked token passes it until it expires, within
did-grant.access-token-lifetime(provin-line/oss#85).
- Introspection takes client authentication only: a
- Added to upstream's app: the did-grant, dplaax-registry and
keys.signing.secret/secret-encodingkeys and variables (a
deployment sharing an HS256 secret with a policy-verifier sets
utf8), the DID grant type in the example client's allowance, the
registry inoutbound.allowandoutbound.plain-internal, and
tokens.subject-check(boot requires it, or a module that checks
subject revocation, which this app does not compose). pnpm-workspace.yaml(pnpm 11 no longer readspackage.json#pnpm)
pins@provin-line/did-dplaaxand@provin-line/auth-did-grantto the
same ref, setsblockExoticSubdeps: falsefor those git sources below the
app's direct dependencies, and allows the git-fetched packages their
preparethroughallowBuilds, keyed by the source pnpm resolves
(docs/create-app.md § 3.4). For a branch or tag under
--allow-unpinned-refnoallowBuildsis written and the first install
names the key to add.- Removed CLI flags and exports:
--port(the port isHTTP_PORT),
--package-manager(upstream's app is pnpm), andDEFAULT_PORTand
DEFAULT_PACKAGE_MANAGERfrom the package.DEFAULT_DEP_VERSIONSnow
holds only what the overlay adds (@noble/ed25519), and
LISTED_MODULESis new.--registry-base-urlis the default
DPLAAX_REGISTRY_URLof.env.example. - Runtime contract of the published
auth-providerimage. It runs
the generated app, so the 0.15 variables (OAUTH_JWT_*,
DPLAAX_REGISTRY_BASE_URL,CLIENT_REPOSITORY_PATH,HTTP_PORTaside)
no longer mean anything. A container sets the 0.18 variables
config/application.confreads (KERNEL_DEPLOYMENT,
STORE_ACCEPT_VOLATILE,TOKENS_REVOCATION,TOKENS_SUBJECT_CHECK,
HTTP_PUBLIC_ORIGIN,HTTP_INSECURE_ORIGIN,ISSUER_URL,
KEYS_SIGNING_*,DID_GRANT_*,DPLAAX_REGISTRY_URL) and mounts a local
layer of lists (outbound.allow,audit.receivers,
clients.registered) named byAPP_CONFIG
(config/local.conf,config/application.conf); the dev composition's
deploy/dev/provider.local.confis an example. The image listens on
HTTP_HOST=0.0.0.0andHTTP_PORT=3000(the app's own default is
8080).--registry-base-urlinpublish-images.ymlnow only fills the
generated examples; the registry at runtime isDPLAAX_REGISTRY_URL. - The scaffold smoke, CI and image build follow:
make smokeboots the
provider's sources against a throwaway DID registry on loopback and
checks a DID-grant round trip (HS256 over the secret's UTF-8 bytes, the
six signed claims, a tampered signature rejected) and the token's
introspection, revocation and introspection again; its ports are
PROVIDER_PORTandVERIFIER_PORT. The root workspace overrides map the
three new packages toworkspace:*.Dockerfile.instance,
deploy/generated-instance.Dockerfileandpublish-images.ymltake an
ENTRY(src/main.tsfor the provider,dist/main.mjsfor the
policy-verifier) and build only if the instance has a build script. The
devdocker-compose.ymlconfigures the provider with the 0.18 variables
anddeploy/dev/provider.local.conf.
- The 0.15 template is gone. The app runs its sources
- BREAKING (types, verifier packages): the policy-verifier baseline is
0.17.0. 0.17.0 makessecretEncodingrequired in theAppConfig
type'soauth.jwtand in `Hs256Rotati...
v0.4.0 — policy-verifier 0.16.1 baseline (BREAKING on the wire for JWKS-verifying deployments); Node.js 24.22.1; vitest 4.1.12
A minor number, not a patch: the policy-verifier baseline moves to 0.16.1,
which changes /verify's answer for JWKS-verifying deployments (503
instead of 401 when the keys cannot be fetched), and that must not reach
consumers that track the moving v0.3 image tag. provin.oss's quickstart pins
v0.3 and moves to v0.4 on its own schedule.
It includes 0.3.1's security fixes (Node.js 24.22.1 under every image, the
refreshed lockfile entries), and adds the generated scaffolds' vitest 4.1.12
pin and Dependabot updates for the Node minor pin.
Changed
-
BREAKING (wire, JWKS-verifying deployments only): the policy-verifier
baseline is 0.16.1. Workspace packages require
@o3co/auth.policy-verifier.{core,builtins,server}^0.16.1, and
create-policy-verifier(now 0.3.0, per create-app.md § 3.3) emits the
same as exact pins. zod is pinned to 4.6.7 in the workspace and in the
generated verifier'spnpm.overrides(server 0.16.1 requires^4.6.6).
The bump needs no code change here: 0.13–0.16 otherwise change surfaces
this composition does not use (Cedar engines, static role/permission
collectors, delegated-token ranges, caller authentication).What changes on the wire: a verifier configured with
OAUTH_JWT_JWKS_URI
that cannot get the keys a token needs (fetch failure or timeout, non-200,
a body that is not a key set) answers503 verification_unavailable
instead of401 invalid_token. A cached key set keeps verifying through an
outage only until it isoauth.jwt.jwksCacheMaxAgeMsold (upstream
default 10 minutes). A token that fails verification is still401. The
generated verifier uses a shared HS256 secret unlessOAUTH_JWT_JWKS_URI
is set, and only then is it affected. If it is: alerts, SLOs, retry-on-5xx
and mesh outlier detection now see an IdP outage as 5xx, and
provin.oss'sprotobuf.interceptorsendpoint reports it to the caller as
an internal error rather than as unauthenticated. See
upstream compatibility for existing
instances.
Fixed
- The generated verifier passes its pino logger to
createApp, so the
verifier's own lines (e.g.jwt_verification_unavailable,
unauthenticated_non_loopback_bind) are NDJSON instead of console text. policy-verifier-dplaax-module's docs no longer say that the upstream
evaluator allows a request no rule applies to. They state its
deny-by-default (onEmptyRuleSet, upstream's default since 0.4.0) and why
DefaultDenyRuleCollectorstays enabled.
Security
Node.js 24.22.1 (node:24.22-alpine under every image) and the refreshed
lockfile entries (proxy-addr, brace-expansion, source-map-js, vite, vitest)
shipped in 0.3.1. On top of them:
- Dependabot keeps the Node minor pin current. A minor tag such as
node:24.22-alpinestops receiving updates once a newer minor ships, so
Dependabot'sdockerecosystem now proposes those bumps for this
repository's Dockerfiles. - Generated scaffolds pin vitest 4.1.12 (
@vitest/mocker:
GHSA-qxg6-89mg-rqv8, GHSA-xp26-3454-mjgp), so a newly generated instance,
and the published images, start on it.create-provideris now 0.3.0, per
create-app.md § 3.3. An already generated instance moves its ownvitest
pin by hand.
Images
Built by publish-images from 24ee0a2 (run https://github.com/provin-line/auth/actions/runs/39719822044). The digest is the release identity; v0.4.0, v0.4, latest and sha-24ee0a2… are locators.
ghcr.io/provin-line/auth-provider@sha256:28d026a7c017ab2fd9ffaf5a06547faa1e09aa876d7be710728d295cdf00722eghcr.io/provin-line/auth-policy-verifier@sha256:28e21404935c0e32d7c4c77b57c8458d07b506de2ae8d8fa81abf6b2c62a381e
v0.3.1 — Node.js 24.22.1 under every image; vulnerable lockfile entries refreshed
A patch on the 0.3 line, so the moving v0.3 image tag that provin.oss's
quickstart pins picks up the Node.js and proxy-addr fixes below. It changes
no behaviour, configuration or wire answer; the policy-verifier 0.16
baseline ships in 0.4.0.
Security
- Node.js 24.22.1, the October 2026 security release (eight CVEs on the
24.x line, four of them high: two TLS hostname-verification bypasses, a
Brotli decoder out-of-bounds write, and DNS name truncation). Every image
builds onnode:24.22-alpineinstead of the floatingnode:24-alpine:
the published images (deploy/generated-instance.Dockerfile), the dev
composition image (Dockerfile.instance) and both generator templates,
so a newly generated instance starts on it. An already generated instance
keeps its own Dockerfile; change itsFROMline by hand. CI installs the
newest 24.22 patch (check-latest), andengines.nodeis
>=24.22.1 <25. - Refresh vulnerable lockfile entries: proxy-addr 2.0.8
(GHSA-jqcg-44mw-7w3h, via express), brace-expansion 5.0.12
(GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p),
source-map-js 1.2.2 (GHSA-68fv-2mgg-jv7q), vite 8.3.4 (GHSA-587h-7wmj-h5pm,
GHSA-698v-4r3x-3vcv, GHSA-r825-w8mm-5pxw, GHSA-9jrq-w75r-8gcw,
GHSA-rq7h-c2jc-7f22) and vitest 4.1.12 (@vitest/mocker:
GHSA-qxg6-89mg-rqv8, GHSA-xp26-3454-mjgp). Only proxy-addr is a runtime
dependency; the rest is test and build tooling. - Generated scaffolds, and therefore the published images, keep their
vitest 4.1.11 development pin, which carries the two@vitest/mocker
advisories (development-only). 0.4.0 moves it.
Images
Built by publish-images from 88f04d2 (run https://github.com/provin-line/auth/actions/runs/39718284450). The digest is the release identity; v0.3.1, v0.3, latest and sha-88f04d2… are locators.
ghcr.io/provin-line/auth-provider@sha256:237aed263e38bfe2303a4cba9dfca23ef2cbe03095356954812ebbc298511a98ghcr.io/provin-line/auth-policy-verifier@sha256:6351f3db03310c4962c5759a52f392f22eb8275e10a88837d03fd5420b52cb88
v0.3.0 — auth baseline provider 0.15 / verifier 0.12; audit and revocation wired; DID tokens bound to their client
A minor number, unlike 0.2.1's patch: this release carries a breaking
configuration change (audit.sink.type = "none" no longer boots) and new
public exports, and it must not reach consumers that track the moving v0.2
image tag. provin.oss's quickstart pins v0.2 and moves to v0.3 on its own
schedule.
The auth baseline is now the latest upstream (provider 0.15.0, verifier
0.12.0). The provider composition records audit events and can revoke tokens —
by RFC 7009 for the client a DID token was issued to, and by subject for every
token already issued to a DID — and deployment.mode = "multi" refuses to boot
on in-process stores. The OWNER login contracts are wired into the DID grant.
Added
-
The provider composition wires every security capability upstream can
read, instead of declaring them absent. Previously a DID-issued token could
not be revoked before it expired, and no audit event was recorded.
buildModulesnow includes:auditSinkModule(new export): the sink named byaudit.sink.type,
"console"by default.token.issuedandtoken.issued.failureevents
now reach stdout.createAuditSinkModule({ registerSinks })(new export)
builds the same module with additional sinks foraudit.sink.typeto
select.- core's
memoryAccessTokenDenylistModule: RFC 7009 revocation of an access
token writes to it, and the provider's verification and introspection
consult it. With the client binding (see Changed), the client a DID token
was issued to can now revoke it. inMemorySubjectRevocationModule(new export): thesubjectRevocation/
subjectSessionIndexpair.subjectRevocation.revokeBefore(did, …)
invalidates every token already issued to that DID.
Both kinds of revocation take effect where the provider verifies a token.
A resource server that verifies JWTs locally against the JWKS, such as the
generated policy-verifier, consults neither store; the token lifetime is
still its bound. The two in-memory stores are single-process and declare
themselves replica-unsafe.
DplaaxBuildModulesOverridesgainsauditSinkModule,
accessTokenDenylistModuleandsubjectRevocationModule, so a
multi-replica deployment can pass shared implementations. -
The OWNER login contracts (
OWNER_AUTHENTICATION_LOGIN@1,
OWNER_ASSERTION_CONTROL_LOGIN@1) are now wired into the DID grant's
request handler.handle()dispatches on the configuredauthContract:
LEGACY_DID_LOGIN@1is unchanged; eitherOWNER_*value now requires and
enforces a versioned login transcript (login-transcript-v1, now eleven
required fields — see Changed, below) — the three-way match between the
JWS headerkid, the transcript'sverification_method, and the
resolver-selected method id (auth.grant.kid-match), explicitly checked
against the crypto-verified method too; the Fork-Y relationship check
(authenticationforOWNER_AUTHENTICATION_LOGIN@1,assertionMethod
forOWNER_ASSERTION_CONTROL_LOGIN@1); and a requiredaudienceclaim.
A minted OWNER token always carriesaudand its actually-enforced
auth_contract_id. Three boot-time asserts oncreateDidGrant, all
mirroring the existingallowedAudiences/revocationLatencyBoundSec
fail-closed style, now gate an OWNERauthContract:
oauth.grants.did.tokenEndpointmust be configured;
ownerMigrationRatifiedmust betrue(ruleauth.migration.enable-gate
— re-asserts whatdidConfigSchemaalready requires at parse time, for a
hand-built config that skips it); and every configured
supportedAlgorithmsentry must be header-bearing (JWS-family —
ed25519_raw/ed25519_prehashsign no JWS protected header at all, so
the three-way kid match is unsatisfiable on them; this includes the
didConfigSchemadefault of["ed25519_raw"]). See
README.md § P0 Auth Contract.
Changed
-
@o3co/auth.utilsis no longer emitted into generatedpackage.json.
Its two helpers live in the scaffold (above). This was the package's last
consumer across the auth family. -
BREAKING (config): the provider composition no longer accepts security
capabilities declared absent.buildModulesnow wires an audit sink, an
access-token denylist and the subject-revocation pair (see Added), so
audit.sink.type = "none"fails boot. That refusal is this composition's
policy: core would accept"none"as a declared absence, but the slot is
always filled here.DplaaxConfigSchemanow takes any sink name instead of
only"none". The generatedapplication.confselects
audit.sink.type = "console"(override:AUDIT_SINK_TYPE) and
oauth.revocation.accessToken = "denylist", and drops
revocation.subject = "unsupported". For instances generated earlier, only
the"none"line breaks boot; the migration is in
upstream compatibility. -
DID-issued tokens are bound to the authenticated client. The DID grant
used to stampazpwith the token's audience and mint noclient_id, so
a token named no owning client. It now setsclient_idandazpto the
client that/tokenauthenticated (RFC 9068 §2.2) — never the request
body'sclient_id— and mints neither when there is none.audstill
carries the audience. No consumer in the provin family readsazp. -
deployment.mode = "multi"is enforced.DplaaxConfigSchemaused to
stripdeployment, so core's replica-safety guard never saw it and an
instance booted multi-replica on in-process stores. The schema now keeps
it,DplaaxAppConfigcarries it, and the template reads it from
DEPLOYMENT_MODE.inMemoryCodeRepositoryModule, andoauthDidModulewhen
it falls back to its in-process nonce store, now declare themselves
replica-unsafe as well, so"multi"refuses to boot until every in-process
store is replaced. -
The auth baseline is the released upstream, not a 0.3.x / 0.5.x pin with a
compatibility shim. Every workspace package now requires
@o3co/auth.policy-verifier.{core,builtins,server}^0.12.0and
@o3co/auth-provider-{core,oauth}^0.15.0— the latest releases — and the
generators emit the same as exact pins (DEFAULT_DEP_VERSIONS: 0.12.0 /
0.15.0), with both generators bumped to 0.2.0 per create-app.md § 3.3.
create-provideralso emitsexpress-session, a peer of
@o3co/auth-provider-oauthsince 0.11.0, as an exact pin. Upstream core
evaluate()is async since verifier 0.10.0; no production code here called
it. The dual-path shim that let the collectors
readpayloadorsubjectand reachreadUntrustedRequestContextby
reflection (collectors/context.mts) is removed: collectors read
context.subjectand callreadUntrustedRequestContextdirectly, and the
policy-verifier template and the integration test import
builtinKeyResolversModulerather than probing for it. The code had already
crossed the intervening upstream BREAKING changes; what changes here is that
the released-0.3.x branch of each dual path is gone.@o3co/ts.hoconstays
at its current pin — its 0.1 → 1.x move is a separate migration. -
Refresh vulnerable transitive lockfile entries: js-yaml 4.3.2, qs 6.16.0,
nanoid 3.3.18 and brace-expansion 5.0.9. CI audits the dependency graph.
vitest and @vitest/coverage-v8 move to^4.1.11(GHSA-82fw-gwwq-j7x9,
path traversal via the @vitest/mocker redirect mock), and both generators
emit vitest 4.1.11. -
Prepare generated instances for current upstream auth: align Zod 4.5.4, wire
separated JWKS/key-resolver modules, update required configuration and support
verified subject bags plus explicitly untrusted request context. -
Generated Verifiers now require an explicit Owner DID rule on the declared
surface instead of relying on empty-rule allow. Scopeless DID tokens skip only
the scope group; undeclared operations and non-Owner subjects remain denied.
See upstream compatibility for migration. -
login-transcript-v1(unreleased) gains an eleventh required field,
did, alongside the existingsubject_did—validateOwnerLoginnow
also checkstranscript.did === transcript.subject_did.didis the
field name every built-in signature verifier's own internal payload-binding
check already reads (parsedMessage.did !== did); the transcript needed
its own copy under that exact name so a real signed request can satisfy
both that check andparseLoginTranscript's field requirements from a
single signed payload — previously, no signed payload could actually be
simultaneously valid for both, so the wired-in Added item above shipped
correct only against test fixtures that added the field without it being
part of the documented schema.
Fixed
-
Generated instances never logged NDJSON. Both scaffolds took their logger
from@o3co/auth.utils, which treats pino as an optional peer and falls
back toconsolewhen the import fails — and the generator never emitted
pino, so every instance created bycreate-provideror
create-policy-verifierlogged bare[name] …console lines that no
aggregator parses. The scaffold now ships its ownsrc/logger.mtson pino
(a direct runtime dependency, exact-pinned like the rest), honouring
logging.levelfrom the application config withLOG_LEVELas the
environment override, and serialisingerrso an Error keeps its stack. -
Generated instances could hang on SIGTERM and always exited zero. The
same package'sgracefulShutdowncalledserver.close()with no deadline,
so one stuck request meant the process never exited on its own and the
orchestrator's SIGKILL cut it down mid-flight; cleanup failures went to
console.error. The scaffold now shipssrc/shutdown.mtswith the contract
auth.provider (#290), auth.proxy (#81) and auth.policy-verifier (#210) each
ad...
v0.2.1 — P0 auth contract hardening; auth-provider image rename
A patch number for work that includes contract hardening, deliberately: while
the version is 0.x this file already states that even minor releases may
change the public API, so patch-vs-minor carries no compatibility promise here.
What the number does buy is a stable consumer pin — provin.oss's quickstart
tracks the moving v0.2 tag, and cutting v0.3 would have forced that pin to
move for no benefit before the first announcement.
Changed
-
The published auth-provider image is
ghcr.io/provin-line/auth-provider, no
longerauth-auth-provider. That name came from concatenating anauth-
prefix onto a component already calledauth-provider— it said nothing
twice.Breaking for anyone pinning the old name, done now precisely because
nobody can be: the packages became public shortly before the rename and no
release had been announced. GHCR has no rename and no alias, so the operation
is publish-the-right-name and delete-the-wrong-one. -
One component name now derives every published identifier, with each
namespace adding only the prefix its own context does not already supply:component provider | policy-verifier directory packages/create-${c} inside this repo — "auth" is given by where the file is npm create-auth-${c} scope is provin-line, not auth image auth-${c} org is provin-line, not authSo directories lose a prefix their location already implies
(create-auth-provider→create-provider,auth-provider-did→
provider-did,auth-provider-dplaax-module→provider-dplaax-module) and
two npm names gain one that was missing
(create-auth-policy-verifier,auth-policy-verifier-dplaax-module). This
deliberately breaks the directory↔package-name mirror; pnpm resolves by the
nameinpackage.json.Every package is unpublished on npm, which is the only reason this is a
refactor rather than a breaking change.The same conflation was live inside both generators, and there it was not
cosmetic: one list served as both npm names and directory paths, so after the
rename a generated scaffold would have carried a git spec pointing at a
packages/path that no longer exists — an install failure, not a cosmetic
oddity.
P0 auth contract work (dplaax.spec): hardens DID resolution, tightens
verification-method selection, adds an OWNER-path login transcript
(validated but not yet wired in), a pluggable nonce store, six signed token
claims with an explicit outcome split, and a conformance vector runner. See
README.md § P0 Auth Contract for the full,
accuracy-checked surface — including what does not work yet (OWNER
contracts are fail-closed).
Added
- Resolver hardening for
DplaaxDidResolver's DID-document fetch: a finite
timeout and response-body cap enforced while streaming (never after full
buffering), bounded concurrency via a shared semaphore, strict RFC 8259
JSON decoding (rejects duplicate object keys and trailing data; preserves
unknown members — including__proto__, kept as an own property, not a
prototype write — rather than stripping them), byte-exactidequality
against the requested DID, and redirect refusal (redirect: "error") so
the serving connection is always the requested URL, never a redirect
target. Rules:auth.resolve.resource-floor,auth.resolve.id-equality,
auth.resolve.unknown-member,auth.resolve.origin-pin,
auth.resolve.failure-mapping. - Verification-method selection hardening in
@provin-line/auth-provider-did:
exact-idmatch with a controller check for the OWNER path, duplicate
verificationMethodid rejection on every path, and a relationship-array
check that trusts only string references — an embedded object whose
idhappens to match is rejected rather than silently accepted, closing
a path that could otherwise smuggle a method intoauthentication
inline. JWS-based verifiers (ed25519_jws,es256_jws,es256k_jws) now
surface the protected header'skidand the signed payload's
verification_methodonParsedMessage. Rules:auth.grant.exact-method,
auth.method.relationship,auth.method.string-reference-only,
auth.forky.authentication-login. - A versioned OWNER-path login transcript (
login-transcript-v1) and its
validator,validateOwnerLogin: binds all ten transcript fields, checks
the transcript's audience against the configured allowlist, enforces a
domain-separation tag so a transcript signed for a different purpose
cannot be replayed as a login, and enforces the three-way match between
the JWSkid, the transcript'sverification_method, and the
resolver-selected method id. Built and unit-tested, but not yet called
from the DID grant's request handler — see "OWNER contract fail-closed
stopgap" under Security, below. Rules:auth.transcript.bound-fields,
auth.transcript.audience-required,auth.transcript.domain-separation,
auth.grant.kid-match. - A pluggable
NonceStoreinterface for the DID grant's replay protection,
with an in-memory default (swept periodically,.unref()'d so it never
blocks process shutdown on its own). Deployments needing cross-replica
replay protection can inject a shared-store implementation. - Six signed claims on every minted DID-grant token —
auth_contract_id,
verification_method,did_document_snapshot(sha256:<64-hex>),
lifecycle_state_ref,lifecycle_freshness_ref, and
authorization_scope(alwaysAUTHORIZATION_AT_ISSUANCE_WITH_MAX_AGE@1,
the only scope this package mints) — assembled from a single bound
evaluation input per request instead of being re-derived piecemeal, plus
an explicit outcome split: resolver outage maps to HTTP 503
temporarily_unavailable, resolver/method rejection maps to HTTP 400
invalid_grant, and neither outcome mints a token. Rules:
auth.token.signed-claims,auth.token.issuance-vs-request,
auth.resolve.single-input-binding. - A conformance vector runner (
integration/conformance) that loads
dplaax.spec-shaped vectors and executes them against the real
resolve/grant code paths, fails closed (throws) on a vector naming a rule
with no registered executor rather than silently skipping it, and carries
aDPLAAX_SPEC_DIR-gated drift check against the upstream spec's vector
bytes. Rule:auth.contract.normative-sot.
Security
- The DID grant's audience allowlist, revocation-latency bound, and
LEGACY-contract TTL cap are now fail-closed configuration: an
empty/absentallowedAudiences, a missingrevocationLatencyBoundSec, or
anoauth.accessToken.expiresInthat exceeds either bound now throws at
grant construction instead of silently accepting any audience or an
unbounded token lifetime (previously, an empty/absent audience allowlist
meant "accept any audience"). Selecting anOWNER_*authContractalso
requires an explicitownerMigrationRatified: trueopt-in at the
config-schema level. Rules:auth.token.lifetime-bound,
auth.legacy.did-login,auth.migration.enable-gate. - OWNER contract fail-closed stopgap:
createDidGrantnow throws at
construction time ifauthContractis set to
OWNER_AUTHENTICATION_LOGIN@1orOWNER_ASSERTION_CONTROL_LOGIN@1. The
OWNER validation path (validateOwnerLogin, above) is implemented but not
yet wired into the request handler, which would otherwise mint a token
labeledOWNER_*while only performing LEGACY (relationship-blind)
validation — a token that misrepresents its own assurance level. This
guard closes that gap until the OWNER path is wired into the handler; see
README.md § P0 Auth Contract.
v0.2.0 — separated-topology evidence surface
Reference policy-verifier authorization surface for the separated
network/pipeline topology (lock-step with provin.oss's cmd/network +
cmd/pipeline recomposition — PR2 evidence-wire and the tlog-mirror surface).
Added
- Reference policy verifier: declare the evidence-write and transparency-log
resource/actions the separated data plane exercises butv0.1.0did not
authorize —chain/report-health(publisher emit-health reporting),
payloads/retain(by-reference payload deposit),audit/register
(evidence registration), andtlog/mirror(custody-log mirroring). The
v0.1.0image predated these wire calls and403d them with
undeclared_resource_action, blocking the provin.oss quickstart's separated
topology from reaching aVERIFIEDverdict.