AI security: secrets, .env files and access rights for coding agents
Threat model in one paragraph
An AI coding agent runs shell commands with your authority and reads your files into its context. Everything it reads — including secrets — then flows into tool output, session transcripts, filesystem snapshots, debug logs, shared links and exported sessions, and on free tiers possibly into model training. Security with AI assistants is therefore two disciplines: keep secrets out of the agent context in the first place, and give each agent only the access rights its role needs, as in Creating and developing agents and Three models, one project.
How aredel splits secrets today
This repo already separates non-secret config from real secrets. Both files below are tracked in git deliberately; the password lives nowhere near them:
container/tools/deploy/ ├── site.env ├── site.env.example ├── deploy-ftp.sh └── DEPLOY_FTP.md ~/.config/aredel/ └── ftp-creds.env
container/tools/deploy/site.env
~/.config/aredel/ftp-creds.env
Rules this setup follows, verified in the repo:
- site.env is tracked and non-secret. Its own header forbids passwords. Connection details mirror the secret file minus the password.
- The real password lives outside the repo in ~/.config/aredel/ftp-creds.env, chmod 600, ignored by git via *-creds.env.
- Precedence is CLI flags, then creds file, then SITE_ENV, then built-ins — and deploy-ftp.sh prints a warning when FTP_PASSWORD shows up in SITE_ENV, catching the exact mistake of pasting a secret into the tracked file.
- Public ids are not secrets. Analytics ids visible in page HTML need no protection; do not waste access controls on them.
What can go wrong
- Free-tier training. Muse Spark 1.3 Free is a contributor tier: prompts and completions may train future models. One pasted password or customer record is unrecallable.
- .env read into context. The agent reads the creds file once for a legit deploy question, and the secret then sits in the transcript, snapshots, and any session export or shared link made later.
- Prompt injection from tool content. Web pages, MCP results and browser output are untrusted data, not instructions. A fetched page can tell the agent to exfiltrate a file with a one-line upload — and a shell-allowed agent obeys.
- Shell with your authority. An agent allowed to run arbitrary shell can read ~/.config, list processes with credentials in arguments, and send anything anywhere. Every broad shell rule is a standing exfiltration permit.
- Secrets in tool output. env, set, debug logs and error dumps print secrets into results the agent then quotes back into the conversation.
- Sticky approvals. One careless Allow always on a shell pattern persists as a project rule and silently approves later variants.
- Single-key blast radius. One provider key for CI, deploy scripts and experiments means one leak revokes everything. The OpenRouter habit of per-key limits exists for exactly this.
Access rights that contain the blast radius
Default-deny posture for OpenCode, from the live trio config in this repo root. Global rules first, per-role exceptions after — last match wins:
{ "$schema": "https://opencode.ai/config.json", "permissions": [ { "action": "shell", "resource": "*", "effect": "ask" }, { "action": "shell", "resource": "git push *", "effect": "deny" }, { "action": "shell", "resource": "sudo *", "effect": "deny" }, { "action": "read", "resource": "*.env", "effect": "deny" }, { "action": "read", "resource": "*creds*", "effect": "deny" } ] }
Roles from Four complete agents, tightened for secrets:
| Agent | Read .env | Edit | Shell | Why |
|---|---|---|---|---|
| triage on Spark Free | deny | deny | deny | Free tier must never touch secrets at all |
| builder on Luna | deny | allow | ask, tests pre-allowed | Builds code, never deploys, never reads creds |
| reviewer on Opus 5.5 | deny | deny | deny | Judges diffs; findings reference secret handling without quoting values |
Deploy questions go to a human running deploy-ftp.sh, not to an agent with creds-file access. Team-wide blocks that projects cannot override belong in policies, not permissions — for example a global deny on tool.use shell:sudo *. Review sticky Allow always rules quarterly and delete the ones nobody can justify.
Secret hygiene that survives AI
- Split tracked config from secrets exactly like the aredel setup: site.env committed, site.env.example as template, real values outside the repo at chmod 600.
- One key per purpose. CI, deploy, experiments each get their own key with the smallest limit the provider allows, so one leak costs one revocation.
- Rotate on suspicion, not on schedule. After any transcript, export or share that might have seen a secret, rotate it — assume the value travelled.
- Secret work runs local or paid. Anything touching creds uses Ollama per Ollama basics or a paid zero-retention model, never the free contributor tier.
- Export sanitized. opencode session export --sanitize redacts transcripts before they leave the machine; share nothing by default.
- Watch the bill. opencode stats --cost --models weekly plus Console monthly limits — a sudden spike is also an intrusion signal.
Catch leaks before commit
Honest gap: this repo has no secret-scanning pre-commit hook today — php -l, PHPCS and reflow gates check code, not credentials. Until one lands, the manual gate is a staged-diff grep for the known shapes before every commit:
git diff --cached | grep -n -i "FTP_PASSWORD\|API_KEY\|BEGIN.*PRIVATE KEY" | grep -v "example\|TEMPLATE\|secret-here"
Review every hit by hand: placeholders in *.example files are fine, values are not. The durable fix is a scanner hook (gitleaks or equivalent) added to .pre-commit-config.yaml — tracked as follow-up work, not as an excuse to skip the manual grep now.
Risk checklist for this repo
| Risk | Status in aredel | Action |
|---|---|---|
| Secrets in tracked files | Split done, deploy script warns | Keep the split; run the staged-diff grep |
| Agent reads creds file | Denied in root config for all roles | Verify with opencode debug config |
| Free-tier training leak | Triage denies edits, shell and all but map skill | Never route creds tasks to Spark Free |
| Prompt injection via tools | Shell asks by default | Keep it ask; never Allow always on shell |
| Pre-commit secret scan | Missing | Manual grep now, scanner hook next |
| Key blast radius | Outside-repo creds, one deploy purpose | Separate keys per new purpose |
Official sources:
- opencode.ai/v2/docs/permissions
- opencode.ai/v2/docs/policies
- docs.anthropic.com data usage
- platform.openai.com your data
Article author: Arthur Isaev
| AI | |
| AI security: secrets and access rights | |
| Creating and developing agents | |
| OpenCode coding agent | |
| Writing skills for AI assistants | |
| MCP: give AI assistants real tools |