mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Merge pull request #846 from MBombeck/fix/restore-perms-trust-proxy
fix: write restored PHI 0o600 and let TRUST_PROXY_HOPS reach the container
This commit is contained in:
5 files changed
+35
-9
No files matched your search
@@ -169,6 +169,12 @@ API_TOKEN_HMAC_KEY="REPLACE-WITH-OUTPUT-OF-openssl-rand-hex-32"
|
||||
# regex- and length-bounded), but the gating decision is up to the
|
||||
# operator — there is no way for the server to detect whether the flag
|
||||
# matches the real deployment topology. When in doubt, leave it off.
|
||||
# Number of trusted reverse-proxy hops in front of the app. The client IP is
|
||||
# read that many entries from the right of the X-Forwarded-For chain. Leave
|
||||
# unset for a directly-exposed host; set to 1 behind a single reverse proxy.
|
||||
# A wrong value makes the app refuse to read XFF and share one rate-limit
|
||||
# bucket across all anonymous callers.
|
||||
# TRUST_PROXY_HOPS="1"
|
||||
# TRUST_CF_CONNECTING_IP=""
|
||||
|
||||
|
||||
|
||||
@@ -306,6 +306,12 @@ API_TOKEN_HMAC_KEY="<openssl rand -hex 32>"
|
||||
# address and the geo lookup resolves the wrong location. Set this to "1"
|
||||
# ONLY when Cloudflare is in front -- a direct-exposed self-host must leave
|
||||
# it unset, or an attacker could forge the header. Off by default.
|
||||
# Number of trusted reverse-proxy hops in front of the app; the client IP is
|
||||
# read that many entries from the right of the X-Forwarded-For chain. Unset
|
||||
# for a directly-exposed host, 1 behind a single reverse proxy. A wrong
|
||||
# value makes the app refuse XFF and collapse anonymous callers into one
|
||||
# rate-limit bucket.
|
||||
# TRUST_PROXY_HOPS="1"
|
||||
# TRUST_CF_CONNECTING_IP="1"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -75,6 +75,13 @@ x-healthlog-env: &healthlog_env
|
||||
# free ip-api.com endpoint) — off by default, an explicit operator choice.
|
||||
# Listed here so operator values in .env reach the container (the
|
||||
# `environment:` block is a whitelist — vars not listed never propagate).
|
||||
# TRUST_PROXY_HOPS is the number of trusted reverse-proxy hops in front of
|
||||
# the app: the client IP is read that many entries from the right of the
|
||||
# X-Forwarded-For chain. Wrong value and the app refuses to read XFF and
|
||||
# collapses every anonymous caller into one rate-limit bucket. Listed here
|
||||
# so an operator value in .env reaches the container (the `environment:`
|
||||
# block is a whitelist).
|
||||
TRUST_PROXY_HOPS: "${TRUST_PROXY_HOPS:-}"
|
||||
TRUST_CF_CONNECTING_IP: "${TRUST_CF_CONNECTING_IP:-}"
|
||||
IP_GEO_LOOKUP_URL: "${IP_GEO_LOOKUP_URL:-}"
|
||||
IP_GEO_LOOKUP_DISABLED: "${IP_GEO_LOOKUP_DISABLED:-}"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "./env-manifest.schema.json",
|
||||
"description": "Manifest of env vars required by a HealthLog production deployment. Used by `pnpm check-env` to catch missing/empty vars BEFORE a deploy ships. v1.4.42 W6 — born out of the v1.4.40 AP-2 .p8 install gap that shipped silently for 3 days.",
|
||||
"description": "Manifest of env vars required by a HealthLog production deployment. Used by `pnpm check-env` to catch missing/empty vars BEFORE a deploy ships. v1.4.42 W6 \u2014 born out of the v1.4.40 AP-2 .p8 install gap that shipped silently for 3 days.",
|
||||
"groups": [
|
||||
{
|
||||
"name": "Core",
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Withings OAuth",
|
||||
"description": "Withings API credentials are entered per user in Settings → Integrations (no env-var path). Only the webhook shared secret is operator-level.",
|
||||
"description": "Withings API credentials are entered per user in Settings \u2192 Integrations (no env-var path). Only the webhook shared secret is operator-level.",
|
||||
"required": false,
|
||||
"variables": [
|
||||
{
|
||||
@@ -100,7 +100,7 @@
|
||||
},
|
||||
{
|
||||
"name": "OIDC SSO options",
|
||||
"description": "Optional tuning for an enabled OIDC provider. Each stands alone (deliberately NOT allOrNone — any subset is valid) and each has a safe default when unset.",
|
||||
"description": "Optional tuning for an enabled OIDC provider. Each stands alone (deliberately NOT allOrNone \u2014 any subset is valid) and each has a safe default when unset.",
|
||||
"required": false,
|
||||
"variables": [
|
||||
{
|
||||
@@ -157,7 +157,7 @@
|
||||
},
|
||||
{
|
||||
"name": "APNs (iOS push)",
|
||||
"description": "All four core APNS_* vars together. Missing any disables APNs silently — the v1.4.40 AP-2 gap that motivated this manifest. allOrNone trips the synthetic <all-or-none> required row whenever 1-3 of the 4 are set.",
|
||||
"description": "All four core APNS_* vars together. Missing any disables APNs silently \u2014 the v1.4.40 AP-2 gap that motivated this manifest. allOrNone trips the synthetic <all-or-none> required row whenever 1-3 of the 4 are set.",
|
||||
"required": false,
|
||||
"allOrNone": true,
|
||||
"variables": [
|
||||
@@ -175,7 +175,7 @@
|
||||
},
|
||||
{
|
||||
"name": "APNS_KEY",
|
||||
"purpose": "APNs auth key (.p8 contents). Use APNS_KEY for 12-factor env-block deploys; APNS_KEY_FILE is the filesystem alternative — at least one of the two must be set.",
|
||||
"purpose": "APNs auth key (.p8 contents). Use APNS_KEY for 12-factor env-block deploys; APNS_KEY_FILE is the filesystem alternative \u2014 at least one of the two must be set.",
|
||||
"anyOf": ["APNS_KEY", "APNS_KEY_FILE"]
|
||||
}
|
||||
]
|
||||
@@ -202,13 +202,13 @@
|
||||
"variables": [
|
||||
{
|
||||
"name": "TLS_LEAF_SPKI_PINS",
|
||||
"purpose": "Comma-separated known-good leaf SPKI pins, base64(sha256(DER subjectPublicKeyInfo)) — the same value the native client pins. Hold both the current and the next pin during a dual-pin renewal window."
|
||||
"purpose": "Comma-separated known-good leaf SPKI pins, base64(sha256(DER subjectPublicKeyInfo)) \u2014 the same value the native client pins. Hold both the current and the next pin during a dual-pin renewal window."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SMTP (email channel)",
|
||||
"description": "Operator SMTP transport for the email notification channel (v1.17.1). All of SMTP_HOST, SMTP_PORT, SMTP_FROM together enable the channel; SMTP_USER/SMTP_PASS are optional (unauthenticated relay). Missing any of the three core vars disables email silently — the channel is never offered and the Settings card hides itself.",
|
||||
"description": "Operator SMTP transport for the email notification channel (v1.17.1). All of SMTP_HOST, SMTP_PORT, SMTP_FROM together enable the channel; SMTP_USER/SMTP_PASS are optional (unauthenticated relay). Missing any of the three core vars disables email silently \u2014 the channel is never offered and the Settings card hides itself.",
|
||||
"required": false,
|
||||
"allOrNone": true,
|
||||
"variables": [
|
||||
@@ -250,7 +250,7 @@
|
||||
},
|
||||
{
|
||||
"name": "BACKUP_ENCRYPTION_KEY",
|
||||
"purpose": "Separate AES key for the backup body — MUST NOT match ENCRYPTION_KEY."
|
||||
"purpose": "Separate AES key for the backup body \u2014 MUST NOT match ENCRYPTION_KEY."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -268,6 +268,11 @@
|
||||
"name": "SESSION_COOKIE_SECURE",
|
||||
"purpose": "Forces the Secure flag on every cookie the app issues. Unset, it tracks NODE_ENV. Set false for a LAN / VPN-only self-host served over plain HTTP, where the default makes login look broken.",
|
||||
"example": "false"
|
||||
},
|
||||
{
|
||||
"name": "TRUST_PROXY_HOPS",
|
||||
"purpose": "Trusted reverse-proxy hop count; the client IP is read that many entries from the right of X-Forwarded-For.",
|
||||
"example": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
writeFileSync(out, plaintext, "utf8");
|
||||
// The restored file is decrypted PHI in plaintext. Write it 0o600 so it is
|
||||
// not world-readable on the operator's disk while it sits there.
|
||||
writeFileSync(out, plaintext, { encoding: "utf8", mode: 0o600 });
|
||||
console.log(
|
||||
`Restored userId=${body.userId} (${plaintext.length} bytes) -> ${out}`,
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user