Skip to content

fix(core): clean error mapping + SSRF v6 hardening (review L1/L2/L3/L5)#118

Merged
vinsonconsulting merged 1 commit into
mainfrom
fix/review-batch-c2-hygiene
Jun 29, 2026
Merged

fix(core): clean error mapping + SSRF v6 hardening (review L1/L2/L3/L5)#118
vinsonconsulting merged 1 commit into
mainfrom
fix/review-batch-c2-hygiene

Conversation

@vinsonconsulting

Copy link
Copy Markdown
Owner

Batch C2 of the review fixes — the four remaining confirmed-Low findings, all in @limner/core. This closes every confirmed finding from the review.

ID Fix
L1 ProjectStore.create (D1 + local) now catches the UNIQUE violation from a raced duplicate-name INSERT → clean project already exists: <name> (the handler's getByName pre-check is TOCTOU). Protects MCP + CMA.
L2 ProjectStore.addNote wraps the INSERT to map a FOREIGN KEY violation (concurrent project delete after the F5 guard) → clean projectNotFound().
L3 httpResponseToError no longer embeds the raw upstream (OpenAI/Recraft) body in the client-facing PipelineError message — it's console.warn'd server-side; the client gets a clean status-coded message. Corrected the stale server.ts comment that claimed these were already sanitized.
L5 isBlockedIpv6 now blocks an embedded private/loopback IPv4 across the IPv4-compatible (::a.b.c.d) and NAT64 (64:ff9b::/96) forms, not just IPv4-mapped (::ffff:).

Tests

create maps duplicate-name to a clean error on both backends; NAT64 + IPv4-compatible private literals rejected by the SSRF guard. Suite 695 → 700. Typecheck + lint clean.

🤖 Generated with Claude Code

…SRF v6 (review L1/L2/L3/L5)

Four confirmed-Low findings from the release review, all in @limner/core.

L1 — ProjectStore.create (D1 + local): the handler's getByName pre-check is
TOCTOU; a concurrent same-name create raced the INSERT and surfaced a raw
"D1_ERROR: UNIQUE constraint failed". create() now catches the UNIQUE violation
and maps it to the clean "project already exists: <name>" message (the same the
pre-check produces). Protects MCP + CMA.

L2 — ProjectStore.addNote (D1 + local): the F5 get()-before-INSERT guard is
likewise TOCTOU against a concurrent project delete. The INSERT is now wrapped
to map a FOREIGN KEY violation to the clean projectNotFound() message instead of
the raw error.

L3 — _http.ts httpResponseToError embedded up to 500 chars of the upstream
(OpenAI/Recraft) response body in the PipelineError message, which reaches the
MCP client verbatim (third-party text that can echo request fragments/ids). The
body is now logged server-side (console.warn) only; the client gets a clean
status-coded message. Corrected the stale server.ts comment that claimed these
messages were already sanitized.

L5 — _url-guard.ts isBlockedIpv6 only blocked an embedded private IPv4 in the
IPv4-mapped (::ffff:) form. Extended to the deprecated IPv4-compatible
(::a.b.c.d) and NAT64 (64:ff9b::/96) forms too, so a private/loopback v4 embedded
in any of the three is rejected. (Low real-world impact on Workers egress, but
the shared core also runs in the Node/MCPB host.)

Tests: create maps duplicate-name to a clean error on both backends; NAT64 +
IPv4-compatible private literals rejected by the SSRF guard. Suite 695 -> 700.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Jim Vinson <jim@vinson.org>
@vinsonconsulting vinsonconsulting merged commit 8b6ec6d into main Jun 29, 2026
2 checks passed
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