Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7564dbf
feat: host-bound credential injection (--inject-bearer/--inject-header)
pjcdawkins Jun 15, 2026
f2aeb45
feat: respect a user-provided ALL_PROXY
pjcdawkins Jun 15, 2026
56255e3
fix: harden credential-injection host handling
pjcdawkins Jun 15, 2026
09598d5
fix: address second-round credential-injection review
pjcdawkins Jun 15, 2026
f2f7145
fix: address third-round credential-injection review
pjcdawkins Jun 15, 2026
0961281
doc: correct --inject-bearer description in CLAUDE.md
pjcdawkins Jun 15, 2026
ffb59cf
refactor: header-agnostic credential injection via ENV_VAR=HOST
pjcdawkins Jun 15, 2026
6ecfb8e
fix: address credential-injection review
pjcdawkins Jun 15, 2026
cbd0d7b
refactor: simplify credential-injection comments and drop "sealing" term
pjcdawkins Jun 15, 2026
37d7cc6
refactor: deduplicate credential-injection helpers
pjcdawkins Jun 15, 2026
5ba60cc
fix: support credential injection on macOS
pjcdawkins Jun 15, 2026
3e5b7eb
fix: address credential-injection review findings
pjcdawkins Jun 16, 2026
819a5c5
feat: extend --inject-header with colon syntax, ports, IPs, host lists
pjcdawkins Jun 16, 2026
6b12ca5
fix: address credential-injection review round
pjcdawkins Jun 16, 2026
c82079a
fix: address credential injection regressions
pjcdawkins Jun 30, 2026
8648632
fix: address credential injection regressions
pjcdawkins Jun 30, 2026
53a6e4c
fix: renew injected certificates for long sessions
pjcdawkins Jun 30, 2026
638cc84
fix: address credential-injection review findings
pjcdawkins Jul 1, 2026
d890cf1
refactor: apply cleanup review to credential injection
pjcdawkins Jul 1, 2026
12c8152
fix: address review comments on CA base precedence and bracketed IPv6
pjcdawkins Jul 1, 2026
0624439
fix: authorize injection targets before the proxy-requirement check
pjcdawkins Jul 1, 2026
63f74ee
test: add end-to-end coverage for injection binding scope and opt-out
pjcdawkins Jul 1, 2026
6fd928f
test: make Expect: 100-continue test deterministic; close idle test c…
pjcdawkins Jul 2, 2026
2793f0b
fix: address Copilot review comments on injection opt-out and env hint
pjcdawkins Jul 2, 2026
dc1a9da
refactor: share --env wildcard handling between flags and CURB_ENV
pjcdawkins Jul 2, 2026
aa0ac76
refactor: simplify credential injection internals
pjcdawkins Jul 2, 2026
fbb674d
fix: address Copilot review comments on the injection proxy
pjcdawkins Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ go test ./sandbox/ -run TestCurb_FS_ -v # run a subset

