Skip to content

security: config hardening — CORS, CSP, master_key, Sidekiq, DNS rebinding, profiler, Lookbook#1516

Open
dgilperez wants to merge 13 commits intowe-promise:mainfrom
dgilperez:security/pr4-config
Open

security: config hardening — CORS, CSP, master_key, Sidekiq, DNS rebinding, profiler, Lookbook#1516
dgilperez wants to merge 13 commits intowe-promise:mainfrom
dgilperez:security/pr4-config

Conversation

@dgilperez
Copy link
Copy Markdown
Contributor

@dgilperez dgilperez commented Apr 19, 2026

Summary

Pure configuration and infrastructure hardening — no controller/model changes. Introduces several env vars required in production.

Part of the security hardening originally bundled in #1104 (closed), now split by functional area per @jjmata's feedback. Companion PRs: #1517 #1518 #1519 #1520 #1521.

Findings addressed (7)

ID Severity CWE Fix
FIX-09 HIGH CWE-1393 Sidekiq Web UI default credentials sure/sure — require SIDEKIQ_WEB_USERNAME + SIDEKIQ_WEB_PASSWORD, mount disabled if unset (config/initializers/sidekiq.rb)
FIX-10 MED CWE-200 Lookbook engine mounted in production — restricted to development (config/routes.rb)
FIX-16 LOW CWE-522 require_master_key disabled — enforced in config/environments/production.rb
F-02 MED CWE-942 CORS wildcard origins "*" — replaced with ALLOWED_ORIGINS / APP_DOMAIN allowlist (config/initializers/cors.rb)
F-03 MED CWE-1021 CSP disabled — enabled in report-only mode with PostHog/Pusher gated on app_mode.managed? (config/initializers/content_security_policy.rb)
F-05 MED CWE-350 DNS rebinding protection disabled — config.hosts = [APP_DOMAIN] when set (config/environments/production.rb)
F-12 LOW CWE-215 rack-mini-profiler loads in production — moved to :development group (Gemfile) + env guard in initializer

Migration notes — required env vars for production

Env var Purpose Required?
SIDEKIQ_WEB_USERNAME + SIDEKIQ_WEB_PASSWORD Basic Auth for /sidekiq — UI inaccessible if unset Required if you use Sidekiq Web
RAILS_MASTER_KEY (or config/master.key) App refuses to boot without it Required
ALLOWED_ORIGINS Comma-separated CORS allowlist (preferred) Required if mobile/browser clients use the API
APP_DOMAIN Fallback CORS origin + DNS-rebinding config.hosts Recommended

Boot-time [SECURITY] warning is logged if none of ALLOWED_ORIGINS / APP_DOMAIN is set.

Drive-by fix

config/initializers/sidekiq.rb on main used & (bitwise AND) instead of && between the two secure_compare calls. The FIX-09 rewrite corrects this.

CSP caveat

The policy sets report_uri "/csp-violation-report" but that path is not wired up. Browsers will 404 on reports. Leaving as-is for a follow-up to wire to Sentry or equivalent.

Tests

  • test/integration/cors_test.rb rewritten to assert the allowlist contract (7/7 pass).
  • Full suite: 3225 runs, 0 failures.
  • bin/rubocop clean, bin/brakeman 0 warnings.

Out of scope

Related

Summary by CodeRabbit

  • New Features

    • CSP violation reporting endpoint
    • Content Security Policy enabled in report-only mode
    • CORS origin allowlisting to restrict cross-origin API access
  • Chores

    • Enforce master key at boot and add host allowlisting to mitigate DNS rebinding
    • Require Sidekiq Web credentials via environment variables
    • Limit profiling tool to development and mount design system only in development
    • Tighten OAuth CORS routes and enable credentialed API/sessions CORS
  • Documentation

    • Added production hardening guide for Docker
  • Tests

    • Added/updated tests for CORS and CSP reporting behavior

Remove hardcoded default "sure"/"sure" Basic Auth credentials from the
Sidekiq Web UI in production. Authentication now fails closed: if the
SIDEKIQ_WEB_USERNAME or SIDEKIQ_WEB_PASSWORD env vars are not set, the UI
becomes inaccessible and a [SECURITY] warning is logged at boot. This
prevents unauthenticated access to background job internals on deployments
that forget to override the defaults.

Also fixes a latent bug in the previous code path: the credential check
used `&` (bitwise AND) instead of `&&` (short-circuit logical AND), which
returned an integer instead of a boolean from the auth block.

New required env vars for production (Sidekiq Web UI access):
  - SIDEKIQ_WEB_USERNAME
  - SIDEKIQ_WEB_PASSWORD

If either is unset, the /sidekiq endpoint is still routed but will
reject every request.
Previously Lookbook::Engine was mounted at /design-system in every
environment, exposing component preview, inspector, and eval-style
rendering surfaces in production. Lookbook is a development-only tool
and is not designed to be exposed to untrusted clients.

Guard the mount so the engine only mounts in development, mirroring
the existing guard already applied to rswag below it.
Enable config.require_master_key = true in production so the app will
refuse to boot if no Rails master key is available via
ENV["RAILS_MASTER_KEY"], config/master.key, or
config/credentials/production.key.

Without this flag, Rails silently boots without the master key, which
means encrypted credentials (OAuth client secrets, signed cookies, etc.)
are not available and anything depending on them either fails at
request time or — worse — falls back to defaults. Fail fast at boot
instead.

