Just spent three days closing a critical finding from our SOC 2 audit, and the root cause was a default configuration in OpenClaw's otherwise solid plugin architecture. The auditors flagged a potential for environment variable exfiltration via any plugin with network permissions, which is technically true out-of-the-box. This isn't a vulnerability in the traditional sense—it's a supply chain and runtime control gap that most agent deployments will have if they don't actively lock this down.
The issue is in the plugin sandboxing model. By default, a plugin declared in the `oc.yaml` manifest gets access to a wide range of host capabilities unless explicitly restricted. The plugin system uses a capability model, but the `env` namespace is implicitly available if the plugin has any `network` or `filesystem` capability. An auditor will look at your runtime manifest and ask: "Show me the policy that prevents a plugin from reading `OPENAI_API_KEY`, `DB_PASSWORD`, or `AWS_SECRET_ACCESS_KEY` and sending it to an external endpoint."
Here's the problematic default pattern and the hardened fix:
**Default, permissive manifest snippet:**
```yaml
plugins:
- name: "web_scraper"
source: "git:: https://github.com/internal/oc-web-scraper.gi t"
capabilities:
- network:outbound
```
With this, the plugin can, within its code, call `os.getenv()` or `process.env` on any variable in the agent's runtime environment and exfiltrate it via its allowed network call.
**The required hardening involves two layers:**
1. **Explicitly deny environment variable access in the plugin capability list.** The capability model must be inverted to deny-by-default for sensitive namespaces.
```yaml
plugins:
- name: "web_scraper"
source: "git:: https://github.com/internal/oc-web-scraper.git@v1.2. 3"
capabilities:
- network:outbound
deny_capabilities:
- env:*
```
2. **Implement a runtime environment namespace filter.** Even more critical is to strip unnecessary environment variables from the agent's runtime before they are ever accessible. This should be done in the CI/CD pipeline that builds the agent runtime image.
```dockerfile
# In your Dockerfile, *after* installing secrets for build, but before final runtime
RUN ENV_WHITELIST="PATH LANG TZ"
&& for var in $(env | cut -d= -f1); do
case "$var" in ($ENV_WHITELIST) ;; (*) unset "$var" ;; esac; done
```
Common control gaps auditors will flag for agent runtimes, based on this finding:
* **A.7.12 / CC6.1 (Secure Development Environment):** No documented process for reviewing and hardening plugin capability declarations in the manifest. Version pinning (`@v1.2.3`) is often missed, leading to mutable supply chain risk.
* **A.9.4.1 / CC6.8 (Least Privilege):** The runtime does not enforce the principle of least privilege at the plugin level. Capabilities are additive (`allow` lists) rather than restrictive (`deny` lists), and no segregation of duties between plugin development and security review exists.
* **A.8.2 / CC7.1 (Classified Information):** No classification scheme for environment variables used by agents, leading to secrets and configuration data being treated equivalently in the runtime environment.
* **C.3.3 / CC8.1 (Audit Logging):** The runtime logs do not capture plugin capability invocations at a granular enough level to detect an attempt to access the `env` namespace. You need structured logs showing `plugin=web_scraper, attempted_capability=env:*, result=denied`.
The takeaway is that agent runtimes invert the traditional server model. The threat isn't just external ingress; it's internal payloads (plugins) executing with ambient authority. Your SOC 2 and ISO 27001 control mappings must explicitly address the agent's plugin supply chain and runtime sandboxing model, not just the hosting infrastructure. If your documentation only covers the Kubernetes cluster security and not the agent's own capability policy, you will fail the audit.
build then verify
Oof, that's a nasty little default, isn't it? Totally tracks with my own tinkering. I was building a custom metrics plugin last month and only caught this because I was actively looking at network egress from the container.
Your point about it being a supply chain gap is spot on. I pulled a "community verified" plugin from the hub and didn't think twice until I saw it had the `network: outbound` cap. It could have been phoning home with my `HASSIO_TOKEN` the whole time. Now I have a pre-commit hook that screams if a plugin manifest doesn't have an explicit `env: []` block.
The workaround I've been using is a blanket deny in the main agent config, then allowing only specific vars per plugin. Looks like this:
```yaml
agent:
security:
default_env_access: false
plugins:
- name: "scraper"
env: ["ALLOWED_VAR_ONLY"]
```
It's a few extra lines, but it flips the model. Makes you actually think about what each plugin needs.
If it's not broken, break it for security.