Skip to content

Releases: netresearch/ofelia

v0.25.1

16 May 07:55
v0.25.1
1b1b9ad

Choose a tag to compare

Highlights

A patch release that closes the docker hijack regression class that crept in alongside v0.25.0's TLS/scheme rework, plus a webhook multi-instance dedup bug and the long-standing url-only-webhook documentation gap. Anyone running run_exec jobs through tecnativa/docker-socket-proxy or any plain-TCP/HTTP Docker host should upgrade. Anyone wiring more than one webhook per job (typically one for success, one for error) needs the multi-webhook fix. Anyone who tried to configure a webhook with only a url = ... since v0.16.0 will find that it actually works now.

🐳 Docker daemon connectivity

  • DOCKER_HOST=tcp://... (plain HTTP) run_exec jobs work again. v0.25.0 regressed plain-tcp:// hijack APIs (ContainerExecAttach, ContainerAttach, ContainerLogs --follow) with the unhelpful error tls: first record does not look like a TLS handshake. Container discovery kept working because the regular HTTP path was unaffected; only run_exec and friends failed. Root cause was a Go stdlib × Docker SDK interaction: net/http lazily auto-configures HTTP/2 on the first request and allocates TLSClientConfig in place for ALPN — and the SDK's hijack dialer reads that as "TLS required" and dials tls.Dial against your plaintext daemon. The fix suppresses the lazy auto-config on non-TLS transports. TLS Docker hosts (https://, tcp+tls://) are unaffected — ALPN h2 negotiation still works there. (#681, fixes #668)
  • DOCKER_HOST=http://... hijack now works for the same APIs. Same SDK dialer, different failure mode: net.Dial("http", addr) is rejected by Go's net package ("http" isn't a valid network name) so the call surfaced as dial http: unknown network http. The fix installs an explicit TCP DialContext so the SDK picks our dialer instead of reaching the broken fallback. Path-bearing hosts (http://daemon:port/v1.43) and IPv6 literals (http://[::1]:2375) round-trip cleanly via url.Parse, matching how the SDK already handles tcp://. (#686, fixes #682)

🔁 Webhooks

  • Url-only webhooks finally work out of the box. The docs/webhooks.md "Custom Webhooks" section has been promising since v0.16.0 that you could declare a webhook with just url = ... and have Ofelia POST a JSON payload to it — but the code path actually returned preset specification cannot be empty and refused to attach. v0.25.1 ships a new bundled json-post preset and a [global] webhook-default-preset selector that ships with json-post as the default fallback. A webhook with only url = ... (or Docker label ofelia.webhook.<name>.url: ...) now attaches and fires, no custom preset YAML required.
    • Audit-and-pin upgrade impact: a stale URL-only config that previously sat inert will now actually POST to whatever's in url. Pin webhook-allowed-hosts to a specific allow-list if you want to restrict egress; set webhook-default-preset = (empty) to keep pre-upgrade behavior and require every webhook to declare preset explicitly. Three-state semantics: nil = "operator did not set" (use bundled fallback), non-nil empty = "explicit opt-out", non-nil non-empty = "operator's chosen fallback name". (#677, fixes #676)
  • Jobs that reference more than one webhook now fire every listed webhook. The middleware container deduplicated middlewares by Go type, so handing it two *Webhook instances (e.g. one with trigger: success, one with trigger: error) kept only the first and silently dropped the rest — the error webhook attached to nothing and never ran. The same dedup also shadowed scheduler-level webhooks against per-job webhooks during propagation, so any job declaring webhooks: silently lost global notifications too. The fix attaches a single per-job composite carrying the union of [global] webhook-webhooks and the job's own webhooks selector, deduplicated by name. The previously-silent failure path (unknown webhook name, preset-load failure, missing required variable) now emits an slog.Error keyed by job name and webhook list so misconfigurations show up in the log. (#671, fixes #670)
  • Webhook retries no longer hold the daemon hostage at shutdown. (*Webhook).sendWithRetry's inter-attempt backoff used a bare time.Sleep that ignored ctx cancellation, so SIGTERM mid-retry pinned a goroutine for up to retry-delay × retry-count. The in-flight HTTP request had the same shape — its context was derived from context.Background(), so even a hung POST blocked shutdown for the full timeout. Both halves now drain on scheduler cancellation. Worst-case shutdown contribution from a single webhook drops from retry-delay × retry-count + timeout (multi-minute on aggressive retry budgets) to roughly one network round-trip on cancel. Callers see an error chain wrapping context.Canceled / context.DeadlineExceeded rather than the previous "all N attempts failed" message after the full timeout elapsed. (#685, fixes #673)

