Skip to content

Fulcio signer falls back to interactive OAuth flow even when tokens are available, causing 2-minute timeout #627

@colek42

Description

@colek42

Bug Description

The Fulcio signer in witness has a bug where it correctly fetches tokens (from GitHub Actions, files, or command line flags) but then doesn't use them with StaticTokenGetter. This causes the signing process to timeout after 2 minutes due to the interactive OAuth flow timeout.

Problem

The issue is in signer/fulcio/fulcio.go. After fetching tokens in various ways (GitHub Actions, file, or flag), the code doesn't use these tokens with StaticTokenGetter. Instead, it only uses the interactive OAuth flow when it's a terminal session, which has a hardcoded 2-minute timeout.

Current Code Flow

// In signer/fulcio/fulcio.go
var raw string
switch {
case fsp.Token == "" && fsp.TokenPath == "" && os.Getenv("GITHUB_ACTIONS") == "true":
    // Fetches GitHub Actions token
    raw, err = fetchToken(tokenURL, requestToken, "sigstore")
case fsp.TokenPath \!= "" && fsp.Token == "":
    // Reads token from file
    raw = string(f)
case fsp.Token \!= "" && fsp.TokenPath == "":
    // Uses provided token
    raw = fsp.Token
case fsp.Token == "" && isatty.IsTerminal(os.Stdin.Fd()):
    // Interactive flow - but this only runs for terminals\!
    tok, err := oauthflow.OIDConnect(..., oauthflow.DefaultIDTokenGetter)
    raw = tok.RawString
}

// Problem: After getting 'raw' token, it's not used unless we're in a terminal\!

Root Cause

The interactive OAuth flow timeout is in the sigstore library:

// vendor/github.com/sigstore/sigstore/pkg/oauthflow/interactive.go
func getCode(doneCh chan string, errCh chan error) (string, error) {
    timeoutCh := time.NewTimer(120 * time.Second) // 2-minute timeout\!
    select {
    case <-timeoutCh.C:
        return "", errors.New("timeout")
    }
}

Failing Test

I've included a test that demonstrates this issue:

func TestGitHubActionsTokenTimeout(t *testing.T) {
    // Set up GitHub Actions environment
    os.Setenv("GITHUB_ACTIONS", "true")
    os.Setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "https://example.com/token")
    os.Setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "fake-token")

    fsp := New(
        WithFulcioURL("https://fulcio.sigstore.dev"),
        WithOidcIssuer("https://oauth2.sigstore.dev/auth"),
        WithOidcClientID("sigstore"),
    )

    start := time.Now()
    _, err := fsp.Signer(context.Background())
    elapsed := time.Since(start)

    // This should fail quickly with our fake URL, not after 2 minutes
    if elapsed > 30*time.Second {
        t.Errorf("Signer took %v, suggesting interactive OAuth timeout", elapsed)
    }
}

Proposed Fix

After collecting the token, use StaticTokenGetter when a token is available:

// After the switch statement that sets 'raw'...
if raw \!= "" {
    // Use static token getter for pre-existing tokens
    staticGetter := &oauthflow.StaticTokenGetter{
        RawToken: raw,
    }
    tok, err := staticGetter.GetIDToken(nil, oauth2.Config{})
    if err \!= nil {
        return nil, err
    }
    // Continue with the token...
} else if isatty.IsTerminal(os.Stdin.Fd()) {
    // Only use interactive flow if no token is available AND we're in a terminal
    tok, err := oauthflow.OIDConnect(...)
    if err \!= nil {
        return nil, err
    }
    raw = tok.RawString
} else {
    return nil, errors.New("no token available and not in interactive terminal")
}

Impact

This bug affects:

  • GitHub Actions workflows where the attested command takes more than 2 minutes
  • Any automated (non-terminal) usage where tokens are provided via file or environment
  • Makes witness unusable for long-running processes like test suites or complex builds

Reproduction

  1. Use witness-run-action in GitHub Actions with any command that takes >2 minutes
  2. The runner will receive a shutdown signal after exactly 2 minutes
  3. The workflow will fail with a timeout error

The attached test file (signer/fulcio/fulcio_timeout_test.go) demonstrates this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions