Skip to content

fix(api): reject invalid version skill ids - #144

Merged
dirtybits merged 1 commit into
mainfrom
fix/reject-invalid-version-skill-id
Aug 21, 2026
Merged

fix(api): reject invalid version skill ids#144
dirtybits merged 1 commit into
mainfrom
fix/reject-invalid-version-skill-id

Conversation

@dirtybits

Copy link
Copy Markdown
Owner

Summary

  • return 404 Skill not found for malformed repo skill UUIDs on POST /api/skills/[id]/versions
  • reject the identifier before request upload parsing, storage work, or PostgreSQL ::uuid casts
  • add a no-database regression and use a valid UUID across version-route fixtures

Test plan

  • npm test --workspace @agentvouch/web -- __tests__/api/skills-versions.test.ts --maxWorkers=1 --no-fileParallelism
  • npm run format:check
  • npm run lint:web
  • npm run typecheck
  • npm test --workspace @agentvouch/web -- --maxWorkers=1 --no-fileParallelism
  • npm exec --workspace @agentvouch/web -- next build --webpack
  • git diff --check

The production build passed with the existing viem Tempo dynamic-import warning and expected local DATABASE_URL static-generation fallbacks. No live flow was run; this is request-boundary validation only.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

Copy link
Copy Markdown
Owner Author

Code review

Manual review standing in for Qodo.

Verdict: no findings. Approve.

The guard is the first statement in POST, ahead of parseSkillUploadRequest, putSkillTree/pinSkillContent, and all three ${id}::uuid casts — so a malformed id is now rejected before any upload parsing or storage work, not just before the DB. That ordering is the right call for this route specifically, since it's the one endpoint in this series that does expensive work before touching Postgres.

Checked for regressions, all clean:

  • No legitimate non-UUID caller. Every skills.id comes from randomUUID()/gen_random_uuid() (v4), which always satisfies the version and variant nibbles in UUID_RE. Verified the insert sites in skills/route.ts, mirror/sync.ts, db-cutover.ts, and seed/route.ts.
  • Callers pass the DB UUID. SkillDetailClient.tsx uses skill.id from the API record; the CLI path is documented as "Repo skill UUID"; skill.md states the contract.
  • No missing chain- branch. Unlike install/update, this route has no chain path to preserve — chain-<pubkey> ids are synthesized by the route resolver and never exist in skills, so they previously 500'd and now 404.
  • The test rewrites aren't vacuous. The SKILL_ID substitution keeps assertions meaningful — "signed skill id does not match" still signs "other-uuid" against SKILL_ID, so the 401 mismatch path is still exercised. The new no-DB test runs before any mockSql.mockReturnValue, so expect(mockSql).not.toHaveBeenCalled() is a real assertion.

skills-versions.test.ts 10/10, typecheck clean.

Follow-up: two routes still unguarded

I audited every ${id}::uuid cast under app/api/skills/[id] to see what this series still leaves open. After #143 and #144 merge, two routes remain:

Route Cast sites Status
[id]/route.ts (GET, PATCH) 612, 706, 772, 813, 848 unguarded
[id]/purchase/verify/route.ts 271 unguarded

Both take id straight from params with no validation, so both still return a 500 on a malformed id while install, update, and now versions return 404. purchase/verify is the one I'd prioritize — it's on the payment path.

Not filed as a finding on this PR; it's pre-existing and outside the diff. Flagging it because it looks like the natural next step in this sequence.


Generated by Claude Code

@dirtybits
dirtybits merged commit 2890b42 into main Aug 21, 2026
3 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