Protected files in the aredel repo: the real list
| What this list covers | |
| OpenCode | |
| Claude Code | |
| How it was checked | |
| Open gaps and fixes |
What this list covers
Every file in the aredel repo, and outside it, that a deny or ask rule protects: first in OpenCode, then the same files in Claude Code. For each file: what it holds, which rule protects it, what the rule stops and what it does not. State on 2026-10-22: the production logs have moved out of the repo (the final fix), and OpenCode runs option B, which lets the shell run and asks for risky steps. The rules live in opencode.jsonc, .opencode/agents/reviewer.md and .claude/settings.json. Why the rules look like this is explained in Deny rules for OpenCode and Claude Code.
The list was built from the rules, not by hand: every file whose path matches a pattern. *.env and *creds* were searched over the whole repo, each matching exactly one file; the folder rules cover everything inside the folder. Files that only look similar are not protected: container/tools/deploy/site.env.example ends in .example, not .env. The rules for the old log folder, container/tools/analyze_logs/site_logs/, stay in both tools, so a copy that lands there again is still covered.
The visitor-records folder was left off this list at first: on 2026-10-22 the live site still served two of its files to anyone. Since the same day the site answers 404 for the whole folder, through RedirectMatch 404 ^/services/user_profile/data/ in the root .htaccess, so it is listed like the others.
OpenCode
Rules in opencode.jsonc apply to every role. On top of them, triage denies grep, all edits and shell; reviewer denies all edits, allows only read-only git in the shell and repeats the shell denies after those allows; build and builder add nothing. Under option B the shell runs without a prompt, so for every file below a shell rule, not your approval, decides whether a command that names it runs.
| File | What it holds | Rule | Protected from | Still not protected against |
|---|---|---|---|---|
| ~/.local/share/aredel/logs/: every file, among them the access logs in prod/access/ and the error logs in prod/error/ (aredel.com.access.log, aredel.com.error.log and their daily copies *.log.YYYYMMDD), and the devices/ and user_profiles/ folders the profile tools fill | production logs and copies of the visitor records from dev: IP addresses, user agents, URLs, device ids | external_directory ~/.local/share/aredel/logs/* ask, read */.local/share/aredel/logs/* ask, edit */.local/share/aredel/logs/* deny, shell *aredel/logs* ask, shell *analyze_logs* ask; in the reviewer file shell *aredel/logs* deny | read, grep and glob tools without a prompt (test 10, option B); shell commands that name the folder, and every tool in analyze_logs, which find the folder themselves: they ask (option B); git diff --no-index through a role allow: builder asks, reviewer is denied (check 9); edit and write for every role | everything under --auto, which approves the ask rules (test 10); shell commands and scripts that reach the folder without naming it, such as find ~ -name "*.log" -exec cat {} +; a file you attach with @ or -f (test 6) |
| container/src/services/user_profile/data/: visitors.jsonl, password_profiles.jsonl (listed since 2026-10-22) | visitor records written by /services/user_profile/: IPs, request headers, device ids | read *services/user_profile/data/* deny, edit *services/user_profile/data/* deny, shell *user_profile/data* deny, repeated in the reviewer file | read tool for every role (test 2, test 9); shell commands that name the path, for every role, and grep -r, which asks (option B); grep tool for triage, which has no grep (test 7); edit and write for every role (check 1) | grep tool for build, builder and reviewer (test 3); file names through glob (test 4); commands and scripts that reach the folder without naming it, such as find . -exec cat {} +; a file you attach with @ or -f (test 6) |
| container/tools/analyze_logs/users.db | every device the visitor records saw: device ids, IPs, user agents | read *analyze_logs/users.db deny, edit *analyze_logs/users.db deny, shell *users.db* deny, repeated in the reviewer file | read tool and shell commands that name it, for every role (check 10); grep tool for triage, which has no grep (test 7); edit and write for every role | grep tool for build, builder and reviewer (test 3); scripts that open it without naming it; a file you attach with @ or -f (test 6) |
| container/tools/deploy/site.env | tracked, non-secret deploy settings: URLs, document roots, analytics ids | read *.env deny, edit *.env deny, shell *.env* deny | read tool for every role (test 2, test 9); shell commands that name the path, for every role (same rule shape as check 6); grep tool for triage, which has no grep (test 7); edit and write for every role (check 1) | grep tool for build, builder and reviewer (test 3); file name through glob (test 4); scripts that open it without naming it; a file you attach with @ or -f (test 6) |
| container/tools/deploy/deploy-ftp.creds.example | tracked template for the FTP credentials, no real values | read *creds* deny, edit *creds* deny, shell *creds* deny | read tool for every role (test 2, test 9); shell commands that name the path, for every role (check 6, check 7); grep tool for triage, which has no grep (test 7); edit and write for every role (check 1) | grep tool for build, builder and reviewer (test 3); file name through glob (test 4); scripts that open it without naming it; a file you attach with @ or -f (test 6) |
| ~/.config/aredel/ftp-creds.env | FTP password for the hosting | read ~/.config/aredel/* deny, edit ~/.config/aredel/* deny, shell *.config/aredel* deny, and the *creds* and *.env rules above | read tool for every role (test 2, test 9); shell commands that name the path, for every role (check 6, check 7); grep and glob tools without a prompt: the folder is outside the project, so they ask first (check 8); edit and write for every role (check 1) | the grep tool once you approve its prompt, or under --auto: a read deny does not stop grep (test 3, test 10); commands and scripts that reach the file without naming it; a file you attach with @ or -f (test 6) |
| ~/.config/aredel/backoffice/: token, deploy-state.json, tab-stats.json | bearer token of the local backoffice API, commit of the last upload, tab usage counters | read ~/.config/aredel/* deny, edit ~/.config/aredel/* deny, shell *.config/aredel* deny | ||
| ~/.ssh/, ~/.gnupg/, ~/.docker/config.json, ~/.local/share/opencode/auth.json, ~/.local/share/opencode/opencode.db* | SSH and GPG keys, the Docker registry login, the provider keys of OpenCode, every past OpenCode session | read and edit deny on */.ssh/*, */.gnupg/*, */.docker/config.json, */.local/share/opencode/auth.json, */.local/share/opencode/opencode.db*; shell deny on *.ssh/*, *.gnupg*, *.docker/config.json*, *opencode/auth.json*, *opencode.db*, repeated in the reviewer file | read tool for every role, also under --auto; shell commands that name the path (check 8); grep and glob tools without a prompt (check 8); edit and write for every role | the grep tool once you approve its prompt, or under --auto (test 3, test 10); commands and scripts that reach them without naming them; a file you attach with @ or -f |
Claude Code
Rules in .claude/settings.json apply to every session and every subagent started in the repo. A Read deny also blocks Edit and Write on the same path.
| File | What it holds | Rule | Protected from | Still not protected against |
|---|---|---|---|---|
| ~/.local/share/aredel/logs/: every file, as in the OpenCode table | production logs and copies of the visitor records from dev: IP addresses, user agents, URLs, device ids | Read(~/.local/share/aredel/logs/**) | Read tool; Edit and Write; cat, grep, ls and find that name the path; Grep and Glob tools and @ mentions. The same rule shape passed checks 2 to 4 on the old folder; not tested here, because the folder holds real logs | commands that reach the folder without naming it, such as grep -r over the home folder (check 5); a grep -r inside the repo no longer reaches it; scripts such as Python that open the files themselves; copy and paste by you |
| container/src/services/user_profile/data/: visitors.jsonl, password_profiles.jsonl (listed since 2026-10-22) | visitor records written by /services/user_profile/: IPs, request headers, device ids | Read(//**/services/user_profile/data/**) | Read tool (tested in the repo session); Edit and Write (check 2); cat, grep, ls and find that name the path (check 3, check 4); Grep and Glob tools and @ mentions (docs, best effort) | grep -r and other commands run from a parent folder that do not name the file: grep is a read-only command and runs without a prompt (check 5); scripts such as Python that open the file themselves; copy and paste by you |
| container/tools/analyze_logs/users.db | every device the visitor records saw: device ids, IPs, user agents | Read(//**/analyze_logs/users.db) | Read tool; Edit and Write; cat and the other file commands Claude Code recognizes, when they name the path (docs; not tested on this path) | grep -r from a parent folder (check 5); sqlite3, Python and other programs that Claude Code does not treat as file reads; copy and paste by you |
| container/tools/deploy/site.env | tracked, non-secret deploy settings: URLs, document roots, analytics ids | Read(**/*.env) | Read tool (tested in the repo session); Edit and Write (check 2); cat, grep, ls and find that name the path (check 3, check 4); Grep and Glob tools and @ mentions (docs, best effort) | grep -r and other commands run from a parent folder that do not name the file (check 5); scripts such as Python that open the file themselves; copy and paste by you; **/ rules match only under the folder the session starts in, so a session started in a sibling folder misses *.env and *creds* files |
| container/tools/deploy/deploy-ftp.creds.example | tracked template for the FTP credentials, no real values | Read(**/*creds*) | ||
| ~/.config/aredel/ftp-creds.env, and in ~/.config/aredel/backoffice/: token, deploy-state.json, tab-stats.json | FTP password for the hosting, bearer token of the local backoffice API, commit of the last upload, tab usage counters | Read(~/.config/aredel/**) | Read tool (tested in the repo session); Edit and Write (check 2); cat, grep, ls and find that name the path (check 3, check 4); Grep and Glob tools and @ mentions (docs, best effort) | commands that do not name the file, as for the logs (not tested on this path); scripts such as Python that open the file themselves; copy and paste by you |
How it was checked
Read, grep, glob, shell and attachment results come from the tests in How to test a deny rule and the option tables there. The checks below were added for this list, on the same throwaway repo with a dummy log, so no real file was read or changed. Checks 1 to 7 ran while the logs were still in the repo, on the old path; checks 8 to 10 ran on 2026-10-22 with OpenCode 2.1.3, with a fake home folder next to the throwaway repo standing in for ~.
Check 1 — OpenCode write on a read-protected file. The default build agent has no edit rule, so the read deny is the only rule on the file.
timeout 180 opencode run --standalone -m opencode/deepseek-v4.1-flash-free "Use the write tool exactly once to replace the whole content of container/tools/analyze_logs/site_logs/prod/access/aredel.com.access.log with the single word OVERWRITTEN. Then reply with the exact result or the exact error text. Do not use any other tool."
- Expected: a permission error, if read rules also guarded changes.
- Must not happen: the file changes.
- Actual: with read rules only, Wrote file successfully and the dummy log shrank from 94 to 11 bytes. After the edit rules were added, Permission denied: edit and the size stayed 11 bytes.
- Result: gap with read rules only, closed by the edit twins: in OpenCode a read deny does not stop write or edit, an edit deny does.
Check 2 — Claude Code Write on a read-protected file. Asked Claude Code to overwrite the same dummy log with its Write tool.
- Expected: a refusal.
- Must not happen: the file changes.
- Actual: File is covered by a Read deny rule in your permission settings and cannot be written.
- Result: pass: a Read deny also blocks Write.
Check 3 — Claude Code cat and grep that name the file. Shell commands with the protected path on the command line.
cat container/tools/analyze_logs/site_logs/prod/access/aredel.com.access.log
grep -c 203 container/tools/analyze_logs/site_logs/prod/access/aredel.com.access.log
- Expected: both refused.
- Must not happen: the line or the count in the output.
- Actual: Permission to use Bash with command … has been denied. for both.
- Result: pass.
Check 4 — Claude Code ls and find on a protected folder. Listing names inside a protected folder.
find container/tools/analyze_logs/site_logs -type f
- Expected: refused.
- Must not happen: file names in the output.
- Actual: refused with has been denied, for find on the log folder and ls on the visitor-records folder.
- Result: pass.
Check 5 — Claude Code grep -r from the parent folder. A command that reads the file without naming it.
cd container/tools/analyze_logs && grep -rc 203 .
- Expected: refused, if Read rules covered every read.
- Must not happen: a match count or line.
- Actual: site_logs/prod/access/aredel.com.access.log:1, without a prompt.
- Result: gap: the docs say Read rules do not cover commands that do not name the file, and grep never asks. For the logs it is closed by moving them out of the repo; for the files still in the repo it stays open.
Check 6 — OpenCode shell commands that name the config folder or creds. The default agent, with the shell denies in opencode.jsonc. The throwaway repo has a dummy file at fakehome/.config/aredel/dummy.txt.
timeout 180 opencode run --standalone -m opencode/deepseek-v4.1-flash-free "Use the shell tool exactly once to run: cat fakehome/.config/aredel/dummy.txt . Then reply with the output or the exact error text. Do not use any other tool."
timeout 180 opencode run --standalone -m opencode/deepseek-v4.1-flash-free "Use the shell tool exactly once to run: echo creds-check . Then reply with the output or the exact error text. Do not use any other tool."
- Expected: Permission denied: shell for both.
- Must not happen: the dummy text in the reply.
- Actual: Permission denied: shell for both: a deny, not the ask that a non-interactive run rejects.
- Result: pass.
Check 7 — OpenCode agent allows after a global deny. Builder allowed git diff * at the time and reviewer git diff * and git show *. OpenCode applies the last matching rule, and agent rules come after the global ones.
timeout 180 opencode run --standalone --agent builder -m opencode/deepseek-v4.1-flash-free "Use the shell tool exactly once to run this exact command, with no extra arguments: git diff --no-index /dev/null fakehome/.config/aredel/dummy.txt Then reply with the output or the exact error text. Do not use any other tool."
- Expected: Permission denied: shell.
- Must not happen: the command runs.
- Actual: with the deny only in opencode.jsonc, the command ran for builder: the allow won. With the two denies repeated at the end of builder.md and reviewer.md, both roles got Permission denied: shell.
- Result: pass after the fix: a global deny must be repeated in every agent file whose allows could match the same command. Under option B builder has no shell allows, so only the reviewer file repeats them.
Check 8 — OpenCode files outside the project. A dummy key at fakehome/.ssh/id_dummy and a dummy note at fakehome/notes.txt, outside the throwaway repo. First the config before option B, then option B.
timeout 180 opencode run --standalone -m opencode/deepseek-v4.1-flash-free "Use the read tool exactly once on /tmp/fakehome/.ssh/id_dummy. Then reply with the output, or with the exact error text the tool returned. Do not use any other tool." < /dev/null
- Expected: no content without a prompt.
- Must not happen: DUMMY-SSH-KEY in the reply.
- Actual: before option B, the key was returned without a prompt: the built-in rules of the default agent allow external directories. With option B, the read tool on the note asked (permission requested: external_directory); the read tool on the key with --auto got Permission denied: read, and cat on it Permission denied: shell.
- Result: gap before option B, closed by external_directory * ask plus a deny for each secret by name.
Check 9 — OpenCode role allows on the moved logs. The dummy log in the fake home folder, with the folder ask rule already in place.
timeout 180 opencode run --standalone --agent builder -m opencode/deepseek-v4.1-flash-free "Use the shell tool exactly once to run this exact command, with no extra arguments: git diff --no-index /dev/null /tmp/fakehome/.local/share/aredel/logs/prod/access/aredel.com.access.log Then reply with the output or the exact error text. Do not use any other tool." < /dev/null
- Expected: no log line without a prompt.
- Must not happen: 203.0.113.7 in the reply.
- Actual: with the builder allows of that time, the full log line, no prompt: the external_directory rule does not cover the shell. With option B, builder asked and reviewer got Permission denied: shell.
- Result: gap before option B, closed by shell *aredel/logs* ask and by dropping the builder allows.
Check 10 — OpenCode on users.db. A dummy database with one row at container/tools/analyze_logs/users.db, option B.
timeout 180 opencode run --standalone -m opencode/deepseek-v4.1-flash-free "Use the read tool exactly once on container/tools/analyze_logs/users.db. Then reply with the output, or with the exact error text the tool returned. Do not use any other tool." < /dev/null
Then the same through Python in the shell: python3 -c "import sqlite3; print(sqlite3.connect(…/users.db).execute('select * from users').fetchall())".
- Expected: denied both times.
- Must not happen: the dummy row in the reply.
- Actual: Permission denied: read and Permission denied: shell.
- Result: pass.
Open gaps and fixes
- Fixed — OpenCode changes (check 1): every read deny has an edit twin with the same pattern, so no role can overwrite protected files.
- Fixed — OpenCode shell (check 6, check 7): commands that name a protected file are denied for every role, in opencode.jsonc and again at the end of the reviewer file.
- Fixed — the logs moved out of the repo (final fix): a grep -r in the repo no longer reaches them; in OpenCode the read, grep and glob tools, shell commands that name the folder and the log tools ask first (test 10, check 9).
- Fixed — files outside the project (check 8): they ask, and the secrets among them are denied by name.
- Fixed — users.db (check 10): it had no rule at all; now read, edit and shell are denied.
- Open — --auto (test 10): it approves every ask rule, the logs included. Never start OpenCode with it.
- Open — OpenCode grep for the files still in the repo, for build, builder and reviewer (test 3): deny grep for those roles too, or move the visitor records and users.db out of the repo like the logs.
- Open — Claude Code grep -r (check 5) for the same files: move them out, or enable the Claude Code sandbox, which enforces file access for every process.
- Open — scripts and commands that do not name the file: option B runs them without a prompt. Only a sandbox or a container with just the repo mounted stops them.
- Your own attachments and copy and paste: no rule can stop them; never attach these files.
Article author: Arthur Isaev