chore(release): pending release v0.44.1#642
Merged
Merged
Conversation
fix(gateway): reject non-globally-routable IPs in OBJECT_STORE_HOSTS
OBJECT_STORE_HOSTS previously accepted any IP literal because RFC 1123
labels permit pure digits. That left an SSRF vector via the env var:
an operator (or attacker with deploy-config access) could point
OBJECT_STORE_HOSTS at cloud metadata services (169.254.169.254),
loopback (127.0.0.1), RFC 1918 private ranges (10.x, 172.16-31.x,
192.168.x), CGNAT (100.64/10), or documentation ranges — all reachable
from inside the workspace network on some providers.
The MinIO/self-hosted use case still needs IP support, so a blanket
ban would over-correct. Instead each entry is parsed through Python's
ipaddress module and rejected unless it reports `is_global` True.
Why `is_global` rather than the individual flags: Python's ipaddress
module returns False for `is_private`, `is_reserved`, `is_link_local`,
`is_multicast`, AND `is_global` on the 100.64.0.0/10 CGNAT range.
A guard built on the individual flags silently accepts CGNAT. Switching
to `not ip.is_global` is both simpler and more complete — it correctly
rejects private, loopback, link-local, multicast, reserved, unspecified,
CGNAT, documentation (192.0.2/24, 198.51.100/24, 203.0.113/24,
2001:db8::/32), and benchmarking (198.18/15) ranges in one check.
Validation order:
empty-skip
→ wildcard-reject
→ ip-literal-validate (new)
→ port-reject
→ hostname-validate
→ lowercase-normalize
→ append
The IP check fires before port-reject so bare IPv6 literals (::1,
fe80::1, fc00::1) get caught by the IP validator rather than the
colon-detection heuristic. The port-reject step still catches
bracket-form IPv6 with a port ([2001:db8::1]:9000) — that case is
not supported.
Tests: deploy/mitmproxy/test_allowlist.py 31 -> 44 (+13).
- accepts_public_ipv4 — 8.8.8.8 path covered by the decision-evolution
test, no separate redundant case
- rejects_metadata_service_ipv4 — 169.254.169.254
- rejects_private_ipv4_10, _172, _192
- rejects_loopback_ipv4 — 127.0.0.1
- rejects_unspecified_ipv4 — 0.0.0.0
- rejects_cgnat_ipv4 — 100.64.0.1 (Python ipaddress quirk regression guard)
- rejects_documentation_ipv4 — 192.0.2.1 (TEST-NET-1, RFC 5737)
- accepts_public_ipv6 — 2001:4860:4860::8888
- rejects_loopback_ipv6 — ::1
- rejects_link_local_ipv6 — fe80::1
- rejects_unique_local_ipv6 — fc00::1
- rejects_documentation_ipv6 — 2001:db8::1
Two existing tests updated for the new behavior:
- test_object_store_hosts_accepts_ipv4_literal now asserts that a
public IP is accepted AND a private IP is rejected, with the
docstring documenting the decision evolution.
- test_object_store_hosts_rejects_ipv6_literal now asserts the IP
validator catches ::1 with a "globally-routable" / "loopback"
message instead of the previous IPv6+port heuristic.
CI: deploy/** added to the paths-filter `config` anchor so
deploy-only PRs trigger Build/Test/Lint/Test GitHub Action. Without
this, deploy-only changes skip every job and the action review
workflow (which gates on Build) never runs.
Verification: 44/44 Python tests, 82/82 gateway TS, lint/types/build
clean, compose config validates.
Co-authored-by: fro-bot[bot] <109017866+fro-bot[bot]@users.noreply.github.com>
fix(gateway): plumb DISCORD_GUILD_ID through compose, fix empty-file secret read, document testing-only config Three real friction points for someone bringing up the deploy stack for the first time on their own Discord server: 1. compose.yaml didn't wire DISCORD_GUILD_ID through to the gateway container. config.ts already read it via readOptionalSecret, but the secret file mount and the *_FILE env-var pointer were missing. Without this, slash commands register globally and propagate over up to an hour — instead of guild-scoped registration which propagates in ~5 seconds. Critical for dev iteration. 2. readOptionalSecret returned the empty string '' for an empty file instead of null. The path was that an operator would `touch deploy/secrets/discord-guild-id` to satisfy the bind-mount without actually setting a guild, intending to register globally — but the empty string flowed into Discord's slash-command registration API as an explicit guild ID. .trim() + explicit empty-string→null guard fixes it; two regression tests cover empty and whitespace-only files. 3. deploy/README.md implied that S3 setup is required for everything. The v1 gateway daemon validates S3 credentials at startup but does not actually write to S3 — that arrives in Units 5-7 with the workspace agent. New "Testing-Only Configuration" subsection documents the minimum viable secrets for verifying just the Discord plumbing, and the bucket-scoping subsection now cross-references it. Also adds deploy/.env.example (gitignored copy target) documenting OBJECT_STORE_HOSTS with v1 context, and deploy/.gitignore as belt-and-suspenders for .env exclusion at the directory level. Tests: packages/gateway 82 -> 84 (+2). docker compose config validates end-to-end with all six secret files present (including the new discord-guild-id, which may be empty).
Co-authored-by: fro-bot[bot] <109017866+fro-bot[bot]@users.noreply.github.com>
Co-authored-by: fro-bot[bot] <109017866+fro-bot[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pending Release: v0.44.1
This PR tracks changes pending release. Released on the next auto-release cycle (Sunday/Wednesday) or via manual dispatch.
Merge this PR to trigger a release. Releases also run automatically on Sunday/Wednesday at 20:00 UTC, or via manual workflow dispatch.
Commits Since Last Release
Auto-generated by the release pipeline. Updated: 2026-05-19 07:10 UTC