Pulp Engine Document Rendering
Get started
Release v0.20.0

Pulp Engine v0.20.0 — Release Notes

Attributable Editor Sessions and Mutation Audit Logs

Summary

v0.20.0 closes the last major commercial-readiness gap in Pulp Engine’s operator trust model: editor sessions are now attributable, and all write operations produce structured audit log events.

What changed:

  • Operators may supply an optional actor label (display name, email, or any identifier) when signing in to the visual editor. The label is cryptographically bound into the HMAC-signed session token via a new 4-part token format.
  • The API propagates the verified actor label as request.actor through the full request lifecycle.
  • Every template and asset write operation emits a structured info-level audit log event that includes the actor, credential scope, operation name, and resource identifier.
  • The editor login form gains an optional “Your name or identifier” field.

Security posture: This is audit attribution in an operator-managed model, not per-user authentication. An actor label identifies who claimed to make a change — it does not cryptographically prove a unique human identity beyond possession of the shared signing key (API_KEY_EDITOR or API_KEY_ADMIN). Use it for audit traceability within a trusted team. The actor field must not be used for authorization decisions.

No new environment variables. No database schema changes. No API breaking changes. Fully backward-compatible with v0.19.0 3-part tokens.


Token format extension

FormatPartsHMAC payloadUsed when
Legacy (pre-v0.19.0){expiry}.{sig}editor:{expiry}Old tokens — still accepted, cannot mint
v0.19.0{iat}.{expiry}.{sig}editor:{iat}:{expiry}No actor supplied
v0.20.0{iat}.{expiry}.{actor_b64url}.{sig}editor:{iat}:{expiry}:{actor_raw}Actor supplied at login

The dot-count of the token determines which format is in use. Since base64url never contains dots, the count is unambiguous.

“No actor” is strictly the 3-part format. The server never mints a 4-part token with an empty actor segment; the verifier rejects any 4-part token whose decoded actor is empty. This avoids a second representation of “no actor” and keeps the payload/signature rules unambiguous.

Backward compatibility: 3-part tokens (minted without actor) are identical to v0.19.0 format and continue to work without modification. Existing call sites mintEditorToken(key, now, ttlMs) are unchanged.

Cross-version incompatibility (cluster-upgrade rule): A v0.20.0 actor-bearing (4-part) token presented to a v0.19.0 verifier is correctly rejected (the old verifier treats the actor segment as part of the expiry, producing NaN — token invalid). This is the correct fail-safe. In multi-instance deployments, upgrade all instances to v0.20.0 before users begin supplying actor labels at login. Using the 3-part format during the rollout window is safe across mixed versions.


Actor validation

Server-side validation at mint time (POST /auth/editor-token):

  • Whitespace trimmed from both ends.
  • Empty after trim → treated as no actor; 3-part token minted; actor: null in response.
  • Length > 200 chars → 400 Bad Request.
  • Contains control characters (\x00–\x1f or \x7f) → 400 Bad Request.
  • Validation applies to the post-trim value.
  • HMAC is computed over raw UTF-8 bytes of the trimmed actor string.

API changes

POST /auth/editor-token — request

{
  "key": "<API_KEY_EDITOR or API_KEY_ADMIN>",
  "actor": "alice@example.com"
}

actor is optional. Omit it or send a non-empty string (max 200 chars, no control characters). Do not send null — represent “no actor” by omitting the field.

POST /auth/editor-token — response

{
  "token": "<opaque session token>",
  "expiresAt": "2026-03-24T18:00:00.000Z",
  "actor": "alice@example.com"
}

actor is the server-confirmed operator-supplied actor label embedded in the session token, or null if no actor was supplied. Always use the server-confirmed value from the response — never invent or re-derive it client-side.

New error:

StatusCondition
400 Bad Requestactor exceeds 200 chars or contains control characters

Audit log events

editor_token_minted (updated from v0.19.0)

Now includes actor:

{ "event": "editor_token_minted", "keyScope": "editor", "issuedAt": "...", "expiresAt": "...", "actor": "alice@example.com" }

template_mutation (new)

Emitted after every successful template write:

