Skip to content

fix(security): validate UUID in GET /workspaces/:id — 500→400 (#687) - #699

Closed
molecule-ai[bot] wants to merge 1 commit into
mainfrom
fix/issue-687-uuid-path-traversal
Closed

fix(security): validate UUID in GET /workspaces/:id — 500→400 (#687)#699
molecule-ai[bot] wants to merge 1 commit into
mainfrom
fix/issue-687-uuid-path-traversal

Conversation

@molecule-ai

@molecule-ai molecule-ai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Finding (#687): GET /workspaces/:id is on the open router with no authentication. Double-percent-encoded path traversal strings (e.g. ..%252f..%252fetc%252fpasswd) decode to ..%2f..%2fetc%2fpasswd after one URL-decode pass. Gin's path normalisation doesn't catch this, so the raw string reaches the handler and gets passed to PostgreSQL as a UUID parameter. PostgreSQL's type system rejects it, and the error propagated as HTTP 500 — leaking that a database is involved and providing a side-channel confirmation that the UUID parser was reached.

Expected: HTTP 400 for any non-UUID :id.

Fix

// Get handles GET /workspaces/:id
func (h *WorkspaceHandler) Get(c *gin.Context) {
    id := c.Param("id")
    // Validate UUID format before touching the DB (#687).
    if _, err := uuid.Parse(id); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid workspace ID"})
        return
    }
    // ... DB query proceeds only for valid UUIDs

WorkspaceAuth-gated routes (most /workspaces/:id/* paths) already return 401 for non-UUID IDs because ValidateToken() fails when workspace_id is not a UUID — those routes do NOT return 500. The open GET /workspaces/:id is the primary attack surface.

Test changes

Existing TestWorkspaceGet_* tests used non-UUID IDs (ws-get-1, ws-nonexistent, ws-dberr, ws-fin-1) — updated to valid UUID-format strings so they still exercise the DB query path. New test:

  • TestWorkspaceGet_InvalidUUID_Returns400 — verifies 400 for path traversal strings, legacy ws-* IDs, and empty string; confirms no DB calls are made.

Test plan

  • go build ./... — clean compile
  • go test ./internal/handlers/... -run TestWorkspaceGet — all 5 tests pass
  • CI: full go test ./... passes
  • Manual: curl -s http://localhost:8080/workspaces/..%252f..%252fetc%252fpasswd → 400
  • Manual: curl -s http://localhost:8080/workspaces/not-a-uuid → 400
  • Manual: curl -s http://localhost:8080/workspaces/<valid-uuid> → 200 or 404 (no 500)

Closes #687

🤖 Generated with Claude Code

Double-percent-encoded path traversal strings (e.g. ..%252f..%252fetc%252fpasswd)
decode to non-UUID values that bypass Gin's path normalization and reach the DB
handler. PostgreSQL's UUID type parser rejects the invalid string with an internal
error that previously propagated as HTTP 500, leaking that a database is involved.

Fix: call uuid.Parse() at the top of Get() before any DB access. Invalid UUIDs
now return 400 Bad Request. The open-router GET /workspaces/:id is the primary
attack surface (no WorkspaceAuth in the path); WorkspaceAuth-gated routes already
surface 401 because ValidateToken() fails on the malformed workspace_id.

Test updates: existing TestWorkspaceGet_* tests used non-UUID IDs (ws-get-1,
ws-nonexistent, etc.) — updated to valid UUID-format strings so they still reach
the DB query path. New TestWorkspaceGet_InvalidUUID_Returns400 verifies the fix
across path-traversal strings, legacy workspace IDs, and empty input.

Closes #687

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@molecule-ai

molecule-ai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by PR #701 (fix/issue-685-686-687-688-input-validation) which covers #687 plus #685, #686, #688 in a single reviewed bundle.

@molecule-ai molecule-ai Bot closed this Apr 17, 2026
@molecule-ai
molecule-ai Bot deleted the fix/issue-687-uuid-path-traversal branch May 20, 2026 06:22
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.

[OFFENSIVE] MEDIUM: Double-encoded path traversal returns 500 — unhandled UUID parse error

0 participants