Cross-tenant repository access via predictable resource id
The /api/repos/{id}/secrets endpoint authenticates the
caller's session but does not check whether the requested repo is
owned by the caller's tenant. Any authenticated user can read
credentials and webhooks from any other tenant's repository simply
by guessing the integer id.
Discovery
Arbiter ingested 1,847 captured exchanges from a HAR file and built the state graph. The /api/repos/{id}/secrets endpoint appeared in 4 distinct user sessions. From those sessions Arbiter inferred the constraint:
session.tenant_id == path.repo.owner_tenant_id
Proof of concept
Arbiter probed the constraint by issuing a request from one tenant's authenticated session for another tenant's repo:
$ curl -sS -b "session=$ATTACKER_SESSION" \ https://target.example.com/api/repos/8/secrets { "repo_id": 8, "owner_tenant": 4, "secrets": { "AWS_ACCESS_KEY_ID": "AKIA ··· XQYV", "DATABASE_URL": "postgres://···", "SLACK_WEBHOOK": "https://hooks.slack.com/···" } }
Browser verification
Headless Chrome navigated to the same URL with the attacker's session cookie. Every signal Arbiter records confirms the vulnerability:
- HTTP 200 · response body contained credentials in plaintext
- DOM snapshot includes a <pre> rendering the secrets
- No CSP violations, no auth errors in the console
- Screenshot captured: cross-tenant-secrets-render.png
Recommendation
Enforce row-level tenant scoping at every read endpoint that keys off a user-controlled resource id:
where repo.owner_tenant_id = current_session.tenant_id
Pattern matchers will not catch this — the request is syntactically identical to a legitimate one. Only constraint inference catches an authorization gap that has no payload.
Export
This finding is one click away from any of:
- HackerOne submission · markdown with severity, repro, impact
- Bugcrowd / Intigriti format · identical content, platform-specific frontmatter
- SARIF 2.1.0 · machine-readable for CI/CD pipelines
- Raw HTTP · full request & response capture for replay