e2b-dev / e2b-dev/runtime

bug(envd/auth): expiration check after ConstantTimeCompare leaks timing oracle in validateSigning

Open Beginner friendly
#3,561 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

Summary

validateSigning in packages/envd/internal/api/auth.go performs HMAC comparison before checking signature expiration. This ordering creates a measurable timing side-channel that lets an attacker distinguish a correct-but-expired signature from an incorrect one.

Affected code

packages/envd/internal/api/auth.go lines 148-160:

// signature validation — runs first
if subtle.ConstantTimeCompare([]byte(expectedSignature), []byte(*signature)) != 1 {
    return errors.New("invalid signature")
}

// signature expiration — runs second, ONLY if HMAC passed
if signatureExpiration != nil {
    exp := int64(*signatureExpiration)
    if exp < time.Now().Unix() {
        return errors.New("signature is already expired")
    }
}

Why it matters

subtle.ConstantTimeCompare is constant-time within a single invocation, but the presence or absence of the expiration check that follows is not. An attacker observing response latency can distinguish two cases:

Request Path through code Relative latency
Wrong HMAC ConstantTimeCompare → returns early shorter
Correct HMAC + expired ConstantTimeCompare → expiration check → returns slightly longer

This leaks a binary oracle: "your HMAC candidate is correct". Combined with the expiration timestamp already embedded in the signed string (and therefore in the request the attacker controls), this narrows a brute-force search considerably — the attacker can craft requests with a past timestamp, submit candidates, and use timing to confirm a correct HMAC without ever needing to produce a non-expired token.

The envd signing scheme is used for file download/upload URLs that are embedded in client responses. A compromised signing key lets an attacker forge arbitrary file-access requests to any running sandbox.

Fix

Move the expiration check before ConstantTimeCompare:

// Check expiration first — before HMAC — so a correct-but-expired
// signature does not produce a measurably longer response than an
// incorrect one, eliminating the timing oracle.
if signatureExpiration != nil {
    exp := int64(*signatureExpiration)
    if exp < time.Now().Unix() {
        return errors.New("signature is already expired")
    }
}

// HMAC validation (constant-time).
if subtle.ConstantTimeCompare([]byte(expectedSignature), []byte(*signature)) != 1 {
    return errors.New("invalid signature")
}

Expired requests are rejected before any HMAC work is done, so timing no longer carries HMAC-validity information.

Severity

Low-medium. Exploiting this requires many precise latency measurements against a live envd endpoint, and the envd port is not externally exposed by default. However the signing key is long-lived (tied to sandbox lifetime) and file API access is unrestricted once forged, making the impact of a successful key recovery significant.

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/envd/internal/api/auth.go at validateSigning and inspect the expiration and ConstantTimeCompare checks around lines 148-160. Verify that expired signatures are rejected before HMAC comparison and that incorrect signatures still return the existing error; done means the timing distinction described in the issue is removed without changing other validation behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication, backend, security
Issue type
Bug
Difficulty
1/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.