Skip to content

Secure credential passing for exec.command() and git.push() on reconnected boxes #120

@shtefcs

Description

@shtefcs

Use Case

We run an AI coding agent platform on Upstash Box. Users can connect their GitHub account (OAuth) and push their sandbox code to GitHub. The GitHub token is stored encrypted on our server — the sandbox never "knows" it permanently.

The challenge: we need to pass a GitHub token to the sandbox at push time (not at creation time), and we want to avoid the token appearing in the exec.command() string.

What We've Tried

We use GIT_ASKPASS — write the token to a temp file (chmod 600), create a helper script that reads + deletes it, and set GIT_ASKPASS=/tmp/script.sh in the push command. This keeps the token out of the git push process args and error output.

But the token still appears in the exec.command() call that writes the temp file:

// The token is in this command string, which hits the Box API
await box.exec.command(`printf '%s' '${token}' > /tmp/.token && chmod 600 /tmp/.token`);

Questions

1. Does exec.command() log command strings server-side?

If Upstash stores or logs the command strings sent via exec.command(), any secret embedded in a command is persisted in your logs. This affects any user passing API keys, tokens, or credentials through shell commands.

This isn't a criticism — just trying to understand the security boundary so we can document it accurately for our users.

2. Can exec.command() accept per-call environment variables?

Something like:

await box.exec.command('git push ...', { 
  env: { GIT_ASKPASS_TOKEN: 'secret' }  // injected into process env, not in command string
});

This would let us pass the token through the process environment rather than the command string. The env values would only need to exist for the duration of that single command.

3. Can attachHeaders be updated on a running/reconnected box?

We see attachHeaders in BoxConfig — it's perfect for injecting auth headers into outbound HTTPS requests. But it appears to be set-only at creation time. Our use case: the box is created first, the user connects GitHub later. Can we call something like:

const box = await Box.get(id, { apiKey });
await box.updateAttachHeaders({
  "github.com": { Authorization: `Bearer ${token}` }
});

If this already works, we missed it in the docs. If not, this would be the ideal solution — the token never touches a command string at all.

4. Does Box.get({ gitToken }) configure server-side git auth?

We pass gitToken when reconnecting:

const box = await Box.get(id, { apiKey, gitToken: '...' });
await box.git.push({ branch: 'main' });

But git.push() fails with auth errors. We suspect gitToken in BoxGetOptions is local-only (client-side metadata) and doesn't configure the box's git credentials on the server. Can you confirm?

If gitToken IS supposed to work on reconnect, we might have a bug to report.

Our Current Architecture

User clicks "Push to GitHub"
  → Our server decrypts GitHub token from encrypted DB storage
  → Reconnects to existing Upstash Box via Box.get()
  → Writes token to temp file via exec.command (⚠️ token in command string)
  → Runs git push with GIT_ASKPASS helper (✅ token NOT in git args)
  → Cleans up temp files

The ideal flow would be either:

  • Option A: exec.command(cmd, { env }) — token in process env, not command string
  • Option B: box.updateAttachHeaders(...) — token injected by proxy, never in sandbox at all
  • Option C: Box.get({ gitToken }) actually configuring server-side git auth for box.git.push()

Any of these would eliminate the token from the command string entirely.

Environment

  • @upstash/box: 0.1.30
  • Use case: AI coding agent with user-connected GitHub OAuth

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