Feature Request: Zero-Trust Architecture for Environment Variable Security
Feature Request: Zero-Trust Secret Protection
Summary
Implement client-side secret detection and placeholder replacement to prevent real secrets from being transmitted to LLM providers, even when users explicitly read environment files.
Problem Statement
When OpenCode reads .env files or other configuration containing secrets, the actual secret values are transmitted to the LLM provider. This creates security risks:
- Multi-provider exposure: OpenCode supports 75+ LLM providers—each provider receiving secrets multiplies the attack surface
- Accidental leakage: Users may not realize configuration files contain sensitive data
- No transmission-time protection: Even if
.gitignoreis respected by search tools, thereadtool can still access and transmit.envcontents directly
Current Workaround Limitations
Blocking .env files entirely (via ignore patterns) prevents legitimate use cases like analyzing configuration structure, debugging environment setup, or reviewing deployment scripts.
Proposed Solution: Content-Level Transformation
Instead of blocking files, transform sensitive values while preserving structure:
┌─────────────────────────────────────────────────────────────────┐
│ .env (local file) │ What LLM receives │
│ ──────────────────────────────── │ ─────────────────────────│
│ CLOUDFLARE_API_TOKEN=cf_abc123.. │ CLOUDFLARE_API_TOKEN= │
│ DATABASE_URL=postgres://user:pass │ <SECRET_001> │
│ STRIPE_SECRET_KEY=sk_live_xyz... │ DATABASE_URL=<SECRET_002> │
│ │ STRIPE_SECRET_KEY= │
│ │ <SECRET_003> │
├────────────────────────────────────┴───────────────────────────┤
│ Local Execution: Placeholders resolved to real values │
│ API Transmission: Only placeholders sent to provider │
└─────────────────────────────────────────────────────────────────┘
The LLM can analyze configuration structure, identify missing variables, and suggest changes—without ever seeing the actual credentials.
Implementation Approach
Using OpenCode's Plugin System
import type { Plugin } from 'opencode';
const SECRET_PATTERNS = [
/^[A-Za-z0-9+/]{40,}={0,2}$/, // Base64 secrets
/^(sk|pk|api|key|token|secret)[-]/i, // Common prefixes
/^[a-f0-9]{32,64}$/i, // Hex API keys
/^(ghp|gho|ghu|ghs|ghr)[A-Za-z0-9]+/, // GitHub tokens
/^sk-[A-Za-z0-9]{48,}/, // OpenAI keys
/postgres://[^:]+:[^@]+@/, // Connection strings
];
const secretMap = new Map<string, string>();
let counter = 0;
function replaceSecrets(content: string): string {
return content.replace(/^([A-Z_][A-Z0-9_]*)=(.+)$/gm, (match, key, value) => {
if (SECRET_PATTERNS.some(p => p.test(value))) {
const placeholder = <SECRET_${String(++counter).padStart(3, '0')}>;
secretMap.set(placeholder, value);
return ${key}=${placeholder};
}
return match;
});
}
export const SecretProtection: Plugin = async () => ({
tool: {
execute: {
// Transform before sending to LLM
before: async (input, output) => {
if (input.tool === 'read') {
const path = output.args.filePath.toLowerCase();
if (path.includes('.env') || path.includes('secret')) {
output.result = replaceSecrets(output.result);
}
}
},
// Resolve placeholders for local execution
after: async (input, output) => {
if (input.tool === 'bash') {
let cmd = output.args.command;
for (const [placeholder, secret] of secretMap) {
cmd = cmd.replace(placeholder, secret);
}
output.args.command = cmd;
}
}
}
}
});
Configuration
{
"$schema": "https://opencode.ai/config.json",
"security": {
"secretProtection": {
"enabled": true,
"patterns": {
// Additional custom patterns for your org
"custom": ["^ACME_.*_KEY$", "^INTERNAL_.*_TOKEN$"]
},
"files": {
"protected": [".env*", "*.secrets", "credentials.*"],
"excluded": [".env.example", "*.sample"]
},
"mode": "replace" // "replace" | "redact" | "warn"
}
}
}
Agent-Specific Behavior
{
"agent": {
"plan": {
// Plan agent: always redact, never resolve
"security": { "secretProtection": { "mode": "redact" } }
},
"build": {
// Build agent: replace for analysis, resolve for execution
"security": { "secretProtection": { "mode": "replace" } }
}
}
}
Use Cases
- Configuration analysis: Review
.envstructure without exposing credentials - Debugging: Analyze environment setup issues safely
- Code review: Review deployment scripts while keeping secrets local
- Team collaboration: Share sessions without exposing team secrets
Benefits
| Benefit | Description |
|---|---|
| Defense in Depth | Protection even if users accidentally share sensitive files |
| Provider Agnostic | Works across all 75+ supported LLM providers |
| Preserves Functionality | Users can still analyze .env structure and patterns |
| Backward Compatible | Opt-in feature, existing workflows unchanged |
| Plugin Architecture | Leverages OpenCode's existing extensibility model |
Prior Art
- Claude Code Issue #2695: Similar zero-trust proposal for Anthropic's Claude Code
- 1Password CLI: Shell integration that resolves secrets at execution time
- AWS Secrets Manager: Runtime secret resolution pattern
This feature request builds upon and complements several existing security-related issues. Please check these related discussions:
- #539: [SECURITY] allow ignoring files to prevent secrets being leaked from .env to LLM (directly referenced)
- #2033: .env files loaded without warning (directly referenced)
- #4308: Session accessing .env file without asking the user (directly referenced)
- #3164: Exclude files from being used (directly referenced)
- #2748: Add Permission Control for MCP Tools (directly referenced)
- #3056: pii/secrets censor - Similar goal of protecting sensitive data from transmission
- #4318: [FEATURE]: Allow storage of secrets in system credential store - Complementary approach to credential management
- #231: add ability to load secrets from external command or environment variables in the config file - Related credential handling
- #318: feature: Ability to pass credentials without storing in environment file - Related to secure credential passing
This proposal offers a comprehensive solution (zero-trust architecture with placeholder replacement) that could address multiple issues at once. Feel free to review these related issues to ensure this implementation doesn't duplicate existing efforts.
anyway we can add a summarized version of this, that's a long description, also some of the linked issues aren't issues and were addressed a while ago
I’m interested in this proposal and definitely empathize with the concerns behind it.
There’s something I want to run by you. In my own proposal #5091, data moves through ports labeled by ownership. I only outlined “us” and “them,” though it could easily support more categories. We treat company-hosted models as trusted, so they fall into the “us” bucket — but I understand the point you’re raising about model providers being their own kind of risk.
That’s actually why your idea caught my attention. If the system exposes the right hooks, a plugin could sit in the data path and apply whatever redaction or security posture someone needs. Every request passing through a well-designed pipeline of events would give a plugin the chance to enforce its own methodology.
And I suspect there will be a lot of different strategies here. I’m not sure OpenCode is going to land on a single perfect answer — I don’t think anyone will, which is why I see security as layers afforded by plugins. But the concern you’re raising is real and worth the plugin.
What I can't tell is if the current system does provide the necessary hooks to support your plugin. Is that a working plugin or just an idea?