From e451c7b9e5be0e698d50f8442aaacc78fd867a33 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 23 Jul 2026 09:40:23 +0530 Subject: [PATCH 1/5] fix(deploy): repair crash-looping CLI flags, wire missing security flags --enable-totp-login/--enable-mfa/--enable-email-otp/--enable-sms-otp don't exist in the authorizer binary; cobra rejects unknown flags so every deploy from this Dockerfile crash-loops on boot. Renamed to the current --disable-totp-login/--disable-webauthn-mfa/--disable-email-otp/ --disable-sms-otp/--disable-mfa flags. Also added --url (CWE-640 host-header-injection mitigation, was never wired to any env var), --oauth2-1-strict, and --enable-org-discovery, and surfaced AUTHORIZER_URL/OAUTH2_1_STRICT in render.yaml. Re-pinned the base image to 2.4.0-rc.7: 2.3.0 predates the flag rename above, so it doesn't have the --disable-* flags either. Move to the stable 2.4.0 tag once it ships. --- Dockerfile | 18 +++++++++++++----- render.yaml | 10 ++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8c7c11d..d9fcd6b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ # Base runs as USER authorizer (uid 1000). For SQLite, ensure mounted /data is writable by that user. -FROM quay.io/authorizer/authorizer:2.3.0 +# Pinned to the 2.4.0 release candidate because it is the first published +# image with the current CLI flag surface (--url, --oauth2-1-strict, +# --enable-org-discovery, --disable-totp-login/-webauthn-mfa/-email-otp/ +# -sms-otp/-mfa). Re-pin to the stable 2.4.0 tag once it ships. +FROM quay.io/authorizer/authorizer:2.4.0-rc.7 # Override so CMD runs in a shell and env vars (e.g. for Render) are expanded. See base image comment. # Use exec-form CMD with a single string so /bin/sh -c gets one argument; shell-form CMD can be split and drop into a shell. ENTRYPOINT ["/bin/sh", "-c"] @@ -28,6 +32,7 @@ CMD ["exec ./authorizer \\\n\ --allowed-origins=\"${ALLOWED_ORIGINS}\" \\\n\ --default-authorize-response-type=\"${DEFAULT_AUTHORIZE_RESPONSE_TYPE}\" \\\n\ --default-authorize-response-mode=\"${DEFAULT_AUTHORIZE_RESPONSE_MODE}\" \\\n\ + --oauth2-1-strict=\"${OAUTH2_1_STRICT:-false}\" \\\n\ --organization-name=\"${ORGANIZATION_NAME}\" \\\n\ --organization-logo=\"${ORGANIZATION_LOGO}\" \\\n\ --smtp-host=\"${SMTP_HOST}\" \\\n\ @@ -38,6 +43,7 @@ CMD ["exec ./authorizer \\\n\ --smtp-sender-name=\"${SENDER_NAME}\" \\\n\ --reset-password-url=\"${RESET_PASSWORD_URL}\" \\\n\ --backchannel-logout-uri=\"${BACKCHANNEL_LOGOUT_URI}\" \\\n\ + --url=\"${AUTHORIZER_URL}\" \\\n\ --env=\"${ENV}\" \\\n\ --host=\"${HOST:-0.0.0.0}\" \\\n\ --metrics-port=\"${METRICS_PORT:-8081}\" \\\n\ @@ -51,6 +57,7 @@ CMD ["exec ./authorizer \\\n\ --rate-limit-burst=\"${RATE_LIMIT_BURST:-20}\" \\\n\ --rate-limit-fail-closed=\"${RATE_LIMIT_FAIL_CLOSED:-false}\" \\\n\ --enable-login-page=\"${ENABLE_LOGIN_PAGE:-true}\" \\\n\ + --enable-org-discovery=\"${ENABLE_ORG_DISCOVERY:-false}\" \\\n\ --enable-playground=\"${ENABLE_PLAYGROUND:-true}\" \\\n\ --disable-admin-header-auth=\"${DISABLE_ADMIN_HEADER_AUTH:-true}\" \\\n\ --enable-graphql-introspection=\"${ENABLE_GRAPHQL_INTROSPECTION:-true}\" \\\n\ @@ -84,16 +91,17 @@ CMD ["exec ./authorizer \\\n\ --smtp-local-name=\"${SMTP_LOCAL_NAME}\" \\\n\ --smtp-skip-tls-verification=\"${SMTP_SKIP_TLS_VERIFICATION:-false}\" \\\n\ --enable-strong-password=\"${ENABLE_STRONG_PASSWORD:-true}\" \\\n\ - --enable-totp-login=\"${ENABLE_TOTP_LOGIN:-false}\" \\\n\ --enable-basic-authentication=\"${ENABLE_BASIC_AUTHENTICATION:-true}\" \\\n\ --enable-email-verification=\"${ENABLE_EMAIL_VERIFICATION:-false}\" \\\n\ --enable-mobile-basic-authentication=\"${ENABLE_MOBILE_BASIC_AUTHENTICATION:-true}\" \\\n\ --enable-phone-verification=\"${ENABLE_PHONE_VERIFICATION:-false}\" \\\n\ --enable-magic-link-login=\"${ENABLE_MAGIC_LINK_LOGIN:-false}\" \\\n\ --enforce-mfa=\"${ENFORCE_MFA:-true}\" \\\n\ - --enable-mfa=\"${ENABLE_MFA:-false}\" \\\n\ - --enable-email-otp=\"${ENABLE_EMAIL_OTP:-false}\" \\\n\ - --enable-sms-otp=\"${ENABLE_SMS_OTP:-false}\" \\\n\ + --disable-totp-login=\"${DISABLE_TOTP_LOGIN:-false}\" \\\n\ + --disable-webauthn-mfa=\"${DISABLE_WEBAUTHN_MFA:-false}\" \\\n\ + --disable-email-otp=\"${DISABLE_EMAIL_OTP:-false}\" \\\n\ + --disable-sms-otp=\"${DISABLE_SMS_OTP:-false}\" \\\n\ + --disable-mfa=\"${DISABLE_MFA:-false}\" \\\n\ --enable-signup=\"${ENABLE_SIGNUP:-true}\" \\\n\ --twilio-account-sid=\"${TWILIO_ACCOUNT_SID}\" \\\n\ --twilio-api-key=\"${TWILIO_API_KEY}\" \\\n\ diff --git a/render.yaml b/render.yaml index ddc2c91..a4a08ae 100644 --- a/render.yaml +++ b/render.yaml @@ -26,10 +26,20 @@ services: generateValue: true - key: JWT_TYPE value: HS256 + - key: AUTHORIZER_URL + # Canonical/trusted base URL of this deployment (e.g. your Render + # service URL). When set, it is the only source used to build + # verification/reset/magic-link URLs, the JWT iss claim, and OIDC + # discovery URLs. Leaving it empty exposes host-header-injection + # account takeover (CWE-640) — set this in the Render dashboard + # once the service URL is known. + sync: false # ---------------------------------------------------------------- # April 2026 security hardening flags. See # https://docs.authorizer.dev/core/security for the full reference. # ---------------------------------------------------------------- + - key: OAUTH2_1_STRICT + value: "false" - key: TRUSTED_PROXIES # Render terminates TLS at their edge and routes traffic through # their own router. Set this in the Render dashboard to the From fb229d8f0ebea92d329c533636ce7986d4d7dabd Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 18:00:26 +0530 Subject: [PATCH 2/5] fix: pin 2.4.0-rc.15 and wire --encryption-key Mirrors the same change in the helm chart. Without a distinct encryption key, rotating the JWT secret locks out every enrolled TOTP user with no re-encryption path. --- Dockerfile | 3 ++- README.md | 1 + render.yaml | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d9fcd6b..4133abf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # image with the current CLI flag surface (--url, --oauth2-1-strict, # --enable-org-discovery, --disable-totp-login/-webauthn-mfa/-email-otp/ # -sms-otp/-mfa). Re-pin to the stable 2.4.0 tag once it ships. -FROM quay.io/authorizer/authorizer:2.4.0-rc.7 +FROM quay.io/authorizer/authorizer:2.4.0-rc.15 # Override so CMD runs in a shell and env vars (e.g. for Render) are expanded. See base image comment. # Use exec-form CMD with a single string so /bin/sh -c gets one argument; shell-form CMD can be split and drop into a shell. ENTRYPOINT ["/bin/sh", "-c"] @@ -18,6 +18,7 @@ CMD ["exec ./authorizer \\\n\ --redis-url=\"${REDIS_URL}\" \\\n\ --jwt-type=\"${JWT_TYPE}\" \\\n\ --jwt-secret=\"${JWT_SECRET}\" \\\n\ + --encryption-key=\"${ENCRYPTION_KEY}\" \\\n\ --jwt-private-key=\"${JWT_PRIVATE_KEY}\" \\\n\ --jwt-public-key=\"${JWT_PUBLIC_KEY}\" \\\n\ --jwt-role-claim=\"${JWT_ROLE_CLAIM}\" \\\n\ diff --git a/README.md b/README.md index 13af32d..61dab3f 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Authorizer v2 requires the following variables. Configure them in Render's envir | `DATABASE_URL` | Database connection string | *(auto-configured by Render)* | | `JWT_TYPE` | JWT signing algorithm | `HS256` | | `JWT_SECRET` | JWT signing secret | `test` | +| `ENCRYPTION_KEY` | At-rest key for TOTP secrets and OTP digests. Required with `RS*`/`ES*` | *(output of `openssl rand -hex 32`)* | | `ADMIN_SECRET` | Admin secret for admin operations | `admin` | | `CLIENT_ID` | Client identifier **(required)** | `123456` | | `CLIENT_SECRET` | Client secret **(required)** | `secret` | diff --git a/render.yaml b/render.yaml index a4a08ae..be90741 100644 --- a/render.yaml +++ b/render.yaml @@ -24,6 +24,11 @@ services: generateValue: true - key: JWT_SECRET generateValue: true + # Encrypts TOTP secrets and OTP digests at rest. Required with RS*/ES*; + # with HS* it falls back to JWT_SECRET, but a distinct value keeps JWT + # secret rotation from locking out enrolled TOTP users. + - key: ENCRYPTION_KEY + generateValue: true - key: JWT_TYPE value: HS256 - key: AUTHORIZER_URL From deea60ee847aa12f6059d7bbde41937d52c18678 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 18:02:10 +0530 Subject: [PATCH 3/5] feat: wire 2.4.0 flags, document boot-fatal SMTP combo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds --fga-allow-unconstrained-agents, --microsoft-allowed-tenants and --oauth-allow-unverified-provider-email. Both booleans default false, matching the server's secure-by-default posture. 2.4.0 also made --enable-email-verification with no SMTP a fatal boot error rather than a per-user quirk. Nothing here sets it, so no template ships a crash-looping default, but an operator can set it from the platform env — so the READMEs now say what happens. --- Dockerfile | 3 +++ README.md | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4133abf..0f79156 100644 --- a/Dockerfile +++ b/Dockerfile @@ -83,6 +83,7 @@ CMD ["exec ./authorizer \\\n\ --database-cert-key=\"${DATABASE_CERT_KEY}\" \\\n\ --fga-store=\"${FGA_STORE}\" \\\n\ --fga-store-url=\"${FGA_STORE_URL}\" \\\n\ + --fga-allow-unconstrained-agents=\"${FGA_ALLOW_UNCONSTRAINED_AGENTS:-false}\" \\\n\ --couchbase-bucket=\"${COUCHBASE_BUCKET}\" \\\n\ --couchbase-scope=\"${COUCHBASE_SCOPE}\" \\\n\ --couchbase-ram-quota=\"${COUCHBASE_RAM_QUOTA}\" \\\n\ @@ -120,7 +121,9 @@ CMD ["exec ./authorizer \\\n\ --microsoft-client-id=\"${MICROSOFT_CLIENT_ID}\" \\\n\ --microsoft-client-secret=\"${MICROSOFT_CLIENT_SECRET}\" \\\n\ --microsoft-tenant-id=\"${MICROSOFT_TENANT_ID}\" \\\n\ + --microsoft-allowed-tenants=\"${MICROSOFT_ALLOWED_TENANTS}\" \\\n\ --microsoft-scopes=\"${MICROSOFT_SCOPES}\" \\\n\ + --oauth-allow-unverified-provider-email=\"${OAUTH_ALLOW_UNVERIFIED_PROVIDER_EMAIL:-false}\" \\\n\ --apple-client-id=\"${APPLE_CLIENT_ID}\" \\\n\ --apple-client-secret=\"${APPLE_CLIENT_SECRET}\" \\\n\ --apple-scopes=\"${APPLE_SCOPES}\" \\\n\ diff --git a/README.md b/README.md index 61dab3f..a06c015 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,33 @@ Authorizer v2 requires the following variables. Configure them in Render's envir | `RATE_LIMIT_FAIL_CLOSED` | `true` = 503 on rate-limit backend errors (`--rate-limit-fail-closed`) | `false` | | `REDIS_URL` | Redis for sessions + shared rate limits if you scale to multiple instances | *(unset)* | -These are mapped to CLI flags at startup. Please refer to the [server configuration docs](https://docs.authorizer.dev/core/server-config) for all available flags. +These are mapped to CLI flags at startup. + +### Upgrading to 2.4.0 + +`ENABLE_EMAIL_VERIFICATION=true` with no SMTP configured is now a **fatal boot +error**, not a warning. Every account-recovery route ends at the same mailbox, +so without a mail path a user is created unverified and can never recover. If +you set it, also set `SMTP_HOST`, `SMTP_PORT` and `SMTP_SENDER_EMAIL` — all +three — or the container will exit on start. + +`APP_COOKIE_SAME_SITE` is now validated at boot too: an unrecognised value +exits rather than silently falling back to `lax`. + +Two optional flags were added for the 2.4.0 security changes, both defaulting +to the secure behaviour: + +- `OAUTH_ALLOW_UNVERIFIED_PROVIDER_EMAIL` — a social login whose provider did + not attest the email address no longer reaches an existing account. Set + `true` only as a temporary compatibility measure. +- `FGA_ALLOW_UNCONSTRAINED_AGENTS` — a delegated (agent-acting-for-user) check + against an authorization model with no `type agent` now denies. Set `true` + only while migrating a model. + +`MICROSOFT_ALLOWED_TENANTS` restricts which Entra tenants may sign in when +`MICROSOFT_TENANT_ID` is a multi-tenant alias (`common`/`organizations`/ +`consumers`). + Please refer to the [server configuration docs](https://docs.authorizer.dev/core/server-config) for all available flags. ## Notes From a0811738eb6ded5f396587c0dabab938761e1c08 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 19:03:31 +0530 Subject: [PATCH 4/5] fix: pin 2.4.0-rc.16, the first RC with these flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rc.15 was tagged and released but its build was cancelled, so no image was ever pushed — and it was cut from a commit predating the audit, so it could not have carried these flags anyway. rc.16 is the first RC that has them; verified against the published image. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0f79156..3f064a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # image with the current CLI flag surface (--url, --oauth2-1-strict, # --enable-org-discovery, --disable-totp-login/-webauthn-mfa/-email-otp/ # -sms-otp/-mfa). Re-pin to the stable 2.4.0 tag once it ships. -FROM quay.io/authorizer/authorizer:2.4.0-rc.15 +FROM quay.io/authorizer/authorizer:2.4.0-rc.16 # Override so CMD runs in a shell and env vars (e.g. for Render) are expanded. See base image comment. # Use exec-form CMD with a single string so /bin/sh -c gets one argument; shell-form CMD can be split and drop into a shell. ENTRYPOINT ["/bin/sh", "-c"] From 078d975c5f561382f71497cd80eba01873e1a185 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 21:39:33 +0530 Subject: [PATCH 5/5] fix: pin 2.4.0-rc.17 rc.17 adds the verify-email decision core and the empty-subject token rejection, which rc.16 was built one commit too early to include. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3f064a8..2ebb3d6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # image with the current CLI flag surface (--url, --oauth2-1-strict, # --enable-org-discovery, --disable-totp-login/-webauthn-mfa/-email-otp/ # -sms-otp/-mfa). Re-pin to the stable 2.4.0 tag once it ships. -FROM quay.io/authorizer/authorizer:2.4.0-rc.16 +FROM quay.io/authorizer/authorizer:2.4.0-rc.17 # Override so CMD runs in a shell and env vars (e.g. for Render) are expanded. See base image comment. # Use exec-form CMD with a single string so /bin/sh -c gets one argument; shell-form CMD can be split and drop into a shell. ENTRYPOINT ["/bin/sh", "-c"]