e2b-dev / e2b-dev/runtime

fix(api): PostTemplatesTags queries database and begins transaction before team ownership check (tag enumeration oracle)

Open Beginner friendly
#3,573 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
1.6k
Forks
438
PR merge metrics
No merged PRs in 30d

Description

Problem

In POST /templates/tags (packages/api/internal/handlers/template_tags.go), the control plane handler initiates a database transaction via a.sqlcDB.WithTx(ctx) and executes client.GetTemplateWithBuildByTag before validating whether the authenticated user's team owns the requested template (if aliasInfo.TeamID != team.ID).

This ordering defect causes two distinct issues:

  1. Tag Enumeration / Information Disclosure Oracle:
    When a user sends POST /templates/tags targeting a template owned by a different team:

    • If the requested tag does not exist on that template: GetTemplateWithBuildByTag returns sql.ErrNoRows, which triggers ErrTemplateNotFound, responding with 404 Not Found ("Template '<target>' with tag '<tag>' not found").
    • If the requested tag exists on that template: the query succeeds, and only afterwards at line 115 is aliasInfo.TeamID != team.ID checked, responding with 403 Forbidden ("You don't have access to sandbox template '%s'").
      An unauthorized tenant can therefore determine whether arbitrary private build tags (e.g., staging, v2.0.0-rc1, hotfix) exist on other teams' templates by probing the endpoint and observing the difference between HTTP 404 and HTTP 403.
  2. Unnecessary Database Load and Transaction Allocation:
    Every unauthorized or invalid tag request against another tenant's template allocates a Postgres transaction connection from the pool (WithTx), acquires read locks, executes queries, and then rolls back upon failure.

Root Cause

In packages/api/internal/handlers/template_tags.go:L75-L122, tag resolution and database transaction creation precede the team ownership authorization check:

// Current flow in template_tags.go:
aliasInfo, err := a.templateCache.ResolveAlias(ctx, templateID)
if err != nil { ... }

// BUG: Transaction started and DB queried BEFORE checking aliasInfo.TeamID == team.ID
txErr := a.sqlcDB.WithTx(ctx).Exec(func(queries *queries.Queries) error {
    build, err := queries.GetTemplateWithBuildByTag(ctx, ...)
    if err != nil {
        return ErrTemplateNotFound // Returns 404 to unauthorized caller if tag missing
    }
    ...
    if aliasInfo.TeamID != team.ID {
        return a.sendAPIStoreError(c, http.StatusForbidden, ...) // Returns 403 only if tag exists
    }
})

In contrast, peer handlers such as DeleteTemplatesTags (template_tags.go:L139) and GetTemplatesTemplateIDTags (template_tags.go:L21) enforce if aliasInfo.TeamID != team.ID immediately after alias resolution:

Handler Authorization Check Timing Tag Enumeration Vulnerable?
GET /templates/{templateID}/tags Immediate after ResolveAlias No
DELETE /templates/tags Immediate after ResolveAlias No
POST /templates/tags (Current) Deferred after GetTemplateWithBuildByTag DB query Yes (404 vs 403 Oracle)
POST /templates/tags (Expected) Immediate after ResolveAlias No (403 Forbidden)

Reproduction Steps

  1. Create a template template-A under team-1 with tag v1.0.0.
  2. Authenticate as an unrelated user team-2.
  3. Send POST /templates/tags targeting template-A with tag: "v9.9.9" (non-existent).
    • Observed: HTTP 404 Not Found ({"code": 404, "message": "Template 'template-A' with tag 'v9.9.9' not found"})
  4. Send POST /templates/tags targeting template-A with tag: "v1.0.0" (existing tag).
    • Observed: HTTP 403 Forbidden ({"code": 403, "message": "You don't have access to sandbox template 'template-A'"})
  5. Expected: Both requests must return HTTP 403 Forbidden without leaking metadata about tag existence.

Technical Context

  • File affected: packages/api/internal/handlers/template_tags.go
  • Subsystem: Control Plane API / Templates & Tags
  • Impact: Medium (Security/Privacy: cross-tenant metadata disclosure & unnecessary database transaction overhead)

Proposed Changes

# Change File(s) Affected Complexity
1 Move if aliasInfo.TeamID != team.ID check immediately after ResolveAlias before WithTx packages/api/internal/handlers/template_tags.go Trivial
2 Add unit test TestPostTemplatesTags_RejectsOtherTeamTemplate verifying 403 Forbidden packages/api/internal/handlers/template_tags_test.go Low

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/api/internal/handlers/template_tags.go at PostTemplatesTags and compare its authorization order with GetTemplatesTemplateIDTags and DeleteTemplatesTags. Then inspect or add the focused case in packages/api/internal/handlers/template_tags_test.go and run the relevant API tests. Done means unauthorized requests consistently return 403 without querying the tag or starting a transaction.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, postgres
Domain
authorization, backend-api-design, databases, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.