Skip to content

Instantly share code, notes, and snippets.

@bonus414
Last active May 23, 2026 21:19
Show Gist options
  • Select an option

  • Save bonus414/0af75934169a86d20fc8a44381bdf4c0 to your computer and use it in GitHub Desktop.

Select an option

Save bonus414/0af75934169a86d20fc8a44381bdf4c0 to your computer and use it in GitHub Desktop.
Harness-agnostic secret-file read guard: blocks printing raw fnox.toml / .env contents to stdout. Works as a CLI/git-hook (exit code), a Claude Code PreToolUse hook (JSON), or via plain stdin.

Secret-file read guard (harness-agnostic)

A small shell guard that blocks any command from printing the raw contents of a secret-store file (fnox.toml or any *.env) to stdout. Agent transcripts and shell histories persist, so even an age-encrypted fnox.toml should never be echoed.

It catches the file-read vector: cat/grep/sed/awk/head/tail/less/xxd/..., language one-liners like open(".env"), input redirection < .env, and git diff/show/blame/log of the file.

It deliberately does not block:

  • fnox exec -- <cmd> — consumes a secret without printing it
  • git add / commit / restore — reference the file without printing its contents

One file, three ways to call it

The detection logic is identical across all three — only the input/output contract differs, so the same script drops into different setups.

1. CLI / git pre-commit / any tool that checks an exit code

secret-read-guard.sh '<command>'   # exit 0 = safe, exit 1 = would leak (reason on stderr)

Use it as a building block anywhere — a git pre-commit hook, a Makefile target, a shell precmd, or any agent harness that runs a command and honors the exit code.

2. Claude Code PreToolUse(Bash) hook

Pipe Claude Code's hook JSON in on stdin; on a match it emits a deny decision, otherwise it stays silent.

// ~/.claude/settings.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "$HOME/.claude/hooks/secret-read-guard.sh" }
        ]
      }
    ]
  }
}

3. Generic stdin

printf '%s' '<command>' | secret-read-guard.sh   # exit-code semantics, like mode 1

Install

curl -o secret-read-guard.sh <raw-gist-url>
chmod +x secret-read-guard.sh

Modes 1 and 3 need only a POSIX shell + grep. Mode 2 also needs jq (to parse the JSON).

Test

secret-read-guard.sh 'cat .env'        # exit 1 (blocked)
secret-read-guard.sh 'fnox exec -- env' # exit 0 (allowed)
secret-read-guard.sh 'git add .env'     # exit 0 (referenced, not printed)

Limitations

The detection is portable; the enforcement is only as good as the trigger. Something has to call this before a command runs. Modes 1 and 3 cover anything that checks an exit code; mode 2 covers Claude Code. A harness with no pre-execution hook of any kind can't be protected by this (or anything else) — there's nothing to intercept.

#!/usr/bin/env bash
# Secret-file read guard — harness-agnostic.
#
# Detects shell commands that would print the RAW CONTENTS of a secret-store
# file (fnox.toml or any *.env) to stdout, and blocks them.
#
# Why: agent transcripts and shell histories persist on servers. Even an
# age-encrypted fnox.toml must never be echoed. This blocks the file-read
# vector: cat/grep/sed/awk/head/tail/less/xxd/..., language one-liners like
# open(".env"), input redirection < .env, and git diff/show/blame/log of the
# file. It deliberately does NOT block:
# - `fnox exec -- <cmd>` (consumes a secret without printing it)
# - `git add` / `commit` / `restore` (reference the file, don't print it)
#
# THREE WAYS TO CALL IT (auto-detected) — the detection logic is identical;
# only the I/O contract differs, so the same file drops into any harness:
#
# 1. CLI / git-hook / any harness that checks an exit code:
# secret-read-guard.sh '<command>'
# exit 0 = safe, exit 1 = would leak (reason printed to stderr).
#
# 2. Claude Code PreToolUse(Bash) hook — pipe the hook JSON on stdin:
# emits {"permissionDecision":"deny",...} on a match; else stays silent.
#
# 3. Generic stdin — plain command text piped in:
# printf '%s' '<command>' | secret-read-guard.sh
# exit-code semantics, same as mode 1.
#
# Requires: jq ONLY for mode 2 (parsing Claude Code's JSON). Modes 1 and 3
# have no dependencies beyond a POSIX shell + grep.
set -uo pipefail
# --- core: harness-agnostic detection -------------------------------------
# Usage: detect_secret_read "<command string>"
# Returns 0 if the command would leak a secret file, 1 otherwise.
detect_secret_read() {
local cmd="$1"
[ -n "$cmd" ] || return 1
# A secret-store file token.
local secret='(fnox\.toml|[^[:space:]"'"'"']*\.env([.][[:alnum:]_-]+)?)'
# stdout-printing read utilities.
local readers='(cat|bat|tac|nl|grep|egrep|fgrep|rg|ag|head|tail|sed|awk|gawk|less|more|most|strings|od|xxd|hexdump|cut|tee)'
# 1) a reader whose arguments reach the secret file in the same segment
printf '%s' "$cmd" | grep -Eq "\b${readers}\b[^|;&]*${secret}" && return 0
# 2) file-read APIs of the secret file (python/ruby/node/powershell one-liners),
# tolerating an escaped quote (open(\".env\")) inside an outer quoted arg
printf '%s' "$cmd" | grep -Eq "(open|readFileSync|readFile|read_file|file_get_contents|Get-Content)[[:space:]]*\(?[[:space:]]*\\\\?['\"][^'\"]*${secret}" && return 0
# 3) input redirection from the secret file
printf '%s' "$cmd" | grep -Eq "<[[:space:]]*[^[:space:]|;&]*${secret}" && return 0
# 4) git subcommands that print file contents (diff/show/log -p/blame). Note:
# git add/commit/restore reference the file without printing it -> allowed.
printf '%s' "$cmd" | grep -Eq "git[[:space:]]+([a-z-]+[[:space:]]+)*(diff|show|blame|log)[^|;&]*${secret}" && return 0
return 1
}
REASON='BLOCKED: this command reads the raw contents of a secret-store file (fnox.toml or a .env). Histories and transcripts persist, so even encrypted values must never be printed. Use `fnox exec -- <cmd>` to consume a secret without printing it. To see only KEY NAMES, strip the values first.'
# --- mode 1: CLI / git-hook (command passed as arguments) -----------------
if [ "$#" -gt 0 ]; then
if detect_secret_read "$*"; then
printf '%s\n' "$REASON" >&2
exit 1
fi
exit 0
fi
# --- read stdin for modes 2 and 3 -----------------------------------------
input="$(cat)"
[ -n "$input" ] || exit 0
# --- mode 2: Claude Code PreToolUse(Bash) hook JSON on stdin --------------
if command -v jq >/dev/null 2>&1 &&
printf '%s' "$input" | jq -e 'type=="object" and (has("tool_input") or has("tool_name"))' >/dev/null 2>&1; then
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
[ "$tool" = "Bash" ] || exit 0
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
[ -n "$cmd" ] || exit 0
if detect_secret_read "$cmd"; then
jq -n --arg r "$REASON" \
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}'
fi
exit 0
fi
# --- mode 3: plain command text on stdin ----------------------------------
if detect_secret_read "$input"; then
printf '%s\n' "$REASON" >&2
exit 1
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment