docs/release_notes/v1.17.8.md
This update contains the following bug and security fix:
X-Forwarded-Host (CWE-346)When an application schedules a workflow with a deterministic instance ID and a later request re-schedules the same ID after the previous run has reached a terminal state, the previous run's retention reminder can keep firing in the scheduler indefinitely instead of draining.
Any deployment running workflows that are scheduled with stable, repeatable instance IDs is affected whenever the same ID is scheduled again within the configured retention window.
The most visible trigger is an at-least-once delivery against an idempotent handler that uses REUSE_ID_ACTION_TERMINATE, but plain ScheduleNewWorkflow calls against an already-completed instance ID follow the same path.
Visible symptoms include:
dapr_runtime_workflow_operation_count{operation=purge_workflow,status=failed} metric increments at exactly one tick per second per affected workflow, for as long as the bug is active.When a workflow is scheduled with an instance ID that already corresponds to a completed run, the workflow runtime resets the existing state and starts a fresh run. The retention reminder queued by the previous run is anchored to the previous run's completion time and is intentionally left in the scheduler so it can fire on the previous run's terminal-state retention TTL.
When that retention reminder fires, the workflow has been put back into a non-completed state by the fresh run, so the workflow runtime's purge path correctly refuses to delete state that is in flight, and signals "this run has not completed".
The retentioner treated that signal as a hard failure and re-raised it.
The retention reminder is created with a failure policy of Constant{Interval: 1s, MaxRetries: nil} (retry every second, forever), so the scheduler then retried the same reminder once per second indefinitely.
Only an operator-driven scheduler delete could clear it.
The retentioner now treats the "this run has not completed" signal the same way it already treats "this run no longer exists": as a drain-silently signal that the retention reminder is no longer applicable. Either the run has been purged (nothing to retain) or the run has been superseded by a fresh run (the fresh run will queue its own retention reminder on completion). Either way, the previous run's retention reminder is obsolete and the scheduler can let it drop.
Workflows already stuck in this state on existing 1.17 deployments recover automatically once the sidecar is upgraded to 1.17.8: the next retention reminder fire drains, the reminder is removed from the scheduler, and the workflow proceeds normally. No operator intervention or manual scheduler delete is required after the upgrade.
X-Forwarded-Host (CWE-346)When the Dapr Sentry OIDC HTTP server was started without a statically configured JWT issuer (--jwt-issuer) and without an allowed-hosts list (--oidc-allowed-hosts), the X-Forwarded-Host request header fully controlled the issuer and jwks_uri fields returned by /.well-known/openid-configuration.
An attacker with network reach to the OIDC endpoint (typically via a reverse proxy or ingress that forwards X-Forwarded-Host) could inject an arbitrary issuer URL on a single request.
Because the discovery response is served with Cache-Control: public, max-age=3600, the poisoned document could be cached by HTTP intermediaries for up to an hour.
Any deployment that enabled the Sentry OIDC server (--oidc-enabled --jwt-enabled) without configuring either a static issuer (--jwt-issuer) or an allowed-hosts list (--oidc-allowed-hosts) was affected.
This configuration is most likely on operators who turned on OIDC federation for Sentry-issued JWTs (e.g. to allow AWS IAM, GCP Workload Identity, or Azure AD relying parties to validate Dapr-issued tokens) without yet pinning the external hostname.
Visible exposure included:
issuer and jwks_uri pointed at an attacker-controlled host after a single request carrying X-Forwarded-Host: attacker.example.Setting either --jwt-issuer or --oidc-allowed-hosts already mitigated the issue on affected versions; relying parties that pinned the expected issuer rejected the forged token regardless.
handleDiscovery in pkg/sentry/server/oidc/oidc.go derives the OIDC issuer dynamically when no static --jwt-issuer is configured, as a convenience for operators who have not yet pinned an external hostname.
The derivation read X-Forwarded-Host unconditionally and substituted it for r.Host whenever the header was present, with no validation.
The companion allowedHostsValidationHandler middleware would have rejected unknown hosts, but is a no-op when --oidc-allowed-hosts is unset (the default), so neither layer authenticated the header in the default configuration.
handleDiscovery now only honors X-Forwarded-Host when --oidc-allowed-hosts is configured, i.e. when the allowed-hosts middleware has already validated the header against the operator's allowlist.
With no allowlist, the issuer and jwks_uri are derived from r.Host only, and the header is ignored.
Operators who deliberately rely on X-Forwarded-Host to advertise the proxy's public hostname in the discovery document should set --oidc-allowed-hosts to the set of hostnames they expect (e.g. --oidc-allowed-hosts=sentry.example.com); this is consistent with the existing guidance for reverse-proxy deployments and is now also the precondition for the header to influence the discovery document.
Operators who cannot upgrade immediately can mitigate by setting either --jwt-issuer (preferred, pins the issuer statically) or --oidc-allowed-hosts (restricts which hostnames may be reflected back).