diff --git a/.gitignore b/.gitignore index 9f71d48..44f8e03 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,16 @@ opinionated_catalog.md docs/superpowers +# ──────────────── Fisher-Managed Plugin Files ─────────────── +# Fisher writes these into the repo directory on install/update. +# They are owned by Fisher — do not commit them. +functions/fisher.fish +completions/fisher.fish +conf.d/sponge.fish +functions/_sponge*.fish +functions/sponge_*.fish +!functions/sponge_filter_secrets.fish + # ─────────────────── AI Sessions and Rules ────────────────── # Matches CLAUDE.md, .claud*, etc. [Cc][Ll][Aa][Uu][Dd][Ee].[Mm][Dd] diff --git a/README.md b/README.md index 8b8155f..4a27f46 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ This config layers on top of the CachyOS base Fish configuration and adds: - **Catppuccin Mocha** theming throughout (prompt, FZF, syntax highlighting) - **Starship** prompt with VI key bindings -- **Fisher** plugin management with FZF key bindings and Catppuccin syntax colors +- **Fisher** plugin manager bootstrapped automatically; manages `sponge` (failed-command history filter); FZF bindings, Catppuccin theme, done, autopair, and puffer-fish are bundled directly with the config as customized versions - **Smart CLI wrappers** that prefer modern tools (`eza`, `bat`, `btop`, `dust`, `prettyping`) with graceful fallbacks - **Auto Python venv** activation on directory change (direnv-aware) - **Kitty terminal** deep integration for splits, tabs, and SSH @@ -66,7 +66,7 @@ mv ~/.config/fish ~/.config/fish.bak git clone https://git.rootiest.dev/rootiest/fish-config.git ~/.config/fish ``` -Then open a new Fish shell — Fisher and all plugins will be installed automatically on first launch, and the Catppuccin Mocha theme will be applied. +Then open a new Fish shell — Fisher will be installed automatically on first launch and the Catppuccin Mocha theme will be applied. All plugin functionality is bundled directly with this config and requires no additional installation. A [chezmoi](https://www.chezmoi.io/) dotfile manager is also configured — secrets are sourced from `~/.config/.user-dots/fish/secrets.fish` and excluded from version control. diff --git a/conf.d/first_run.fish b/conf.d/first_run.fish index b6f6e48..ef96983 100644 --- a/conf.d/first_run.fish +++ b/conf.d/first_run.fish @@ -35,7 +35,7 @@ if not type -q fisher if curl -sL https://raw.githubusercontent.com/jorgebucaran/fisher/main/functions/fisher.fish | source echo " [first-run] Fisher installed." if not fisher update 2>/dev/null - echo " [first-run] Plugin sync failed — run 'fisher update' manually." >&2 + echo " [first-run] Fisher update failed — run 'fisher update' manually." >&2 end else echo " [first-run] Fisher install failed — run 'fisher update' manually." >&2 diff --git a/conf.d/sponge_privacy.fish b/conf.d/sponge_privacy.fish new file mode 100644 index 0000000..49a1d17 --- /dev/null +++ b/conf.d/sponge_privacy.fish @@ -0,0 +1,126 @@ +# 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 + + set -l sensitive_vars (set --names --export | string match --regex -- \ + '(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)') + + 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 diff --git a/docs/fish-config.index b/docs/fish-config.index index 74e80b1..06dacd4 100644 --- a/docs/fish-config.index +++ b/docs/fish-config.index @@ -29,8 +29,8 @@ keybindings=# 3. KEY BINDINGS bindings=# 3. KEY BINDINGS key-bindings=# 3. KEY BINDINGS keys=# 3. KEY BINDINGS -fzf-bindings=## FZF Bindings (from fzf --fish integration) -fzf=## FZF Bindings (from fzf --fish integration) +fzf-bindings=## FZF Bindings (bundled from PatrickF1/fzf.fish) +fzf=## FZF Bindings (bundled from PatrickF1/fzf.fish) # ── Section 4: Abbreviations ────────────────────────────────── abbreviations=# 4. ABBREVIATIONS @@ -166,10 +166,29 @@ universal=## Fish Universal Variables # ── Section 8: Fisher Plugins ───────────────────────────────── plugins=# 8. FISHER PLUGINS fisher=# 8. FISHER PLUGINS +fisher-managed=## Fisher-Managed Plugins +sponge=## Fisher-Managed Plugins +sponge-filtering=## Sponge History Filtering +history-filtering=## Sponge History Filtering +privacy=## Sponge History Filtering +bundled-plugins=## Bundled Plugin Functionality +fish-plugins-manifest=## fish_plugins Manifest -# ── Section 9: Viewing This Manual ─────────────────────────── -viewing=# 9. VIEWING THIS MANUAL -manual=# 9. VIEWING THIS MANUAL +# ── Section 9: Installation ──────────────────────────────────── +installation=# 9. INSTALLATION +install=# 9. INSTALLATION +sentinel=## Return Sentinel +updating=## Updating + +# ── Section 10: Personalization ─────────────────────────────── +personalization=# 10. PERSONALIZATION +personalize=# 10. PERSONALIZATION +secrets=## secrets.fish +local-config=## local.fish + +# ── Section 11: Viewing This Manual ────────────────────────── +viewing=# 11. VIEWING THIS MANUAL +manual=# 11. VIEWING THIS MANUAL ov=## With ov (recommended) man-page=## As a man page (if compiled) manpage=## As a man page (if compiled) diff --git a/docs/fish-config.md b/docs/fish-config.md index af8c2fb..8f4fa0f 100644 --- a/docs/fish-config.md +++ b/docs/fish-config.md @@ -279,7 +279,7 @@ are active in Insert, Normal, and Visual modes unless noted. line to open an fzf picker and insert a selection at the cursor position. -## FZF Bindings (from PatrickF1/fzf.fish) +## FZF Bindings (bundled from PatrickF1/fzf.fish) Ctrl+R Search command history Ctrl+Alt+F Search git-tracked files @@ -1395,9 +1395,9 @@ To customize, override FZF_DEFAULT_OPTS in local.fish. ### Catppuccin Mocha Syntax Highlighting -The catppuccin/fish plugin applies syntax highlight colors on first run. -Colors are stored in fish_variables (universal). To switch variants, install -a different theme from the themes/ directory: +The Catppuccin Mocha theme ships with this config in themes/ and is applied +on first run via `conf.d/first_run.fish`. Colors are stored in fish_variables +(universal). To switch variants, install a different theme from themes/: fish_config theme save "Catppuccin Latte" @@ -1417,18 +1417,74 @@ testing), run: Then open a new shell. -The plugin list is maintained in fish_plugins at the config root. +## Fisher-Managed Plugins + +The following plugins are fully managed by Fisher. Their files are installed +into the repo directory by Fisher and are listed in `.gitignore` — do not +commit them. Fisher installs and updates them automatically. jorgebucaran/fisher Plugin manager itself - catppuccin/fish Catppuccin Mocha color theme - PatrickF1/fzf.fish fzf integration for Fish - franciscolourenco/done Desktop notification when long commands finish - jorgebucaran/autopair.fish Auto-pair brackets and quotes meaningful-ooo/sponge Remove failed commands from history - nickeb96/puffer-fish !! / !$ / ./ expansion -Run `fisher update` to update all plugins, or `fish-deps update` which -calls fisher update as its first step. +## Sponge History Filtering + +Sponge removes failed commands from history and, via conf.d/sponge_privacy.fish, +also filters privacy-sensitive commands through three layers: + +Layer 1 — Static patterns (universal, persistent across sessions): +Commands matching any of these structural signatures are never recorded: + + --password / --token / --passphrase / --api-key flags with values + Inline env assignments: GITHUB_TOKEN=xxx, MY_API_KEY=abc + Fish set with sensitive names: set -gx GITHUB_TOKEN xxx + URLs with embedded credentials: https://user:pass@host + HTTP Authorization headers: curl -H "Authorization: ..." + Basic auth flags: curl -u user:pass + sshpass, docker login -p, openssl -passin/-passout + +Layer 2 — Dynamic secret values (session globals, refreshed each login): +On the first prompt, after secrets.fish has loaded, the literal values of +all exported variables whose names suggest credentials (TOKEN, PASSWORD, +SECRET, API_KEY, etc.) are collected, regex-escaped, and added as a +session-scoped overlay. Because globals shadow universals in Fish, the +combined list is what sponge sees. Rotating a token takes effect on the +next login automatically. + +Layer 3 — Per-command filter (sponge_filter_secrets): +Catches credentials in variables exported after login, such as tokens +sourced from a project .env file mid-session. + +To add your own persistent patterns: + + set -U -a sponge_regex_patterns 'your-regex-here' + +## Bundled Plugin Functionality + +The remaining plugin functionality is bundled directly with this config rather +than managed through Fisher. The bundled versions include customizations for +Fish 4.x compatibility and improved behavior that differ from their upstream +releases. Installing them through Fisher would overwrite these customizations. + +Bundled components and their upstream origins: + + catppuccin/fish → themes/ + conf.d/theme.fish + PatrickF1/fzf.fish → functions/_fzf_*.fish + conf.d/fzf.fish + franciscolourenco/done → conf.d/done.fish + jorgebucaran/autopair.fish → functions/_autopair_*.fish + conf.d/autopair.fish + nickeb96/puffer-fish → functions/_puffer_fish_*.fish + conf.d/puffer.fish + +Do not run `fisher install` for these — it will overwrite the customized +versions. To update their behavior, edit the relevant bundled files directly. + +## fish_plugins Manifest + +The `fish_plugins` file at the config root: + + jorgebucaran/fisher Plugin manager itself + meaningful-ooo/sponge Remove failed commands from history + +To update all Fisher-managed plugins, run `fisher update` or `fish-deps +update` which calls it as its first step. --- @@ -1439,8 +1495,9 @@ This configuration is managed as a git repository. To deploy on a new machine: mv ~/.config/fish ~/.config/fish.bak # back up any existing config git clone https://git.rootiest.dev/rootiest/fish-config.git ~/.config/fish -Then open a new Fish shell. Fisher and all plugins install automatically on -first launch and the Catppuccin Mocha theme is applied. +Then open a new Fish shell. Fisher installs automatically on first launch +and the Catppuccin Mocha theme is applied. All other plugin functionality is +bundled directly with this config and requires no additional installation. ## Return Sentinel diff --git a/fish_plugins b/fish_plugins index 594dfc0..5dd7d8e 100644 --- a/fish_plugins +++ b/fish_plugins @@ -1 +1,2 @@ jorgebucaran/fisher +meaningful-ooo/sponge diff --git a/functions/sponge_filter_secrets.fish b/functions/sponge_filter_secrets.fish new file mode 100644 index 0000000..66ffa76 --- /dev/null +++ b/functions/sponge_filter_secrets.fish @@ -0,0 +1,55 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# SYNOPSIS +# sponge_filter_secrets +# +# DESCRIPTION +# Custom sponge filter that prevents commands from being stored in history +# when they contain the literal value of any exported environment variable +# whose name indicates it holds a credential (TOKEN, PASSWORD, SECRET, +# API_KEY, etc.). This catches shell-expansion leakage where a variable +# value is embedded directly in the command string at execution time — a +# case that static regex patterns cannot cover. +# +# Any variable whose name matches the sensitive-name heuristic and whose +# value is longer than 8 characters (excluding bare paths) is checked. +# The value is escaped for literal regex matching before comparison. +# +# ARGUMENTS +# command The exact command that was entered +# exit_code Exit code of the command (unused) +# previously_in_history "true"/"false" flag (unused) +# +# RETURNS +# 0 Command contains a secret value — filter out of history +# 1 No secret value found — keep in history +# +# EXAMPLE +# # Register with sponge (done automatically by conf.d/sponge_privacy.fish): +# set -U -a sponge_filters sponge_filter_secrets + +function sponge_filter_secrets --argument-names command + # Find all exported variables with security-sensitive names + set -l sensitive_vars (set --names --export | string match --regex -- \ + '(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)') + + for var in $sensitive_vars + # Take only the first element — array vars yield multiple values + set -l value $$var[1] + # Guard before string length: if var is unset, value is an empty list + # and string length receives zero arguments, breaking test. + set -q value[1]; or continue + + # Skip short or path-like values — not real credentials + test (string length -- $value) -gt 8; or continue + string match --quiet --regex '^[/~]' -- $value; and continue + + # Filter if the literal value appears anywhere in the command + if string match --quiet --regex -- (string escape --style=regex -- $value) $command + return 0 + end + end + + return 1 +end diff --git a/requirements.md b/requirements.md deleted file mode 100644 index 0462373..0000000 --- a/requirements.md +++ /dev/null @@ -1,120 +0,0 @@ -# Fish Config Requirements - -Non-standard applications required by this fish shell configuration. - -## Terminal Emulators - -| Tool | Description | Notes | -|------|-------------|-------| -| `kitty` | GPU-accelerated terminal emulator | Primary terminal; used heavily for tab/window management via `kitty @` | -| `wezterm` | Cross-platform GPU-accelerated terminal | Referenced as alternative | -| `konsole` | KDE terminal emulator | Used in `tab.fish` as fallback | - -## File & Directory Tools - -| Tool | Description | Notes | -|------|-------------|-------| -| `lsd` | `ls` replacement with icons and colors | Wraps `ls`, `l`, `ll`, `lt`, `lstree`, `lS`, `ltr` | -| `bat` | `cat` replacement with syntax highlighting | Wraps `cat` | -| `dust` | Disk usage analyzer (Rust) | Used in `du.fish` | -| `duf` | Disk usage/free utility | Used in `du.fish` | -| `dua` | Fast disk space analyzer | Used in `du.fish` | -| `trash` / `trash-cli` | Safe file deletion to trash | Wraps `rm` | - -## Editors - -| Tool | Description | Notes | -|------|-------------|-------| -| `nvim` | Neovim text editor | Primary editor; used in `edit`, `view`, `nlazyup`, `nvimup` | -| `kate` | KDE text editor | Abbreviation target | - -## Git & Version Control - -| Tool | Description | Notes | -|------|-------------|-------| -| `lazygit` | Terminal Git UI | Abbreviation `lg` | -| `gitui` | Fast terminal Git UI | `gitui.fish` wrapper | -| `clone-in-kitty` | Clone repos into a Kitty window | Used in `clone.fish`, `clonet.fish` | - -## System & Process Monitoring - -| Tool | Description | Notes | -|------|-------------|-------| -| `btop` | Modern resource monitor | Wraps `top` | -| `fastfetch` | System info display | `ffetch.fish`, `cffetch.fish` | -| `neofetch` | System info display (fallback) | Fallback in `ffetch.fish` | - -## Package Management - -| Tool | Description | Notes | -|------|-------------|-------| -| `paru` | AUR helper (Arch Linux) | Used in `install`, `search`, `upgrade`, `parur` | - -## Navigation & Search - -| Tool | Description | Notes | -|------|-------------|-------| -| `zoxide` | Smart `cd` with frecency | Initialized in `zoxide.fish` | -| `fzf` | Fuzzy finder | Used in `parur.fish` and elsewhere | -| `ripgrep` / `rg` | Fast line-oriented search | Wrapped in `rg.fish` | - -## Shell Prompt & Theme - -| Tool | Description | Notes | -|------|-------------|-------| -| `starship` | Cross-shell prompt | Initialized in `config.fish` | - -## Terminal Multiplexers - -| Tool | Description | Notes | -|------|-------------|-------| -| `zellij` | Terminal workspace/multiplexer | `zellij.fish` wrapper | - -## Network & Remote - -| Tool | Description | Notes | -|------|-------------|-------| -| `tailscale` | VPN client | `tailscale.fish`, `tailart.fish` | -| `curl` | HTTP/data transfer | Used in `bd-pull.fish` | - -## Developer & Productivity Tools - -| Tool | Description | Notes | -|------|-------------|-------| -| `chezmoi` | Dotfile manager | Numerous abbreviations | -| `llm` | CLI for LLM interaction | `llm.fish` wrapper | -| `cheat` | Cheatsheet viewer | `cheat.fish`, `cheat.conf.d` | -| `wakatime` | Developer time tracking | Initialized in `wakatime.fish` | -| `bd` / `beads` | Lightweight issue tracker | `bd-pull.fish`, abbreviations | -| `lazybeads` | Terminal UI for beads | Abbreviation target | -| `agy` | antigravity-cli AI assistant | `antigravity.fish` wrapper, abbreviations | -| `antigravity-ide` | antigravity-ide editor | `antigravity-ide.fish` wrapper, abbreviations | - -## Container Tools - -| Tool | Description | Notes | -|------|-------------|-------| -| `docker` | Container platform | `dops.fish`, abbreviations | -| `lazydocker` | Terminal Docker UI | `ld` function in `config.fish` | - -## Command Replacements / Enhancements - -| Tool | Description | Notes | -|------|-------------|-------| -| `ov` | Modern pager | Sets `$PAGER`; wraps `less` with hierarchy fallback | -| `prettyping` | Pretty `ping` wrapper | Wraps `ping` | -| `sudo-rs` | Rust rewrite of sudo | Wraps `sudo` | - -## Data & System Utilities - -| Tool | Description | Notes | -|------|-------------|-------| -| `jq` | JSON processor | Used in `bd-pull.fish` | -| `wl-colorpicker-plasma` | Wayland color picker (KDE) | `get-color.fish` | - -## Fish Shell Framework & Plugins - -| Tool | Description | Notes | -|------|-------------|-------| -| `fisher` | Fish plugin manager | Manages plugins via `fish_plugins` | -| `catppuccin/fish` | Catppuccin theme for Fish | Listed in `fish_plugins` |