Gate /api/project/{slug}/versions by canViewProject (C-4) #99

Merged
qwc merged 1 commit from fix/api-versions-access-check into main 2026-05-18 16:16:26 +02:00
Owner

Summary

Workstream item #4. Audit finding C-4 (Critical).

handleAPIVersions did no access check and returned the version list — tags, content types, creation timestamps — for any project regardless of visibility, to any caller (including unauthenticated). Contrast handleAPIProjects (already filtered) and handleAPISearch (results filtered by canViewProject).

Changes

  • handleAPIVersions now loads the user from context (the route is wrapped in withSession, so the session-cookie user is available or nil for anonymous) and returns 404 if canViewProject denies — same status as a missing project so existence isn't leaked.
  • Regression test file api_versions_access_test.go:
    • Public reachable anonymously (sanity)
    • Private hidden from anonymous (the actual fix)
    • Custom hidden from anonymous (the actual fix)
    • Nonexistent slug 404 (sanity)
    • A logged-in viewer with a per-project grant on a custom-visibility project still sees the list (no over-blocking)

Test plan

  • go test ./... passes
  • New tests cover the four anonymous cases plus the granted-viewer sanity case
  • Manual: hit /api/project/<private-slug>/versions from curl without a cookie, confirm 404
  • Manual: hit it with a cookie of a user who has access, confirm 200 with the list

FYI: PR #95 (Honor per-project access on private-visibility projects — the original #94 fix) is still open, not merged. The broader audit assumes it will land — without it, an editor who creates a private project still can't see it (the original #94 symptom). Worth merging alongside.

🤖 Generated with Claude Code

## Summary Workstream item #4. Audit finding **C-4 (Critical)**. `handleAPIVersions` did no access check and returned the version list — tags, content types, creation timestamps — for any project regardless of visibility, to any caller (including unauthenticated). Contrast `handleAPIProjects` (already filtered) and `handleAPISearch` (results filtered by `canViewProject`). ## Changes - `handleAPIVersions` now loads the user from context (the route is wrapped in `withSession`, so the session-cookie user is available or nil for anonymous) and returns **404** if `canViewProject` denies — same status as a missing project so existence isn't leaked. - Regression test file `api_versions_access_test.go`: - Public reachable anonymously (sanity) - Private hidden from anonymous (the actual fix) - Custom hidden from anonymous (the actual fix) - Nonexistent slug 404 (sanity) - A logged-in viewer with a per-project grant on a custom-visibility project still sees the list (no over-blocking) ## Test plan - [x] `go test ./...` passes - [x] New tests cover the four anonymous cases plus the granted-viewer sanity case - [ ] Manual: hit `/api/project/<private-slug>/versions` from `curl` without a cookie, confirm 404 - [ ] Manual: hit it with a cookie of a user who has access, confirm 200 with the list --- **FYI**: PR #95 (`Honor per-project access on private-visibility projects` — the original #94 fix) is still open, not merged. The broader audit assumes it will land — without it, an editor who creates a private project still can't see it (the original #94 symptom). Worth merging alongside. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Gate /api/project/{slug}/versions by canViewProject
All checks were successful
CI / test (pull_request) Successful in 1m20s
CI / build (pull_request) Successful in 1m7s
CI / docker (pull_request) Has been skipped
d03d587d2f
Audit finding C-4 (Critical). handleAPIVersions did no access check and
returned the version list — tags, content types, creation timestamps —
for any project regardless of visibility, to any caller (including
unauthenticated). Contrast handleAPIProjects (already filtered) and
handleAPISearch (results filtered by canViewProject).

Loads the user from context (the route is wrapped in withSession so the
session-cookie user is available, nil otherwise) and returns 404 if
canViewProject denies — same status as a missing project, so existence
isn't leaked.

Regression test covers four cases: public reachable anonymously, private
hidden from anonymous, custom hidden from anonymous, nonexistent slug
also 404. A second test confirms a viewer with a per-project grant on a
custom-visibility project still sees the list (no over-blocking).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qwc merged commit edcf0cfc9a into main 2026-05-18 16:16:26 +02:00
qwc deleted branch fix/api-versions-access-check 2026-05-18 16:16:26 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
qwc-open/asiakirjat!99
No description provided.