Skip to content

feat(security): allow named internal hosts with ALLOWED_HOSTS - #21

Merged
ChiragAgg5k merged 2 commits into
mainfrom
feat/allowed-hosts
Oct 5, 2026
Merged

ChiragAgg5k merged 2 commits into
mainfrom
feat/allowed-hosts

Conversation

@ChiragAgg5k

Copy link
Copy Markdown
Member

What does this PR do?

#18 (released in 0.3.5) refuses every host that isn't publicly routable. That's right for Appwrite Cloud, which captures public preview URLs, but it stops self-hosted Appwrite from upgrading: self-hosted captures sites through the internal appwrite service (_APP_WORKER_SCREENSHOTS_ROUTER=http://appwrite), and that name resolves to a private Docker-network address.

This adds ALLOWED_HOSTS, a comma-separated list of exact hostnames, matched case-insensitively, that may resolve privately:

  • isAllowedHost(host): allowed if the host is listed, otherwise the existing isPublicHost check.
  • assertPublicUrl is renamed assertAllowedUrl, since a listed internal host now passes.
  • Both /v1/screenshots and /v1/reports use it for the first URL and for every request the page makes.
  • Empty by default, so behaviour is unchanged unless an operator sets it.
  • Matching is by exact hostname: listing appwrite doesn't allow 127.0.0.1, 169.254.169.254, or any other private name or address.
  • The README has a new Configuration section.

Self-hosted then sets ALLOWED_HOSTS=appwrite on the browser service and can move to the release that includes this.

Out of scope: redirect hops still aren't checked (#20).

Test Plan

  • bun test tests/unit: 94 pass. New cases cover parsing, a listed host allowed case-insensitively, other private and metadata hosts still blocked with a list set, public hosts unaffected, and nothing private allowed by default.
  • bun run lint (Biome), bun run check (ESLint) and bun run type-check are clean.
  • Real server with Chromium, against a page served on localhost:8099:
Server http://localhost:8099/ http://127.0.0.1:8099/ http://169.254.169.254/ https://example.com
No ALLOWED_HOSTS 400 not publicly routable — — 200 image
ALLOWED_HOSTS=localhost 200 image 400 400 200 image

Related

0.3.5 refuses every non-public host, so a self-hosted Appwrite that captures
its own sites through the internal 'appwrite' service could not upgrade.
ALLOWED_HOSTS lists exact hostnames that may resolve privately; every other
private, loopback and metadata address stays blocked.
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Docker Image Stats

Metric Value
Image Size 494MB
Memory Usage 155.5MiB
Cold Start Time 1.02s
Screenshot Time .87s

Screenshot benchmark: Average of 3 runs on https://appwrite.io

@hansi-codes

hansi-codes Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🟢 Tier S · Ready to merge

The documentation update resolves the outstanding finding without introducing a new defect.

Adds an opt-in ALLOWED_HOSTS list for exact, case-insensitive exceptions to public-host checks. Screenshot and report routes use the new helper, with unit coverage and a configuration example. The README now explicitly documents unchecked redirect hops and recommends outbound network restrictions.

Latest changes: The README qualifies its blocking guarantee and documents the redirect-hop limitation with a recommendation to restrict outbound network access.

Verdict New comments Fixed Still open
✅ Approved 0 1 0
📂 Walkthrough · 5
File Change
README.md Documents ALLOWED_HOSTS, a Compose example, and the redirect-hop security limitation.
src/routes/reports.ts Uses allowed-host checks for initial navigation and intercepted requests.
src/routes/screenshots.ts Uses allowed-host checks for initial navigation and intercepted requests.
src/utils/ssrf.ts Parses the environment allowlist and adds hostname and URL checks with explicit exceptions.
tests/unit/ssrf.test.ts Covers parsing, allowlist matching, and preservation of private-host blocking.
✅ Fixed since the last review · 1
  • Mention the redirect limitation in the security guarantees · README.md:51

Reviewed the commits since 36ba120 · Details · Comment @hansi-codes review to re-run, or mention @hansi-codes with a question.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Tier A · Looks good to merge. Summary

Comment thread README.md Outdated

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Tier S · Looks good to merge. Summary

@ChiragAgg5k
ChiragAgg5k merged commit cb8eab2 into main Oct 5, 2026
4 checks passed
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.

1 participant