Documentation
¶
Overview ¶
Package execsafe provides TOCTOU-safe binary execution primitives. These are used by both collector and tool execution to ensure cryptographic verification of binaries before execution.
Index ¶
- Variables
- func AppendAllowedSecrets(dst []string, names []string, getenv func(string) string) []string
- func BuildRestrictedEnv(environ []string, inheritPath bool) []string
- func BuildRestrictedEnvSafe(environ []string, inheritPath bool) []string
- func FilterEnv(environ []string, allowed []string) []string
- func FilterValidSecrets(secrets []string) []string
- func FstatInode(fd int) (uint64, error)
- func OpenBinaryNoFollow(path string) (int, error)
- func SafePATH() string
- func SecureTempDir(prefix string) (string, func(), error)
- func StripProxyCredentials(environ []string) []string
- func ValidateSecretName(name string) error
- func ValidateSecretNames(names []string) error
- func VerifiedBinaryFD(binaryPath, expectedDigest string) (execPath string, cleanup func(), err error)
- func VerifyDigestFromFD(fd int, expectedDigest string) error
- func WriteSecureConfigFile(config map[string]interface{}, prefix string) (string, func(), error)
- type CommandOptions
- type RestrictedCommand
Constants ¶
This section is empty.
Variables ¶
var AllowedEnvVars = []string{
"HOME",
"USER",
"LANG",
"LC_ALL",
"LC_CTYPE",
"TZ",
"TMPDIR",
"TEMP",
"TMP",
"TERM",
"NO_COLOR",
"CLICOLOR",
"CLICOLOR_FORCE",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"HTTP_PROXY",
"HTTPS_PROXY",
"NO_PROXY",
"http_proxy",
"https_proxy",
"no_proxy",
"XDG_CONFIG_HOME",
"XDG_CACHE_HOME",
"XDG_DATA_HOME",
"XDG_STATE_HOME",
"XDG_RUNTIME_DIR",
}
AllowedEnvVars is the allowlist of environment variables passed to collectors and tools. This prevents credential exfiltration via malicious/compromised binaries. NOTE: PATH is handled separately via SafePATH() - it is NOT in this list.
var DeniedSecretPrefixes = []string{"EPACK_", "LD_", "DYLD_", "_"}
DeniedSecretPrefixes blocks variables that would compromise epack's execution. - EPACK_*: Protocol namespace (run_id, pack_path, etc.) - LD_*/DYLD_*: Dynamic linker hijacking - _*: Reserved by shells/runtimes
Functions ¶
func AppendAllowedSecrets ¶
AppendAllowedSecrets appends allowed secrets from the environment to dst. Only secrets that pass validation and have non-empty values are appended. The getenv function is typically os.Getenv but can be mocked for testing.
SECURITY: This is the canonical way to pass secrets to subprocesses. It filters reserved prefixes and only passes explicitly configured secrets.
func BuildRestrictedEnv ¶
BuildRestrictedEnv builds a restricted environment for binary execution. SECURITY: By default, PATH is set to a safe, deterministic value to prevent PATH injection attacks. Only with inheritPath=true is the ambient PATH inherited (which may contain attacker-controlled directories).
func BuildRestrictedEnvSafe ¶
BuildRestrictedEnvSafe builds a restricted environment with proxy credentials stripped. This is the recommended function for executing untrusted binaries.
SECURITY: This function: 1. Filters to only allowed environment variables 2. Strips credentials from proxy URLs 3. Sets a safe, deterministic PATH (unless inheritPath is true)
func FilterValidSecrets ¶
FilterValidSecrets returns only secrets that pass validation.
func FstatInode ¶
FstatInode returns the inode of a file descriptor.
func OpenBinaryNoFollow ¶
OpenBinaryNoFollow opens a binary with O_NOFOLLOW to prevent symlink attacks. Returns the fd and an error.
func SafePATH ¶
func SafePATH() string
SafePATH returns a deterministic, minimal PATH for binary execution. This prevents PATH injection attacks where a malicious interpreter is placed earlier in PATH than the legitimate one.
SECURITY: On Windows, we use hardcoded paths rather than environment variables. The SystemRoot env var can be controlled by attackers. We use the standard Windows directory path. If Windows is installed elsewhere, the PATH will be wrong but the operation will fail safely (command not found) rather than executing attacker binaries.
func SecureTempDir ¶
SecureTempDir creates a temporary directory with restrictive permissions. Uses umask to ensure the directory is CREATED with 0700 permissions, eliminating the race window between MkdirTemp and Chmod.
func StripProxyCredentials ¶
StripProxyCredentials removes username:password from proxy environment variables. This prevents credential leakage to untrusted binaries while still allowing proxy connectivity.
SECURITY: Proxy URLs can contain embedded credentials like:
http://user:[email protected]:8080
These credentials would be visible to any executed binary. This function strips credentials while preserving the proxy host:port for connectivity.
Returns the sanitized environment variables.
func ValidateSecretName ¶
ValidateSecretName returns an error if the name is a denied prefix.
func ValidateSecretNames ¶
ValidateSecretNames validates a list of secret names.
func VerifiedBinaryFD ¶
func VerifiedBinaryFD(binaryPath, expectedDigest string) (execPath string, cleanup func(), err error)
VerifiedBinaryFD creates a verified, sealed copy of a binary for execution. This eliminates TOCTOU races by hashing bytes AS they are copied to the sealed temp file - we execute exactly the bytes we verified.
SECURITY: The critical invariant is that we never execute bytes we didn't hash. Previous approaches that hash-then-copy or hash-then-exec-fd are vulnerable to race conditions where the source file is modified between operations.
The approach: 1. Open the source binary with O_RDONLY | O_NOFOLLOW (prevents symlink following) 2. Create a sealed temp file in a temp directory 3. Copy bytes through a TeeReader that simultaneously hashes and writes 4. Verify the hash matches AFTER the copy completes 5. Seal the temp directory and return the path to the verified copy
This is race-free because the bytes written to the temp file ARE the bytes that were hashed - there's no window for modification.
func VerifyDigestFromFD ¶
VerifyDigestFromFD verifies a file's digest by reading from an already-open fd. This is used for TOCTOU-safe verification where the caller already has an open fd.
func WriteSecureConfigFile ¶
WriteSecureConfigFile writes config data to a secure temporary JSON file. This is the standard pattern for passing config to collectors and tools.
SECURITY: Uses SecureTempDir for race-free restrictive permissions. The returned cleanup function removes both the file and its parent directory.
Returns empty string and nil cleanup if config is empty.
Types ¶
type CommandOptions ¶
type CommandOptions struct {
// InheritPath uses the ambient PATH instead of SafePATH.
// SECURITY WARNING: Only use this for trusted binaries where PATH
// injection is not a concern (e.g., system tools during build).
InheritPath bool
// StripProxyCredentials removes username:password from proxy env vars.
// Defaults to true for untrusted binaries.
StripProxyCredentials bool
// AdditionalEnv adds extra environment variables to the base restricted set.
// These are added AFTER filtering, so they can include any variables.
// SECURITY: The caller must validate these do not contain secrets.
AdditionalEnv []string
// Secrets is a list of environment variable names to pass through.
// Each name is validated against DeniedSecretPrefixes before lookup.
// Only secrets that exist in the environment AND pass validation are included.
Secrets []string
// WorkDir sets the working directory for command execution.
WorkDir string
}
CommandOptions configures how a restricted command is built.
func DefaultCommandOptions ¶
func DefaultCommandOptions() CommandOptions
DefaultCommandOptions returns secure defaults for command construction.
type RestrictedCommand ¶
type RestrictedCommand struct {
// Path is the executable path (may be a verified copy from VerifiedBinaryFD).
Path string
// Args is the argument list (Args[0] should be the command name).
Args []string
// Env is the security-filtered environment.
Env []string
// Dir is the working directory (empty means current directory).
Dir string
// contains filtered or unexported fields
}
RestrictedCommand holds a prepared command with security-validated environment. Use NewRestrictedCommand to create instances.
func NewRestrictedCommand ¶
func NewRestrictedCommand(binaryPath string, args []string, opts CommandOptions) (*RestrictedCommand, error)
NewRestrictedCommand creates a command with security-hardened environment. This is the REQUIRED entry point for executing collectors and tools.
SECURITY: This function centralizes all security checks for command execution:
- Environment filtering to AllowedEnvVars only
- Safe PATH (no user-controlled directories)
- Secret name validation (blocks EPACK_*, LD_*, DYLD_*, _*)
- Optional proxy credential stripping
Example usage:
cmd, err := execsafe.NewRestrictedCommand(binaryPath, args, execsafe.DefaultCommandOptions())
if err != nil {
return err
}
defer cmd.Cleanup()
execCmd := exec.Command(cmd.Path, cmd.Args[1:]...)
execCmd.Env = cmd.Env
execCmd.Dir = cmd.Dir
return execCmd.Run()
func NewVerifiedRestrictedCommand ¶
func NewVerifiedRestrictedCommand(binaryPath, expectedDigest string, args []string, opts CommandOptions) (*RestrictedCommand, error)
NewVerifiedRestrictedCommand creates a command with both verified binary and security-hardened environment.
SECURITY: This function provides the strongest security guarantees:
- Binary is verified against expected digest (TOCTOU-safe)
- Environment is filtered and sanitized
- A verified copy of the binary is executed
The caller MUST call Cleanup() after execution to remove the verified copy.
Example usage:
cmd, err := execsafe.NewVerifiedRestrictedCommand(
binaryPath, expectedDigest, args, execsafe.DefaultCommandOptions())
if err != nil {
return err
}
defer cmd.Cleanup()
execCmd := exec.Command(cmd.Path, cmd.Args[1:]...)
execCmd.Env = cmd.Env
return execCmd.Run()
func (*RestrictedCommand) Cleanup ¶
func (c *RestrictedCommand) Cleanup()
Cleanup releases resources associated with this command. Safe to call multiple times or on nil.