[FEATURE]: add OPENCODE_PASSWORD_FILE for more password setting options · Issue #52156 · anomalyco/opencode · GitHub
Skip to content

[FEATURE]: add OPENCODE_PASSWORD_FILE for more password setting options #52156

Open

Description

@rektide

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Please can we add OPENCODE_PASSWORD_FILE alongside the existing OPENCODE_PASSWORD support for setting a password?

Among other use cases, this would allow for nice systemd credential integration for folks like myself who enjoy running opencode serve as a systemd service, rather than having opencode manage the children (nice cgroups wins here!). Containers and CI also commonly enjoy this pattern too. Not polluting env vars with secrets can also be safer.

Proposed resolution order:

  1. current OPENCODE_PASSWORD / config password
  2. current OPENCODE_SERVER_PASSWORD (legacy name)
  3. new password read from OPENCODE_PASSWORD_FILE. Fail loudly if not present.
  • trim whitespace to enhance compatibility with systemd, which often has a newline
  1. current randomly generate fallback

We should also add opencode service set password_file too, for parity with current password config too. (One note, this currently does not affect opencode serve, which was a source of surprise to me. I'm tempted to make a new ticket for that. It would be good to at least be able to opt in to config.)

Implementation suggestions: everything is in packages/cli/. src/env.ts has existing Env.password, and we resolve the password file into it when called for. src/server-process.ts consumes. src/services/service-config gets the new password_file handling.

systemd bonus

For even nicer systemd integration, we could also consider supporting --credential-name <name> too, and loading from $CREDENTIALS_DIRECTORY/$name. This bakes in some systemd assumptions, but means LoadCredential= etc just work, without having to set any special env vars.

Activity

  1. rektide commented on Sep 30, 2026

    @rektide
    ContributorAuthor

    I am ready to go on the PR for this by the way. It's pretty contained! Trying to follow contributor guidelines and get design review. 🙇

  2. phetzy commented on Oct 1, 2026

    @phetzy

    A security reason for this, beyond systemd and containers: a process's startup environment stays readable by every same-user process for its whole lifetime (ps eww -p <pid> on macOS, /proc/<pid>/environ on Linux), and deleting the variable after startup does not change that.

    So any child opencode serve starts (a shell tool command, an MCP server, an LSP server) can run ps eww -p $PPID, read OPENCODE_SERVER_PASSWORD, and call the server API with full access, including answering permission requests. Repro on 1.18.32, macOS: start OPENCODE_SERVER_PASSWORD=secret opencode serve, run ps eww -p $PPID from a session shell command, and the password is in the output.

    A file input fixes this as long as the secret itself never goes into the environment or argv. A --password-fd <n> option would also help apps that embed the server and do not want a secret file on disk.

    (Opened separately as #52447; closing that one as a duplicate of this.)

  3. askaramirov commented on Oct 16, 2026

    @askaramirov

    +1, and a security angle: with the password in the environment it also leaks into every tool shell the server spawns.

    Version: opencode 2.0.22 (linux-x64).

    What happens

    opencode serve takes its password from OPENCODE_PASSWORD (or
    OPENCODE_SERVER_PASSWORD) when not in --service mode. The server's
    own child processes inherit that environment. So every shell command an
    agent runs through the server's shell tool can read the password with
    env, and so can anything such a command starts.

    Minimal repro (deterministic, with a scripted model)

    1. A scratch HOME, XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_STATE_HOME,
      XDG_CACHE_HOME and TMPDIR. A provider using
      @ai-sdk/openai-compatible that points at a local OpenAI-compatible
      server, which answers the first tool-enabled request with one shell
      call: env | grep -c ^OPENCODE_PASSWORD= > count.txt.
    2. OPENCODE_PASSWORD=<random> opencode serve --hostname 127.0.0.1 --port <p>.
    3. opencode run --server http://127.0.0.1:<p> --format json -m mock/scripted "count".

    Result (measured 2026-10-15): the client exits 0, and count.txt
    contains 1. The tool's shell has the server's password.

    Today's workaround: serve --service reads the password from
    <XDG_CONFIG_HOME>/opencode/service.json, so it never has to be in the
    environment. The binary's own logic is roughly mode === "service" ? config.password || random : env || random. But --service brings
    service-mode behaviour (registration file, chdir to HOME, autostart by
    clients; see issue 2) that a supervisor may not want.

    Ask

    • --password-file <path> for serve, and for the client's --server
      connection, so neither needs the password in its environment or argv.
    • Independently: strip OPENCODE_PASSWORD / OPENCODE_SERVER_PASSWORD
      from the environment of tool processes the server spawns. The client
      path already strips the env a client sends; the server's own tools do
      not.
  4. Skeptomenos commented on Oct 19, 2026

    @Skeptomenos

    A data point for serve --stdio at v2.0.32, plus one requirement for the design.

    serve --stdio reads OPENCODE_PASSWORD, then deletes it from process.env (packages/cli/src/server-process.ts:80-85). Shells, command templates and tools then do not inherit it. On Linux the initial environment block still holds it for the server's whole life:

    1. Configure a command whose template is !`tr '\0' '\n' < /proc/$PPID/environ | grep '^OPENCODE_PASSWORD=' | cut -d= -f2-` .
    2. Start OPENCODE_PASSWORD=<synthetic> opencode serve --stdio --hostname 127.0.0.1 --port 0, create a session and run the command.
    3. The stored user message contains the password. The same template with printenv OPENCODE_PASSWORD prints nothing.

    A config value {file:/proc/self/environ} also returns it.

    For stdio mode, a launcher needs an input that leaves no copy in the environment block, so OPENCODE_PASSWORD_FILE fits if the server reads the file once at startup. The launcher can then delete the file after the server answers. --password-fd <n>, read once and closed, works the same way. Either keeps the stdin lease of stdio mode unchanged.

    This does not stop a same-user process that can read the server's memory, for example where kernel.yama.ptrace_scope is 0. It removes the copy that a plain file read reaches.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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