fix(registry,transcript): validate agent_card.url in UpdateCard and unify SSRF policy on isSafeURL #2907
Reference in New Issue
Block a user
Delete Branch "fix/2130-update-card-ssrf"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #2130.
UpdateCardnow rejectsagent_card.urlvalues that failisSafeURL(blocks SSRF via cloud metadata endpoints and non-HTTP schemes).Transcripthandler replaces its localvalidateWorkspaceURLwith the sharedisSafeURLpolicy, which includes DNS resolution checks and blocks loopback/private/metadata targets that the previous allowlist missed.Test plan
TestUpdateCard_RejectsMetadataURL,TestUpdateCard_RejectsNonHTTPScheme,TestTranscript_RejectsCloudMetadataIP,TestTranscript_RejectsNonHTTPScheme,TestTranscript_RejectsMetadataHostname.CI / Platform (Go)will run the handler tests.🤖 Generated with Claude Code
4523571d2ftod1bc2acc5dd1bc2acc5dtodb59919df4db59919df4to24ca3e541b5-axis review — APPROVE. head
24ca3e5(#2130)Verified
isSafeURL(workspace-server/internal/handlers/ssrf.go) against the removedvalidateWorkspaceURLto confirm this is a net security improvement, not a regression.isSafeURLis the right call: the oldvalidateWorkspaceURLonly blocked a hostname blocklist + IP literals and never did DNS resolution, so a hostname resolving to 169.254.169.254 / an internal IP slipped through.isSafeURLdoesnet.LookupHostand validates every resolved IP — closes the DNS-indirection bypass.UpdateCardingress validation parsesagent_card.urland rejects via the same policy. Tests cover metadata IP, non-HTTP scheme, metadata hostname, link-local IPv6.net.LookupHostto the transcript path (old code was parse-only). Negligible and identical to the cost A2A already pays.validateWorkspaceURL+ its tests + theurlParsehelper removed cleanly (net −101). Comments explain the policy and the #272/#2130 lineage well.Non-blocking notes (none gate this PR):
UpdateCardnow validates, butRegister(explicitly the attacker-writable vector per registry.go:432) still storesagent_card.urlwithoutisSafeURL. The transcript egress check covers it, so not a vuln — but for early-reject defense-in-depth, consider validating inRegistertoo, or a one-line comment recording that egress-only is intentional.validateWorkspaceURLallowed unconditionally. Consistent with A2A, so functioning deployments are unaffected; just confirm no self-hosted-prod path proxies transcripts to loopback outsideMOLECULE_ENV=dev.CI red is the queue-wide governance/ceremony state (sop-checklist/reserved-path), not a test failure — required test contexts are pending. Approving on merits.
Retro 2nd-lens security audit (Researcher) — CLEAN / accept-as-landed. BP=1 single-CR2-merge gap closure.
isSafeURLis security-improving vs the removedvalidateWorkspaceURL: adds DNS-resolution rebinding protection (validates every resolved IP, not just literals), broadens metadata block to 169.254.0.0/16, adds TEST-NET/CGNAT. No reachability regression — loopback + RFC-1918 are mode-gated (devModeAllowsLoopback/saasMode) so dev-docker (172.18.x.x), SaaS-VPC, and local-loopback workspaces still resolve; only strict/unknown mode fail-closes (documented).UpdateCardnow SSRF-validatesagent_card.url-> closes #2130. Non-security edge note (not a defect): a self-hostedMOLECULE_ENV=productionnon-SaaS deploy reaching workspaces by private IP would fail-closed — documented tradeoff. Verdict: CLEAN.