Only the latest release receives security fixes.
Please do not open a public GitHub issue for a security vulnerability.
Instead, report it privately via GitHub Security Advisories. Provide as much detail as you can: affected versions, reproduction steps, and potential impact. You will receive a response within a few business days.
When env-starter update (or the startup update prompt) applies a new release,
it goes through a two-step verification chain:
- TLS transport integrity — the release assets are fetched from
https://github.comonly; non-HTTPS redirects are rejected. - Cosign signature —
checksums.txtis verified against a detachedchecksums.txt.sigusing ECDSA-P256 over SHA-256. The project's public key is embedded in the binary at build time (internal/update/verify.go). A missing or invalid signature is a hard failure: the update is aborted and the binary is not replaced. - Archive integrity — the sha256 digest of every downloaded archive is
verified against the entry in
checksums.txtbefore extraction. - Extraction safety — zip-slip is prevented (only
filepath.Baseof the archive entry name is used, never the full path); the extracted binary is capped at 512 MiB to guard against decompression bombs.
The binary is replaced atomically (rename) so a partial update cannot leave the tool in a broken state.
Each release archive also carries a Sigstore build-provenance
attestation
(keyless, via actions/attest-build-provenance) proving it was built by the
Release GitHub Actions workflow from a specific commit, and an SPDX
SBOM (<archive>.spdx.json, generated by syft)
listing everything bundled in it. Verify an archive with:
gh attestation verify env-starter_<version>_<os>_<arch>.tar.gz \
--repo adericbourg/env-starterThese are additive transparency features, not part of the runtime trust
chain above: the self-updater still relies solely on the cosign-signed
checksums.txt and the embedded public key, so the offline trust model is
unchanged.
env-starter is a strictly per-user tool. Everything it persists lives under
os.UserCacheDir()/env-starter/ (~/.cache/env-starter on Linux), which is
enforced owner-only (0700): the directory is created private, a
pre-existing directory with looser permissions is tightened, and a directory
owned by another user is rejected outright.
Inside that boundary:
- Daemon socket — the background daemon listens on a unix socket
(
daemon.sock,0600) inside the owner-only directory. The socket is created with a restrictive umask so it is never connectable by other users, even briefly. As defence in depth the daemon also verifies each connecting peer's uid against its own (SO_PEERCREDon Linux,LOCAL_PEERCREDon macOS) and rejects mismatches. This matters because a connected peer can start configured environments — i.e. run the owner's commands — and shut the daemon down. - Source cache — downloaded url sources and GitHub clones live in predictable subdirectories and are executed as code, so pre-existing cache content is only reused after the ownership check above passes.
- Log files — per-command logs are
0600in an owner-only directory and are never written to a shared location: if the per-user cache directory cannot be resolved, env-starter fails instead of falling back to a world-readable temp dir.
There is no setuid, no TCP listener, and no cross-user IPC of any kind.
url sources are downloaded over https only (redirects must stay on
https) and are then executed as code. Declare a checksum so a tampered
or swapped artifact at the origin is detected; without one the download is
trusted on TLS alone and env-starter prints a startup warning. Set
require-checksums: true at the top level of the config to make a missing
checksum a hard validation error (recommended for shared configs; an overlay
can never relax it).
A command's run, setup, teardown, and readiness.shell fields are
passed directly to sh -c. This is intentional — they are shell scripts —
so env-starter does not sanitize them; instead it gates which config files
it will load from at all.
Every config file (the base config and any --config-overlay) is hashed with
sha256, and env-starter refuses to load, execute, or hot-reload a file whose
current hash has not been explicitly approved. Approval is recorded under the
owner-only cache dir (trust.json) and is invalidated the instant a file's
content changes, so a later edit — legitimate or a tampered/slipped-in
change — always requires a fresh review before anything from it runs.
Review and approve a config with:
env-starter allow # preview every run/setup/teardown/readiness.shell command, then prompt
env-starter allow --print # preview only, writes no approval
env-starter allow --yes # approve without prompting (e.g. in scripts)This defends against a config or overlay that was tampered with or slipped in
without your knowledge — it does not make the fields themselves any
safer to author. Only write (or approve) run/setup/teardown commands
you would be comfortable running yourself: approving a config is equivalent
to granting it code execution under your user account.
Commands and environments can declare env (environment variables) — see
Configuration reference: env and
Secrets and overrides. This is
the intended place for secrets such as API keys and tokens:
- Never commit or share secrets in the base config. Put them in a
separate, non-committed, non-shared file and apply it with
--config-overlay, added to.gitignore. The overlay still goes through the same approval gate as the base config. - Prefer
envover inlining secrets intorun/setup/teardown. Values passed viaenvare not visible inpsor/proc/<pid>/cmdline, unlike values interpolated directly into a command string. (/proc/<pid>/environremains readable by the same user or root — standard OS behavior, unrelated to env-starter.) - env-starter never logs env values. Configuration validation errors
(including the conflict check for a shared command's
env, see Configuration reference: Validation rules) reference key names and environment names only, never values. - A shared/overlaid
envthat sets a variable likePATHorLD_PRELOAD/DYLD_INSERT_LIBRARIEScan influence another environment's command the next time it starts. This is equivalent to arbitrary code viarun/setup/teardownand is covered by the same config-trust boundary above — only approve configs and overlays you trust. - Command output is not redacted (see Command log confidentiality below): a process that echoes an env var containing a secret will have it appear in its log.
Command stdout/stderr is stored in log files under
os.UserCacheDir()/env-starter/. These files are created with 0600
permissions (owner read/write only). Logs are not redacted: if a
subprocess echoes secrets (tokens, passwords), those appear in the log file.
Protect the cache directory accordingly.