Install Claude Code skills without leaking your envStep-by-step, tested on macOS
Community skills can run arbitrary shell commands in your session. The late-April permission-bypass incident showed how little stands between a skill and your environment. This is the install flow that survives that threat model.
-
01
Step
Audit before install
read source, map env reads, map shell calls
0 tools installed yet -
02
Step
Isolated smoke test
separate profile, fake credential, TruffleHog scan
daily config untouched -
03
Step
Graduate on pass
audit log clean, redactor silent, skill promoted
~/.claude/settings.json
TL;DR· the answer, in twenty seconds
What: Claude Code skills run as trusted shell commands in your session. A skill that reads process.env or shells out without your knowledge can silently exfiltrate credentials. The late-April 2026 permission bypass (a Cyrillic 'a' substituted in "bash") proved the allow-list is not a complete boundary.
Fix: Before installing any community skill: read its source, install it into a throwaway Claude Code profile directory, run a fake credential through the session and verify TruffleHog reports it, then check the audit log. Graduate the skill to your daily config only after the log is clean.
Lesson: Skills expand the tool surface of your agent. Any expansion needs a quarantine step before it touches production credentials. The process below takes about 15 minutes the first time and five minutes per skill afterward.
In late April 2026, a researcher demonstrated a permission bypass in Claude Code's allow-list: a Cyrillic 'a' character substituted into the string "bash" produced a tool call that matched none of the configured deny rules but still executed shell commands. Anthropic patched the specific bypass within days. The class of problem remains: any skill that runs shell commands operates inside the same process boundary as your session, with access to whatever your shell inherits.
Community skills are markdown files that tell the agent how to behave. The best ones are just prompts with no side effects. Others invoke Bash() tool calls, read environment variables, or make network requests. You cannot tell which category a skill falls into until you read it.
This guide covers the six-step install flow that keeps your credentials out of unfamiliar skill code. Every command runs on macOS. Linux users can follow the same steps with path adjustments.
What to know before you start
- A Claude Code "skill" is a markdown file Claude reads as an instruction set. It can contain plain text, or it can guide the agent to run specific shell commands, fetch URLs, or call MCP tools.
- Knostic found in February 2026 that Claude Code captured live environment variables into
settings.local.json. That file's gone from the default config, but the mechanism that captured it (ambient shell environment) is still present in every session. - Skills do not require a code review process to publish. The Model Context Protocol spec mandates no signature requirement for servers or extensions.
- The quarantine flow below is manual. Run it once per new skill source, not once per skill from a source you have already vetted.
- After the flow, the skill lives in your daily config. Before it passes, it does not.
Step 1: read the skill source before anything else
Find the skill file. Community skills are typically .md files in a GitHub repository or published as part of a Claude Code plugin package.
Two things to look for:
First, scan for Bash( or bash invocations. A skill that guides the agent to run shell commands is riskier than one that only shapes the agent's behavior in text. Note every command pattern.
grep -n "Bash\|bash\|shell\|exec\|eval\|process\.env\|env\." skill-name.md
Second, look for references to environment variables. $HOME, $PATH, $API_KEY, or any pattern like process.env.X tells you the skill expects or reads ambient credentials.
grep -n '\$[A-Z_]\{3,\}\|process\.env\|os\.environ\|getenv' skill-name.md
If you find eval, curl | sh, bash <(...), or any dynamic execution pattern, treat the skill as high-risk regardless of who published it. Snyk Security Labs documented in early 2026 how MCP-adjacent tools chain these patterns for prompt-injection payloads. The same shape appears in malicious skills.
Write down what you found. You will check the audit log against this list in step 5.
Step 2: install into an isolated profile, not your daily config
Claude Code reads settings from ~/.claude/settings.json and from .claude/settings.json inside the project directory. You can point a Claude Code session at a different home directory by setting CLAUDE_CONFIG_HOME.
Create the isolated profile directory:
mkdir -p ~/claude-skill-sandbox
cp ~/.claude/settings.json ~/claude-skill-sandbox/settings.json
Edit ~/claude-skill-sandbox/settings.json to strip the environment:
{
"model": "claude-opus-4-5",
"permissions": {
"defaultMode": "ask",
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(nc *)",
"Bash(ssh *)",
"Bash(eval *)",
"Bash(* | sh)",
"Bash(* | bash)"
]
},
"env": {
"HOME": "/tmp/skill-sandbox-home",
"PATH": "/usr/local/bin:/usr/bin:/bin"
}
}
The env block is the key move. PATH and HOME only. No tokens, no database URLs, no API keys. If the skill tries to read $STRIPE_KEY, it finds nothing.
A broker like hasp makes this the default rather than a per-skill manual step. Instead of constructing a stripped env block every time you test a new skill, the broker holds your credentials and the session inherits references, not values. A skill that reads the environment finds nothing usable regardless of whether you remembered to strip it.
Create the sandbox home directory so the session has somewhere to write:
mkdir -p /tmp/skill-sandbox-home
Now install the skill file into the sandbox profile:
cp path/to/skill-name.md ~/claude-skill-sandbox/skills/
Claude Code's skills directory follows the same convention as ~/.claude/. Check the skill's README for the exact install path it expects.
Step 3: pre-flight scan with TruffleHog or Gitleaks
Before starting the session, scan the sandbox directory for any credentials that may have leaked from a previous test or copied over from your main config.
TruffleHog is the right tool here. It ships with 700+ detector rules covering API key formats from Stripe, AWS, GitHub, OpenAI, and most major providers:
trufflehog filesystem ~/claude-skill-sandbox --no-verification --json 2>/dev/null | jq '.SourceMetadata.Data.Filesystem.file + ": " + .Raw'
If TruffleHog reports nothing, the directory is clean. If it reports hits, remove the flagged files before proceeding. A credential in your sandbox config defeats the isolation.
Gitleaks is the alternative if you prefer it:
gitleaks detect --source ~/claude-skill-sandbox --no-git
Run one of these two. Running both is fine. The scan takes under a second on a small directory.
Step 4: run the session with a fake credential
Now start Claude Code with the isolated profile and a deliberate canary token:
CLAUDE_CONFIG_HOME=~/claude-skill-sandbox \
CANARY_TOKEN=ghp_FAKECREDENTIAL1234567890abcdefghij \
claude
The CANARY_TOKEN value looks like a real GitHub personal access token (ghp_ prefix, 40-character suffix). It is not. You made it up. A scanner that knows the GitHub token format will match it.
Inside the session, activate the skill you are testing and run it through its core use case. If the skill is supposed to help with code review, do a code review. If it shells out, let it. That is the point.
After the session ends, exit Claude Code. Do not close the terminal yet.
Now scan the sandbox for the canary value:
grep -r "FAKECREDENTIAL1234567890abcdefghij" ~/claude-skill-sandbox /tmp/skill-sandbox-home
Also scan with TruffleHog to catch the pattern rather than the literal string:
trufflehog filesystem ~/claude-skill-sandbox --no-verification --json 2>/dev/null | jq .
What success looks like: both commands return nothing.
What failure looks like: grep finds the string in a log file, a settings file, or a state cache. TruffleHog detects the ghp_ pattern in any file under the sandbox directory. Either result means the skill wrote your credentials somewhere it should not have.
If the canary appears, find the file:
grep -rl "FAKECREDENTIAL1234567890abcdefghij" ~/claude-skill-sandbox
Read that file. Understand why the skill wrote the value. Decide whether the write path is acceptable before continuing.
Step 5: check the audit log
Claude Code writes a session audit log. On macOS the default location is:
ls -lt ~/.claude/logs/ | head -5
Find the log file from your sandbox session. The timestamp on the most recent file should match when you ran the session. Read it:
cat ~/.claude/logs/<session-id>.jsonl | jq '.toolUse.name, .toolUse.input' 2>/dev/null | grep -v null
Cross-reference the tool calls in the log against the list you built in step 1. If the skill called a tool you did not expect, or passed an argument that looks like an environment variable value, that is the issue to investigate.
Specifically look for:
Bashcalls with full environment variable values as arguments- Network calls (
WebFetch,curl) to domains outside the skill's stated purpose - File writes outside the project directory
A clean audit log shows only the tool calls the skill is documented to make, with no credential values in arguments.
Step 6: graduate the skill to your daily config
Pass all five checks (step 1 read clean, step 3 scan clean, step 4 canary not found, step 5 log matches documented behavior), then copy the skill into your real profile:
cp path/to/skill-name.md ~/.claude/skills/
If the skill needs specific environment variables to function (an API key for its intended purpose, for example), add those to the project-level .claude/settings.json for the projects that need it, not to your global settings:
{
"env": {
"SKILL_API_KEY": "${SKILL_API_KEY}"
}
}
The ${SKILL_API_KEY} syntax tells Claude Code to read the value from your current shell at session start, not to hardcode it. The skill gets the value it needs for that session only.
Update your .claude/settings.json deny rules if step 1 revealed that the skill needs specific shell access. Add narrow allow rules rather than widening the global deny list:
{
"permissions": {
"allow": [
"Bash(gh pr view *)",
"Bash(gh issue list *)"
]
}
}
The point of the narrow allow is that the skill's documented behavior is now visible in version-controlled config. A future PR that changes the allow list is a code review event.
What gets missed in most skill install guides
Most write-ups for installing community tools say "copy the file and restart the session." That is fine for a skill that is truly just a prompt. It is not fine for a skill that calls Bash().
The bypass incident in late April makes this concrete. The researcher's proof of concept used a Cyrillic 'а' (U+0430) in place of the Latin 'a' in "bash". The allow-list matched against the visual string and passed. The shell did not distinguish. Anthropic's patch covered that specific encoding, but there are 11 common encoding tricks for bypassing string matching in tool-call allow-lists, and the skill source code is the only place to catch them before they run.
Reading the source is not about trusting the author. A well-intentioned skill can read environment variables it does not need because the developer was debugging something. The skill ships with that debug code still in. Your environment has things worth protecting. Those two facts together are the incident.
The canary token test catches a different class of problem: skills that write to unexpected locations. A skill that logs its inputs for debugging, writes a cache file, or sends telemetry will leave the canary behind in step 4. No amount of source-reading reliably catches this, because the write path may be inside a library the skill calls rather than in the skill itself.
A checklist you can paste into a PR
## Community skill install checklist
- [ ] Read skill source; noted all Bash() calls and env-var reads
- [ ] No eval, curl | sh, or dynamic execution patterns in source
- [ ] Sandbox profile created at ~/claude-skill-sandbox
- [ ] Sandbox settings.json env block: PATH and HOME only
- [ ] Pre-flight TruffleHog scan of sandbox directory: clean
- [ ] Session run with CANARY_TOKEN=ghp_FAKE... value
- [ ] Post-session grep for canary value: not found
- [ ] Post-session TruffleHog scan: no credential patterns
- [ ] Audit log reviewed; tool calls match documented behavior
- [ ] Skill graduated to ~/.claude/skills/ after all checks pass
- [ ] Project .claude/settings.json updated with narrow allow rules
- [ ] SKILL_API_KEY (if needed) set per-project, not globally
What this means for your stack
Skills extend what the agent can do. Every extension is also an extension of the attack surface. The six steps above take the skill install from "copy a file and hope" to a process with a documented pass/fail gate. The gate is not airtight. A skill that passes all six steps could still have latent issues. But it has survived a canary test, a pre-flight credential scan, and an audit log review. That is a different risk profile than a file you copied because a blog post told you to.
The deeper issue is the same one that produced the February 2026 settings.local.json incident: ambient credentials in your shell environment are available to every process in your session, including skills you did not write. The quarantine process above limits what a skill finds during testing. In production, the durable fix is to keep credentials out of the ambient environment entirely.
hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, connect a project, hand the next session a reference instead of a key. Source-available (FCL-1.0), local-first, macOS and Linux, no account.
The canary test in step 4 becomes a non-issue when there is nothing in the environment worth exfiltrating. That is the property worth building toward.
Sources· cited above, in one place
- Anthropic Security advisories and Claude Code release notes
- Anthropic security Vulnerability disclosure and Trust Center
- TruffleHog Secret scanner
- Gitleaks Git secret scanner
- Knostic Research on AI code editor secret leakage (Claude Code, Cursor)
- Model Context Protocol Specification
- Snyk Security Labs MCP prompt-injection and supply-chain research
Stop handing the agent your real keys.
hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.
- Local, encrypted vault — no account, no cloud, no telemetry by default.
- Brokered run — agent gets a reference, the child process gets the value.
- Pre-commit + pre-push hooks catch managed values before they ship.
- Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.