New required env var for production (if config/master.key is not
shipped): RAILS_MASTER_KEY.
Previously Rack::Cors was configured with origins "*" for /api/*,
/oauth/*, and /sessions/*. Combined with credentials-bearing endpoints
this is unsafe: a wildcard origin reflects back as the caller's origin
for every request, neutralising SameSite protections for any browser
client with valid credentials.

The allowlist is now driven by env:
  - ALLOWED_ORIGINS: comma-separated list of full origins
    (e.g. "https://app.example.com,https://staging.example.com")
  - APP_DOMAIN: fallback when ALLOWED_ORIGINS is unset; treated as
    "https://$APP_DOMAIN"

If neither env var is set, a [SECURITY] warning is logged at boot and
no cross-origin requests are accepted (fail closed). /oauth/* is also
narrowed to just /oauth/token and /oauth/revoke (the two endpoints
actually used by cross-origin clients); credentials: true is now
explicit on /api/* and /sessions/* so cookie-bearing requests are
handled correctly.

New env vars for production:
  - ALLOWED_ORIGINS (preferred, comma-separated origins)
  - APP_DOMAIN (fallback, bare domain)
Rails' CSP initializer was fully commented out, so the application sent
no Content-Security-Policy header. Enable a strict policy in
report-only mode so the browser reports violations to
/csp-violation-report while we tune the allowlist, after which the
report-only flag can be removed to enforce.

Policy highlights:
  - default-src 'self'
  - object-src 'none'
  - script-src restricted to self + Plaid + Stripe; PostHog/Pusher
    added only in managed mode (analytics / ActionCable are bundled
    services on the managed SaaS, not self-hosted)
  - style-src 'self' + 'unsafe-inline' (required by Tailwind)
  - frame-src restricted to Plaid and Stripe iframes
  - script nonces wired through importmap/Hotwire (session.id)

No new env vars. The /csp-violation-report path is intentionally not
routed — reports 404 for now, the important signal is in the access
log and browser devtools. A future commit can wire up a proper
reporting endpoint or ship violations to Sentry.
Rails ships DNS rebinding / Host-header attack protection via
config.hosts, but production had it commented out, so the app accepted
any Host header.

Enable it when APP_DOMAIN is set (which it needs to be anyway for
action_mailer.default_url_options and for the CORS fallback added in
F-02). The existing /up health check and reverse-proxy paths continue
to work because infra-level health probes hit the server over its
configured hostname.

If APP_DOMAIN is not set, behavior is unchanged from before (all
hosts accepted) — the warning is surfaced implicitly via the CORS
initializer. Operators running managed deployments must set APP_DOMAIN.

New required env var (recommended) for production: APP_DOMAIN.
rack-mini-profiler exposes detailed SQL, allocation, and memory data
for every request via a floating badge and an internal dashboard.
Previously the gem was declared at top-level in the Gemfile and so
loaded in every environment including production, where — even when
the UI is hidden — the middleware still instruments every request and
stores profile data in the cache, surfaces paths like
/mini-profiler-resources/..., and widens the attack surface.

Move the gem to the :development group and guard the initializer on
defined?(Rack::MiniProfiler) so production (where the constant is no
longer present) is a clean no-op.

No production env-var changes.
…F-02)

test/integration/cors_test.rb asserted the old wildcard
(Access-Control-Allow-Origin: "*") behaviour for /api, /oauth, and
/sessions endpoints. That was the vulnerability — these assertions
would fail every time the allowlist is enforced.

Rewrite the tests to verify the new contract: when no ALLOWED_ORIGINS
(or APP_DOMAIN) is configured (the test-env default), requests from a
non-allowed Origin MUST NOT receive "*" back, and MUST NOT receive the
sender's origin echoed. The middleware presence check is kept so the
stack assertion still guards regressions.
@brin-security-scanner brin-security-scanner Bot added contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis. labels Apr 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Enable production hardening (require master key, conditional host allowlist), tighten CORS and CSP (report-only with per-request nonces and a report endpoint), add CSP report ingestion and tests, scope dev tools and Sidekiq Web UI to env-driven credentials, and update docs/tests accordingly.

Changes

Cohort / File(s) Summary
Dependency / Dev tools
Gemfile, config/initializers/mini_profiler.rb, config/routes.rb
Scope rack-mini-profiler to :development; guard MiniProfiler initializer with defined?(Rack::MiniProfiler); mount Lookbook::Engine only in development.
Production runtime config
config/environments/production.rb
Enable config.require_master_key = true; set config.hosts at boot from ENV["APP_DOMAIN"] or parsed ENV["ALLOWED_HOSTS"], otherwise leave empty and emit a [SECURITY] warn.
Content Security Policy
config/initializers/content_security_policy.rb
Activate CSP in report-only mode; use per-request SecureRandom nonce; restrict nonce directives to script-src; expand img_src/font_src to include :data/:blob; add base/form/frame hardening directives; conditionally include PostHog/pusher hosts when managed; set report endpoint to /csp-violation-report.
CORS handling & tests
config/initializers/cors.rb, test/integration/cors_test.rb
Replace wildcard CORS with allowed_origins derived from ALLOWED_ORIGINS or APP_DOMAIN (or none with a boot warning); enable credentials: true and max_age: 86400 for API/session routes; narrow OAuth routes to /oauth/token and /oauth/revoke; update tests to assert absence of CORS allow-origin when no whitelist.
Sidekiq Web UI auth
config/initializers/sidekiq.rb
Read SIDEKIQ_WEB_USERNAME / SIDEKIQ_WEB_PASSWORD from env in production; warn and deny access when missing; compare hashes using secure_compare against env-derived SHA256 hashes (no hardcoded fallback).
CSP report intake & tests
app/controllers/csp_reports_controller.rb, test/controllers/csp_reports_controller_test.rb, config/routes.rb
Add CspReportsController#create to accept CSP reports (read up to 8,192 bytes, parse JSON or log truncated/raw snippet on parse errors) and route POST /csp-violation-report; add integration tests covering valid, empty, malformed, oversized, and invalid-UTF8 payloads.
Docs
docs/hosting/docker.md
Add “Production hardening (recommended)” documenting new env vars and boot-time behaviors for master key, Sidekiq UI, CORS, and host allowlisting.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant App as Rails App
    participant Logger as Rails.logger

    Browser->>App: POST /csp-violation-report\n(Content-Type: application/csp-report)\nbody (<= 8192 bytes)
    App->>App: read up to MAX_BODY_BYTES\nApp->>App: parse JSON or build truncated/raw snippet on error
    App->>Logger: warn with parsed report JSON or parse-failure snippet
    App-->>Browser: 204 No Content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • matthieuEv
  • sokie

Poem

🐰 I hopped through rails at break of dawn,
I checked the hosts and made nonces spawn,
No wildcards loose, no secrets bare,
I logged each whisper with cautious care,
Securely snug in my burrowed lair.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: security configuration hardening across CORS, CSP, master_key enforcement, Sidekiq credentials, DNS rebinding, rack-mini-profiler scoping, and Lookbook engine mounting.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/environments/production.rb`:
- Around line 104-105: The current config.hosts assignment silently skips host
authorization when ENV["APP_DOMAIN"] is blank; update the initialization so
production fails closed: if ENV["APP_DOMAIN"] is present set config.hosts =
[ENV["APP_DOMAIN"]], otherwise require an explicit fallback (e.g.
ENV["ALLOWED_HOSTS"] parsed into an array) or raise a clear boot-time error to
abort startup; locate the config.hosts assignment and replace the conditional
compact/if logic with an explicit presence check that either populates
config.hosts from APP_DOMAIN or ALLOWED_HOSTS (split by comma) or raises an
exception when neither is provided.

In `@config/initializers/content_security_policy.rb`:
- Line 61: policy.report_uri is configured to send reports to
"/csp-violation-report" but there is no endpoint to receive them; add a route
for POST "/csp-violation-report" and implement a CspReportsController (e.g.,
CspReportsController#create) that accepts JSON POST bodies, parses the report
payload, logs or forwards the report (and any identifying request metadata) and
returns an empty 204 response so browsers consider the report delivered; ensure
the controller is CSRF-exempt or uses appropriate skip_before_action for API
requests and that the route accepts JSON content-type.
- Around line 64-66: The current nonce generator uses request.session.id
(config.content_security_policy_nonce_generator) which is constant per session;
replace it with a per-response cryptographically random nonce generated via
SecureRandom (e.g. a URL-safe or base64/hex random string) so each response gets
a fresh unpredictable nonce before enabling CSP enforcement; update
config.content_security_policy_nonce_generator to call SecureRandom for each
invocation and keep config.content_security_policy_nonce_directives =
%w[script-src] unchanged.

In `@test/integration/cors_test.rb`:
- Around line 14-73: Change the CORS assertions to assert the header is
absent/blank rather than just not equal to two values: in the tests "cors does
not reflect wildcard origin for api endpoints", "cors preflight does not allow
arbitrary origin for api endpoints", "cors does not reflect wildcard origin for
oauth token endpoint", "cors preflight does not allow arbitrary origin for oauth
token endpoint", "cors does not reflect wildcard origin for session endpoints",
and "cors preflight does not allow arbitrary origin for session endpoints"
replace the assert_not_equal checks against "*" and the evil origin with a
single assertion that response.headers["Access-Control-Allow-Origin"] is
nil/blank (e.g., assert_nil or assert_blank). Also add a new test mirroring the
oauth token tests that calls POST "/oauth/revoke" and OPTIONS "/oauth/revoke"
with the same Origin and preflight headers and asserts the
Access-Control-Allow-Origin header is absent/blank to cover the changed OAuth
resource.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d426e8a0-ad69-41bb-9d24-864003bbd58c

📥 Commits

Reviewing files that changed from the base of the PR and between b32e9db and 57b10c1.

📒 Files selected for processing (8)
  • Gemfile
  • config/environments/production.rb
  • config/initializers/content_security_policy.rb
  • config/initializers/cors.rb
  • config/initializers/mini_profiler.rb
  • config/initializers/sidekiq.rb
  • config/routes.rb
  • test/integration/cors_test.rb

Comment thread config/environments/production.rb Outdated
policy.frame_src "https://cdn.plaid.com",
"https://js.stripe.com"

policy.report_uri "/csp-violation-report"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Expect: a route and controller/action that accepts CSP violation reports.
rg -n -C3 'csp-violation-report|csp.?violation|content_security_policy.*report'

Repository: we-promise/sure

Length of output: 4490


🏁 Script executed:

#!/bin/bash
# Search for routes that handle CSP violations
find . -name "routes.rb" -type f | head -5 | xargs cat | grep -n -i "csp\|violation"

Repository: we-promise/sure

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for controllers that might handle CSP reports
rg -n "def.*csp|def.*violation|def.*report" --type rb -i | grep -i "csp\|violation.*report"

Repository: we-promise/sure

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Look for any references to the endpoint path in controllers/routes
rg -n "csp.violation|csp_violation" --type rb

Repository: we-promise/sure

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Find routes.rb file
find . -name "routes.rb" -type f

Repository: we-promise/sure

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Search for controllers with CSP or violation handling
find . -path "*/app/controllers/*.rb" -type f | xargs rg -l "csp|violation" -i

Repository: we-promise/sure

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Broader search for any action that might handle /csp-violation-report
rg -n "csp.violation|csp_violation" -i

Repository: we-promise/sure

Length of output: 961


🏁 Script executed:

#!/bin/bash
# Examine routes.rb for CSP report endpoint
cat ./config/routes.rb

Repository: we-promise/sure

Length of output: 16234


🏁 Script executed:

#!/bin/bash
# Search routes.rb specifically for the endpoint
rg "csp|violation" ./config/routes.rb -i

Repository: we-promise/sure

Length of output: 41


Wire the CSP report endpoint before relying on report-only telemetry.

report_uri points browsers at /csp-violation-report, but no route or controller exists to handle those requests. CSP violations will result in 404 errors and go uncollected, preventing the policy tuning that report-only mode is designed for.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/initializers/content_security_policy.rb` at line 61, policy.report_uri
is configured to send reports to "/csp-violation-report" but there is no
endpoint to receive them; add a route for POST "/csp-violation-report" and
implement a CspReportsController (e.g., CspReportsController#create) that
accepts JSON POST bodies, parses the report payload, logs or forwards the report
(and any identifying request metadata) and returns an empty 204 response so
browsers consider the report delivered; ensure the controller is CSRF-exempt or
uses appropriate skip_before_action for API requests and that the route accepts
JSON content-type.

Comment thread config/initializers/content_security_policy.rb Outdated
Comment thread test/integration/cors_test.rb Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 57b10c1ff3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread config/environments/production.rb Outdated
config.active_record.dump_schema_after_migration = false

# Enable DNS rebinding protection and other `Host` header attacks.
config.hosts = [ ENV["APP_DOMAIN"] ].compact if ENV["APP_DOMAIN"].present?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exempt health checks from strict host authorization

Restricting config.hosts to only APP_DOMAIN will also block the built-in /up health endpoint whenever a load balancer or orchestrator probes with a different Host header (commonly an IP, internal DNS name, or localhost). In that deployment pattern, readiness/liveness checks start returning 403 and can take instances out of service even though the app is healthy; add a host authorization exclusion for /up (or allow probe hosts) when enabling this restriction.

Useful? React with 👍 / 👎.

if ENV["ALLOWED_ORIGINS"].present?
ENV["ALLOWED_ORIGINS"].split(",").map(&:strip)
elsif ENV["APP_DOMAIN"].present?
[ "https://#{ENV['APP_DOMAIN']}" ]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive CORS fallback origin scheme from SSL settings

The APP_DOMAIN fallback always builds an https:// origin, so deployments running without forced SSL (e.g., RAILS_FORCE_SSL=false or HTTP-only internal environments) will never match browser Origin: http://... headers. In those environments, cross-origin browser clients lose CORS access even though APP_DOMAIN is configured, so the fallback should account for HTTP (or allow both schemes) instead of hardcoding HTTPS.

Useful? React with 👍 / 👎.

- CSP nonce: replace session.id-based generator with per-response SecureRandom
  (a session-constant nonce provides no CSP guarantee once observed).
- CSP reports: add POST /csp-violation-report endpoint (CspReportsController)
  so the report_uri is no longer a dangling reference.
- DNS rebinding: accept ALLOWED_HOSTS (comma-separated) as fallback to APP_DOMAIN,
  emit [SECURITY] boot warning if neither is set. Fail-closed was considered but
  rejected to preserve backward compatibility with existing self-hosted deploys.
- CORS tests: assert the header is nil (stricter than assert_not_equal), add
  coverage for /oauth/revoke (POST and preflight).
- Docs: document the new required/recommended env vars in docs/hosting/docker.md.
@dgilperez
Copy link
Copy Markdown
Contributor Author

Thanks — addressed all four.

CSP nonce (content_security_policy.rb:65): Good catch. A session-constant nonce defeats its purpose. Replaced the generator with per-response SecureRandom.base64(16). This was inherited from the old closed #1104 branch and never re-audited during the split.

CSP report_uri endpoint (content_security_policy.rb:61): Agreed. Added a minimal CspReportsController (inherits from ActionController::Base to bypass auth/CSRF) that logs the report as JSON and returns 204. Route: POST /csp-violation-report. Tests cover happy path, empty body, and malformed JSON.

CORS tests (test/integration/cors_test.rb): Agreed — assert_not_equal "*" leaves room for unexpected values. Switched to assert_nil via a small helper, added coverage for POST /oauth/revoke and its preflight.

DNS rebinding fail-closed (production.rb:104): Partially accepted. I added ALLOWED_HOSTS (comma-separated) as an alternative to APP_DOMAIN, matching the ALLOWED_ORIGINS/APP_DOMAIN pattern on the CORS side. A [SECURITY] warning is now emitted at boot when neither is set.

I did not raise on missing configuration. Rationale: this PR lands on a self-hosted deploy base where many existing operators don't set APP_DOMAIN today. Hard-failing on upgrade would take them offline without them touching anything. The warn-then-permit posture nudges operators toward configuration without breaking upgrades. I think this is the right tradeoff for a self-hostable Rails app; happy to revisit if maintainers prefer stricter defaults.

docs/hosting/docker.md now documents all five variables (RAILS_MASTER_KEY, SIDEKIQ_WEB_USERNAME/PASSWORD, ALLOWED_ORIGINS, APP_DOMAIN, ALLOWED_HOSTS) in a "Production hardening" section.

— wolfgang 🤖

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
test/controllers/csp_reports_controller_test.rb (1)

1-26: LGTM — solid coverage of the three parse branches.

Tests correctly exercise the three code paths in parse_report (JSON success, blank body, JSON::ParserError) and match the browser Content-Type: application/csp-report contract. Framework/style is compliant.

Optional: consider adding a test that posts a body larger than MAX_BODY_BYTES (8_192) to lock in the truncation behavior — it's the one behavior in the controller not currently exercised.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/controllers/csp_reports_controller_test.rb` around lines 1 - 26, Add a
test in CspReportsControllerTest that posts a request body larger than the
controller's MAX_BODY_BYTES (8_192) to "/csp-violation-report" to exercise the
truncation branch in parse_report; construct a payload string whose byte size
exceeds MAX_BODY_BYTES, send it with Content-Type "application/csp-report", and
assert the response is :no_content and any expected side-effect (e.g. that no
exception is raised or that any truncated-handling path is triggered) so the
truncation behavior in the controller is covered.
config/initializers/content_security_policy.rb (1)

37-48: Derive the PostHog CSP hosts from the same env var the rest of the app uses.

config/initializers/posthog.rb resolves the PostHog host from ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com"), but this file hardcodes https://us.i.posthog.com and https://us-assets.i.posthog.com. If a managed deployment ever overrides POSTHOG_HOST (e.g., EU region, self-hosted PostHog), CSP will start reporting/blocking the very traffic the initializer enabled — silent drift between two sources of truth.

Keeping them in sync avoids that, and also aligns with the guideline to prefer env vars over hardcoded values in configuration.

♻️ Proposed change
     if managed_mode
+      posthog_host = ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com")
+      posthog_assets_host = posthog_host.sub("://us.i.", "://us-assets.i.")
       script_src += [
-        "https://us.i.posthog.com",
-        "https://us-assets.i.posthog.com"
+        posthog_host,
+        posthog_assets_host
       ]

       connect_src += [
-        "https://us.i.posthog.com",
-        "https://us-assets.i.posthog.com",
+        posthog_host,
+        posthog_assets_host,
         "wss://*.pusher.com"
       ]
     end

As per coding guidelines: "Use environment variables instead of hard-coded values in configuration."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/initializers/content_security_policy.rb` around lines 37 - 48, Replace
the hardcoded PostHog CSP entries with values derived from the same env var the
app uses (ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com")) so managed_mode
appends the resolved POSTHOG_HOST (and the assets host derived from it, e.g.,
replacing host scheme/hostname with "-assets" subdomain) to script_src and
connect_src instead of the literal "https://us.i.posthog.com" and
"https://us-assets.i.posthog.com"; update the block that mutates script_src and
connect_src (inside the managed_mode branch) to compute posthog_host =
ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com") and an assets_host derived
from posthog_host, then push those variables into script_src and connect_src
(keeping wss://*.pusher.com unchanged).
app/controllers/csp_reports_controller.rb (1)

13-20: Consider log-flood protection for this unauthenticated endpoint.

A misconfigured page or a hostile site embedding the app in an iframe can emit a large volume of CSP reports, each producing a Rails.logger.warn line. With reports enabled in report-only mode across every page load, noisy policies during tuning will amplify this further. Operationally, consider one or more of:

  • Sampling or rate-limiting via Rack::Attack (throttle by IP and by request.referer).
  • Lowering severity to :info (or logging at :debug once tuning stabilizes) to avoid flooding alert-backed WARN channels.
  • Dropping reports whose document-uri / source-file doesn't match ALLOWED_HOSTS / APP_DOMAIN, so cross-origin pages cannot poison the log.

Not blocking — flagging since this endpoint has no auth and no throttling in front of it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/controllers/csp_reports_controller.rb` around lines 13 - 20, The create
action in CspReportsController currently unconditionally logs every parsed CSP
report (see create, request.body.read(MAX_BODY_BYTES), parse_report and
Rails.logger.warn), which can flood logs; update this method to (1) apply
rate-limiting or sampling before logging (e.g. integrate a Rack::Attack throttle
keyed by request.ip and request.referer or implement a simple in-process
sampler), (2) drop or ignore reports whose report['document-uri'] or
report['source-file'] do not match allowed hosts/app domain (use your
ALLOWED_HOSTS/APP_DOMAIN config to validate), and (3) lower the log severity
from warn to info or debug when appropriate; implement these checks around the
parse_report result and only call Rails.logger.warn/info when the report passes
filtering and rate limits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/controllers/csp_reports_controller.rb`:
- Around line 23-28: The parse_report method should not only rescue
JSON::ParserError but any StandardError raised by hostile input; change the
rescue to catch StandardError (or a broad exception class) around JSON.parse in
parse_report and return a safe, sanitized raw payload instead of calling
body.to_s.truncate(512) directly. When building the fallback { raw: ... } value,
coerce and sanitize the body to valid UTF-8 (e.g., use
scrub/force_encoding+encode or an equivalent safe-sanitizer) and then truncate
to 512 bytes/chars inside the rescue so that encoding/truncation cannot raise;
also ensure you log the caught exception via the existing logger for visibility.
Use references to parse_report, JSON.parse, JSON::ParserError, and the raw
fallback to locate the changes.

In `@config/initializers/content_security_policy.rb`:
- Around line 16-62: Replace the hard-coded PostHog hosts in the
content_security_policy block by reading the POSTHOG_HOST env var (as used in
config/initializers/posthog.rb) and deriving the script/connect host(s) (e.g.,
"#{POSTHOG_HOST}" and assets variant) before merging into script_src and
connect_src arrays (update the logic around managed_mode, script_src,
connect_src and policy.script_src/policy.connect_src). Also add the missing
directives inside the same policy block: policy.base_uri :self,
policy.form_action :self, and policy.frame_ancestors :none so they are reported
in report-only mode via policy.report_uri.

---

Nitpick comments:
In `@app/controllers/csp_reports_controller.rb`:
- Around line 13-20: The create action in CspReportsController currently
unconditionally logs every parsed CSP report (see create,
request.body.read(MAX_BODY_BYTES), parse_report and Rails.logger.warn), which
can flood logs; update this method to (1) apply rate-limiting or sampling before
logging (e.g. integrate a Rack::Attack throttle keyed by request.ip and
request.referer or implement a simple in-process sampler), (2) drop or ignore
reports whose report['document-uri'] or report['source-file'] do not match
allowed hosts/app domain (use your ALLOWED_HOSTS/APP_DOMAIN config to validate),
and (3) lower the log severity from warn to info or debug when appropriate;
implement these checks around the parse_report result and only call
Rails.logger.warn/info when the report passes filtering and rate limits.

In `@config/initializers/content_security_policy.rb`:
- Around line 37-48: Replace the hardcoded PostHog CSP entries with values
derived from the same env var the app uses (ENV.fetch("POSTHOG_HOST",
"https://us.i.posthog.com")) so managed_mode appends the resolved POSTHOG_HOST
(and the assets host derived from it, e.g., replacing host scheme/hostname with
"-assets" subdomain) to script_src and connect_src instead of the literal
"https://us.i.posthog.com" and "https://us-assets.i.posthog.com"; update the
block that mutates script_src and connect_src (inside the managed_mode branch)
to compute posthog_host = ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com")
and an assets_host derived from posthog_host, then push those variables into
script_src and connect_src (keeping wss://*.pusher.com unchanged).

In `@test/controllers/csp_reports_controller_test.rb`:
- Around line 1-26: Add a test in CspReportsControllerTest that posts a request
body larger than the controller's MAX_BODY_BYTES (8_192) to
"/csp-violation-report" to exercise the truncation branch in parse_report;
construct a payload string whose byte size exceeds MAX_BODY_BYTES, send it with
Content-Type "application/csp-report", and assert the response is :no_content
and any expected side-effect (e.g. that no exception is raised or that any
truncated-handling path is triggered) so the truncation behavior in the
controller is covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7328b1d-138d-437a-9367-344ae4c49290

📥 Commits

Reviewing files that changed from the base of the PR and between 57b10c1 and 70148ae.

📒 Files selected for processing (7)
  • app/controllers/csp_reports_controller.rb
  • config/environments/production.rb
  • config/initializers/content_security_policy.rb
  • config/routes.rb
  • docs/hosting/docker.md
  • test/controllers/csp_reports_controller_test.rb
  • test/integration/cors_test.rb
✅ Files skipped from review due to trivial changes (1)
  • docs/hosting/docker.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • config/routes.rb
  • config/environments/production.rb
  • test/integration/cors_test.rb

Comment thread app/controllers/csp_reports_controller.rb
Comment thread config/initializers/content_security_policy.rb
CSP report controller:
- parse_report now rescues StandardError (not just JSON::ParserError) so a
  hostile body with an odd encoding or a class that makes JSON.parse raise
  something other than ParserError doesn't 500. Forces UTF-8 + scrub before
  truncating so String#truncate can't blow up on malformed bytes. Folds the
  exception class into the fallback payload + logs it for operator visibility.
- New tests: body > MAX_BODY_BYTES (truncated valid JSON becomes unparseable
  mid-stream), invalid UTF-8 sequence — both must return 204, not 500.

CSP policy:
- Added base_uri :self, form_action :self, frame_ancestors :none — standard
  hardening directives that cost nothing and close tag-hijack / form-relay /
  framing vectors.
- PostHog hosts now derived from ENV["POSTHOG_HOST"] (matching
  config/initializers/posthog.rb) instead of hardcoded. Managed deploys in
  non-US regions won't have CSP silently blocking the very traffic the
  PostHog initializer enabled.

Deferred (follow-up): adding rate-limiting / host-filtering to the CSP
reports endpoint. Current endpoint logs at :warn, which is operationally
noisy under heavy tuning. Worth a separate PR alongside a Sentry / log
aggregator wiring; out of scope for the initial hardening.
@dgilperez
Copy link
Copy Markdown
Contributor Author

Second round addressed.

parse_report (csp_reports_controller.rb:23): Broadened the rescue to StandardError — a hostile body with a weird encoding can raise things other than JSON::ParserError (e.g., Encoding::CompatibilityError). Now force-encodes + scrubs to UTF-8 before truncate, so String#truncate can't blow up on invalid bytes either. Fallback payload folds the exception class into { raw:, parse_error: } so operators can see what the client actually sent.

CSP hardening directives (content_security_policy.rb): Added base_uri :self, form_action :self, frame_ancestors :none — they cost nothing and close tag-hijack / form-relay / framing vectors. Report-only mode still, so any legitimate page that needs them will surface a violation before enforcement.

PostHog hosts derived from env (content_security_policy.rb:37-48): Good catch on the drift with config/initializers/posthog.rb. Now reads ENV.fetch("POSTHOG_HOST", "https://us.i.posthog.com") and derives the assets host from it; a managed deploy in a non-US region won't have CSP silently blocking its own analytics.

Test for MAX_BODY_BYTES truncation (csp_reports_controller_test.rb): Added two: one posts a payload larger than MAX_BODY_BYTES, one posts an invalid UTF-8 sequence. Both must return 204.

CSP report log flood / host filtering / severity (csp_reports_controller.rb:13-20): Legitimate ops concern but IMO belongs in a follow-up. Doing it right means wiring a Rack::Attack throttle by IP + referer and plumbing a host allow-list through from ALLOWED_HOSTS/APP_DOMAIN — coupled work that'll be easier after this lands alongside a Sentry/log aggregator integration. Happy to open a follow-up PR once tuning signals what the actual volume shape looks like. Noted in the commit body.

All changes in commit 44201035. Full suite: 3237 runs, 0 failures. Rubocop + brakeman clean.

— wolfgang 🤖

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/initializers/content_security_policy.rb`:
- Around line 47-50: The derivation of posthog_assets_host using
posthog_host.sub("://us.i.", "://us-assets.i.") only covers the US region;
update the initializer to first check ENV.fetch("POSTHOG_ASSETS_HOST", nil) and
use that if present, otherwise derive the assets host from the parsed
posthog_host hostname (use URI.parse(posthog_host).host) and replace the region
prefix (e.g., turn "us.i.posthog.com" or "eu.i.posthog.com" into
"us-assets.i.posthog.com"/"eu-assets.i.posthog.com") or, for self-hosted/legacy
hosts, fall back to a sensible default (e.g., append "-assets" to the first
hostname segment) before adding it to script_src; modify the
posthog_host/posthog_assets_host logic (variables posthog_host and
posthog_assets_host and where script_src is extended) accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b008f32e-4ffd-46a6-ad07-2768ceac9ebb

📥 Commits

Reviewing files that changed from the base of the PR and between 70148ae and 4420103.

📒 Files selected for processing (3)
  • app/controllers/csp_reports_controller.rb
  • config/initializers/content_security_policy.rb
  • test/controllers/csp_reports_controller_test.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/controllers/csp_reports_controller.rb

Comment thread config/initializers/content_security_policy.rb
CodeRabbit round 3: the previous
`posthog_host.sub("://us.i.", "://us-assets.i.")` literal only worked for
the US region; an EU deployment (`eu.i.posthog.com`) or a self-hosted one
would keep the wrong assets host in the CSP allow-list and silently block
its own analytics.

Now:
- `POSTHOG_ASSETS_HOST` env var wins if set (escape hatch for self-hosted
  PostHog with non-standard layouts).
- Otherwise derive by parsing `POSTHOG_HOST` with URI and replacing the
  first label with its `-assets` variant:
    us.i.posthog.com     → us-assets.i.posthog.com
    eu.i.posthog.com     → eu-assets.i.posthog.com
    posthog.example.com  → posthog-assets.example.com
@dgilperez
Copy link
Copy Markdown
Contributor Author

Third round addressed.

PostHog assets host for non-US regions (content_security_policy.rb:47-50): Agreed — the sub("://us.i.", "://us-assets.i.") literal was US-only and would silently break CSP for any managed deploy pointed at eu.i.posthog.com or a self-hosted PostHog.

Now:

  • POSTHOG_ASSETS_HOST env var wins if set (escape hatch for self-hosted PostHog with non-standard host layouts).
  • Otherwise derives by parsing POSTHOG_HOST with URI and replacing the first label with its -assets variant:
    • us.i.posthog.comus-assets.i.posthog.com
    • eu.i.posthog.comeu-assets.i.posthog.com
    • posthog.example.composthog-assets.example.com

Commit f4010027. Full suite: 3237 runs, 0 failures. Rubocop + brakeman clean.

— wolfgang 🤖

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented Apr 19, 2026

@coderabbitai re-review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

@jjmata Sure! I'll re-review the PR now, taking into account all the changes made across the three rounds of feedback.


🧠 Learnings used
Learnt from: alessiocappa
Repo: we-promise/sure PR: 141
File: app/views/settings/hostings/_enable_banking_settings.html.erb:32-38
Timestamp: 2025-09-13T12:16:48.300Z
Learning: In the Sure project settings forms, the user prefers to maintain consistency with existing patterns for credential handling, even when security improvements are identified, and address such concerns comprehensively in separate PRs rather than making isolated changes.

Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-06T12:16:48.265Z
Learning: Applies to app/controllers/**/*.rb : Use strong parameters and CSRF protection throughout the application

