Releases: EgorKhaklin/polaris-id
Release list
v1.0.0-rc.66 — A Polaris credential in a wallet Polaris did not write
a Polaris credential in a wallet Polaris did not write
OpenID4VCI issuance of wallet copies, received by walt.id and Credo; one security fix and three fixes.
Breaking changes
- Breaking:
polaris-verifyand both SDKs refuse an unsigned trust edge past itsvalid_untilor with an unreadable one. - Breaking: a wrong method on
/api/*is a JSON405with itsAllowheader, not the HTML page.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
Every change in this release
Security
- Breaking:
polaris-verifyand both SDKs refuse an unsigned trust edge past itsvalid_untilor with an unreadable one. - Until fixed packages are published, pass
require_signed_attestation=True; SECURITY.md lists what each version lacks.
Fixed
- Breaking: a wrong method on
/api/*is a JSON405with itsAllowheader, not the HTML page. polaris-oid4vprefuses a disclosure name orvctthat is not a string, instead of raisingTypeError.polaris-oid4vprefuses a response JWE with a non-stringencor over 64 levels of nesting, instead of raising.
Added
- OpenID4VCI 1.0 issuance of SD-JWT VC wallet copies of an ACTIVE credential; classical ES256, not ML-DSA-65 (API, design).
CredentialCopyrecord (migrations 2026-09-28-001, -002): the database copies only ACTIVE credentials and computes each status list.- Per-agency ES256 wallet-copy keys (
POLARIS_CREDENTIAL_COPY_KEYS_DIR), checked at load and every use; refused under the HSM-sole-signer profile. polaris-oid4vp serve --issuer-trust-anchor PEMtrusts issuers that sign with anx5ccertificate, as HAIP issuers do.- 26 conformance cases (181 in all) for checks a verifier could skip; a case may set
require_signed_attestation.
The full entry is in CHANGELOG.md under v1.0.0-rc.66, dated 2026-09-28.
v1.0.0-rc.65 — Verifiers refuse what they were never asked to accept
verifiers refuse what they were never asked to accept
Four defects fixed, each against a promise the tree already made: one found by injecting faults into the running system, one by mutating two independent wallets' real output, and two by holding the SDKs and the detached verifier to the wire specification.
Breaking changes
- Breaking: a cross-authority decision with no presented context is refused by every verifier; callers that passed no context were accepting edges from any context.
- Breaking: both SDKs refuse a signed trust edge past its
valid_untilor with an unreadable one, as the detached verifier already did. - Breaking:
polaris-oid4vprefuses avp_tokennot keyed by the DCQL query id it asked for. - Breaking: errors on
/api/*paths are always JSON; clients that received the HTML error page for framework errors now receive{"error", "request_id"}.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
-
Breaking: a cross-authority decision with no presented context is refused by every verifier; callers that passed no context were accepting edges from any context.
-
Breaking: both SDKs refuse a signed trust edge past its
valid_untilor with an unreadable one, as the detached verifier already did. -
Breaking:
polaris-oid4vprefuses avp_tokennot keyed by the DCQL query id it asked for. -
Breaking: errors on
/api/*paths are always JSON; clients that received the HTML error page for framework errors now receive{"error", "request_id"}. -
Errors on
/api/*paths that come from the framework (an unhandled failure, an unknown path, an oversized body, a rate limit) are JSON{"error", "request_id"}as the API reference promises, not the HTML error page. -
The Python and TypeScript SDKs' cross-authority decision refuses a signed trust edge past its
valid_until, or whosevalid_untilcannot be read, as the detached verifier already did; before, a fresh manifest carrying an expired edge was accepted. -
A cross-authority decision with no presented context rejects, in the detached verifier and both SDKs (wire spec section 4, conformance case
cross-authority-no-context); before, a missing context matched a trust edge from any context. -
polaris-oid4vprefuses avp_tokenkeyed by anything other than the DCQL credential query id it asked for (OpenID4VP 1.0 section 8.1); before, one presentation under any key was taken as the answer. Found by mutating a genuine Credo presentation (lab/interop/credo/adversarial/).
The full entry is in CHANGELOG.md under v1.0.0-rc.65, dated 2026-09-27.
v1.0.0-rc.64 — A credential's signature cannot be borrowed from any other artifact
a credential's signature cannot be borrowed from any other artifact
Two verifier defects fixed, found by trying to separate the public surface from the issuer.
Breaking changes
- Breaking: the issuer, the database and every verifier refuse a
token_valuethat is not a credential serial (more than 128 bytes of UTF-8, beginning with{, or containing control characters); every published credential already conforms.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
-
Breaking: the issuer, the database and every verifier refuse a
token_valuethat is not a credential serial (more than 128 bytes of UTF-8, beginning with{, or containing control characters); every published credential already conforms. -
polaris-verify, both SDKs and issuance: a pack'stoken_valuemust be a credential serial (at most 128 bytes, not beginning with{, no control characters); before, any authority-signed artifact re-wrapped as a pack verified as an authentic credential from a trusted issuer.IdentityTokenrefuses a non-serial value (chk_token_value_is_a_serial, migration 2026-09-27-004). -
polaris-verify: an exchange receipt that states no context was reported requester-authorized by an attestation from any context; the receipt's context must now match exactly, as for the request. -
Conformance cases for an exchange in use (
exchange-use): whether an envelope, receipt or mint statement is by the party expected, whether the requester was attested in its context at a stated instant (and by which authority, for a receipt), whether the bodies held match the commitments, and which responder a genuine receipt names. Both SDKs gainverify_exchange_request,verify_exchange_receiptandverify_exchange_mint(verifyExchangeRequest,verifyExchangeReceipt,verifyExchangeMint). -
polaris-verify:verify_exchange_requesttakesnow, deciding the requester's authorization at a stated instant asverify_exchange_receiptalready did; without it the wall clock decides, as before.
The full entry is in CHANGELOG.md under v1.0.0-rc.64, dated 2026-09-27.
v1.0.0-rc.63 — The pooler keeps each operator's scope; the application role writes less
the pooler keeps each operator's scope; the application role writes less
Security fixes found by attacking the deployment as a compromised application and by measuring it under load.
Breaking changes
- Breaking: the pooler refuses
PGBOUNCER_POOL_MODE=transactionandstatementat start; remove the override or setsession. - Breaking:
quota-set,discretion-set,user-create,user-passwd,user-deactivate,key-register,key-retire,key-compromise,rp-registerandrp-policyexit 3 under the application role; run them with the schema owner (POLARIS_DB_USER), and apply the three migrations of 2026-09-27.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
-
Breaking: the pooler refuses
PGBOUNCER_POOL_MODE=transactionandstatementat start; remove the override or setsession. -
Breaking:
quota-set,discretion-set,user-create,user-passwd,user-deactivate,key-register,key-retire,key-compromise,rp-registerandrp-policyexit 3 under the application role; run them with the schema owner (POLARIS_DB_USER), and apply the three migrations of 2026-09-27. -
A referee's recorded level is now derived from their proofing, a co-signer must be proofed, and the vouching bound is enforced by the database; the application role can no longer write vouchings.
-
The application role can no longer write identity-proofing records, so it cannot record an assurance level no evidence supports.
-
The application role can no longer set an authority's quota or revocation bound;
quota-setanddiscretion-setrun as the schema owner. -
The application role can no longer create, promote, deactivate or reset the password of an operator account; it keeps only the lockout columns.
user-create,user-passwdanduser-deactivaterun as the schema owner. -
The application role can no longer register an authority key, record a card personalization or write a retention policy;
key-register,key-retireandkey-compromiserun as the schema owner. -
The application role can no longer register a relying party or change its secret, scope or privacy policy; it stamps only
last_used_at.rp-registerandrp-policyrun as the schema owner. -
The connection pooler runs in session mode and resets each server connection between clients; transaction and statement pooling are refused. In transaction mode a request inherited the previous operator's authority scope.
-
Conformance cases for an agent grant in use (
agent-grant-use): whether the action is in scope, whether a revocation ends the grant, whether the agent's proof binds this grant, action and nonce, and whether the grant's key is one an issuer bound under an active binding. Both SDKs gainagent_proof_proves/agentProofProvesandgrant_principal_bound/grantPrincipalBound. -
The trigger refusal drill runs on every push.
-
The constitution mutation drill runs on every push, and the application mutation drill runs weekly over every refusal; neither ran in CI before. Tests now notice the 25 application refusals nothing did.
-
polaris-verify: a delegated grant signed with a holder key whose binding the issuer had revoked was reported bound and usable. -
polaris-id --helpexamples forrevokeandquota-shownow parse.
The full entry is in CHANGELOG.md under v1.0.0-rc.63, dated 2026-09-27.
v1.0.0-rc.62 — Fifty-nine fixes, one release
Fixes from rc.4 to rc.62, released as one candidate. No change to the published packages; polaris-oid4vp 1.0.0rc7 remains the certified version.
Highlights
- Security: the application's database role can no longer write around the rules: registries, proof policy, recovery channels, anchoring, epochs and the migration registry are owner-only or procedure-only.
- Security: an operator bound to one authority sees and acts only as that authority.
- Fixed: expiry and activation are judged on the UTC date everywhere.
- Fixed: a recovery can be approved through the product again.
- Fixed: a credential cannot be moved to another person, extended or re-dated outside issuance and recovery.
- Tests: refusals inside triggers and row-level security policies are now mutation-tested.
Breaking changes
Integrations that wrote to the database directly as polaris_app are refused where only a procedure or the owner may write. Use the routes, CLI or procedures instead.
Upgrade
./scripts/polaris-migrate.sh --up
./scripts/polaris-migrate.sh --sync-objectsThen roll the images (scripts/polaris-deploy.sh prod) or systemctl restart polaris.
Verify
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idMeasured at this tag: 1244 product tests, 126 of 131 crypto-witness tests, 334 invariant checks.
All 59 fixes
- rc.4: a credential that might be revoked
- rc.5: asking is opt-in, and the answer can be absent
- rc.6: the capability rc.5 shipped, reachable from the thing you use
- rc.7: total on hostile input, this time actually
- rc.8: the offline answer agrees with the online one about expiry
- rc.9: a migration run says what it could not do
- rc.10: a sealed store cannot write outside its destination
- rc.11: a range check that NaN cannot pass
- rc.12: ending a pilot does not erase somebody another authority serves
- rc.13: a session ended by a role change is ended, not an error
- rc.14: every id space is sized, and three that ran out are widened
- rc.15: an operator bound to one authority acts only as that authority
- rc.16: a deactivated admin authorizes nothing
- rc.17: a document someone only looked at is not SUPERIOR evidence
- rc.18: the SQL console cannot be scoped, so a bound account cannot use it
- rc.19: revocation's only door was a setting the caller could set
- rc.20: a pilot's wind-down left its spare credentials alive
- rc.21: the quantum migration left spare credentials on the fallen algorithm
- rc.22: a verification of a revoked credential was recorded as a success
- rc.23: rc.22's guard read status alone, and an expired credential still reads ACTIVE
- rc.24: an expired credential still signed its holder in
- rc.25: a card could be made for an expired credential
- rc.26: a refused attestation stood anyway
- rc.27: expiry was judged on the server's date, and the signature on UTC's
- rc.28: the database judged dates on its own clock
- rc.29: rc.28 moved one clock, and two comparisons assumed it had not
- rc.30: a bound admin decided another authority's recovery
- rc.31: a bound admin could rewrite another authority's record
- rc.32: the transition route asked about an optional field
- rc.33: naming yourself as the actor revoked another authority's credential
- rc.34: a rate that only moved when something happened
- rc.35: an expired credential could prove membership for a whole epoch
- rc.36: a device could be bound to an expired credential
- rc.37: reporting a credential lost could activate an expired reserve
- rc.38: the application role could invent entries in the change records
- rc.39: the application role could set a person's enrollment status
- rc.40: the application role could empty the audit of record through its partitions
- rc.41: a bound operator could read other authorities' credentials through a view
- rc.42: a bound operator's session blinded the database's own rules
- rc.43: a bound operator could bind a device to another authority's credential
- rc.44: binding an operator to an authority did not reach their live session
- rc.45: two operator features failed for the role a deployment runs as
- rc.46: an epoch too small to hide anyone is refused, and a security test cannot skip in CI
- rc.47: a client's timezone no longer moves an expiry decision
- rc.48: Polaris's own database sessions run in UTC whatever PGTZ says
- rc.49: an epoch is written only by the procedure that enforces the anonymity floor
- rc.50: a federation trust edge is recorded and revoked only through its admin-gated procedures
- rc.51: the anchoring layer is written only by the procedure that closes a batch
- rc.52: duress events, archive checkpoints and erasure records cannot be fabricated
- rc.53: a credential cannot be created around issuance
- rc.54: three recovery channels are no longer one role's word; an issued batch cannot be re-opened
- rc.55: a credential cannot be widened, revived or un-revoked by a plain write
- rc.56: a credential cannot be moved to another person, extended or rewritten by a plain write
- rc.57: an authority's key cannot be swapped without the register
- rc.58: a retired algorithm cannot be revived, and no authority can grant itself one
- rc.59: a context's proof policy cannot be lowered by the application
- rc.60: a recovery can be approved through the product again
- rc.61: the constitution Athena shows is the owner's to write
- rc.62: an upgrade applies every migration it is given
v1.0.0-rc.3 — A genuine signature is not a trusted issuer
a genuine signature is not a trusted issuer
Two published-contract ambiguities, found by an outside design-intent review and
resolved against the owner's stated principles: signature validity must not silently
imply issuer trust, historical authorization and current-key status are separate facts,
and where a fact cannot be established safely the answer is UNKNOWN rather than a guess.
Breaking changes
None.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
issuer_authorized_at_signing: was this key authorized for this authority at the
instant the credential was signed? Survives an ordinary rotation.issuer_key_current: is that key still active for the authority today? A rotation makes
this false, which is a fact about the key and not a verdict on the credential.
The full entry is in CHANGELOG.md under v1.0.0-rc.3, dated 2026-09-17.
v1.0.0-rc.2 — A defect found in the candidate
a defect found in the candidate
The contract says a defect found in the candidate makes rc.2 and nothing else moves the
number. The trigger fired twenty-two times in two days, across all four external doors and
the application, so this is that release and not a larger one.
Breaking changes
None.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
The full entry is in CHANGELOG.md under v1.0.0-rc.2, dated 2026-09-17.
v1.0.0-rc.1 — A version a stranger can read
a version a stranger can read
The tree version moves from 9.467 to 1.0.0-rc.1, and the four standalone packages from 0.1.0 to
1.0.0-rc.1. What changed is what the number means; the two defects found on the way are below.
Breaking changes
None.
Upgrade
Pull the tag, rebuild the images, and run scripts/polaris-deploy.sh prod (the expand-contract migration policy keeps the running app safe during the roll); on a Linux host, systemctl restart polaris after git pull.
Verify this release
Every release carries SPDX SBOMs with signed SLSA build provenance (sbom-python.spdx.json and sbom-image-{app,caddy,pgbouncer,postgres}.spdx.json, attached by the sbom workflow after publication):
gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-idDetails
The full entry is in CHANGELOG.md under v1.0.0-rc.1, dated 2026-09-15.