-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 4-layer secret leak prevention #1811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ in | |
| ./factory | ||
| ./gemini | ||
| ./git-ai | ||
| ./gitleaks | ||
| ./gomi | ||
| ./ghostty | ||
| ./hermes | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| title = "dotfiles gitleaks config" | ||
|
|
||
| # Extend the default gitleaks rules | ||
| [extend] | ||
| useDefault = true | ||
|
|
||
| [[rules]] | ||
| id = "japanese-password-field" | ||
| description = "Detects Japanese password field patterns" | ||
| regex = '''(?i)(パスワード|password|pw|pass)\s*[::=]\s*["']?[A-Za-z0-9!@#$%^&*\-_=+]{6,}["']?''' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False-positive prone — no
Two small fixes that materially reduce noise without losing coverage: [[rules]]
id = "japanese-password-field"
description = "Detects Japanese password field patterns"
keywords = ["password", "pw", "pass", "パスワード"]
regex = '''(?i)\b(パスワード|password|pw|pass)\b\s*[::=]\s*["']?[A-Za-z0-9!@#$%^&*\-_=+]{6,}["']?'''
|
||
|
|
||
| [[rules]] | ||
| id = "japanese-api-key-field" | ||
| description = "Detects Japanese API key field patterns" | ||
| regex = '''(?i)(APIキー|api[-_ ]?key|secret[-_ ]?key|access[-_ ]?token)\s*[::=]\s*["']?[A-Za-z0-9!@#$%^&*\-_=+]{16,}["']?''' | ||
|
|
||
| [allowlist] | ||
| description = "Global allowlist for templates, examples, and known-safe dummies" | ||
| paths = [ | ||
| '''(.*?)\.template\.md$''', | ||
| '''(.*?)\.example$''', | ||
| '''\.env\.example$''', | ||
| '''docs/.*\.template\.md$''', | ||
| '''dotagents/.*''', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Avoid globally allowlisting the entire Prompt for AI agents |
||
| '''bun\.lock$''', | ||
| '''Cargo\.lock$''', | ||
| '''flake\.lock$''', | ||
| '''node_modules/.*''', | ||
| '''target/.*''', | ||
| ] | ||
| regexes = [ | ||
| '''DUMMY_PASSWORD''', | ||
| '''<your-password-here>''', | ||
| '''<PLACEHOLDER>''', | ||
| '''xxxxxxxxxxxxxxxx''', | ||
| '''example\.com''', | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { config, ... }: | ||
| { | ||
| # Gitleaks reads $GITLEAKS_CONFIG when run outside a repo with a local .gitleaks.toml. | ||
| home.file.".config/gitleaks/config.toml".source = ./config.toml; | ||
| home.sessionVariables = { | ||
| GITLEAKS_CONFIG = "${config.home.homeDirectory}/.config/gitleaks/config.toml"; | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Shared PreToolUse hook for Claude Code and Codex Write/Edit operations. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Blocks writes containing secrets detected by gitleaks. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # See: https://zenn.dev/takna/articles/secret-leak-prevention-4-layer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Hook is supplementary, not primary defense. Skip silently if deps missing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command -v jq >/dev/null 2>&1 || exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| command -v gitleaks >/dev/null 2>&1 || exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PAYLOAD=$(cat) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Extract content across Claude and Codex payload shapes (Write/Edit/MultiEdit). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONTENT=$(printf '%s' "$PAYLOAD" | jq -r ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tool_input.content // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tool_input.new_string // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tool.input.content // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .tool.input.new_string // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolInput.content // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolInput.new_string // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (.tool_input.edits // [] | map(.new_string) | join("\n")) // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (.tool.input.edits // [] | map(.new_string) | join("\n")) // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Codex MultiEdit silently bypasses this hook. jq's Reproduced: Downstream the script hits Fix by coalescing the array sources before the (((.tool_input.edits // .tool.input.edits // .toolInput.edits) // [])
| map(.new_string) | join("\n")) //
empty |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| empty | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ' 2>/dev/null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [[ -z $CONTENT || $CONTENT == "null" ]] && exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| TMP_DIR=$(mktemp -d -t secret-guard.XXXXXX) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trap 'rm -rf "$TMP_DIR"' EXIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| printf '%s' "$CONTENT" >"$TMP_DIR/payload.txt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONFIG_ARG=() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if [[ -f "$PWD/.gitleaks.toml" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONFIG_ARG=(--config "$PWD/.gitleaks.toml") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [[ -n ${GITLEAKS_CONFIG:-} && -f $GITLEAKS_CONFIG ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONFIG_ARG=(--config "$GITLEAKS_CONFIG") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif [[ -f "$HOME/.config/gitleaks/config.toml" ]]; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CONFIG_ARG=(--config "$HOME/.config/gitleaks/config.toml") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if gitleaks dir "$TMP_DIR" "${CONFIG_ARG[@]}" --no-banner --redact >/dev/null 2>&1; then | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gitleaks errors are misreported as "secret found". gitleaks returns 0 for clean, 1 for leaks, and other non-zero codes for errors (unknown subcommand on older builds, bad config, panic, etc.). Consider capturing the exit code and falling open (or surfacing stderr) on anything that isn't GITLEAKS_OUT=$(gitleaks dir "$TMP_DIR" "${CONFIG_ARG[@]}" --no-banner --redact 2>&1)
rc=$?
case "$rc" in
0) exit 0 ;;
1) ;; # fall through to block message
*) printf 'secret-guard: gitleaks errored (rc=%s); skipping\n%s\n' "$rc" "$GITLEAKS_OUT" >&2; exit 0 ;;
esacThat keeps the hook's self-stated "supplementary, not primary defense" posture (deps missing → silent exit 0).
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gitleaks v8 (which is standard in Nixpkgs) has replaced the
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cat >&2 <<'EOF' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret-guard: Blocked Write/Edit due to detected secrets. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Move credentials to .gitignore'd paths (_credentials/, *.secret.md, etc.) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Use explicit placeholders like <PLACEHOLDER> or DUMMY_PASSWORD | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - See: https://zenn.dev/takna/articles/secret-leak-prevention-4-layer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| EOF | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| exit 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pre-commit: | ||
| parallel: true | ||
| commands: | ||
| gitleaks: | ||
| run: gitleaks git --staged --redact --verbose | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
**/draft/matches anydraft/directory at any depth, not just under credential paths. In a dotfiles tree that nests.worktrees/,dotagents/, and other repos, this can silently swallow legitimatedraft/content in unrelated projects.The other patterns in this block (
_credentials/,*.local.md,*.secret.md,credentials*.md) are intentionally narrow — consider anchoring this one too, e.g./_credentials/draft/, or switching to a filename pattern like*.draft.mdto match the existing style.