{ "event": "template_mutation", "operation": "create", "templateKey": "my-template", "credentialScope": "editor", "actor": "alice@example.com" }

Operations: create, update, delete, restore.

asset_mutation (new)

Emitted after every successful asset write:

{ "event": "asset_mutation", "operation": "upload", "assetId": "uuid-here", "credentialScope": "editor", "actor": "alice@example.com" }

Operations: upload, delete.

actor: null means the write was performed via direct X-Api-Key auth, or no actor label was supplied at login.

Hygiene: Raw API key values and token strings are never included in log payloads.

Important: actor and resource identifier fields (templateKey, assetId) are intentionally higher-cardinality than operational metrics. They appear in Pino structured log fields only — never as Prometheus metric labels.


Visual editor changes

The editor login form now includes an optional “Your name or identifier” field below the API key field:

  • type="text", maxLength={200}, autoComplete="name", placeholder e.g. alice@example.com
  • The field is optional — leaving it blank results in no actor attribution.
  • The session token stores only the server-confirmed operator-supplied actor label returned by POST /auth/editor-token.
  • When the session expires (AUTH_EXPIRED_EVENT), the actor input is cleared along with the key input.

Changed files

FileChange
apps/api/src/lib/editor-token.ts4-part token format; actor 4th param on mintEditorToken; verifyEditorToken return type { actor: string | null } | null; strict base64url regex validation
apps/api/src/plugins/auth.plugin.tsrequest.actor decoration and extraction from verified token
apps/api/src/routes/auth/auth.tsActor extraction + validation (trim, length, control chars); mint audit log includes actor; response includes actor
apps/api/src/schemas/shared.tsEditorTokenRequestSchema adds optional actor string; EditorTokenResponseSchema adds actor: string | null
apps/api/src/routes/templates/index.tstemplate_mutation audit log on all 4 mutation handlers
apps/api/src/routes/assets/assets.routes.tsasset_mutation audit log on upload and delete
apps/api/src/__tests__/editor-session.test.tsReturn-type migration; actor unit/integration tests; auth-precedence tests; route-level audit log tests
apps/editor/src/lib/auth.tsACTOR_KEY storage constant; getStoredActor(); setStoredToken actor param; clearStoredToken removes actor; EditorTokenResponse adds actor; requestEditorToken spreads actor into body
apps/editor/src/components/auth/LoginGate.tsxOptional actor input field; handleLogin passes actor and stores server-confirmed label; handleAuthExpired clears actor input
apps/editor/src/lib/auth.test.tsActor storage tests; requestEditorToken body and response tests
apps/editor/src/components/auth/LoginGate.test.tsxActor field visibility, filled actor, empty actor, AUTH_EXPIRED_EVENT clears actor
docs/api-guide.mdUpdated POST /auth/editor-token request/response; token format table; “Attributed operator sessions” subsection
docs/editor-guide.mdActor label field description in Authentication section
docs/deployment-guide.mdAudit log fields in “Key log fields” table; audit event note; cluster-upgrade note
docs/runbook.mdNew “Audit log events” reference section
README.mdOne-sentence attribution note in the security section

Upgrade notes

No new environment variables. Existing deployments require no changes.

No database schema changes.

No API breaking changes. The POST /auth/editor-token response now includes actor: null — existing callers that only consume token and expiresAt are unaffected.

Token format: v0.20.0 tokens are 4-part when an actor is supplied, or 3-part (identical to v0.19.0) when not. Old-format tokens continue to be accepted. The verifier handles all three formats.

Multi-instance deployments: If all instances are on v0.19.0, you can upgrade them one at a time. Users should not supply actor labels until all instances are on v0.20.0. Using the 3-part format (empty actor field) during the rollout window is safe.


What is not in scope (planned follow-up)

  • Per-user token issuance (distinct tokens per operator account)
  • Surfacing confirmed actor label in the editor header (“Signed in as Alice”)
  • Structured log query examples / Grafana dashboard for template_mutation events
  • Per-user authentication and RBAC — Pulp Engine remains operator-managed; the actor label does not change the authorization model