$ shellfirm
How It Works

The Check Pipeline

Step-by-step breakdown of what happens when shellfirm intercepts a command

Every command you type passes through shellfirm's analysis pipeline before execution. Here is exactly what happens at each stage.

Pipeline stages

1. Shell hook intercepts the command

Your shell's pre-execution hook sends the command string to shellfirm pre-command. This happens before the shell executes anything -- the command is just a string at this point.

2. Quoted strings are stripped

shellfirm removes content inside single and double quotes to prevent evasion. Without this step, a command like rm -rf "/" could bypass pattern matching because the / is inside quotes.

Input:  rm -rf "/important/data"
After:  rm -rf

3. Command is split on shell operators

The command is split on &&, ||, |, ;, and newlines. Each part is analyzed independently:

Input:  echo hello && rm -rf / || echo "safe"
Parts:  ["echo hello", "rm -rf /", "echo \"safe\""]

This ensures that dangerous commands chained with safe ones are still caught.

4. Parallel pattern matching

Each command part is matched against all active check patterns using regex. Matching runs in parallel via rayon for fast throughput even with 100+ patterns.

Each check pattern has:

  • A regex to match against the command
  • A severity level (Info, Low, Medium, High, Critical)
  • A group (fs, git, docker, kubernetes, etc.)
  • Optional filters and alternatives

5. Post-match filters are applied

After a regex matches, post-match filters determine whether the check should actually fire. All filters in a check must pass (logical AND):

  • NotContains -- Keep the match only if the command does NOT contain a substring. Example: git push --force matches, but git push --force-with-lease does not (filtered out by NotContains: "--force-with-lease").

  • Contains -- Keep the match only if the command DOES contain a substring.

  • PathExists -- Keep the match only if the captured file path actually exists on disk. This prevents false positives when you type rm -rf some-typo and the path does not exist.

6. Runtime context is detected

shellfirm checks your environment for risk signals:

SignalHow detectedRisk level
SSH sessionSSH_CONNECTION or SSH_TTY environment variableElevated
Root userEUID=0Critical
Protected git branchgit rev-parse --abbrev-ref HEAD matches main, master, production, or release/*Critical
Production k8s contextkubectl config current-context contains prod, production, prd, or liveCritical
Production env varsNODE_ENV=production, RAILS_ENV=production, ENVIRONMENT=productionCritical

The detected risk level is used to escalate challenges.

7. Project policy is merged

If a .shellfirm.yaml file exists in the current directory or any parent directory, its rules are merged with your global settings. Policies are additive only -- they can escalate severity or add deny-listed patterns, but they can never weaken global protections.

See Team Policies for details.

8. Severity threshold is applied

If you have configured a min_severity threshold, checks below that threshold are skipped. Skipped checks are still logged to the audit trail with outcome Skipped, so you have full visibility.

9. Blast radius is computed

For supported check groups, shellfirm computes the real-world impact of the command at runtime. This runs with a 3-second timeout and graceful degradation -- if any computation fails, the challenge prompt is shown without the blast radius line.

Examples:

  • rm -rf ./src -- "Deletes ~347 files (12.4 MB) in ./src"
  • git push --force -- "Force-pushes 5 commits to origin/main"
  • docker system prune -a -- "Prunes up to 12 images, 3 containers, 2 volumes"

See Blast Radius for supported groups and details.

10. Challenge is presented or command is allowed

If any checks matched (after filtering and severity thresholds), the challenge type is determined by the escalation pipeline:

  1. Start with the configured default (e.g., Math)
  2. Escalate based on the highest matched severity (Critical→Yes, High→Enter by default)
  3. Apply any group or check-id overrides from settings
  4. Escalate based on runtime context (SSH→Enter, root/prod→Yes)
  5. Apply project policy overrides from .shellfirm.yaml

Each layer can only make the challenge harder, never easier.

If no checks matched, the command proceeds without interruption.

Pipeline result

The pipeline produces a PipelineResult containing:

  • active_matches -- All checks that matched after filtering
  • context -- Detected runtime context with risk level and labels
  • alternatives -- Safer command suggestions
  • blast_radii -- Computed impact metrics for matched checks
  • max_severity -- Highest severity among all matches
  • is_denied -- Whether any matched pattern is on the deny list