Skip to content

docs: staging environment design + Phase 36 + Tunnel migration plan - #934

Merged
HongmingWang-Rabbit merged 1 commit into
mainfrom
feat/cloudflare-tunnel-per-tenant
Apr 18, 2026
Merged

HongmingWang-Rabbit merged 1 commit into
mainfrom
feat/cloudflare-tunnel-per-tenant

Conversation

@HongmingWang-Rabbit

Copy link
Copy Markdown
Contributor

Summary

Why

Today's session broke CI twice and caused hours of edge cache issues. No infra change should reach production without staging verification.

Implementation order

  1. Phase 36 (staging) — ~2.5 hours
  2. Phase 33 (Tunnel on staging) — test first
  3. Phase 35 (security) — test on staging
  4. Promote all to production

🤖 Generated with Claude Code

Full staging environment that mirrors production. Every infra change
ships to staging first before promotion. Gates Phase 33 (Tunnel) and
Phase 35 (security hardening).

Components: Railway staging env, Neon branch, staging DNS, tagged
Docker images, promotion workflow, automated smoke tests.

Also marks Phase 33 as migrating from Worker to Cloudflare Tunnel
(issue #933), prerequisite: staging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@HongmingWang-Rabbit
HongmingWang-Rabbit merged commit ac2923b into main Apr 18, 2026
@HongmingWang-Rabbit
HongmingWang-Rabbit deleted the feat/cloudflare-tunnel-per-tenant branch April 18, 2026 04:40
molecule-ai Bot pushed a commit that referenced this pull request Apr 18, 2026
…#934)

GET /workspaces/:id sits on the open router (no auth required, used by
canvas nodes for bootstrap polling).  A security audit found that six
fields were being returned to any unauthenticated caller who knew a
workspace UUID:

  • url            — internal container / loopback address
  • current_task   — live prompt snippet
  • uptime_seconds — operational metric
  • last_error_rate — operational metric
  • last_sample_error — error detail text
  • workspace_dir  — host filesystem path

Additionally, agent_card.url exposed the same internal container address
in A2A discovery format.

Fix: extend the existing financial-field stripping block (PR #611) to
unconditionally delete all six operational keys from the response map,
and scrub agent_card.url via a JSON unmarshal → delete → re-marshal
round-trip before the response is written.

Add two regression tests:
  • TestWorkspaceGet_OperationalFieldsStripped — verifies all six fields
    absent; confirms id/name/status/active_tasks/runtime still present.
  • TestWorkspaceGet_AgentCardURLScrubbed — verifies agent_card.url is
    absent while agent_card.name / agent_card.version survive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 18, 2026
…kspaces/:id

DAST audit confirmed that GET /workspaces/:id (open router, no middleware
auth) returned the full workspace map to any caller who knew a valid UUID:
- current_task   — live prompt snippet (46-char truncation confirms live data)
- url            — internal loopback address (http://127.0.0.1:<port>)
- agent_card.url — internal container address (http://<container>:8000)
- parent_id      — org hierarchy enumeration
- uptime_seconds, last_error_rate — operational telemetry

SSRF risk: agent_card.url / url consumed server-side by A2A peer discovery
path, enabling container SSRF from any caller with a sibling workspace UUID.

Fix: replace unconditional field stripping (#934) with auth-conditional
response:

  Authenticated (valid bearer token, any workspace) → full response as-is
  Unauthenticated / invalid token            → {id, name, agent_card} only
                                               agent_card filtered to
                                               {name, description, version}

The endpoint stays 200 in both cases — not 401 — so public A2A peer
discovery continues to work for agent cards without credentials.

Changes:
- workspace.go: wsauth.BearerTokenFromHeader + wsauth.ValidateAnyToken gate
  before c.JSON; unauthenticated path returns gin.H with 3 keys only
- workspace.go: new publicAgentCard() helper — filters json.RawMessage to
  {name, description, version}; url and all other keys dropped
- workspace_test.go: replace #934 unconditional tests with:
    TestWorkspaceGet_Unauthenticated_OnlySafeFields  — all ops fields absent
    TestWorkspaceGet_Authenticated_ReturnsFullResponse — ops fields present
    TestWorkspaceGet_AgentCardURLAbsentUnauthenticated — card url absent,
      skills absent, description/version intact
- workspace_test.go: update TestWorkspaceGet_Success to reflect unauth behavior
- handlers_test.go: rename TestWorkspaceGet_CurrentTask →
  TestWorkspaceGet_CurrentTask_Authenticated; add bearer token + auth mock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
molecule-ai Bot pushed a commit that referenced this pull request Apr 21, 2026
…enant

docs: staging environment design + Phase 36 + Tunnel migration plan
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