fix(security): scope screenshots, baselines and metrics to the caller's team - #134
Merged
Merged
Conversation
…'s team Several endpoints let an authenticated user of one team read or change another team's data. All were confirmed against a running instance as a plain user of a second team. Screenshots - GET /metrics/screenshot, /screenshot/grouped/platform and /screenshot/grouped/tag returned every team's screenshots (thumbnails, build ids and server paths included). They now only search builds of the caller's teams, as do the /screenshot/views and /screenshot/tags name lookups. - POST /screenshot accepted uploads into any team's build. It now requires access to the build's team and removes the uploaded file when rejected. Baselines - Baselines now record the team of their screenshot's build. Listing, reading and updating are limited to that team; a baseline can only be created from, or pointed at, a screenshot of the same team; and the "already exists" check only considers that team's baselines. - Comparing a screenshot against its baseline only uses the screenshot's own team's baseline. Before, a team using the same view name was compared against (and got a diff image of) another team's baseline. - GET /baseline takes an optional teamId. Admins, who can read every team, still see all teams' baselines without it. - Existing baselines are given their team on startup (idempotent backfill), so comparisons keep working after the upgrade. - GET /baseline/:id read the id from the query string and always returned 404; it now reads the path parameter. Other - POST /execution accepted executions into any team's build; it now requires access to the build's team. - GET /metrics/phase returned any team's metrics, test titles included; it now requires access to the team. - PUT /environment/:id and PUT /phase/:id were open to every user and API token. Environments and phases are shared, so renaming them is now admin-only, like creating and deleting them. - GET /screenshot/tags without a limit always failed with a 500 (MongoDB rejects $limit: 0); it now defaults to 10 like the view lookup. Adds test/team-scoping.tests.js (20 tests) and documents the 403 responses, the baseline team and the teamId filter in swagger.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KgQXSUjuLVXmLWxobnMfSf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A security review found several endpoints that let an authenticated user of one team read or change another team's data. Each was confirmed against a running instance, acting as a plain (non-admin) user of a second team. This PR adds team checks to all of them.
GET /metrics/screenshot?thumbnail=true,/screenshot/grouped/platform,/screenshot/grouped/tag/screenshot/views,/screenshot/tagsDELETEchecked access. Any user could list, read, edit (e.g. ignore the whole image) and create baselines for any team. Comparison found the baseline by view and platform only, so a team using another team's view name got a diff image of that team's baselinePOST /execution,POST /screenshotGET /metrics/phase?teamId=PUT /environment/:id,PUT /phase/:idBaselines in detail
screenshotIdmust belong to the same team.GET /baseline:teamId, which must be a team the caller can read.baselineUtils.backfillTeams) that sets each legacy baseline's team from its screenshot's build, so existing comparisons keep working after the upgrade. A legacy baseline whose screenshot or build no longer exists is left without a team and is only accessible to admins.Also fixed
GET /baseline/:idread the id fromreq.queryinstead ofreq.params, so it always returned 404.GET /screenshot/tagswithout alimitalways returned 500, because it defaulted to$limit: 0, which MongoDB rejects. It now defaults to 10, like/screenshot/views. This was broken onmastertoo.Compatibility
GET /baselineuntil the UI passesteamId. That's a small follow-up in angles-ui and angles-javascript-client.Testing
test/team-scoping.tests.js(20 tests). Each acts as a user of one team against another team's data and asserts 403 / 404 / filtered results. It also covers:npx eslint app server.jsis clean.Baseline team backfill: 1 of 1 baseline(s) updated.Notes
POST /execution, so whichever PR merges second may need a small conflict resolved.infoin the UI, login rate limiting, and clickjacking / security headers.🤖 Generated with Claude Code
https://claude.ai/code/session_01KgQXSUjuLVXmLWxobnMfSf
Generated by Claude Code