Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/docs/security/runbook.md
T

10 KiB

Security Header Runbook

This runbook defines the expected security-header contract for the web server and Hono API runtime, plus deterministic checks for local and production triage. It intentionally avoids secrets, tenant identifiers, environment-specific values, and placeholder language.

Expected Web Headers

The Node web server in server/server.ts applies these headers to React Router responses, redirects, and static asset responses:

Header Expected value Purpose
x-content-type-options nosniff Prevent MIME sniffing.
referrer-policy strict-origin-when-cross-origin Keep same-origin referrer detail while reducing cross-origin leakage.
x-frame-options DENY Prevent clickjacking through legacy frame controls.
permissions-policy camera=(), microphone=(), geolocation=() Disable high-risk browser capabilities by default.
cross-origin-opener-policy same-origin Isolate top-level browsing context where supported.
cross-origin-resource-policy same-origin Restrict cross-origin resource embedding by default.
cross-origin-embedder-policy unsafe-none Preserve compatibility for embedded/static assets while making the policy explicit.

Web checks must also confirm that existing response behavior is preserved:

  • cache-control remains unchanged for immutable static assets such as /assets/**.
  • Redirect status and location behavior remain unchanged when React Router emits redirects.
  • Header assignment uses single values rather than appended comma-joined duplicates.

Expected API Headers

The API app in apps/api/src/app.ts applies these headers to health, OpenAPI, preflight, error, and /v1 responses:

Header Expected value Purpose
x-content-type-options nosniff Prevent MIME sniffing.
referrer-policy no-referrer Avoid leaking API request paths through referrers.
x-frame-options DENY Prevent API documents or error pages from being framed.
permissions-policy camera=(), microphone=(), geolocation=() Disable browser feature access for API responses.
cross-origin-opener-policy same-origin Keep browser isolation policy explicit.
cross-origin-resource-policy same-origin Limit cross-origin resource reuse by default.
cross-origin-embedder-policy unsafe-none Avoid over-broad isolation that could break clients.

API checks must also confirm that existing middleware contracts are preserved:

  • x-request-id is still emitted and forwarded according to the request-id middleware contract.
  • Allowed CORS requests still include the expected access-control-allow-origin value.
  • Canonical app and admin browser origins are accepted from routing config, and explicit WEB_ORIGIN and ADMIN_ORIGIN values are treated as full origins rather than host-only aliases.
  • Denied origins, including malformed variants and https://attacker.example.invalid, do not emit access-control-allow-origin while still returning x-request-id for incident correlation.
  • Preflight responses keep CORS behavior while receiving the request-id and security headers.
  • No response header includes secrets, personal data, tenant identifiers, or raw environment values.

Local Verification Commands

Run focused checks from the repository root. These commands are scoped to the files and tests that define the security-header and CORS contracts:

pnpm --dir server exec eslint server.test.ts server.ts --max-warnings=0
pnpm --dir server exec vitest run server.test.ts
pnpm exec eslint apps/api/src/app.test.ts apps/api/src/cors.test.ts apps/api/src/app.ts apps/api/src/cors.ts --max-warnings=0
pnpm exec vitest run apps/api/src/app.test.ts apps/api/src/cors.test.ts packages/config/src/env-access-enforcement.test.ts packages/config/src/api.test.ts
test -s docs/security/runbook.md
python - <<'PY'
from pathlib import Path
text = Path('docs/security/runbook.md').read_text()
assert 'apps/api/src/cors.test.ts' in text
assert 'attacker.example.invalid' in text
assert 'T' + 'BD' not in text
assert 'TO' + 'DO' not in text
PY

Browser, Playwright, Cypress, or other E2E execution is manual unless a human explicitly approves the exact focused run in the current session.

Production Curl Checklist

Use the deployed application origin and API origin appropriate for the environment under investigation. Do not paste secrets or authenticated tokens into shared logs.

Web HTML response

curl -sSI https://example-web-origin/ | tr -d '\r' | grep -Ei '^(x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that every expected web security header is present with the values listed above.

Web static asset response

curl -sSI https://example-web-origin/assets/app.js | tr -d '\r' | grep -Ei '^(cache-control|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that static assets still expose the intended cache-control policy while also carrying the security headers.

API health response

curl -sSI https://example-api-origin/healthz | tr -d '\r' | grep -Ei '^(x-request-id|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that x-request-id and all expected API security headers are present.

API CORS allowed app origin response

curl -sSI https://example-api-origin/v1/example \
  -H 'Origin: https://example-web-origin' | \
  tr -d '\r' | grep -Ei '^(access-control-allow-origin|x-request-id|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that access-control-allow-origin exactly matches https://example-web-origin, that x-request-id is present, and that the API security headers match the values above.

API CORS allowed admin origin response

curl -sSI https://example-api-origin/v1/example \
  -H 'Origin: https://example-admin-origin' | \
  tr -d '\r' | grep -Ei '^(access-control-allow-origin|x-request-id|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that access-control-allow-origin exactly matches https://example-admin-origin. If the admin origin is configured through ADMIN_ORIGIN, compare against the full configured origin string only; do not rewrite it to a bare host in notes or logs.

API CORS denied origin response

curl -sSI https://example-api-origin/v1/example \
  -H 'Origin: https://attacker.example.invalid' | \
  tr -d '\r' | grep -Ei '^(access-control-allow-origin|x-request-id|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):' || true

Confirm that no access-control-allow-origin header is emitted for https://attacker.example.invalid, while x-request-id and the API security headers remain present for safe incident correlation.

API CORS preflight response

curl -sSI -X OPTIONS https://example-api-origin/v1/example \
  -H 'Origin: https://example-web-origin' \
  -H 'Access-Control-Request-Method: GET' | \
  tr -d '\r' | grep -Ei '^(access-control-allow-origin|access-control-allow-methods|access-control-allow-credentials|x-request-id|x-content-type-options|referrer-policy|x-frame-options|permissions-policy|cross-origin-(opener|resource|embedder)-policy):'

Confirm that the allowed origin is preserved, CORS preflight headers remain present, x-request-id is present, and security headers are still attached to the preflight response.

CORS Configuration Guidance

  • Prefer canonical routing-derived app and admin origins for normal deployments, then add WEB_ORIGIN and ADMIN_ORIGIN only when an environment needs explicit browser origins.
  • Treat WEB_ORIGIN and ADMIN_ORIGIN as complete origin strings including scheme and host, for example https://example-web-origin and https://example-admin-origin; do not document or compare them as host-only aliases.
  • Do not paste raw environment dumps, secrets, tokens, tenant identifiers, or customer-specific domains into tickets or shared logs. Record only the generic origin label under test, the response header names observed, and the x-request-id value when needed.
  • Test both positive and negative origins during triage. A positive-only check can miss an over-broad allowlist regression.

Known Interactions

  • Security headers are set rather than appended, so a response should contain one effective value per header.
  • Web security headers must not overwrite static asset cache-control headers.
  • API security headers run alongside request-id and CORS middleware; missing x-request-id on any API response is a regression in middleware ordering, and missing access-control-allow-origin is expected only for denied browser origins.
  • cross-origin-embedder-policy: unsafe-none is intentional for both runtimes to avoid breaking existing embedding and asset-loading behavior.
  • CORS remains the source of truth for allowed browser origins; the security-header layer should not relax or replace CORS decisions.

Rollback and Triage Notes

  1. Reproduce with the focused local Vitest command for the affected runtime before changing middleware order.
  2. Compare curl output from a known-good response against the failing path, checking both missing security headers and preserved headers such as cache-control, x-request-id, and access-control-allow-origin.
  3. If security headers are missing only on static assets, inspect the static response path in server/server.ts before changing React Router forwarding logic.
  4. If API CORS headers disappear, inspect middleware ordering around request-id, security headers, and CORS in apps/api/src/app.ts.
  5. If duplicate comma-joined policy values appear, verify that code uses set or setHeader semantics rather than append.
  6. Roll back the smallest runtime-specific header change first; do not remove request-id, CORS, or cache-control behavior as part of header rollback.