Migrated Project Settings
A project moved from another host usually reads environment variables, $config overrides, header
rules and redirects that the old host supplied. Five names carry them.
| name | form | what it does |
|---|---|---|
DRUPAL_ENV_<NAME> |
var or secret, one per name | PHP reads it as getenv('<NAME>'), $_ENV['<NAME>'] and $_SERVER['<NAME>'] |
DRUPAL_CONFIG |
JSON object, as a secret | merged over $config with array_replace_recursive at boot |
DRUPAL_SETTINGS |
JSON object, as a secret | merged over $settings with array_replace_recursive at boot |
RESPONSE_HEADERS |
JSON array, KV-overridable | [{"path":"/blog/*","set":{"X-Frame-Options":"DENY"}}] |
REDIRECTS |
JSON array, KV-overridable | [{"from":"/old/*","to":"/new/*","status":301}] |
Only the DRUPAL_ENV_ prefix, DRUPAL_CONFIG and DRUPAL_SETTINGS reach PHP. The owner token,
PW_DIAGNOSTICS, mail credentials and API tokens live on the same environment and are never read
into it. A malformed value is ignored, and /serve-stats lists the names that were ignored under
deploymentEnv. A heap image is not taken or restored while any of them is set, because the image
would carry the old values. Values change with a redeploy.
DRUPAL_SETTINGS is for what settings.php carried on the old host, such as a module's
$settings['...'] entries. It is read up to 64 KiB, and it cannot move the keys the Worker sets
itself: trusted_host_patterns, config_sync_directory, container_yamls and the public, private
and temp file paths. A key it names that is protected is dropped and listed in the ignored names.
RESPONSE_HEADERS matches an exact path or a /prefix*. It cannot set Set-Cookie, anything
starting x-cfw-, or the headers that frame the body. REDIRECTS matches an exact path (a trailing
slash is ignored) or a trailing * that carries the rest of the path into the target. It answers
before routing, so a redirect costs no Durable Object request, and it never applies to the Worker's
own routes such as /settings. Status is 301, 302, 307 or 308, 301 by default, and at most 100 rules
are read.
Every response the Worker answers carries X-Content-Type-Options: nosniff,
X-Frame-Options: SAMEORIGIN and Referrer-Policy: strict-origin-when-cross-origin, and over https
Strict-Transport-Security: max-age=31536000, with no includeSubDomains or preload. A
RESPONSE_HEADERS rule that names one of them replaces it, and a rule value of "" removes it, which
a site embedded in another page needs for X-Frame-Options.
A Drupal module can set a header too. Security Kit's policy and a custom subscriber's
X-Frame-Options: DENY reach the browser, from the object, the edge cache, the isolate memo, KV, the
stale answer and the compiled plan for a signed-in page. The render reports
the response headers and the host keeps only these names: Content-Security-Policy,
Content-Security-Policy-Report-Only, Permissions-Policy, X-Frame-Options,
X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security, Cross-Origin-Opener-Policy,
Cross-Origin-Embedder-Policy, Cross-Origin-Resource-Policy and X-Permitted-Cross-Domain-Policies.
A value with a line break, a character outside printable ASCII, or more than 4,096 bytes is dropped,
and Set-Cookie, Location, Access-Control-*, Content-* and Link are never forwarded. The
stored page carries them in the headers column of cfw_page, written by the same statement as the
page, so a fill costs no extra row. The order is the Worker's default, then the module's header, then
the operator's RESPONSE_HEADERS rule, so a module's X-Frame-Options: DENY replaces the default
SAMEORIGIN and a rule replaces both. A compiled plan holds the headers its two sample renders
agreed on, and sessions whose renders disagree on one do not compile a plan. The page mirror in R2
does not carry them (R2 serves a fixed header set).
CSP_MODE adds a baseline Content-Security-Policy to every HTML response: off,
report-only or enforce. It defaults to report-only on the paid plan and off on free, and an
unrecognised value falls back to that default. The policy is
object-src 'none'; base-uri 'self'; frame-ancestors 'self'; report-uri /csp-report. It sets no
default-src, script-src or style-src, so core's inline script and style keep working, and it
has no form-action, because Chromium applies that directive to the redirect after a form
submission, so a form that posts here and redirects to another origin (a login button for an
identity provider) would be blocked. A page
that already carries an enforcing policy, from a module or from a rule, keeps only its own in either
mode. One that carries only a report-only policy keeps it in report-only mode; in enforce mode
the baseline is still added, because a report enforces nothing. CSP_MODE
is on KV_OVERRIDABLE, so PUT /settings and the settings document set it, and Drupal's settings
form cannot: the form's submit reports it under refused. A strict script-src with nonces is not
part of it.
Browsers post a violation to POST /csp-report, which the Worker answers with 204 and never
passes to the Durable Object. It takes application/csp-report and application/reports+json, up
to 8 KiB, and writes one cfw-csp-report line per report to Workers observability with the blocked
URI (no query), the violated directive, the page path (no query) and the first 80 characters of the
sample. Each address may send 60 reports a minute and an isolate takes 600, counted in memory; past
either limit the answer is still 204 and nothing is logged. Static files the asset layer answers
(/core, /modules, /themes, /agg) carry the cache rules in assets/_headers and none of
these.
/enable is not needed for a text asset a migrated module ships: a .css, .js or .svg file
stored in cfw_module_file is served from /modules, /themes, /profiles and /libraries when
the pack does not hold the path. Binary assets are not served this way, because the table stores
source as text.
A migrated site claims with POST /firstrun and a body of {"migrated": true}. That mints the owner
token and marks the site claimed without changing uid 1, the site name or any mail setting. It takes no
adminPass, and an already claimed site answers 409.