Skip to content
winfunc
Disclosure record · flyctl

MCP client setup exposes Fly API tokens to other local users

A long-lived Fly bearer token was serialized into a client config created with ambient file permissions

flyctlDisclosedSource record

CVSS 3.1 base score

High
8.2/ 10

Vector: CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

Record

Project
flyctl
Severity
High8.2
CVE
None listed
Disclosed
Trace
3 steps

Summary

Source

runServer()

internal/command/mcp/server.go:132

Sink

UpdateConfig()

internal/command/mcp/config.go:570

The stream/SSE MCP setup command placed the user's reusable Fly API token in a client configuration argument list. On a normal Unix umask 022, the config could be created or replaced as mode 0644, allowing another local account to read the credential.

Root cause. runServer() appended --bearer-token to the persisted MCP proxy arguments and UpdateConfig() used ordinary file creation/rename semantics without enforcing an owner-only mode on the final file.

Remediation evidence. Fly opened PR #5192 the day after the disclosure and merged it on 8 September 2026. The exact patch writes new MCP configuration at 0600, repairs permissive existing files during replacement, and includes Unix permission regression tests.

Severity

Attack vectorAV
Local
Attack complexityAC
Low
Privileges requiredPR
Low
User interactionUI
Required
ScopeS
Changed
ConfidentialityC
High
IntegrityI
High
AvailabilityA
High

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlledinternal/command/mcp/server.go:132

    runServer()

  2. Step 01internal/command/mcp/server.go:132

    The setup workflow reads the logged-in user's Fly token and places it in the persistent proxy argument list.

    go
    if token := getAccessToken(ctx); token != "" {
      args = append(args, "--bearer-token", token)
    }
    
  3. Step 02internal/command/mcp/server.go:145

    The complete credential-bearing command is passed to the client-specific configuration updater.

    go
    return mcpConfig.UpdateConfig(client, serverName, command, args)
    
  4. Step 03internal/command/mcp/config.go:570

    Before the fix, normal creation and rename honored the ambient umask, so a newly serialized secret could remain world-readable.

    go
    if err := os.WriteFile(tmp, contents, 0o666); err != nil { return err }
    return os.Rename(tmp, path)
    
  5. Sinkinternal/command/mcp/config.go:570

    UpdateConfig()

Impact

Reported impact

A separate local user can steal the victim's long-lived Fly API token and exercise the token's cloud permissions, potentially reading application data, changing deployments, or deleting resources according to the victim's account authority.

Attack surface

Local multi-user Unix systems where flyctl mcp server configures a stream/SSE MCP client and persists a Fly bearer token.

Preconditions

The victim must run the setup command while logged in to Fly, and the generated client config must be in a directory readable by another local user. The attacker needs only an unprivileged local account.

Attack path

  1. 1

    Wait for the victim to configure a supported MCP client through flyctl.

  2. 2

    Read the resulting JSON configuration from the victim's home directory when its mode is 0644.

  3. 3

    Extract the --bearer-token argument.

  4. 4

    Use the token with the same Fly API authority as the victim until it is rotated or expires.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Use an isolated Unix host with two unprivileged accounts and a vulnerable flyctl build. Set the victim shell's umask to 022.

  2. 02

    Target configuration

    Log the victim into a test Fly organization and configure a stream/SSE MCP client so flyctl persists --bearer-token in its JSON settings.

  3. 03

    Exploit delivery

    From the second local account, read the generated configuration and locate the bearer-token argument.

  4. 04

    Expected response

    On the vulnerable build, the file mode is 0644 and the second account can read the complete token value.

  5. 05

    Outcome

    PR #5192 forces the final file to 0600; the second account receives a permission error and cannot read the credential.

Remediation

Guidance

Upgrade to a flyctl release containing PR #5192 and rotate any token previously written to a permissive client config. Prefer an OS credential store or owner-only credential file over process arguments for long-lived secrets.

Before and after−1+3
os.WriteFile(path, contents, 0o666)os.WriteFile(tmp, contents, 0o600)os.Chmod(tmp, 0o600)os.Rename(tmp, path)
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.