Learnt from: CR
Repo: we-promise/sure PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-04-06T12:16:48.265Z
Learning: Run security analysis with `bin/brakeman --no-pager` before submitting PR
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
config/initializers/cors.rb (1)

14-23: Filter empty entries from ALLOWED_ORIGINS for parity with ALLOWED_HOSTS.

A trailing comma or stray whitespace-only entry in ALLOWED_ORIGINS yields "" in the allowlist, which is then passed to origins(*...). config/environments/production.rb already does .reject(&:empty?) for ALLOWED_HOSTS — mirror that here so the two env vars behave consistently and no empty string leaks into Rack::Cors matching.

♻️ Proposed refactor
   if ENV["ALLOWED_ORIGINS"].present?
-    ENV["ALLOWED_ORIGINS"].split(",").map(&:strip)
+    ENV["ALLOWED_ORIGINS"].split(",").map(&:strip).reject(&:empty?)
   elsif ENV["APP_DOMAIN"].present?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/initializers/cors.rb` around lines 14 - 23, The allowed_origins helper
can produce empty-string entries when ALLOWED_ORIGINS contains trailing commas
or whitespace; update the allowed_origins method to strip and then filter out
empty entries (e.g. use
ENV["ALLOWED_ORIGINS"].split(",").map(&:strip).reject(&:empty?) or
reject(&:blank?)) so no "" is passed to origins(...), mirroring the
ALLOWED_HOSTS handling in production.rb and ensuring Rack::Cors doesn't receive
empty origins; change the branch that returns the split list in allowed_origins
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/environments/production.rb`:
- Around line 104-115: The else-branch currently calls Rails.logger.warn while
Rails.logger is nil during early boot; change the warning to use the
already-configured config.logger instead so the boot won't crash when
ENV["APP_DOMAIN"] and ENV["ALLOWED_HOSTS"] are absent. Update the production
environment block that sets config.hosts (the if/elsif/else checking
ENV["APP_DOMAIN"] and ENV["ALLOWED_HOSTS"]) to call config.logger.warn(...)
rather than Rails.logger.warn(...) to safely emit the [SECURITY] warning.

