yegor256/rultor

GPG passphrase leaks to build logs via `set -x` in scripts emitted by `Decrypt.commands()`

Open

#2,325 opened on May 11, 2026

View on GitHub
 (1 comment) (0 reactions) (0 assignees)Java (165 forks)github user discovery
bughelp wanted

Repository metrics

Stars
 (575 stars)
PR merge metrics
 (PR metrics pending)

Description

What happens

In src/main/java/com/rultor/agents/req/Decrypt.java, the GPG decryption command is built by interpolating the symmetric passphrase directly into a shell command line:

final String script = String.format(
    "gpg --batch --decrypt --passphrase %s ...",
    Ssh.escape(String.format("rultor-key:%s", this.profile.name()))
);

The passphrase rultor-key:<profile-name> is the actual symmetric secret used to decrypt the repository's .enc files. It is embedded into the run.sh script that StartsDaemon uploads to the build host, where run.sh runs under set -ex (StartsDaemon writes set -ex -o pipefail at the top of every script). With -x, bash prints every command — including this one with the passphrase — to stdout. That stdout is captured into the rultor build log, which is published.

So a single rultor build of a public project writes the decryption passphrase in plaintext into a log that is publicly readable. Any reader who also obtains the .enc files can decrypt the repository's secrets.

Compounding the leak: the passphrase is a deterministic function of the profile name (rultor-key:<owner>/<repo>), so an attacker who scrapes a public build log for one project does not even need to do additional work to derive future passphrases for that same project, and can guess passphrases for any other project whose name they know.

What should happen

Two changes, either of which closes the leak, both together being best:

  1. Pass the passphrase via --passphrase-file /dev/stdin (or --passphrase-fd 0) and pipe it in, so it never appears in set -x output. As a minimum:

    set +x
    printf '%s' "$passphrase" | gpg --batch --decrypt --passphrase-fd 0 ...
    set -x
    
  2. Derive the passphrase from a per-installation server-side secret (rultor's own configuration) combined with the profile name, rather than from the profile name alone. That way leaking one log only compromises one project, not the whole rultor installation's threat model.

Result: the symmetric passphrase no longer appears in any build log, and one accidental log leak does not enable secret extraction for arbitrary repositories.

Contributor guide