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 — Stave makes zero network connections. It does not import
net/http, open sockets, or make DNS lookups. It reads local files and writes to stdout/stderr. - No subprocess execution — The runtime
stavebinary does not importos/execor spawn subprocesses. See Execution Safety for the full guarantee. - No persistent state — No databases, caches, or config files are created.
- Read-only inputs — Observation and control files are never modified.
- Air-gapped, offline-only, network-isolated — The Stave runtime binary contains no networking code and operates entirely on local files. It is safe to run in air-gapped, network-isolated, and offline-only environments. No conditional flags or environment variables can enable network access. Build and release processes (CI, signing, SBOM generation) require network access; see Offline & Air-Gapped Operation for the full inventory.
- No
--strict-offlineflag — Stave does not provide a--strict-offlineflag because the binary is architecturally incapable of networking. Thenet/http,crypto/tls, andos/execpackages are not imported (enforced by CI tests). A runtime flag would misleadingly imply that a non-strict mode with network capability exists. Verification is instead provided throughstrace(see below),docker --network=none, and the compile-time import guards inTestNoBannedImportsInRuntime. --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, not a security boundary — Stave is architecturally offline regardless.- Runtime offline confirmation — All JSON output includes
"offline": truein therunmetadata, andstave capabilitiesincludes"offline": trueat the top level. These are constant values confirming the 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. Stave does not import net/http, net/rpc, crypto/tls, or any other package that performs network I/O.
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 docs/s3-assessment.md 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).