From c5e35456585c9bd4d0befcd346ec3093a80710c8 Mon Sep 17 00:00:00 2001 From: Andrew Shell Date: Wed, 1 Jul 2026 12:48:07 -0500 Subject: [PATCH 1/3] docs(server): replace stale root .env-sample with apps/server/.env.example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server reads .env from apps/server via dotenv, but the only sample lived at the repo root and carried just DOMAIN/PORT. Move it to apps/server/.env.example (where `cp` actually helps) and document every config key with defaults. Include the loopback SSRF allowlists needed to run the hub against the local client harness (feed + callback on http://localhost:9000), now that the egress guard is always on — with a clear "delete for production" warning. Co-Authored-By: Claude Opus 4.8 (1M context) --- .env-sample | 2 -- apps/server/.env.example | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) delete mode 100644 .env-sample create mode 100644 apps/server/.env.example diff --git a/.env-sample b/.env-sample deleted file mode 100644 index 52f5167..0000000 --- a/.env-sample +++ /dev/null @@ -1,2 +0,0 @@ -DOMAIN=localhost -PORT=5337 diff --git a/apps/server/.env.example b/apps/server/.env.example new file mode 100644 index 0000000..164e2fa --- /dev/null +++ b/apps/server/.env.example @@ -0,0 +1,47 @@ +# rssCloud hub — environment template. +# +# Copy to apps/server/.env (gitignored) and adjust: +# cp apps/server/.env.example apps/server/.env +# +# The server reads .env from its own directory (apps/server) via dotenv, so this +# file must live here, not at the repo root. `pnpm start` runs from here. +# +# The values below are tuned for LOCAL DEVELOPMENT alongside the client harness +# (apps/client), which serves its feed and WebSub callback on http://localhost:9000. + +# --- Hub identity --------------------------------------------------------- +# The client harness hardcodes the hub at http://localhost:5337, so keep these +# matching for local dev. HUB_URL defaults to http://${DOMAIN}:${PORT}${WEBSUB_PATH}. +DOMAIN=localhost +PORT=5337 + +# --- SSRF egress guard: allow loopback for local dev ---------------------- +# The egress guard is ALWAYS ON and refuses loopback/private targets by default. +# For local dev the hub must reach the client on loopback: +# FETCH allowlist -> topic re-fetch on ping (the client's feed) +# CALLBACK allowlist -> WebSub verification GET + delivery, and the rssCloud +# challenge GET / notify sent to the callback +# Covers 127.x (IPv4) and ::1 (IPv6) since `localhost` may resolve to either. +# +# >>> PRODUCTION: delete both lines (or scope them to your real private feed / +# >>> subscriber ranges). Never ship loopback exemptions to a public hub. +WEBSUB_FETCH_ALLOW_CIDRS=127.0.0.0/8,::1/128 +WEBSUB_CALLBACK_ALLOW_CIDRS=127.0.0.0/8,::1/128 + +# --- Optional overrides (defaults shown; uncomment to change) ------------- +# HUB_URL=http://localhost:5337/websub # public WebSub hub URL advertised to subscribers +# WEBSUB_PATH=/websub # WebSub front-door mount path +# DATA_FILE_PATH=./data/subscriptions.json +# STATS_FILE_PATH=./data/stats.json +# STATS_INTERVAL_MS=3600000 # stats regeneration cadence +# REQUEST_TIMEOUT=4000 # outbound fetch timeout (ms), SSRF-guarded +# MIN_SECS_BETWEEN_PINGS=0 # per-resource ping throttle (0 = off) +# CT_SECS_RESOURCE_EXPIRE=90000 # rssCloud subscription lifetime (s) +# MAX_CONSECUTIVE_ERRORS=3 # delivery failures tolerated before drop +# MAX_RESOURCE_SIZE=256000 # largest feed body parsed (bytes) +# FEEDS_CHANGED_WINDOW_DAYS=7 # stats + expiry housekeeping window +# WEBSUB_LEASE_DEFAULT_SECS=86400 # lease granted when hub.lease_seconds omitted +# WEBSUB_LEASE_MIN_SECS=300 # lower clamp for a requested lease +# WEBSUB_LEASE_MAX_SECS=864000 # upper clamp for a requested lease +# WEBSUB_SIGNATURE_ALGO=sha256 # X-Hub-Signature HMAC algorithm +# ENABLE_TEST_API=true # mounts /test/* seed/snapshot routes — TEST ONLY From 6335b56a259a37272aac2601de06a8f926d4c170 Mon Sep 17 00:00:00 2001 From: Andrew Shell Date: Wed, 1 Jul 2026 13:00:37 -0500 Subject: [PATCH 2/3] docs(server): order .env.example keys alphabetically Keep WEBSUB_CALLBACK_ALLOW_CIDRS before WEBSUB_FETCH_ALLOW_CIDRS so all active keys are in ascending order (satisfies dotenv-linter's UnorderedKey). Values and comments unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/server/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/.env.example b/apps/server/.env.example index 164e2fa..3bc212b 100644 --- a/apps/server/.env.example +++ b/apps/server/.env.example @@ -25,8 +25,8 @@ PORT=5337 # # >>> PRODUCTION: delete both lines (or scope them to your real private feed / # >>> subscriber ranges). Never ship loopback exemptions to a public hub. -WEBSUB_FETCH_ALLOW_CIDRS=127.0.0.0/8,::1/128 WEBSUB_CALLBACK_ALLOW_CIDRS=127.0.0.0/8,::1/128 +WEBSUB_FETCH_ALLOW_CIDRS=127.0.0.0/8,::1/128 # --- Optional overrides (defaults shown; uncomment to change) ------------- # HUB_URL=http://localhost:5337/websub # public WebSub hub URL advertised to subscribers From 901016cd16e9da2b367352981ae1112e1bcfdbec Mon Sep 17 00:00:00 2001 From: Andrew Shell Date: Wed, 1 Jul 2026 13:07:18 -0500 Subject: [PATCH 3/3] build(deps): bump js-yaml override to ^4.2.0 to clear GHSA-h67p-54hq-rp68 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit js-yaml 4.1.1 (dev-only, transitive via eslint's @eslint/eslintrc) carries a medium-severity quadratic-complexity DoS in YAML merge-key handling. Pin the override to ^4.2.0 (resolves 4.3.0) — patched and within eslintrc's ^4.x range. A bare >=4.2.0 would jump to the incompatible js-yaml 5.x. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 3 ++- pnpm-lock.yaml | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index e7abcdd..3c54858 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "vite": ">=8.0.16", "esbuild": ">=0.28.1", "diff": ">=8.0.3", - "form-data": ">=4.0.6" + "form-data": ">=4.0.6", + "js-yaml": "^4.2.0" }, "onlyBuiltDependencies": [ "esbuild" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 891fb1c..bce0c5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,7 @@ overrides: esbuild: '>=0.28.1' diff: '>=8.0.3' form-data: '>=4.0.6' + js-yaml: ^4.2.0 importers: @@ -1933,8 +1934,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true json-buffer@3.0.1: @@ -3148,7 +3149,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3922,7 +3923,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.1 + js-yaml: 4.3.0 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -4544,7 +4545,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.1.1: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 @@ -4747,7 +4748,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 4.1.1 + js-yaml: 4.3.0 log-symbols: 4.1.0 minimatch: 9.0.9 ms: 2.1.3