Skip to content

Releases: EgorKhaklin/polaris-id

v1.0.0-rc.66 — A Polaris credential in a wallet Polaris did not write

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 28 Sep 13:10

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-verify and both SDKs refuse an unsigned trust edge past its valid_until or with an unreadable one.
  • Breaking: a wrong method on /api/* is a JSON 405 with its Allow header, 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-id

Details

Every change in this release

Security

  • Breaking: polaris-verify and both SDKs refuse an unsigned trust edge past its valid_until or 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 JSON 405 with its Allow header, not the HTML page.
  • polaris-oid4vp refuses a disclosure name or vct that is not a string, instead of raising TypeError.
  • polaris-oid4vp refuses a response JWE with a non-string enc or 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).
  • CredentialCopy record (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 PEM trusts issuers that sign with an x5c certificate, 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 28 Sep 03:53

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_until or with an unreadable one, as the detached verifier already did.
  • Breaking: polaris-oid4vp refuses a vp_token not 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-id

Details

  • 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_until or with an unreadable one, as the detached verifier already did.

  • Breaking: polaris-oid4vp refuses a vp_token not 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 whose valid_until cannot 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-oid4vp refuses a vp_token keyed 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 27 Sep 22:12

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_value that 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-id

Details

  • Breaking: the issuer, the database and every verifier refuse a token_value that 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's token_value must 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. IdentityToken refuses 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 gain verify_exchange_request, verify_exchange_receipt and verify_exchange_mint (verifyExchangeRequest, verifyExchangeReceipt, verifyExchangeMint).

  • polaris-verify: verify_exchange_request takes now, deciding the requester's authorization at a stated instant as verify_exchange_receipt already 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 27 Sep 16:58

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=transaction and statement at start; remove the override or set session.
  • Breaking: quota-set, discretion-set, user-create, user-passwd, user-deactivate, key-register, key-retire, key-compromise, rp-register and rp-policy exit 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-id

Details

  • Breaking: the pooler refuses PGBOUNCER_POOL_MODE=transaction and statement at start; remove the override or set session.

  • Breaking: quota-set, discretion-set, user-create, user-passwd, user-deactivate, key-register, key-retire, key-compromise, rp-register and rp-policy exit 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-set and discretion-set run 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-passwd and user-deactivate run 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-retire and key-compromise run 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-register and rp-policy run 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 gain agent_proof_proves / agentProofProves and grant_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 --help examples for revoke and quota-show now 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 26 Sep 20:05

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-objects

Then roll the images (scripts/polaris-deploy.sh prod) or systemctl restart polaris.

Verify

gh attestation verify sbom-python.spdx.json --repo EgorKhaklin/polaris-id

Measured 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 18 Sep 02:12

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-id

Details

  • 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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 17 Sep 16:16

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-id

Details

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

Choose a tag to compare

@EgorKhaklin EgorKhaklin released this 16 Sep 15:49

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-id

Details

The full entry is in CHANGELOG.md under v1.0.0-rc.1, dated 2026-09-15.