# 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