Add explicit LC_* vars and PICOCLAW_* prefix blocklist

- Add explicit LC_* vars to default allowlist (locale vars)
- Remove prefix matching for allowlist (use explicit only)
- Add isBlocked() function for blocklist prefix matching
- All PICOCLAW_* vars now blocked from LLM override
- Update config and docs
This commit is contained in:
Keith Patrick 2026-03-09 03:32:19 +00:00
parent 09b42753bf
commit 00508b9636
3 changed files with 44 additions and 6 deletions

View file

@ -58,13 +58,15 @@ The exec tool sanitizes the environment passed to child processes:
1. **Default allowlist** — Only these variables are inherited from the parent process:
- `PATH`, `HOME`, `USER`, `LANG`, `SHELL`, `TERM`, `PWD`, `OLDPWD`, `HOSTNAME`, `LOGNAME`, `TZ`, `DISPLAY`, `TMPDIR`, `EDITOR`, `PAGER`
- Plus: `PICOCLAW_CONFIG`, `PICOCLAW_HOME`, `PICOCLAW_SERVICE_NAME`, `PICOCLAW_EXE`, `PICOCLAW_WORKSPACE`
- Plus: `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`
- Plus any variable starting with `LC_`
- Plus locale: `LC_ALL`, `LC_CTYPE`, `LC_MESSAGES`, `LC_MONETARY`, `LC_NUMERIC`, `LC_TIME`, `LC_PAPER`, `LC_NAME`, `LC_ADDRESS`, `LC_TELEPHONE`, `LC_MEASUREMENT`, `LC_IDENTIFICATION`, `LC_COLLATE`
- Plus: `PICOCLAW_HOME`, `PICOCLAW_CONFIG`, `PICOCLAW_AGENT_WORKSPACE`, `PICOCLAW_EXE`, `PICOCLAW_SERVICE_NAME`, `PICOCLAW_EXEC_TIME`, `PICOCLAW_EXEC_TIMEOUT`
2. **Config env_set** — Variables from config are merged (can override inherited values)
3. **LLM env injection** — The LLM can inject additional variables per-call via the `env` parameter:
3. **Config env_allowlist** — Additional explicit variable names to allow (extends default)
4. **LLM env injection** — The LLM can inject additional variables per-call via the `env` parameter:
```json
{
@ -81,6 +83,7 @@ The exec tool sanitizes the environment passed to child processes:
**Blocked variables** — The LLM cannot override these sensitive variables:
- `PATH`, `HOME`, `USER`, `LOGNAME`, `SHELL`
- `LD_PRELOAD`, `LD_LIBRARY_PATH`, `LD_AUDIT`, `LD_DEBUG`
- All `PICOCLAW_*` variables
### Default Blocked Command Patterns

View file

@ -646,7 +646,7 @@ type ExecConfig struct {
CustomAllowPatterns []string ` env:"PICOCLAW_TOOLS_EXEC_CUSTOM_ALLOW_PATTERNS" json:"custom_allow_patterns"`
TimeoutSeconds int ` env:"PICOCLAW_TOOLS_EXEC_TIMEOUT_SECONDS" json:"timeout_seconds"` // 0 means use default (60s)
EnvSet map[string]string ` json:"env_set"` // env vars to set for all exec commands
EnvAllowlist []string ` json:"env_allowlist"` // additional env vars to allow (extends default)
EnvAllowlist []string ` json:"env_allowlist"` // additional env vars to allow (extends default) - use explicit names, not wildcards
}
type SkillsToolsConfig struct {

View file

@ -35,11 +35,27 @@ var DefaultEnvAllowlist = map[string]bool{
"HTTP_PROXY": true,
"HTTPS_PROXY": true,
"NO_PROXY": true,
// Locale
"LC_ALL": true,
"LC_CTYPE": true,
"LC_MESSAGES": true,
"LC_MONETARY": true,
"LC_NUMERIC": true,
"LC_TIME": true,
"LC_PAPER": true,
"LC_NAME": true,
"LC_ADDRESS": true,
"LC_TELEPHONE": true,
"LC_MEASUREMENT": true,
"LC_IDENTIFICATION": true,
"LC_COLLATE": true,
}
// defaultEnvAllowPrefixes are env var prefixes that are always allowed.
// Currently empty - all allowed vars are explicit in DefaultEnvAllowlist.
var defaultEnvAllowPrefixes = []string{
"LC_",
// Currently empty - all allowed vars are explicit
}
// LLMBlocklist is the set of environment variable names that the LLM
@ -119,6 +135,25 @@ func WithAllowedEnv(envSet map[string]string, extraAllowlist []string) map[strin
return result
}
// LLMBlocklistPrefixes are env var prefixes that the LLM cannot override.
var LLMBlocklistPrefixes = []string{
"PICOCLAW_",
}
// isBlocked returns true if the key is in the blocklist or matches a blocked prefix.
func isBlocked(key string) bool {
norm := envKey(key)
if LLMBlocklist[norm] {
return true
}
for _, prefix := range LLMBlocklistPrefixes {
if strings.HasPrefix(norm, prefix) {
return true
}
}
return false
}
// MergeEnvVars merges multiple env sources into a final []string for exec.Cmd.Env.
// baseEnv is the cached map from AllowedEnv.
// envSet provides explicit key=value pairs (config, not filtered).
@ -141,7 +176,7 @@ func MergeEnvVars(baseEnv map[string]string, envSet, extraEnv map[string]string)
// Merge extraEnv (LLM-provided) - filtered by blocklist
if extraEnv != nil {
for k, v := range extraEnv {
if LLMBlocklist[envKey(k)] {
if isBlocked(k) {
continue // Skip blocked vars
}
vars[envKey(k)] = v