forked from molecule-ai/molecule-core
`TranscriptHandler.Get` previously proxied `agent_card->>'url'` directly
to the outbound HTTP client with no validation. Since `agent_card` is
attacker-writable via /registry/register, a workspace-token holder
could point it at cloud metadata (169.254.169.254), link-local ranges,
or non-http schemes and pivot the platform container against internal
services (IMDS, Redis, Postgres, other containers on the Docker net).
Four required fixes per reviewer:
1. `validateWorkspaceURL(u *url.URL)` — runs before `httpClient.Do`:
- scheme must be http/https (rejects file://, gopher://, ftp://)
- cloud metadata hostname blocklist (GCP + Azure + plain "metadata")
- IMDS IP blocklist (169.254.169.254)
- IPv4/IPv6 link-local blocklist (169.254/16, fe80::/10, multicast)
- IPv6 unique-local fd00::/8 blocklist
- loopback + docker.internal still allowed for local dev
2. Query-param allowlist — `target.RawQuery = c.Request.URL.RawQuery`
forwarded everything verbatim, letting a caller smuggle params the
upstream transcript endpoint didn't intend to expose. Replaced with
an allowlist of `since` and `limit`.
3. Sanitized error string — `fmt.Sprintf("workspace unreachable: %v", err)`
leaked the actual internal host/IP via `net.OpError`. Now logs the
real error server-side and returns a plain "workspace unreachable"
to the caller.
4. 10 new regression test cases:
- `TestTranscript_Rejects{CloudMetadataIP,NonHTTPScheme,MetadataHostname,LinkLocalIPv6}`
exercise the handler end-to-end with each attack URL and assert
400 before the HTTP client fires.
- `TestValidateWorkspaceURL` table-drives the validator across
localhost/public/docker-internal (allowed) + IMDS/GCP/Azure/file/
gopher/link-local/multicast (rejected).
- `TestTranscript_ProxyPropagatesAllowlistedQueryParams` asserts
`secret=leak&cmd=rm` is stripped while `since=42&limit=7` pass
through.
Also fixed a pre-existing test bug: `seedWorkspace` was issuing a real
SQL Exec against sqlmock with no expectation set, so the prior test
helpers silently failed in CI. Replaced with `expectWorkspaceURLLookup`
which programs the mock correctly. All 11 tests now pass.
Closes #272
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| a2a_proxy_test.go | ||
| a2a_proxy.go | ||
| activity_test.go | ||
| activity.go | ||
| admin_test_token_test.go | ||
| admin_test_token.go | ||
| agent_test.go | ||
| agent.go | ||
| approvals_test.go | ||
| approvals.go | ||
| bundle.go | ||
| channels_test.go | ||
| channels.go | ||
| config_test.go | ||
| config.go | ||
| container_files.go | ||
| delegation_test.go | ||
| delegation.go | ||
| discovery_test.go | ||
| discovery.go | ||
| events_test.go | ||
| events.go | ||
| handlers_additional_test.go | ||
| handlers_extended_test.go | ||
| handlers_test.go | ||
| memories_test.go | ||
| memories.go | ||
| memory_test.go | ||
| memory.go | ||
| org_path_test.go | ||
| org_test.go | ||
| org.go | ||
| plugins_install_pipeline_test.go | ||
| plugins_install_pipeline.go | ||
| plugins_install.go | ||
| plugins_listing.go | ||
| plugins_sources.go | ||
| plugins_test.go | ||
| plugins.go | ||
| registry_test.go | ||
| registry.go | ||
| restart_context_test.go | ||
| restart_context.go | ||
| schedules_test.go | ||
| schedules.go | ||
| secrets_test.go | ||
| secrets.go | ||
| socket.go | ||
| team_test.go | ||
| team.go | ||
| template_import_test.go | ||
| template_import.go | ||
| templates_test.go | ||
| templates.go | ||
| terminal.go | ||
| traces_test.go | ||
| traces.go | ||
| transcript_test.go | ||
| transcript.go | ||
| viewport_test.go | ||
| viewport.go | ||
| webhooks_test.go | ||
| webhooks_workflow_test.go | ||
| webhooks.go | ||
| workspace_provision_test.go | ||
| workspace_provision.go | ||
| workspace_restart_test.go | ||
| workspace_restart.go | ||
| workspace_test.go | ||
| workspace.go | ||