- `config/` — Config struct (FromFlags, MergeEnv), defaults (platform-split: `defaults_linux.go`, `defaults_darwin.go`), exclusion helpers, config file loading, profiles
- `sandbox/plan.go` — SandboxPlan, PlanBuilder interface, shared resolve* helpers, FSEnforcer interface
- `sandbox/inject.go` — credential injection planning (resolveInject, target authorization, CA bundle delivery)
- `sandbox/plan_linux.go` — linuxPlanBuilder (namespace + Landlock enforcement selection)
- `sandbox/plan_darwin.go` — darwinPlanBuilder (Seatbelt enforcement, path canonicalization)
- `sandbox/plan_other.go` — degradedPlanBuilder (env-only fallback)
Expand All @@ -38,8 +39,10 @@ go test ./sandbox/ -run TestCurb_FS_ -v # run a subset
- `/etc` is not a blanket default. Only specific files/dirs are exposed (ssl, ca-certificates, ld.so, resolv.conf, hosts, passwd, etc.). Use `--read /etc` to restore full access.
- `/proc` is safe as default RO: user namespace blocks ptrace-guarded access across namespace boundaries.
- When the proxy is enabled, `NO_PROXY=localhost,127.0.0.1,::1` is set by default so sandbox-internal servers are reachable. `--host-loopback` suppresses this and forwards localhost traffic through the proxy to the host. User-provided `--env NO_PROXY=...` takes precedence.
- The SOCKS proxy is advertised via `ALL_PROXY`/`all_proxy` (HTTP/HTTPS still prefer `HTTP_PROXY`/`HTTPS_PROXY`). A user-provided value takes precedence, so `--env ALL_PROXY=` suppresses it — useful for HTTP-only workloads with clients that eagerly construct a SOCKS transport (e.g. httpx, which then needs the `httpx[socks]` extra). ssh routing does not use it (it has its own config).
- `--ips` allows connections to specific IP addresses or CIDR ranges. Works alongside or independently of `--domains`. IP-matched connections bypass domain filtering (forwarded directly on any port via SOCKS5 or CONNECT). In IPs-only mode (no `--domains`), DNS queries are not intercepted.
- `--unrestricted-net` skips network namespace creation entirely, allowing unrestricted network access while keeping FS sandboxing. Cannot be combined with `--domains` or `--ips`.
- `--inject-header ENV_VAR:HOST[:PORT][,HOST...]` keeps a credential out of the sandbox. curb sets the child's `ENV_VAR` to a stable per-var placeholder; the child never sees the real value. Each destination is `HOST[:PORT]` (a hostname or IP, port defaulting to 443) and bindings are keyed by `host:port`: the proxy terminates TLS and substitutes the real value (read from the host's `ENV_VAR`) wherever the client put the placeholder among the request headers — header-agnostic (no knowledge of the auth scheme) — but only on that exact host:port, so a connection to the same host on a different port is relayed unchanged. The binding is var-first because a credential belongs to its variable and may be valid for several destinations; the right side is a comma-separated list. Hostname destinations must be allowed by `--domains`, IP destinations by `--ips`. Opt-in per credential: if `ENV_VAR` is unset or empty on the host the binding is skipped (so the `claude` profile is a no-op for OAuth users). Exact passthrough (`--env ENV_VAR`) or an explicit value (`--env ENV_VAR=value`) is an explicit trust decision and disables injection for that variable; wildcard passthrough does not. Injection requires the proxy: with `--unrestricted-net` (or on platforms without network filtering) an active binding fails at planning, suggesting `--env ENV_VAR` instead. The terminated stream is served as HTTP/1.1 (keep-alive, Upgrade/WebSocket, 100-continue; no h2); a plain-HTTP request to a bound host:port is refused. Responses are relayed unscrubbed — a bound endpoint that reflects request headers hands the real value back to the child. The proxy uses a per-run CA delivered to the child by extending each standard CA env var's existing bundle, or system roots when unset; the CA key and real token stay parent-only. Settable via the repeatable flag, one full binding in `CURB_INJECT_HEADER`, or the `inject-header:` config/profile key (merged additively). In YAML, a list item needs no space after the colon (`- FOO:host`); `- FOO: host` parses as a map and is rejected.
- `--domains` validates input: rejects URLs (suggests bare domain), IP addresses (suggests `--ips`), invalid characters, and malformed wildcards. `--ips` validates that values parse as IP addresses or CIDR prefixes.
- `!` prefix in list flags removes from defaults AND actively denies via overmount when the path is under an allowed parent. `--read '!/path'` hides (tmpfs/dev-null), `--write '!/path'` makes read-only, `--exec '!/path'` makes noexec. `!*` clears all defaults (not deny-all). `\!` escapes literal `!`. Sub-path denials require mount NS; Landlock-only mode warns.
- HOME defaults to a private tmpDir. `--env HOME` passes through the host HOME; `--env HOME=/path` sets it explicitly. `~` and `$HOME` in config/profile paths both resolve to the **host** home (the shell's view), not the sandbox HOME — they are host-side shorthands. When sandbox HOME differs from host HOME and a path uses `~`, curb warns that the sandboxed program cannot reach those paths via its own `$HOME`. Built-in profiles include `HOME` in their env passthrough so sandbox HOME = host HOME and `~` paths align. See `docs/configuration.md` for full details.
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,13 @@ Pass through a specific environment variable:
curb --env 'DATABASE_URL' -- ./migrate.sh
```

Inject a credential the sandboxed process never sees — the token stays in
curb's proxy, which adds it to requests bound for the named destination:

```
GH_TOKEN=ghp_xxx curb --domains api.github.com --inject-header 'GH_TOKEN:api.github.com' -- gh api user
```

## CLI reference

### Network
Expand All @@ -162,6 +169,7 @@ curb --env 'DATABASE_URL' -- ./migrate.sh
|------|---------|-------------|
| `--domains` | `CURB_DOMAINS` | Allowed domain patterns (comma-separated). `*.example.com` for subdomains, `*` to allow all. |
| `--ips` | `CURB_IPS` | Allowed IP addresses or CIDR ranges (e.g. `10.0.0.1`, `192.168.0.0/16`, `::1`). |
| `--inject-header` | `CURB_INJECT_HEADER` | Keep a credential out of the sandbox: `ENV_VAR:HOST[:PORT][,HOST...]` sets the sandbox's copy of `ENV_VAR` to a placeholder, and the proxy replaces it with the real value in requests to each destination (terminating TLS), wherever the client sent it (any header — `Authorization: Bearer`, `x-api-key`, etc.), so no auth-scheme knowledge is needed. A destination is a hostname or IP, with `PORT` defaulting to 443; the credential is injected only on that exact host:port. Each destination must also be allowed by `--domains` (hostnames) or `--ips` (IPs). Skipped if `ENV_VAR` is unset, and disabled for that variable by exact passthrough such as `--env ENV_VAR`. Repeatable; `CURB_INJECT_HEADER` holds one full binding; also settable via the `inject-header:` config-file/profile key. |
| `--host-loopback` | `CURB_HOST_LOOPBACK` | Forward localhost/127.0.0.1 traffic to the host loopback. Cannot combine with `--unrestricted-net`. |
| `--unrestricted-net` | `CURB_UNRESTRICTED_NET` | Skip network filtering entirely. Cannot combine with `--domains`, `--ips`, or `--host-loopback`. |

Expand Down
2 changes: 1 addition & 1 deletion cmd/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type flagGroup struct {
// flagGroups defines the order and grouping of flags in help output.
var flagGroups = []flagGroup{
{"Filesystem", []string{"read", "write"}},
{"Network", []string{"domains", "ips", "unrestricted-net", "host-loopback", "allow-unix-sockets"}},
{"Network", []string{"domains", "ips", "inject-header", "unrestricted-net", "host-loopback", "allow-unix-sockets"}},
{"Executables", []string{"exec"}},
{"Environment", []string{"env"}},
{"Profiles & Config", []string{"profiles", "auto", "config-file"}},
Expand Down
4 changes: 3 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ Use -- before the command when it has its own flags.`,
curb --domains example.com -- curl https://example.com # allow specific domains
curb -p node -w . -- npm install # profile + writable cwd
curb -a cargo build # auto-select profile
curb --dry-run -p node -- npm install # preview sandbox plan`,
curb --dry-run -p node -- npm install # preview sandbox plan
curb --domains api.github.com --inject-header GH_TOKEN:api.github.com -- gh pr list # inject a token, kept out of the sandbox`,
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
Expand Down Expand Up @@ -329,6 +330,7 @@ func registerFlags(cmd *cobra.Command) {
f.StringSlice("ips", nil, "allowed IP/CIDR ranges (e.g. 10.0.0.1, 192.168.0.0/16)")
f.Bool("unrestricted-net", false, "skip network restrictions entirely")
f.Bool("host-loopback", false, "forward localhost traffic to host loopback")
f.StringArray("inject-header", nil, "inject ENV_VAR's value as a credential to allowed destinations, kept out of the sandbox (ENV_VAR:HOST[:PORT][,HOST...]; HOST is a hostname or IP, PORT defaults to 443; TLS terminated; skipped if ENV_VAR is unset)")

// Executables.
f.StringSlice("exec", nil, "allowed executables (default: invoked command only)")
Expand Down
77 changes: 57 additions & 20 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Config struct {
EnvSet []string
EnvPassthroughAll bool
AllowedIPs []string
InjectHeader []string
UnrestrictedNet bool
NoFSRestrict bool
NoExecRestrict bool
Expand All @@ -32,7 +33,7 @@ type Config struct {
Quiet bool
DryRun bool
Auto bool
ConfigFilePaths []string
ConfigFilePaths []string
Command []string
}

Expand Down Expand Up @@ -68,6 +69,10 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
if err != nil {
return nil, err
}
injectHeader, err := flags.GetStringArray("inject-header")
if err != nil {
return nil, err
}
if len(allow) > 0 {
if err := policy.ValidateDomains(allow); err != nil {
return nil, err
Expand All @@ -78,6 +83,11 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
return nil, err
}
}
for _, e := range injectHeader {
if _, _, err := policy.ParseInjectHeader(e); err != nil {
return nil, fmt.Errorf("--inject-header %w", err)
}
}
hostLoopback, err := flags.GetBool("host-loopback")
if err != nil {
return nil, err
Expand Down Expand Up @@ -117,18 +127,15 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
if err != nil {
return nil, err
}
// Separate --allow-env values into passthrough names and explicit name=value pairs.
passNames, setPairs := classifyEnvArgs(env)

cfg := &Config{
AllowedDomains: allow,
AllowedIPs: ips,
InjectHeader: injectHeader,
UnrestrictedNet: unrestrictedNet,
ROPaths: ro,
RWPaths: rw,
ExecAllow: execAllow,
EnvPassthrough: passNames,
EnvSet: setPairs,
AllowUnixSockets: allowUnixSockets,
HostLoopback: hostLoopback,
LogFile: logFile,
Expand All @@ -137,7 +144,7 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
Quiet: quiet,
DryRun: dryRun,
Auto: auto,
Command: cmd.Flags().Args(),
Command: cmd.Flags().Args(),
}

// Wildcard handling: '*' in list flags sets the corresponding escape hatch.
Expand All @@ -149,13 +156,10 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
cfg.NoFSRestrict = true
cfg.RWPaths = nil
}
if containsStar(passNames) {
cfg.EnvPassthroughAll = true
cfg.EnvPassthrough = nil
}
if containsStar(cfg.ROPaths) {
cfg.ROPaths = []string{"/"}
}
cfg.applyEnvArgs(env)

return cfg, nil
}
Expand All @@ -169,6 +173,7 @@ func MergeEnv(cfg *Config, cmd *cobra.Command) {
// List values: always additive, with wildcard detection.
cfg.AllowedDomains = appendEnvList(cfg.AllowedDomains, "CURB_DOMAINS")
cfg.AllowedIPs = appendEnvList(cfg.AllowedIPs, "CURB_IPS")
cfg.InjectHeader = appendEnvValue(cfg.InjectHeader, "CURB_INJECT_HEADER")

roEnv := appendEnvList(nil, "CURB_READ")
if containsStar(roEnv) {
Expand All @@ -193,16 +198,9 @@ func MergeEnv(cfg *Config, cmd *cobra.Command) {
cfg.ExecAllow = append(cfg.ExecAllow, execEnv...)
}

// --env via CURB_ENV: split and classify like FromFlags.
// --env via CURB_ENV: same handling as the flag.
if val, ok := os.LookupEnv("CURB_ENV"); ok {
envPass, envSet := classifyEnvArgs(SplitComma(val))
if containsStar(envPass) {
cfg.EnvPassthroughAll = true
cfg.EnvPassthrough = nil
} else {
cfg.EnvPassthrough = append(cfg.EnvPassthrough, envPass...)
cfg.EnvSet = append(cfg.EnvSet, envSet...)
}
cfg.applyEnvArgs(SplitComma(val))
}

// String values: env only if flag not explicitly set.
Expand Down Expand Up @@ -235,6 +233,38 @@ func classifyEnvArgs(args []string) (passNames, setPairs []string) {
return
}

// applyEnvArgs merges --env-style values (from the flag or CURB_ENV) into cfg.
// A literal "*" enables passthrough-all; explicit names are kept even then —
// --env NAME alongside '*' is still a per-variable trust decision (it opts
// NAME out of credential injection).
func (cfg *Config) applyEnvArgs(args []string) {
passNames, setPairs := classifyEnvArgs(args)
for _, name := range passNames {
if name == "*" {
cfg.EnvPassthroughAll = true
continue
}
cfg.EnvPassthrough = append(cfg.EnvPassthrough, name)
}
cfg.EnvSet = append(cfg.EnvSet, setPairs...)
}

// EnvExplicitlyProvided reports whether the user named the variable in an
// --env argument — exact passthrough (--env NAME) or an explicit value (--env
// NAME=value). Either is a per-variable trust decision (it opts NAME out of
// credential injection); wildcard or glob passthrough is not. Explicit names
// are kept even alongside '*' (see applyEnvArgs), so the wildcard does not
// mask them.
func (cfg *Config) EnvExplicitlyProvided(name string) bool {
for _, pair := range cfg.EnvSet {
if k, _, _ := strings.Cut(pair, "="); k == name {
return true
}
}
adds, _, _ := ParseExclusions(cfg.EnvPassthrough)
return slices.Contains(adds, name)
}

// containsStar reports whether the slice contains a literal "*" element.
func containsStar(ss []string) bool {
return slices.Contains(ss, "*")
Expand All @@ -248,6 +278,14 @@ func appendEnvList(existing []string, envKey string) []string {
return append(existing, SplitComma(val)...)
}

func appendEnvValue(existing []string, envKey string) []string {
val, ok := os.LookupEnv(envKey)
if !ok || val == "" {
return existing
}
return append(existing, val)
}

// SplitComma splits a comma-separated string, trimming whitespace and dropping empties.
func SplitComma(s string) []string {
var result []string
Expand All @@ -273,4 +311,3 @@ func EnvBool(key string) bool {
val := os.Getenv(key)
return val == "1" || strings.EqualFold(val, "true")
}

63 changes: 63 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func newTestCmd(args []string) *cobra.Command {
f.StringSlice("exec", nil, "")
f.StringSlice("env", nil, "")
f.StringSlice("ips", nil, "")
f.StringArray("inject-header", nil, "")
f.Bool("unrestricted-net", false, "")
f.Bool("allow-unix-sockets", false, "")
f.Bool("host-loopback", false, "")
Expand Down Expand Up @@ -106,6 +107,28 @@ func TestFromFlags_WildcardEnv(t *testing.T) {
assert.Empty(t, cfg.EnvPassthrough)
}

func TestFromFlags_WildcardEnvKeepsExplicitNames(t *testing.T) {
cmd := newTestCmd([]string{"--env", "FOO", "--env", "*", "--env", "BAR=baz"})
cfg, err := FromFlags(cmd)
require.NoError(t, err)
assert.True(t, cfg.EnvPassthroughAll)
assert.Equal(t, []string{"FOO"}, cfg.EnvPassthrough)
assert.Equal(t, []string{"BAR=baz"}, cfg.EnvSet)
}

func TestEnvExplicitlyProvided(t *testing.T) {
cfg := &Config{
EnvPassthrough: []string{"FOO", "GLOB_*", "!EXCLUDED"},
EnvSet: []string{"BAR=baz", "EMPTY="},
}
assert.True(t, cfg.EnvExplicitlyProvided("FOO"), "exact passthrough")
assert.True(t, cfg.EnvExplicitlyProvided("BAR"), "explicit value")
assert.True(t, cfg.EnvExplicitlyProvided("EMPTY"), "explicit empty value")
assert.False(t, cfg.EnvExplicitlyProvided("GLOB_MATCH"), "glob passthrough is not explicit")
assert.False(t, cfg.EnvExplicitlyProvided("EXCLUDED"), "exclusion is not explicit")
assert.False(t, cfg.EnvExplicitlyProvided("OTHER"))
}

func TestFromFlags_WildcardRead(t *testing.T) {
cmd := newTestCmd([]string{"--read", "*"})
cfg, err := FromFlags(cmd)
Expand All @@ -124,6 +147,25 @@ func TestMergeEnv_ListsAdditive(t *testing.T) {
assert.Equal(t, []string{"b.com", "a.com"}, cfg.AllowedDomains)
}

func TestMergeEnv_InjectHeaderAdditive(t *testing.T) {
cmd := newTestCmd([]string{"--inject-header", "B:b.com"})
cfg, err := FromFlags(cmd)
require.NoError(t, err)

t.Setenv("CURB_INJECT_HEADER", "A:a.com,c.com")
MergeEnv(cfg, cmd)

assert.Equal(t, []string{"B:b.com", "A:a.com,c.com"}, cfg.InjectHeader)
}

func TestFromFlags_InjectHeaderPreservesTargetCommaList(t *testing.T) {
cmd := newTestCmd([]string{"--inject-header", "TOK:a.com,b.com"})
cfg, err := FromFlags(cmd)
require.NoError(t, err)

assert.Equal(t, []string{"TOK:a.com,b.com"}, cfg.InjectHeader)
}

func TestMergeEnv_CommaSeparatedList(t *testing.T) {
cmd := newTestCmd(nil)
cfg, err := FromFlags(cmd)
Expand Down Expand Up @@ -205,6 +247,19 @@ func TestMergeEnv_WildcardEnv(t *testing.T) {
assert.Empty(t, cfg.EnvPassthrough)
}

func TestMergeEnv_WildcardEnvKeepsExplicitEntries(t *testing.T) {
cmd := newTestCmd(nil)
cfg, err := FromFlags(cmd)
require.NoError(t, err)

t.Setenv("CURB_ENV", "FOO,*,BAR=baz")
MergeEnv(cfg, cmd)

assert.True(t, cfg.EnvPassthroughAll)
assert.Equal(t, []string{"FOO"}, cfg.EnvPassthrough)
assert.Equal(t, []string{"BAR=baz"}, cfg.EnvSet)
}

func TestMergeEnv_WildcardEnvValueNotPassthrough(t *testing.T) {
cmd := newTestCmd(nil)
cfg, err := FromFlags(cmd)
Expand Down Expand Up @@ -332,6 +387,14 @@ func TestFromFlags_DomainIsIP(t *testing.T) {
assert.Contains(t, err.Error(), "use --ips instead")
}

func TestFromFlags_InvalidInjectHeader(t *testing.T) {
cmd := newTestCmd([]string{"--inject-header", "TOK:*.example.com"})
_, err := FromFlags(cmd)
require.Error(t, err)
assert.Contains(t, err.Error(), "--inject-header")
assert.Contains(t, err.Error(), "exact hostname")
}

func TestMergeEnv_IPsAdditive(t *testing.T) {
cmd := newTestCmd([]string{"--ips", "10.0.0.1"})
cfg, err := FromFlags(cmd)
Expand Down
7 changes: 7 additions & 0 deletions config/configfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type ConfigFile struct {
Profiles []string `yaml:"profiles"`
Domains []string `yaml:"domains"`
IPs []string `yaml:"ips"`
InjectHeader []string `yaml:"inject-header"`
Read pathList `yaml:"read"`
Write pathList `yaml:"write"`
Exec pathList `yaml:"exec"`
Expand Down Expand Up @@ -109,6 +110,11 @@ func (cf *ConfigFile) validate() error {
return err
}
}
for i, e := range cf.InjectHeader {
if _, _, err := policy.ParseInjectHeader(e); err != nil {
return fmt.Errorf("inject-header[%d]: %w", i, err)
}
}
for _, pair := range [...]struct {
field string
list pathList
Expand Down Expand Up @@ -152,6 +158,7 @@ func FindConfigFile() string {
func mergeConfigLists(cfg *Config, cf *ConfigFile) {
cfg.AllowedDomains = append(cf.Domains, cfg.AllowedDomains...)
cfg.AllowedIPs = append(cf.IPs, cfg.AllowedIPs...)
cfg.InjectHeader = append(cf.InjectHeader, cfg.InjectHeader...)
cfg.ROPaths = append(expandEnvPaths([]string(cf.Read), "read"), cfg.ROPaths...)
cfg.RWPaths = append(expandEnvPaths([]string(cf.Write), "write"), cfg.RWPaths...)
cfg.ExecAllow = append(expandEnvPaths([]string(cf.Exec), "exec"), cfg.ExecAllow...)
Expand Down
Loading
Loading