---

Nitpick comments:
In `@config/initializers/cors.rb`:
- Around line 14-23: The allowed_origins helper can produce empty-string entries
when ALLOWED_ORIGINS contains trailing commas or whitespace; update the
allowed_origins method to strip and then filter out empty entries (e.g. use
ENV["ALLOWED_ORIGINS"].split(",").map(&:strip).reject(&:empty?) or
reject(&:blank?)) so no "" is passed to origins(...), mirroring the
ALLOWED_HOSTS handling in production.rb and ensuring Rack::Cors doesn't receive
empty origins; change the branch that returns the split list in allowed_origins
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ad785b5-111f-4ea2-b5b7-0359788426e2

📥 Commits

Reviewing files that changed from the base of the PR and between b32e9db and f401002.

📒 Files selected for processing (11)
  • Gemfile
  • app/controllers/csp_reports_controller.rb
  • config/environments/production.rb
  • config/initializers/content_security_policy.rb
  • config/initializers/cors.rb
  • config/initializers/mini_profiler.rb
  • config/initializers/sidekiq.rb
  • config/routes.rb
  • docs/hosting/docker.md
  • test/controllers/csp_reports_controller_test.rb
  • test/integration/cors_test.rb

Comment thread config/environments/production.rb
…ORIGINS

CodeRabbit round 4:
- production.rb: use `config.logger&.warn` instead of `Rails.logger.warn`
  in the config.hosts fallback branch — this block runs during environment
  configuration, before Rails.logger is finalized, so the prior call could
  NoMethodError-nil the warning and swallow the [SECURITY] notice.
