Releases: runcycles/cycles-server-admin
Release list
v0.1.25.51 — tenant webhook admin-category boundary (#209, deploy after events 0.1.25.23)
Security release — tenant-owned webhook subscriptions can no longer carry or receive admin-only event classes (issue #209). The admin-plane half of a coordinated three-layer fix (with cycles-server-events 0.1.25.23 and governance spec v0.1.25.40/.41). Enforces governance WEBHOOK SUBSCRIPTION INVARIANT 2: a subscription owned by a concrete tenant (tenant_id present and != "__system__") must not carry or be delivered admin-only selectors/events (api_key.* / policy.* / webhook.* / system.*).
Defense in depth — three layers:
- Write-path gate: the admin webhook create/update paths now validate
event_categories(not justevent_types) against the tenant-accessible boundary for concrete-tenant targets — closing the injection vector where a tenant could smuggle admin-only categories, and the admin-on-behalf-of / admin-plane path that could place them on a tenant-owned row.__system__targets are exempt. SharedWebhookCategoryBoundaryValidatorso the two planes can't drift. - Dispatch boundary (the durable guarantee): live dispatch AND replay skip, per event, any admin-only event to a concrete-tenant subscription — fail-closed on type OR category OR unclassifiable record, independent of reconciler state or stored-selector correctness. A mixed subscription still receives its tenant-accessible events. (The last-mile enforcement at HTTP-send time is in cycles-server-events 0.1.25.23.)
- Startup reconciler: SSCAN-batched, atomic CAS writes, background daemon with retry — strips admin-only selectors from existing concrete-tenant offender rows (disables empty-both rows), and migrates null-owner rows to
__system__. Areconcile-dry-runmode reports without mutating.
Also: the empty-both webhook match-ALL update door is closed; the tenant/admin webhook boundary is normative and conformance-binding (INVARIANT 2). Replay hardening: correct pagination (no equal-timestamp skips, no duplicates), all-or-narrow completeness (a replay delivers every deliverable event in its window or returns 400 to narrow — never a silent partial), max_events validated 1–1000, and structured dispatch classification (a real backend failure logs a degraded warning; an intended concurrent-disable/guard logs benign info). /test synthetic probe is a documented, narrow invariant exception.
⚠ Deploy ordering (operational, important): deploy and fully roll out cycles-server-events 0.1.25.23 FIRST (the last-mile boundary) before this version. This admin version begins blocking admin-only selectors at the write/enqueue path; the events worker enforces the boundary at HTTP send for anything already queued or retried. If admin is deployed before the events fleet is fully upgraded, a mixed-version window exists where an old (0.1.25.22) worker can still send a queued violating delivery. The full-stack compose in this release pins events 0.1.25.23.
Operators — audit/remediate existing rows: the reconciler auto-strips offenders on startup (dry-run available); the 0.1.25.50/.51 CHANGELOG includes a redis-cli/jq audit recipe. Migration for legit per-tenant admin monitoring: use a __system__-owned subscription with client-side tenant filtering on the envelope tenant_id (admin lifecycle events are null-scoped, so a scope_filter cannot select them per tenant; only policy.* is scope-filterable). Residual provenance/ownership follow-up tracked as #209's linked issue.
Compatibility: BEHAVIOR CHANGE — admin create/update requests provisioning admin-only categories/types on a concrete-tenant subscription now return 400 (previously 201/200); replay windows exceeding the scan limit or max_events deliverable count now return 400 (narrow the window). No Redis/wire-schema change. Governance spec v0.1.25.40/.41 (runcycles/cycles-protocol). Version bump 0.1.25.50 → 0.1.25.51.
1,600+ tests green; JaCoCo ≥95% on all modules; external security review (codex) SHIP across the full fix. Full details: CHANGELOG.md [0.1.25.51], AUDIT.md, PR #210.
v0.1.25.50 — security: tenant webhook event_categories boundary
Security release. Closes an authorization gap in tenant self-service webhook subscriptions.
Security
- Tenant-plane webhook
event_categoriesare now validated against the tenant-accessible boundary. Tenant self-service webhook create and update rejected admin-onlyevent_types(api_key/policy/webhook/system), but did not validateevent_categories— and category matching is additive with type matching. A tenant key could create or update a subscription with one allowedevent_typeplus"event_categories": ["api_key"](or policy/webhook/system) and receive admin-only event classes for its tenant. Both create and update now reject admin-only categories with 400INVALID_REQUEST, deriving the boundary from a singleEventCategory.isTenantAccessible()source (governance spec revision v0.1.25.38, runcycles/cycles-protocol#126). Admin-plane webhook endpoints are unaffected. - Operators: audit existing tenant-plane subscriptions created on 0.1.25.49 and earlier for smuggled admin-only categories, and for the match-ALL rows below. A copy-pasteable
redis-cli+jqrecipe (flagging bothADMIN_CATEGORIESandMATCH_ALLrows, with the admin-provisioned-provenance caveat) is in CHANGELOG.md[0.1.25.50]Security.
Fixed
- Empty-both match-ALL door closed.
WebhookService.updateapplied an emptyevent_types: []verbatim; with no categories, the matcher's empty-both branch made the subscription match every event class (admin-only included). Update now rejects a change that would leave a subscription with neitherevent_typesnorevent_categories(both planes; the state is not creatable on either). Category-only subscriptions remain legal.
Follow-up (tracked, not in this release): whether a tenant should retain admin-only categories on a legacy/admin-created subscription it owns is a separate provenance/ownership question — tracked as #209.
Compatibility
- No HTTP request/response schema, Redis data model, or Lua change. Requests that previously smuggled admin-only categories now return 400; legitimate tenant-accessible categories are unaffected. Legacy offender rows keep delivering until repaired — see the audit recipe.
- Full-stack compose bundles
cycles-server0.1.25.47 andcycles-server-events0.1.25.22.
1,566 tests green including integration + contract validation against governance spec v0.1.25.38; JaCoCo ≥95% on all modules; external security review (codex) SHIP. Full details: CHANGELOG.md [0.1.25.50], AUDIT.md, PR #208.
v0.1.25.49 — spec-conformant webhook scope-filter matching + replay filtering
Spec-conformance release: webhook scope_filter matching now implements the admin OpenAPI spec's wildcard semantics, and replay honors the subscription's scope filter.
Changed
- BEHAVIOR CHANGE — webhook
scope_filtermatching is spec-conformant.WebhookRepository.matchesScopepreviously did literal prefix matching (trailing-/*filters matched nothing, anystartsWithscope matched, null-scope events matched every filter). Now per the spec: exact match without a wildcard; trailing*= prefix match on children only (non-empty child segment; the bare base scope and the degeneratebase/do not match); bare*= any event that has a non-blank scope; null/blank event scopes are unscoped and excluded from every scope-filtered subscription; null/blank filters still match everything. See CHANGELOG[0.1.25.49]for the full migration guide (descendants-only rewrites, base+descendants needing two subscriptions, accidental sibling-prefix matches, unscoped events). - With cycles-server v0.1.25.47's matching refinements, both planes now match identically — the same filter delivers the same scopes whether an event is emitted by the runtime or the admin plane, and both matchers pin the same test table.
Fixed
- Webhook replay honors
scope_filter. Replay previously queued every event in the requested range (afterevent_typesfiltering) directly to the subscription, bypassing scope matching — it could deliver events the subscription would never have received live. Replay now applies the same spec-conformant matcher as live dispatch.
Compatibility
- No HTTP request/response schema, Redis data model, or Lua change. Which subscriptions receive a given event (live or replayed) can change as described above — operators using bare-prefix filters should apply the CHANGELOG migration guide.
- Release prep pins: full-stack compose bundles
cycles-server0.1.25.47andcycles-server-events0.1.25.22.
Full details: CHANGELOG.md [0.1.25.49], AUDIT.md entries, PR #206 (11+6 matcher tests + 5 replay tests ported to both planes).
v0.1.25.48 — EventDataTenantCascade payload mapping + fleet pin alignment
Internal-consistency release: typed cascade event payload mapping + fleet pin alignment. No wire change.
Fixed
- Cascade event payloads map to their own typed class. The four
*_via_tenant_cascadeEventTypes were registered inEventPayloadTypeMappingagainst nearest-fit lifecycle classes; the payloadTenantCloseCascadeServiceactually emits fits none of them, so the warn-only payload-shape validator flagged every cascade emission. NewEventDataTenantCascademodel mirrors the spec schema of the same name (governance spec v0.1.25.35); all four kinds remap to it.Event.dataon the wire is unchanged — only the internal validation/registry class moved.
Changed
- Prod compose self-pin →
0.1.25.48; bundled full-stack pins →cycles-server 0.1.25.46(public-endpoint 429 rate limiting) andcycles-server-events 0.1.25.22(delivery-time SSRF guard — webhook targets on http/private networks needallow_http/CIDR config viaPUT /v1/admin/config/webhook-security; see that release's notes).
Compatibility
No HTTP request/response, Redis, event-wire, or spec-surface change.
Full details: CHANGELOG.md [0.1.25.48], AUDIT.md 2026-07-04 entries. 804 tests green, spec coverage 46/46, JaCoCo ≥95%.
v0.1.25.47 — production deployment hardening: image healthcheck, JAVA_OPTS, full-stack readiness + fleet pins
[0.1.25.47] — 2026-06-26
Fixed
- Production Compose image tags now point at
cycles-server-admin:0.1.25.47. - The admin runtime image now includes a built-in readiness healthcheck and
starts Java throughexec java $JAVA_OPTS -jar app.jar, so the JVM receives
container SIGTERM directly and operators can tune JVM flags through
JAVA_OPTS. - Production Compose now sets conservative JVM options for the admin container
throughJAVA_OPTS. - Full-stack production Compose now references current published sibling images:
cycles-server:0.1.25.44andcycles-server-events:0.1.25.20. - Full-stack production Compose now passes
ADMIN_API_KEYto the runtime
service, disables runtime public SpringDoc/Swagger and tenant-labelled custom
Prometheus metrics, and probes runtime readiness rather than aggregate health. - Full-stack production Compose now publishes the events management port
9980
and probes/actuator/health/readiness; the worker app port7980remains
internal. - README/OPERATIONS deployment docs now reflect required production Compose
environment variables, current Spring Boot/Jedis versions, protected docs
defaults,JAVA_OPTS, and the events management-port split.
Compatibility
- Deployment and documentation hardening only. No admin HTTP schema, Redis data
model, event payload, webhook wire, Lua, or cycles-protocol spec change.
v0.1.25.46 — dependency alignment: Jedis 7.5.2 + springdoc 2.8.17
[0.1.25.46] — 2026-06-26
Changed
- Jedis 7.5.0 → 7.5.2 — aligns the admin service with
cycles-server(already
on 7.5.2) and picks up the latest 7.5.x Redis-client patches. - springdoc-openapi 2.8.16 → 2.8.17 — latest 2.8.x, matching
cycles-server.
Compatibility
- Dependency-currency only. No HTTP request/response, Redis data model, Lua,
event, webhook-wire, or spec change.
v0.1.25.45 — production readiness: operational-endpoint auth, Redis health, webhook-secret encryption enforcement
[0.1.25.45] — 2026-06-25
Security
- Hardened production Compose Redis defaults: Redis no longer publishes
6379, requiresREDIS_PASSWORD, authenticates health checks, and restarts
unless stopped. - Protected aggregate actuator, OpenAPI, and Swagger endpoints with
X-Admin-API-Key; exact liveness and readiness probe endpoints remain open. - Production Compose now requires
WEBHOOK_SECRET_ENCRYPTION_KEYand starts
the admin service withWEBHOOK_SECRET_ENCRYPTION_REQUIRED=true, causing
startup to fail closed when webhook secret encryption is required but no key
is configured. - Webhook test delivery now re-validates stored target URLs against the
current SSRF policy before opening an outbound connection.
Changed
- Production health checks and release smoke tests now use
/actuator/health/readiness, which includes Redis readiness. - Generated API docs are disabled by default unless
API_DOCS_ENABLED=true. - Production Compose enables per-source, per-process throttling for repeated
401/403 authentication failures; throttled responses return
429 LIMIT_EXCEEDEDwithout writing another failure audit row. - Production Compose image tags now point at
0.1.25.45.
Compatibility
- No admin request/response body, Redis key/value, Lua, event payload, webhook
payload, or cycles-protocol spec change. - Operational tooling that reads protected actuator or docs endpoints must send
X-Admin-API-Key. Kubernetes-style liveness/readiness probes do not need an
admin key.
v0.1.25.44 — operational logging context + log sanitization (rolls up .43)
[0.1.25.44] — 2026-06-24
Fixed
- Added common CR/LF flattening for dynamic operator-log fields across
exception, auth rejection, controller side-effect, bulk-row, event dispatch,
webhook test/replay, webhook security config, and audit fallback logs. - Extended the same flattening to the data-plane repository, idempotency, and
config logs (api-key, audit, budget, event, policy, tenant, webhook,
webhook-delivery) via a sharedLogSanitizer, so request-derived strings
logged below the controller layer cannot inject log lines either. - Flattened caller-supplied
X-Request-Idat filter ingress and sanitized
request-attribute helper methods before those values are reused in auth,
exception, audit, and controller logs. - Kept the v0.1.25.43 operational context while preventing request/config/
exception strings from injecting misleading log lines.
Compatibility
- No admin HTTP request/response, Redis key/value, Lua, event payload, webhook
wire, or spec change.
[0.1.25.43] — 2026-06-24
Changed
- Ops logging context review. Admin API exception, auth rejection,
controller side-effect, webhook replay/test, event dispatch, idempotency,
audit, and Redis repository logs now include stable operational identifiers
such as method/path/route, status/error code, tenant, key id, budget/policy/
webhook/event/delivery ids, request id, trace id, correlation id, queue names,
and exception class where available. - Replaced the ambiguous
Landed in governance exception handlerlog with a
structuredGovernance exception handledrecord that names the endpoint,
error code, status, request id, trace id, and exception class. - Startup, CORS, Redis, and webhook security config logs now use structured
fields instead of banner-style or context-poor text. - Sensitive or noisy values are avoided in logs: API-key/idempotency secrets are
not emitted, idempotency keys are fingerprinted, and webhook test delivery
failures logtarget_hostinstead of the full subscriber URL.
Compatibility
- No admin HTTP request/response shape change.
- No Redis key, queue, Lua, or payload-shape change.
- No cycles-protocol spec change.
v0.1.25.42 — Apache Tomcat CVE patch (rolls up Jedis 7.5.0 + SB 3.5.15 + CI/supply-chain hardening)
Security and dependency-hygiene rollup accumulated since v0.1.25.41. No code
or wire-format changes — pom, CI, and ops only. The headline is the Apache
Tomcat CVE patch; the same release boundary also rolls up a Jedis major bump,
a Spring Boot patch, and supply-chain/CI hardening.
Changed
- Tomcat 10.1.54 → 10.1.55 (security). Re-introduced the
<tomcat.version>10.1.55</tomcat.version>override to close 3 CRITICAL +
3 HIGH + 1 LOWtomcat-embed-coreCVEs (CVE-2026-43515 / -43512 / -41293
CRITICAL; -43513 / -42498 / -41284 HIGH; -43514 LOW) that Trivy flagged
against Spring Boot 3.5.14's managed 10.1.54. Same override pattern as the
retainedcommons-lang3pin; remove once Spring Boot's BOM manages
10.1.55+. Full CVE breakdown inAUDIT.md. - Jedis 6.2.0 → 7.5.0 (major). Redis-client major bump aligning the
admin service with the rest of the fleet. All call sites use stable APIs;
CI's full suite (782+ tests) passes on 7.5.0. - Spring Boot 3.5.14 → 3.5.15 (patch). Upstream patch release. The
commons-lang3 3.18.0override is retained — SB 3.5.15's BOM still manages
3.17.0, where CVE-2025-48924 is unfixed. - Container log rotation. All four
docker-compose*.ymlfiles gain a
sharedjson-filelogging anchor (max-size: 10m,max-file: 5), capping
each container at 50 MB to prevent unbounded*-json.loggrowth on
long-running deployments.
Security / CI
- Supply-chain hardening: OpenSSF Scorecard workflow, all third-party GitHub
Action SHAs pinned, workflow token permissions tightened, Alpinegnutls
CVE-2026-33845 patched in the image, and a two-phase Trivy-gated release
build (scan before push). - Trivy container gates (release + PR) now set
limit-severities-for-sarif: truesoexit-codehonors the declaredHIGH,CRITICALseverity. In
format: sarifmode the action otherwise gates on any fixable finding of
any severity. A known fixable MEDIUM (jackson-databindCVE-2026-54515,
fix awaiting the unreleased jackson 2.21.5) is reported but no longer
blocks publish; HIGH/CRITICAL still fail the gate.
v0.1.25.41 — drop tomcat override (SB 3.5.14 BOM-managed) + Jedis fleet alignment
Dependency hygiene release. No application-level code or wire-format changes — pom-only patch.
Changed
- Spring Boot 3.5.13 → 3.5.14. Patch upgrade picking up upstream security hardening (constant-time DevTools secret comparison,
RandomValuePropertySourceSecureRandom, consistent SSL hostname verification,ApplicationPidFileWriter/ApplicationTempsymlink fixes). - Drop
<tomcat.version>10.1.54</tomcat.version>override. SB 3.5.14's BOM now manages Tomcat 10.1.54 directly (verified againstspring-boot-dependencies-3.5.14.pom). The explicit pin from v0.1.25.33 (closing CVE-2026-34483 / CVE-2026-34487) is now redundant. - Jedis 5.2.0 → 6.2.0 (major). Aligns with
cycles-server-events(6.2.0) andcycles-server(6.2.0) on a single Redis-client major across the fleet. Jedis 6.1.0 explicitly restored binary compatibility forSetParams(#4225 upstream); all 782 tests pass on 6.2.0. commons-lang3 3.18.0override retained — SB 3.5.14's BOM still manages 3.17.0 (CVE-2025-48924 unfixed there). Comment updated to reference SB 3.5.14.
See CHANGELOG.md for the full entry.
Fleet alignment
Matching releases: cycles-server-events v0.1.25.12, cycles-server v0.1.25.18.