Skip to content

ops: take Redis off the internet, put 5432 behind an allowlist - #300

Merged
ralyodio merged 2 commits into
masterfrom
ops/lock-down-data-ports
Sep 24, 2026
Merged

ralyodio merged 2 commits into
masterfrom
ops/lock-down-data-ports

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-up to #296/#297, closing the two data ports that were still reachable from the internet.

Anthony's call, and it checked out when measured: 12 of 14 Redis connections were containers on dev2 and the app reaches Redis by service name, so nothing on the box needs remote access. The only remote consumer was the prober on scan.crawlproof.com.

Redis: no longer published at all

The prober reaches it through an ssh tunnel instead. Its key on dev2 is restrict,port-forwarding,permitopen="127.0.0.1:6379",command="/bin/false" — no shell, no other port. That is tighter than what it had: the prober's old URL was plaintext redis:// over Railway's public tcp proxy, despite the workflow comment claiming rediss://.

Two traps encoded in the scripts:

  • The prober droplet already runs its own redis-server on 6379, so the tunnel binds 6380 locally. Using 6379 would have silently pointed the prober at the wrong Redis.
  • That local Redis shipped bind 0.0.0.0 + protected-mode no and was internet-reachable (password-protected, but needlessly open). Now loopback.

5432: allowlist, not closed

It stays published because the kit designs for it and the nichedb stack sharing this cluster needs it, but it is now DOCKER-USER: dev box, loopback, and 172.16.0.0/12.

That last entry is load-bearing — other apps on the box run in their own compose projects on their own bridges and arrive from a different docker subnet, so a narrower list cuts them off the moment they start. Coordinated with the nichedb session, who confirmed it covers them.

ufw cannot do this job: Docker inserts its rules ahead of ufw's chains, so a ufw rule for a published container port is decoration.

pg_hba is untouched — TLS required, postgres role only, scram.

Verified after the change

  • 6379 closed from the internet; prober active, connected through the tunnel
  • 164.92.111.224:6379 closed
  • 5432 reachable from the dev box, and a real TLS psql to db.crawlproof.com returned server_version 17.6
  • site green, app 200

Anthony: "we shouldn't need remote access to redis i don't think if
supabase/redis/app are all n teh same server" — right, and measured: 12 of 14
Redis connections were containers on this box and the app reaches it by
service name. The only remote consumer was the prober.

So Redis is no longer published at all. The prober reaches it through an ssh
tunnel whose key is restricted to exactly one forward
(restrict,port-forwarding,permitopen="127.0.0.1:6379",command="/bin/false"), so
it can neither get a shell nor reach another port. That is strictly tighter
than what it had: its old URL was plaintext redis:// over Railway's PUBLIC tcp
proxy, not the rediss:// the workflow comment claims.

The tunnel binds 6380 locally because the prober droplet already runs its own
redis-server on 6379 — using 6379 would have pointed it at the wrong Redis
without erroring.

5432 stays published (the kit's design, and the nichedb stack sharing this
cluster needs it) but is now an allowlist in DOCKER-USER: dev box, loopback and
172.16.0.0/12. That last entry matters — other apps on the box run in their own
compose projects on their own bridges and arrive from a different docker
subnet, so a narrower list cuts them off. ufw cannot do this job: Docker
inserts its rules ahead of ufw's chains.

pg_hba is untouched: TLS required, postgres role only, scram.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread ops/selfhost/README.md Fixed
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

ThreatCrush Security Scan

48 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 31 | LOW: 15

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM js-dynamic-code-execution lib/crawl-limits.ts:67
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:14
MEDIUM js-dynamic-code-execution scripts/test-crawl-limits.mjs:24
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:20
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:24
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:25
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:26
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:31
LOW js-dynamic-code-execution tests/contract/ads-click-cooldown-redis.test.ts:35
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

ThreatCrush flags any credential-shaped connection string in the docs, and it
is right to: a runbook that spells out user:password@host teaches people to
paste one somewhere it will be kept. Same fix as the cloud DB URL earlier -
say which fields to use and where the password lives, and build the URL where
the secret is set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit d2b1cba into master Sep 24, 2026
10 checks passed
@ralyodio
ralyodio deleted the ops/lock-down-data-ports branch September 24, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants