Dev.to AI 🤖 Ai 👁 0 📖 5 min read

Secrets Leak Through Your Tooling, Not Your Prompt

Most secret leaks in AI-assisted work do not happen in the sentence you type. They happen in the tooling around that sentence: the .env file sitting on a remote box, the shell history nobody clears, the stack trace paste

Most secret leaks in AI-assisted work do not happen in the sentence you type. They happen in the tooling around that sentence: the .env file sitting on a remote box, the shell history nobody clears, the stack trace pasted back into a chat panel, and the generated helper script that echoes an environment variable nobody asked it to touch. If you model a model call as one hop inside a chain of custody, then the useful question stops being "what did I put in the prompt" and becomes "where can this value physically travel, and who can read it once it arrives."

That framing gives you two cheap, testable controls, and both fit into a single afternoon. The first is a gate that fails closed before anything leaves your machine, and the second is a sweep that runs afterwards across every artifact the session produced. The sweep is the more interesting half, because you can prove that it works by planting a fake credential and watching the detector catch it.

The four hops a secret can actually take

When you send work to a hosted model, the value you care about has more than one route out of your repository. It can ride along inside the context you deliberately attach, it can be read by a process running on a machine you rent, it can be echoed back inside generated code or error text, and it can be persisted into transcripts and logs you never intended to keep. Each of those is a separate trust boundary with a different reader, so a single "do not paste secrets" rule covers only the first one.

Boundary Who can read it Typical accidental carrier
Local repo to bundle Anyone who receives the bundle .env, fixtures, test snapshots
Bundle to model endpoint The provider, and its retention policy Attached files, pasted traces
Model to workspace Anyone with shell access to the box Generated scripts, .env reads
Workspace to disk Backups, images, support bundles Shell history, nohup.out, logs

Read the table from the bottom up when something goes wrong. The last row is usually the culprit, because a leaked value that reached disk once will keep being copied by whatever backup or snapshot job already existed. Rotation buys you time, but only finding the carrier stops the bleeding.

A gate that fails closed

The gate has one job: assemble the sendable bundle from an explicit allowlist of tracked files, then refuse to continue if anything in that bundle looks like a credential. It runs on your machine, it exits non-zero, and it does not try to be clever about intent. Everything below is a working script, not pseudocode.

#!/usr/bin/env bash
# preflight.sh — build and vet the bundle that leaves your machine
set -euo pipefail
BUNDLE="${1:-.context}"
rm -rf "$BUNDLE" && mkdir -p "$BUNDLE"

# 1. Allowlist: tracked files only, minus obvious secret-adjacent paths
git ls-files -z \
  | grep -zvE '(^|/)\.env|(^|/)secrets?/|\.pem$|\.p12$|(^|/)id_(rsa|ed25519)' \
  | xargs -0 -I{} cp --parents "{}" "$BUNDLE"

# 2. Denylist: known key shapes and private key headers
if grep -rInE 'sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|-----BEGIN [A-Z ]*PRIVATE KEY-----' "$BUNDLE"; then
  echo 'preflight: blocked — secret-shaped string in bundle' >&2
  exit 1
fi
echo "preflight: ok ($(find "$BUNDLE" -type f | wc -l) files)"

Two details matter more than the regexes. First, git ls-files is deliberate, because it silently excludes ignored files, which is where most .env files live; a plain cp -r would happily carry them along. Second, set -euo pipefail makes the grep exit code fatal instead of decorative, so a partial match stops the run rather than printing advice.

A sweep that proves itself with a planted canary

The sweep is a detector, and detectors rot quietly, so give it a self-test. Plant a value with a distinctive prefix, let the workflow run, and confirm the sweep reports it; then remove the plant and confirm it reports nothing. A detector you have never seen fire is just a comment with syntax.

#!/usr/bin/env bash
# sweep.sh — scan artifacts for credential-shaped strings
set -uo pipefail
ROOTS=("$@")
PATTERNS='sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|MC_CANARY_[A-Za-z0-9]{8,}|-----BEGIN [A-Z ]*PRIVATE KEY-----'
hits=0
for r in "${ROOTS[@]}"; do
  [ -e "$r" ] || continue
  while IFS= read -r line; do
    printf '%s\n' "$line"
    hits=$((hits+1))
  done < <(grep -rInE "$PATTERNS" "$r" 2>/dev/null || true)
done
echo "sweep: $hits hit(s)"
[ "$hits" -eq 0 ]

Run the self-test before you trust the output on real work. Export the canary in a shell, write it into a log the way an unsuspecting script would, and point the sweep at that directory.

export MC_CANARY_TOKEN="MC_CANARY_d3adb33f"
mkdir -p notes && echo "token=$MC_CANARY_TOKEN" >> notes/session.log
./sweep.sh notes; echo "exit=$?"     # expect 1 hit, exit=1
sed -i '/MC_CANARY/d' notes/session.log
./sweep.sh notes; echo "exit=$?"     # expect 0 hits, exit=0

When a real hit appears, resist the urge to clean the file first. Rotate the credential, then ask which boundary in the table carried it, because deleting the evidence before you understand the carrier guarantees a repeat next week. In practice the carrier is usually a command that dumped the environment into a file, or a set -x trace that shipped stdin and stdout into the transcript.

Where the free tier fits, and where it does not

A free server option moves you into the third row of the table, because you now run code on a machine you do not own and cannot inspect at the disk level. That is not a reason to avoid it, but it is a reason to put nothing on that machine that you would not paste into a public issue. Free model access has the same shape: the endpoint sits outside your trust boundary, so the gate and the sweep belong in front of it rather than behind it. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The project advertises free model access and a free server option, and the current promotion mentions a token allowance; treat any published number as promotional and re-read the terms before you move real work onto it, since quotas and conditions change. What stays stable is the workflow: build the bundle with preflight.sh, send it, then sweep the workspace, transcripts, and logs with sweep.sh before you archive anything.

What this does not protect you from

A regex sweep is a smoke detector, not a guarantee, and it will miss opaque session tokens, base64-encoded blobs, and any credential shape you have not taught it. It also produces false positives that will tempt you to weaken the patterns, which is the moment the detector starts failing silently. If your workload requires audit trails, per-request attribution, or data residency guarantees, the right answer is your organization's audited infrastructure rather than a free tier, and no amount of local scripting changes that.

Start by planting one canary and making one detector fire today, because a gate you have watched work once is worth more than a policy you have only read.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.