This policy covers the Stave CLI.
Please report security vulnerabilities through GitHub Security Advisories.
Do not open a public issue for security vulnerabilities.
- Description of the vulnerability
- Steps to reproduce
- Affected versions (or "all" if unknown)
- Impact assessment
- Acknowledgment within 3 business days
- Initial assessment within 7 business days
- Fix or remediation timeline communicated after assessment
| Version | Supported |
|---|---|
| Latest release | Yes |
| Previous minor | Best effort |
| Older | No |
Stave is designed with a minimal attack surface:
- No network access in the evaluation engine — The evaluation core (
internal/core/,internal/app/eval/,pkg/stave/) makes zero network connections and does not importnet/http,crypto/tls, oros/exec. This is enforced byTestNoBannedImportsInRuntime. All evaluation commands (apply,diagnose,enforce,validate) operate entirely on local files. - One opt-in network call —
stave version --check-updatemakes a single HTTPS GET to the GitHub Releases API to check for newer versions. This is the only network call in the binary, it is never triggered automatically, and it requires the user to pass--check-updateexplicitly. - Subprocess execution — The CLI uses
os/execin three places:stave bundle(self re-exec for sealed evaluation),stave forge(codegen post-processing), and terminal pager display. The evaluation engine does not spawn subprocesses. - No persistent state — No databases, caches, or config files are created.
- Read-only inputs — Observation and control files are never modified.
- Air-gapped operation — The evaluation engine is architecturally incapable of networking. Stave is safe to run in air-gapped, network-isolated, and offline-only environments. The
--check-updateflag is the only network-capable code path in the binary and is never invoked unless explicitly requested. Build and release processes (CI, signing, SBOM generation) require network access; see Offline & Air-Gapped Operation for the full inventory. --require-offlineenvironment guard — The--require-offlineflag performs a best-effort runtime self-check that refuses to run if proxy environment variables (HTTP_PROXY,HTTPS_PROXY,ALL_PROXY) are set. This is an operational convenience for environments that want to assert no proxy misconfiguration.- Runtime offline confirmation — All JSON output includes
"offline": truein therunmetadata, andstave capabilitiesincludes"offline": trueat the top level. These confirm the evaluation engine's architectural guarantee.
The pflag library (used by Cobra for flag parsing) transitively imports the net package for net.ParseIP() and related IP address parsing functions. This is parsing-only — no sockets are opened, no connections are made, and no network I/O occurs. The evaluation engine does not import net/http, net/rpc, crypto/tls, or any package that performs network I/O. The cmd/ layer imports net/http solely for the opt-in --check-update path.
Stave repository code does not include a recording generator. Terminal recording automation is owned by the sibling publisher workspace (../publisher/generate-recordings.sh) and is outside the Stave runtime/release artifact scope.
Stave enforces strict filesystem safety properties:
- No file deletion during normal operation — Stave does not delete user files during normal operation.
- All writes are user-controlled — Output files are only created when explicitly requested via
--outflags. No implicit temporary files, caches, or config directories. - No-overwrite by default — All write commands refuse to overwrite existing files unless
--forceis passed. This prevents silent data loss. - No-symlink-write by default — All write commands refuse to write through symbolic links unless
--allow-symlink-outputis passed. This prevents symlink-based write attacks where an attacker places a symlink at the output path pointing to a sensitive file. - Path traversal protection — The control registry index (
_registry/controls.index.json) validates all relative paths to prevent directory traversal (e.g.,../../../etc/passwd). Paths are cleaned and verified to remain within the control root. - Bucket name validation — Bucket names from AWS snapshot data are validated against S3 naming rules before being used in file paths, preventing path injection via crafted bucket names.
- Path normalization — All user-supplied paths from CLI flags are cleaned (
filepath.Clean) to normalize./..segments and duplicate separators. - Restricted output permissions — Output directories are created with
0700(owner-only) and files with0600(owner read/write only). Log files use0644for aggregation compatibility.
Shared-system guidance: On multi-user systems, verify your umask is restrictive (umask 077) and write outputs into a private directory (e.g., ~/stave-output/) to prevent other users from reading evaluation results.
See Data Flow and I/O for the per-command I/O model.
For a detailed security assessment, see Security and Trust, Execution Safety, and Data Flow and I/O.
You can independently verify that Stave makes zero network syscalls:
Linux (strace):
strace -f -e trace=network ./stave apply \
--controls ./controls --observations ./obs 2>&1 | grep -E 'socket|connect|sendto|recvfrom|getaddrinfo'
# Expected: no output (zero network syscalls)macOS (dtruss):
# dtruss requires root; run in a test environment
sudo dtruss -f ./stave apply \
--controls ./controls --observations ./obs 2>&1 | grep -iE 'socket|connect'
# Note: macOS dtruss output is noisier; filter for socket/connect onlyContainer-based (works on any OS):
docker run --rm --network=none -v "$(pwd):/work" -w /work golang:1.26 \
./stave apply --controls ./controls --observations ./obs
# If this succeeds, the binary works with zero network accessRuntime: Stave requires zero outbound network connections. Corporate firewalls, egress policies, and NSGs can deny all outbound traffic and Stave will operate normally. No proxy configuration, DNS resolution, or TLS certificates are needed.
Build/CI/Release: Building from source and release signing require network access for Go module downloads, CI runners, Sigstore signing, and artifact uploads. See Offline & Air-Gapped Operation and Release Security for the full inventory.
Stave requires no elevated privileges at any stage:
- Build:
make buildruns as the current user. No root or sudo required. - Install:
make installwrites to$GOPATH/bin/. No system directories are modified. - Run: Stave reads user-owned files and writes to user-owned paths or stdout. It makes no privileged syscalls, requires no
CAP_*capabilities, and has no setuid/setgid bits. - Docker: The demo image runs as a non-root user (UID 10001). See
docs-content/demo/Dockerfile. - Output permissions: All output directories use
0700and files use0600(owner-only access).
Stave is architecturally credential-free. It never requires, reads, or processes cloud credentials:
- Source type validation is local — The
generated_by.source_typefield is validated against a built-in allowlist. Accepted source types do not imply cloud API access; all inputs are local snapshot files. Runstave capabilitiesto see the current allowlist. - No credential environment variables — Stave does not read
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_PROFILE,GOOGLE_APPLICATION_CREDENTIALS, or any other credential-related environment variable. The only environment variable Stave reads isNO_COLOR(per no-color.org). - No credential files — Stave does not read
~/.aws/credentials,~/.aws/config, service account key files, or any credential store. - No cloud API calls — Stave never contacts AWS, GCP, Azure, or any other cloud API. All input is local files.
- Snapshot export is external — Users export configuration snapshots using their own tools (AWS CLI, Terraform) outside of Stave. Stave reads the resulting local files only.
The internal/platform/logging/sanitize.go file contains a list of sensitive key patterns (token, secret, password, etc.) used to sanitize values from log output. This is defensive log hygiene — it prevents accidental logging of secrets that users might pass as CLI arguments. Stave does not consume or process these values.
See https://www.systeminvariant.dev/docs/how-to/s3-assessment for the recommended workflow showing the credential boundary between user tools and Stave.
Terraform plan/state exports and AWS CLI snapshots may contain embedded credentials or sensitive values in rare cases. Stave treats all resource properties as opaque data and does not detect or filter secrets within snapshots.
Recommendations:
- Do not include raw secrets in Terraform state/plan exports passed to Stave.
- Use
--sanitizewhen sharing evaluation output to replace infrastructure identifiers with deterministic tokens. - Treat all snapshot files as sensitive data and apply your organization's data handling policies.
See Sharing Outputs Safely for details on sanitization and scrubbing.
All AWS account IDs (e.g., 123456789012, 777666555444), ARNs, and bucket names appearing in testdata/, case-studies/, and documentation are synthetic placeholders. They do not correspond to real AWS accounts or infrastructure. AWS example credentials (AKIAIOSFODNN7EXAMPLE) are official AWS documentation placeholders.
When sharing Stave outputs for review, use --sanitize to replace infrastructure identifiers with deterministic tokens. See Sharing Outputs Safely for details.
Stave provides two flags for controlling sensitive data in output:
Replaces infrastructure identifiers with deterministic tokens in findings, diagnostics, reports, and validation output. Applies uniformly to JSON, text, and markdown outputs.
What is sanitized:
| Field | Sanitized Form |
|---|---|
Finding.ResourceID |
SANITIZED_<8hex> (deterministic SHA-256 token) |
ARNs in ResourceID |
arn:aws:s3:::SANITIZED_<8hex> (structure preserved) |
Finding.Source.File |
Basename only (directory stripped) |
Evidence.Misconfigurations[].ActualValue |
[SANITIZED] |
SourceEvidence.IdentityStatements |
[SANITIZED] |
SourceEvidence.ResourceGrantees |
[SANITIZED] |
Diagnostic.Evidence asset IDs |
SANITIZED_<8hex> |
CounterfactualReport.IncidentID |
[SANITIZED] |
InputHashes file keys |
Basename only |
| Validation evidence directory/file paths | [SANITIZED] |
What is preserved: Control IDs, control names, rule IDs, counts, durations, timestamps, schema versions, root cause types.
Determinism: Same inputs always produce the same sanitized output. Tokens are derived from SHA-256 of the original value.
Controls how file paths appear in error messages and logs.
| Mode | Behavior |
|---|---|
base (default) |
Absolute paths are replaced with basenames (e.g., /home/user/obs/snap.json becomes snap.json) |
full |
Absolute paths are shown as-is |
Use --path-mode=full to include absolute paths in errors and logs.
# Sanitize identifiers and use basename-only paths
stave apply --controls ./controls --observations ./obs --sanitize
# Evaluate with sanitized output
stave apply --profile aws-s3 --input obs.json --sanitize > report.json
# Diagnostics in JSON with sanitization
stave diagnose --controls ./controls --observations ./obs --format json --sanitizeBy default, panic recovery prints a generic message without the raw panic value. Use -vv to include sanitized panic details (paths shortened per --path-mode).