Skip to content

fix(security): scope plugin subprocess env to an allow-list by default#7

Merged
enowdev merged 1 commit into
enowdev:mainfrom
aguung:fix/plugin-env-permission
Jul 10, 2026
Merged

fix(security): scope plugin subprocess env to an allow-list by default#7
enowdev merged 1 commit into
enowdev:mainfrom
aguung:fix/plugin-env-permission

Conversation

@aguung

@aguung aguung commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Every plugin ran with os.Environ() inherited wholesale from enowX's own process, so a plugin could read any ambient secret enowX's process happened to have — Manifest.Permissions existed in the manifest struct but was never read by anything.
  • Adds a minimal environment allow-list as the default for plugin subprocesses, with an explicit "env:full" permission for a plugin that needs the old behavior.

Problem

  • core/plugins/manager.go's Start built cmd.Env from append(os.Environ(), ...) for every plugin runtime (go/python/node/bin), with no restriction.
  • core/plugins/manifest.go's Permissions []string field was parsed from plugin.json but never checked anywhere — no official plugin and no part of the UI/marketplace sets or surfaces it.
  • Net effect: a plugin (from the marketplace, or hand-installed) can read every environment variable enowX's own process has, whether or not that has anything to do with the plugin's stated purpose.

Solution

  • minimalEnv(): a fixed allow-list (PATH, HOME, USERPROFILE, TMPDIR, TEMP, TMP, SystemRoot) built via os.LookupEnv, so it's correct on every OS without a runtime.GOOS branch (a var simply isn't present on platforms where it doesn't apply). PATH/HOME/TMPDIR are also exactly what node/python/go need to locate their toolchain, caches, and temp files, so switching to this allow-list doesn't break plugins that only need to run their own runtime.
  • buildEnv(man, id, port, enowxPort): starts from minimalEnv(), or from os.Environ() if the manifest lists "env:full" in Permissions — preserving today's behavior for a plugin that explicitly opts in (e.g. one that shells out to git/gcloud/aws relying on the user's own env). Either way, the plugin's own PORT/ENOWX_PLUGIN_ID/ENOWX_API vars are appended, unchanged from before.
  • Unrecognized permission strings are left alone (no validation error) — a manifest naming a permission this enowX version doesn't understand yet stays forward-compatible.

Changed files

File Change
core/plugins/manager.go Adds envAllowList, minimalEnv, buildEnv; Start now calls buildEnv instead of inlining os.Environ().
core/plugins/manager_test.go New tests for minimalEnv/buildEnv, covering the default-scoped case and the env:full opt-in.
core/plugins/manifest.go One-line doc comment on Permissions documenting the recognized "env:full" value.

Known limitation / out of scope

  • This does not sandbox filesystem or network access — a plugin can still read/write anywhere the OS user can and make arbitrary outbound connections. Real sandboxing needs OS-specific primitives (namespaces/seccomp on Linux, sandbox profiles on macOS, Job Objects/AppContainer on Windows) and is a much larger, separate effort.
  • No UI for declaring permissions yet — a plugin author sets it directly in plugin.json.

Test plan

  • go test ./core/plugins/... -run 'TestMinimalEnv|TestBuildEnv' -v passes
  • go build ./... and go test ./... pass with no regressions
  • Manually verified with two real go run plugin subprocesses via plugins.Manager directly (a fresh enx instance can't start any plugin without a cloud entitlement, an unrelated pre-existing gate — see SetGate/authorized in manager.go, only wired up in cmd/enowx/main.go): a plugin without env:full never sees a canary env var set on the parent process, while PATH is still available so its own go run still works; a plugin with env:full does see the canary — confirming the allow-list is enforced and the opt-in still works end-to-end

Manager.Start ran every plugin with the full parent environment
inherited via os.Environ(), so a plugin could read any ambient secret
enowX's own process happened to have in its environment. Default to a
minimal allow-list (PATH/HOME/USERPROFILE/TMPDIR/TEMP/TMP/SystemRoot);
a plugin that declares "env:full" in its manifest keeps today's full
inheritance.
@enowdev enowdev merged commit f7ac32c into enowdev:main Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants