Files
fish-config/conf.d/sponge_privacy.fish
T

138 lines
6.3 KiB
Fish

# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
# ╭──────────────────────────────────────────────────────────╮
# │ Sponge Privacy Pattern Registration │
# ╰──────────────────────────────────────────────────────────╯
#
# Two-layer approach to keeping credentials out of shell history:
#
# Layer 1 — Static patterns (registered as universal, persistent):
# Covers structural signatures: auth flags, env var assignments,
# credential-bearing URLs, Authorization headers, sshpass, etc.
# Patterns are added idempotently; user additions are preserved.
#
# Layer 2 — Dynamic secret values (registered as session globals):
# On the first prompt (after secrets.fish has loaded), reads the
# literal values of all exported variables whose names indicate
# credentials (TOKEN, PASSWORD, SECRET, KEY, etc.) and adds them
# as a session-scoped pattern overlay. Refreshes automatically
# each login, so rotated tokens never leave stale patterns behind.
#
# Layer 3 — Per-command filter (sponge_filter_secrets):
# Catches mid-session variables set after login — e.g. a token
# exported interactively or sourced from a project .env file.
#
# To add your own persistent patterns:
# set -U -a sponge_regex_patterns 'your-regex-here'
if not status is-interactive
return
end
# Only register if sponge is loaded
if not set -q sponge_version
return
end
# ──────────────────── Layer 1: Static patterns ────────────────────
set -l _privacy_patterns
# Common auth flags with values: --password x, --token x, --passphrase x, --api-key x
# Use \- to avoid the pattern string starting with --, which string match
# would misinterpret as an unknown option flag.
set -a _privacy_patterns '\-\-(?:password|passwd|passphrase|token|secret|api[-_]key)(?:\s+|=)\S+'
# Inline env var assignments with sensitive names: GITHUB_TOKEN=xxx, MY_API_KEY=abc
set -a _privacy_patterns '(?i)\b[A-Z][A-Z0-9_]*(?:PASSWORD|PASSWD|SECRET|TOKEN|API_KEY|PRIVATE_KEY|ACCESS_KEY|AUTH_KEY|CREDENTIAL)[A-Z0-9_]*=\S+'
# Fish set with sensitive variable names: set -gx GITHUB_TOKEN xxx, set -U MY_SECRET yyy
set -a _privacy_patterns '(?i)set\s+-\S+\s+\S*(?:password|passwd|token|secret|api.?key|private.?key|access.?key|credential)\S*\s+\S+'
# URLs with embedded credentials: https://user:password@host
set -a _privacy_patterns 'https?://[^:@\s]+:[^@\s]+@'
# HTTP Authorization headers: curl -H "Authorization: Bearer xxx"
set -a _privacy_patterns 'curl\s.*[Aa]uthorization:'
# Basic auth flags: curl -u user:pass, wget --user user --password pass
set -a _privacy_patterns '(?:curl|wget)\s.*(?:-u|--user)\s+\S+:\S+'
# sshpass — exposes credentials as a CLI argument by design
set -a _privacy_patterns '\bsshpass\b'
# Docker login with inline password
set -a _privacy_patterns 'docker\s+login\s.*(?:-p|--password)\s+\S+'
# openssl passphrase arguments: -passin pass:xxx, -passout env:VAR
set -a _privacy_patterns 'openssl\s.*-pass(?:in|out)\s+\S+'
# Remove any previously stored patterns that begin with -- ; string match
# passes patterns before its own -- sentinel and would treat them as flags.
for _i in (seq (count $sponge_regex_patterns) -1 1)
if string match --quiet -- '--*' $sponge_regex_patterns[$_i]
set -Ue sponge_regex_patterns[$_i]
end
end
# Idempotent registration into universal sponge_regex_patterns
for _pattern in $_privacy_patterns
if not contains -- $_pattern $sponge_regex_patterns
set -U -a sponge_regex_patterns $_pattern
end
end
# ──────────── Layer 2: Dynamic secret values (session globals) ────────────
# Runs once on the first prompt — by which point config.fish and secrets.fish
# have fully loaded, so all secret env vars are in scope.
# Builds a session-scoped global that combines the universal static patterns
# with the literal values of any credential-holding env vars. Globals shadow
# universals in Fish, so the combined list is what sponge sees for this session.
function __sponge_register_secret_values --on-event fish_prompt
functions --erase __sponge_register_secret_values # run exactly once
set -l secret_values
# Base credential-name tokens, plus any user-supplied extras from
# __fish_sponge_extra_sensitive (set via config-settings → Sponge page).
set -l _sensitive_names \
TOKEN PASSWORD PASSWD SECRET 'API[_-]KEY' 'PRIVATE[_-]KEY' \
'ACCESS[_-]KEY' 'AUTH[_-]KEY' CREDENTIAL KOPIA_PASSWORD \
$__fish_sponge_extra_sensitive
set -l _sensitive_alt (string join '|' $_sensitive_names)
# --entire returns the full matching variable NAME (e.g. GITHUB_TOKEN), not
# just the matched token substring (TOKEN) — required so $$var below
# dereferences the real variable instead of an unset partial name.
set -l sensitive_vars (set --names --export | string match --regex --entire -- \
"(?i)(?:$_sensitive_alt)")
for var in $sensitive_vars
# Take only the first element — array vars yield multiple values.
set -l value $$var[1]
# If the var is unset or holds an empty list, value has no elements;
# set -q catches that before string length receives zero arguments.
set -q value[1]; or continue
test (string length -- $value) -gt 8; or continue
string match --quiet --regex '^[/~]' -- $value; and continue
set -a secret_values (string escape --style=regex -- $value)
end
if test (count $secret_values) -gt 0
# Merge static universals + dynamic values into a session global
set -g sponge_regex_patterns $sponge_regex_patterns $secret_values
end
end
# ──────────── Layer 3: Mid-session filter (sponge_filter_secrets) ─────────
# Catches credentials in variables exported after login (e.g. project .env files).
# Only register if not already in sponge_filters.
if functions --query sponge_filter_secrets
if not contains -- sponge_filter_secrets $sponge_filters
set -U -a sponge_filters sponge_filter_secrets
end
end