Skip to content

chore(release): pending release v0.44.1#642

Merged
marcusrbrown merged 7 commits into
releasefrom
next
May 19, 2026
Merged

chore(release): pending release v0.44.1#642
marcusrbrown merged 7 commits into
releasefrom
next

Conversation

@fro-bot
Copy link
Copy Markdown
Contributor

@fro-bot fro-bot Bot commented May 17, 2026

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

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.
@fro-bot fro-bot Bot requested review from fro-bot and marcusrbrown as code owners May 17, 2026 21:20
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>
fro-bot Bot added 2 commits May 19, 2026 00:07
Co-authored-by: fro-bot[bot] <109017866+fro-bot[bot]@users.noreply.github.com>
@marcusrbrown marcusrbrown merged commit c285909 into release May 19, 2026
1 check passed
@marcusrbrown marcusrbrown deleted the next branch May 19, 2026 07:20
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