📖 Docs

  • New docs/TROUBLESHOOTING.md sections for both hijack failure modes (tls: first record does not look like a TLS handshake on tcp://, dial http: unknown network http on http://), slotted alongside the existing #605 socket-proxy section so operators scanning by symptom land on the right entry.
  • docs/webhooks.md retry-config table now documents the shutdown drain semantics — retry-count × retry-delay + timeout bounds the worst-case daemon-shutdown contribution from any single webhook, and the retry loop observes scheduler cancellation since this release.
  • Webhook lifecycle flowchart added to docs/webhooks.md for the trigger / dedup / retry / shutdown story. (#680)

Dependencies

No direct dependency bumps in this release. The Go toolchain and module graph stay at v0.25.0's pinned versions.

Thanks

Special thanks to the external reporters whose issues drove the bugfixes in this release:

  • @groknt for #668 — reporting that run_exec jobs failed with the cryptic TLS handshake error on plain-tcp:// socket-proxy setups, and following up with the exact diagnostic dump (env, debug logs, container labels, full compose file) that let us reproduce the bug in a self-contained Go program and isolate it to the Go std-lib × Docker SDK interaction. The reproducer also surfaced the sibling http:// failure mode that ships in this release as #682 / #686.
  • @techsolo12 for #670 — reporting that error-trigger webhooks were silently being dropped from jobs that also declared a success-trigger webhook. The repro pointed straight at the type-keyed dedup in middlewareContainer.Use, which had been a latent footgun waiting for the first N-instance middleware to land.

Container image

ghcr.io/netresearch/ofelia:0.25.1
ghcr.io/netresearch/ofelia:0.25
ghcr.io/netresearch/ofelia:0
ghcr.io/netresearch/ofelia:latest

Verification

All release assets (binaries, SBOMs, checksums) are cosign-signed with keyless OIDC and include SLSA Level 3 build provenance. Verify with:

cosign verify-blob \
  --bundle ofelia-linux-amd64.bundle \
  --certificate-identity-regexp 'https://github\.com/netresearch/(\.github|ofelia)' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ofelia-linux-amd64

Full Changelog: v0.25.0...v0.25.1

v0.25.0

14 May 15:59
v0.25.0
e329279

Choose a tag to compare

Highlights

A security-focused minor release: the Go 1.26.3 toolchain clears six stdlib advisories, three silent-downgrade vectors in TLS handling are closed, and tcp+tls:// Docker hosts work again with the cert material the operator declared. MaxRuntime-bounded jobs now properly clean up their containers, and the [global] config story is finally consistent across INI and Docker labels.

🔐 Security

  • Go toolchain 1.26.21.26.3 clears net/mail, html/template, net, and net/http advisories reachable from this codebase (GO-2026-4986, -4982, -4980, -4977, -4971, -4918). Post-bump, only the two unfixable upstream moby advisories on docker/docker v28.5.2 remain. (#662)
  • No more silent TLS downgrades in three places where Ofelia previously masked an operator misconfiguration as fail-open: HTTPS Docker daemons with broken cert material, SMTP middleware on servers that don't advertise STARTTLS, and webhook URL allow-lists collapsed to ["*"] by a typo. Each one now fails closed with a typed sentinel and a clear log line at startup. (#660, #646)
  • ⚠️ Operator impact if you relied on OpportunisticStartTLS for SMTP relays that don't advertise STARTTLS (legacy local relays, MailHog dev fixtures): set the new smtp-tls-policy = opportunistic (or = none for test fixtures only) to restore the previous behavior. The new default is mandatory. See docs/TROUBLESHOOTING.md for migration recipes.
  • Remote preset fetches (webhook-allow-remote-presets = true) now route through the same TransportFactory() the webhook stack uses, instead of the implicit http.DefaultClient. (#624)

🐳 Docker daemon connectivity

  • tcp+tls:// is back on the DOCKER_HOST allow-list now that the TLS plumbing landed — DOCKER_CERT_PATH / DOCKER_TLS_VERIFY (and the equivalent ClientConfig overrides) are wired into the HTTP transport, and tcp+tls:// without cert material now fails loud instead of silently dialing TLS with the system CA pool. (#625)
  • DOCKER_HOST schemes are now validated against an allow-list (unix://, tcp://, tcp+tls://, http://, https://, npipe://) and normalized to lowercase. Unsupported values (ssh://, fd://, typos) fail at startup with a clear error instead of falling through to plain-TCP. (#612)
  • DOCKER_HOST=tcp://... plus DOCKER_CERT_PATH now actually negotiates TLS end-to-end — the SDK URL is silently upgraded to https:// so the configured cert and pinned CA apply on the wire (mirroring the docker CLI). Previously the cert was loaded but the SDK still spoke plain TCP. (#647)
  • Startup is no longer hostage to a wedged daemon: NegotiateAPIVersion, periodic health/ready pings, the doctor diagnostic, and remaining unbounded Docker SDK calls are all wrapped in context.WithTimeout. (#611, #636)

🧹 Correctness

  • MaxRuntime cancellation now stops and removes the container or swarm service. The deadline-wiring fix in #651 returned control to the wrapper, but the deferred cleanup reused the already-cancelled parent context — so stop/remove API calls were rejected before they reached the daemon. The cleanup path now uses a fresh context.WithTimeout(context.Background(), jobCleanupTimeout) and RunServiceJob mirrors the same fix. Operators previously seeing Exited containers piling up should see them properly cleaned after this release. (#659)
  • [global] Docker label keys now reach the live config across every subsystem. Setting e.g. ofelia.smtp-host=mail.example.com on a service container previously decoded into a scratch struct that was discarded after the per-job merge — Slack, Mail, Save, scheduling, and the runtime knobs (log-level, notification-cooldown) all silently kept their INI defaults. New per-subsystem helpers and an applyAllowListedGlobals aggregator wire every allow-listed non-webhook global through both the boot and reconcile paths with the same "INI wins when set; label only fills empty/default" precedence as mergeWebhookGlobals. (#661)
  • Pervasive nil-guard sweep across the Docker adapter family. Every public method on every *ServiceAdapter (Container, Exec, Image, Event, Network, Swarm, System) returns a typed sentinel (ErrNilDockerClient, ErrNilExecConfig, ErrNilContainerConfig) instead of panicking if its internals are misconstructed; every convertTo* / convertFrom* helper nil-guards its pointer argument and returns a zero value. (#626, #639, #648, #658)

📖 Docs and DX

  • Webhook config has one source of truth now: c.WebhookConfigs.Global aliases &c.Global.WebhookGlobalConfig once in NewConfig(), eliminating the dual-store anti-pattern that needed a hand-rolled syncGlobalWebhookConfig shim. INI live-reload of webhook-allowed-hosts now also re-runs WebhookManager.InitManager() so the URL validator picks up changes at runtime. (#637)
  • Docker label ofelia.webhooks deprecated in favor of ofelia.webhook-webhooks to match the documented INI [global] key name. The legacy form still works and logs a one-shot deprecation warning per process — migrate before the next major release. (#620)
  • Documentation reconciled with reality across Slack middleware (removed never-supported keys), Save middleware (documented restore-history), webhook globals (webhook-webhooks, webhook-trusted-preset-sources, webhook-preset-cache-dir), the poll-interval split, and web-trusted-proxies. New TestConfigGlobalKeysAreDocumented walks the Config.Global reflection tree and asserts every mapstructure key is mentioned in at least one operator-facing docs file, so this drift class is now caught mechanically. (#621, #635, #656)
  • Unified Docker host / scheme resolution behind a single resolveDockerHost seam, with the dispatch table and the allow-list derived from one source so they cannot drift. (#629)

Dependencies

Go toolchain 1.26.21.26.3. Direct deps: docker/cli 29.4.2 → 29.4.3, golang.org/x/crypto 0.50 → 0.51, golang.org/x/term 0.42 → 0.43, golang.org/x/text 0.36 → 0.37. Indirect-graph refresh via go get -u all (otelhttp 0.67 → 0.68, grpc 1.80 → 1.81, x/sys/mod/net/tools bumps, genproto date refresh, docker-credential-helpers 0.9.5 → 0.9.7, morikuni/aec 1.0 → 1.1, plus tertiary bumps).

Thanks

Special thanks to the external reporters whose issues drove substantive fixes in this release:

  • @techsolo12 for #604 — reporting that webhook-allow-remote-presets (and the other documented webhook-* [global] keys) emitted "Unknown configuration key" warnings and silently fell back to defaults. Fixed in #618.
  • @groknt for #605 — reporting that DOCKER_HOST=tcp://... configurations failed to start because the HTTP transport's dialer was hard-pinned to unix:///var/run/docker.sock. The trail from that report cascaded into the wider TLS/scheme rework that landed across #606, #612, #613, and #629.

Container image

ghcr.io/netresearch/ofelia:0.25.0
ghcr.io/netresearch/ofelia:0.25
ghcr.io/netresearch/ofelia:0

Verify your download

Per-asset cosign bundles. Verify any single file:

cosign verify-blob \
  --bundle ofelia-linux-amd64.bundle \
  --certificate-identity-regexp "https://github.com/netresearch/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ofelia-linux-amd64

Each binary also ships an SPDX SBOM (.spdx.json) with its own attached cosign bundle.

v0.24.0

10 May 08:54
v0.24.0
90c1a86

Choose a tag to compare

Highlights

This release fixes Docker Compose job naming so it actually works as documented, adds a real end-to-end test harness that runs the compiled binary, and ships a Go 1.26.2 build with stdlib security patches.

🚨 Breaking: Docker Compose job naming

If you reference jobs across containers in a Compose stack, the documented Cross-Container Job References (Docker Compose) feature is now functional for the first time. Ofelia previously stripped the com.docker.compose.service label before it reached the naming code, so jobs ended up with their fallback names instead of the documented Compose service names.

After upgrading, jobs in Compose setups may appear under different names — those documented in docs/CONFIGURATION.md. Configurations that worked around the bug by referencing the old (incorrect) names need to be updated. (#597)

End-to-end testing

The test suite gains a subprocess-based harness that runs the actual compiled ofelia binary and exercises real schedules, real Alpine containers via Docker, the validate command, and graceful shutdown on SIGTERM/SIGINT. This catches integration regressions that unit tests miss. (#581)

Security

  • Go bumped to 1.26.2 for stdlib security fixes (#557)

Other improvements

  • log-level=... errors now list every accepted level instead of a generic "invalid value" message (#599)
  • make lint works again — golangci-lint is now installed via the v2 module path (#600)
  • .envrc hooks detection works correctly inside git worktrees (#598)
  • .gitignore /ofelia pattern is anchored so it cannot shadow source files (#574)
  • Stabilized flaky tests for scheduler shutdown, retry backoff, and rate limiter (#582, #601)

Build & supply chain

The release pipeline migrated to the unified netresearch/.github reusable workflow (#566, #587), with auto-merge moved to an org-level reusable workflow (#567) and the standalone integration.yml retired in favor of go-check (#579). Checksum signing now uses cosign --bundle (#547).

Dependencies

Routine dependency bumps: github.com/netresearch/go-cron 0.13.1 → 0.14.0, github.com/docker/cli 29.3.0 → 29.4.0, github.com/docker/go-connections 0.6.0 → 0.7.0, github.com/go-viper/mapstructure/v2 2.4.0 → 2.5.0, golang.org/x/{crypto,term,text} updates, OpenTelemetry exporter 1.42 → 1.43, Alpine base image refresh, GitHub Actions group bumps.

Thanks

Special thanks to @smitsyn for diagnosing and fixing the Compose label filtering issue (#597) — a long-standing gap between the documentation and the actual behavior.

Container image

ghcr.io/netresearch/ofelia:0.24.0
ghcr.io/netresearch/ofelia:0.24
ghcr.io/netresearch/ofelia:0

Verify your download

Per-asset signatures are bundled. Verify any single file:

cosign verify-blob \
  --bundle ofelia-linux-amd64.bundle \
  --certificate-identity-regexp "https://github.com/netresearch/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ofelia-linux-amd64

Verify checksums against the signed manifest:

cosign verify-blob \
  --bundle checksums.txt.bundle \
  --certificate-identity-regexp "https://github.com/netresearch/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt
sha256sum -c checksums.txt --ignore-missing

Verify build provenance:

gh attestation verify <artifact> --repo netresearch/ofelia

Verify container image:

cosign verify ghcr.io/netresearch/ofelia:0.24.0 \
  --certificate-identity-regexp "https://github.com/netresearch/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com"
gh attestation verify oci://ghcr.io/netresearch/ofelia:0.24.0 --repo netresearch/ofelia

Full changelog: v0.23.1...v0.24.0

v0.23.1

23 Mar 22:55
v0.23.1
9b41451

Choose a tag to compare

What's Changed

Security

  • Migrate go-viper/mapstructure v1 to v2.4.0 — fixes GO-2025-3787 and GO-2025-3900 (sensitive information leak in logs when processing malformed data) (#544)

Fixed

  • Release pipeline migrated from slsa-github-generator to actions/attest-build-provenance via org-wide reusable workflow at netresearch/.github (#542). This fixes the v0.23.0 release which had no binaries due to SHA-pinning conflict.

Note: v0.23.0 was released without binaries due to the pipeline issue fixed in this release. Users should use v0.23.1 instead.

v0.23.0

22 Mar 16:13
v0.23.0
59b52a1

Choose a tag to compare

What's Changed

Environment File & Container Env Support

Load environment variables from external sources for all job types (#540, closes #314, #336, #351):

  • env-file: Load KEY=VALUE pairs from files, like Docker's --env-file. Supports multiple files, quoted values, export prefix, and special characters.
  • env-from: Copy environment variables from a running Docker container at job execution time.

Merge order (last wins): env-file < env-from < environment (explicit always wins).

Bug Fixes

  • Environment variable values containing # or ; are no longer truncated by INI comment parsing (#539, fixes #538)
  • Environment variable expansion now works in webhook config values, section names, and the log-level pre-parse path (#539)

Security

  • SHA-pin all GitHub Actions and add Dependabot for actions updates (#536)

v0.22.0

20 Mar 10:11
v0.22.0
793791c

Choose a tag to compare

What's Changed

Environment Variable Substitution in INI Config

Support ${VAR} and ${VAR:-default} syntax in INI configuration files (#532, closes #362).

Keep secrets out of version-controlled config files:

[global]
smtp-password = ${SMTP_PASS}

[job-run "backup"]
image = ${BACKUP_IMAGE:-postgres:15}
command = pg_dump ${DB_NAME:-mydb}
  • ${VAR} — replaced if defined and non-empty; kept literal if undefined (typos visible)
  • ${VAR:-default} — uses default when undefined or empty
  • $VAR (no braces) — not substituted, keeping cron expressions and shell commands safe

Thanks to @nut-neek for describing their use case.

Dependencies

  • Bump OpenTelemetry modules to v1.42.0 (#533)
  • Bump step-security/harden-runner to v2.16.0 (#533)
  • Bump aquasecurity/trivy-action to v0.35.0 (#532)
  • Bump google.golang.org/grpc to v1.79.3 (#531)

Full Changelog: v0.21.5...v0.22.0

Verification

All binaries include SLSA Level 3 provenance attestations.

Verify binary provenance

slsa-verifier verify-artifact ofelia-linux-amd64 \
  --provenance-path ofelia-linux-amd64.intoto.jsonl \
  --source-uri github.com/netresearch/ofelia

Verify checksums signature

cosign verify-blob \
  --certificate checksums.txt.pem \
  --signature checksums.txt.sig \
  --certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.22.0" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

Included in this release

View all PRs and Issues included in this release

v0.21.5

18 Mar 08:14
v0.21.5
76f8a19

Choose a tag to compare

What's Changed

Two new features for this release.

Thanks to @sethlinnkuleuven for reporting #527.

Added

  • ofelia version command (#528): Print version information via ofelia version or ofelia --version. Release builds show tag and commit; dev builds fall back to Go build info.
  • Volume support for job-service-run (#529, closes #527): Mount host directories and named volumes into swarm service containers using the volume config key. Same source:target[:ro|rw] format as job-run.
[job-service-run "backup"]
schedule = @daily
image = postgres:15
network = cluster
volume = /host/script.sh:/script.sh
volume = backups:/backups:rw
command = /script.sh

Full Changelog: v0.21.4...v0.21.5

Verification

All binaries include SLSA Level 3 provenance attestations.

Verify binary provenance

slsa-verifier verify-artifact ofelia-linux-amd64 \
  --provenance-path ofelia-linux-amd64.intoto.jsonl \
  --source-uri github.com/netresearch/ofelia

Verify checksums signature

cosign verify-blob \
  --certificate checksums.txt.pem \
  --signature checksums.txt.sig \
  --certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.5" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

Included in this release

View all PRs and Issues included in this release

v0.21.4

17 Mar 00:05
v0.21.4
2414bf2

Choose a tag to compare

What's Changed

Fix job-service-run network attachment being silently dropped — services now correctly join the specified network.

Thanks to @sethlinnkuleuven for reporting the issue.

Fixed

  • Service network not attached (#524): convertToSwarmSpec now reads networks from both ServiceSpec.Networks and TaskTemplate.Networks, fixing a mismatch introduced during the domain/adapter refactor
  • Service inspect missing fields: convertFromSwarmService now converts Mounts, RestartPolicy, Resources, Networks, Mode, Placement, LogDriver, and EndpointSpec — all were previously lost silently

Added

  • Swarm service adapter now converts Placement, LogDriver, and EndpointSpec in both directions — these domain types existed but had no conversion code
  • 13 round-trip tests verifying every service spec field survives the domain→swarm→domain conversion cycle

Full Changelog: v0.21.3...v0.21.4

Verification

All binaries include SLSA Level 3 provenance attestations.

Verify binary provenance

slsa-verifier verify-artifact ofelia-linux-amd64 \
  --provenance-path ofelia-linux-amd64.intoto.jsonl \
  --source-uri github.com/netresearch/ofelia

Verify checksums signature

cosign verify-blob \
  --certificate checksums.txt.pem \
  --signature checksums.txt.sig \
  --certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.4" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

Included in this release

View all PRs and Issues included in this release

v0.21.3

15 Mar 14:45
v0.21.3
7216aef

Choose a tag to compare

What's Changed

Wire missing container spec fields that the Docker API already supports but job types didn't expose, fixing environment not working for job-service-run (#519).

Thanks to @sethlinnkuleuven for reporting the issue.

Fixed

  • job-service-run: environment, hostname, and dir config keys now work — passed through to Docker Swarm ContainerSpec
  • job-run: working-dir config key now sets the container working directory; volumes-from was defined in the struct but never wired to Docker — now functional
  • job-exec: privileged config key now enables privileged exec mode
  • Corrected misleading documentation that claimed job-service-run inherits from RunJob (it embeds BareJob)
  • Removed non-existent swarm fields (replicas, placement-constraints, resource limits) from documentation examples

Full Changelog: v0.21.2...v0.21.3


Included in this release

View all PRs and Issues included in this release

v0.21.2

14 Mar 15:22
v0.21.2
f988960

Choose a tag to compare

Highlights

This security-focused release addresses 5 vulnerabilities and 6 stability issues discovered during a comprehensive code review.

Security hardening

  • Credential leak prevention: /api/config no longer exposes WebPasswordHash and WebSecretKey
  • CSRF bypass removed: The X-Requested-With header bypass has been eliminated
  • Rate limiter DoS fix: Stale entries are now cleaned up to prevent unbounded memory growth
  • IP spoofing prevention: X-Forwarded-For and X-Real-IP headers are only trusted from loopback or explicitly configured proxies
  • Configurable trusted proxies: New web-trusted-proxies option for deployments behind reverse proxies in non-loopback networks

Stability improvements

  • Context propagation to Docker API calls — scheduler shutdown, job removal, and max-runtime cancellation now reach Docker containers
  • Double-close panic on daemon done channel fixed with sync.Once
  • Concurrent map access crash in Config protected with mutex
  • Shutdown hooks execute in priority groups instead of all concurrently
  • Shutdown timeout now enforced even when hooks ignore context cancellation
  • Swarm services correctly return NonZeroExitError for non-zero exit codes

Changes

Security

  • fix(security): hide WebPasswordHash and WebSecretKey from /api/config (#511)
  • fix(security): remove CSRF bypass via X-Requested-With header (#511)
  • fix(security): implement rate limiter cleanup to prevent memory DoS (#511)
  • fix(security): only trust forwarded headers from trusted proxies (#511)
  • fix(security): make trusted proxies configurable (#511)
  • fix(security): also check X-Real-IP in rate limiter middleware (#511)

Bug Fixes

  • fix: propagate context to Docker API calls for cancellation support (#511)
  • fix: prevent double-close panic on daemon done channel (#511)
  • fix: add mutex to Config to prevent concurrent map access crash (#511)
  • fix: execute shutdown hooks in priority groups (#511)
  • fix: enforce shutdown timeout even when hooks ignore context (#511)
  • fix: return NonZeroExitError for non-zero Swarm service exit codes (#511)

Dependencies

  • chore(deps): bump golang.org/x/crypto from 0.48.0 to 0.49.0 (#512)
  • chore(deps): bump github.com/netresearch/go-cron from 0.13.0 to 0.13.1 (#514)
  • chore(deps): bump golang.org/x/time from 0.14.0 to 0.15.0 (#515)

Verification

All binaries include SLSA Level 3 provenance attestations.

Verify binary provenance

slsa-verifier verify-artifact ofelia-linux-amd64 \
  --provenance-path ofelia-linux-amd64.intoto.jsonl \
  --source-uri github.com/netresearch/ofelia

Verify checksums signature

cosign verify-blob \
  --certificate checksums.txt.pem \
  --signature checksums.txt.sig \
  --certificate-identity "https://github.com/netresearch/ofelia/.github/workflows/release-slsa.yml@refs/tags/v0.21.2" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  checksums.txt

Included in this release

View all PRs and Issues included in this release