- cors.rb: mirror the reject(&:empty?) from production.rb's ALLOWED_HOSTS
  handling so a trailing comma or whitespace-only entry in ALLOWED_ORIGINS
  doesn't leak an empty string into Rack::Cors.origins(...).
@dgilperez
Copy link
Copy Markdown
Contributor Author

Round 4.

Rails.logger vs config.logger during boot (production.rb:104-115): Agreed. The block runs during environment configuration, before Rails.logger is finalized, so a call into Rails.logger.warn could quietly swallow the security notice. Switched to config.logger&.warn (nil-safe too).

Filter empty entries from ALLOWED_ORIGINS (cors.rb:14-23): Agreed — parity with how production.rb already strips empty entries from ALLOWED_HOSTS. Added .reject(&:empty?) after the split + strip, so a trailing comma or blank-only entry never leaks an empty string into Rack::Cors.origins(...).

Commit 6c928639. Rubocop clean.

— wolfgang 🤖

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/environments/production.rb`:
- Around line 111-112: Parse ENV["ALLOWED_HOSTS"] into a host list first (e.g.
call split(",").map(&:strip).reject(&:empty?)) and only assign it to
config.hosts when that parsed list is non-empty; do not rely on
ENV["ALLOWED_HOSTS"].present? alone because values like "," are present but
yield an empty array—ensure the check uses the parsed array (hosts.any?) before
setting config.hosts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c4359b52-21e1-49fc-8592-c8e942f4a94f

📥 Commits

Reviewing files that changed from the base of the PR and between f401002 and 6c92863.

📒 Files selected for processing (2)
  • config/environments/production.rb
  • config/initializers/cors.rb

Comment thread config/environments/production.rb Outdated
CodeRabbit round 5: ENV["ALLOWED_HOSTS"].present? is true for inputs
like "," or "  " that reduce to an empty array after split+strip+reject.
Assigning config.hosts = [] means "deny every host", which is strictly
worse than falling through to the warn-and-leave-unset branch.

Parse first, then only assign when the resulting list has at least one
entry; otherwise emit the existing [SECURITY] warning about DNS
rebinding being disabled.
@dgilperez
Copy link
Copy Markdown
Contributor Author

Good catch.

ENV["ALLOWED_HOSTS"].present? is true for strings like "," or " " that reduce to an empty array after split + strip + reject(&:empty?). Assigning config.hosts = [] means "deny every host" — strictly worse than the fall-through branch that leaves config.hosts unset and logs a warning.

Parse first, then only assign when the resulting list has entries; else emit the existing [SECURITY] warning.

Commit f3d11f37. Rubocop clean.

— wolfgang 🤖

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants