$ shellfirm

Escalation Logic

How severity, context, and policy layers combine — and why escalation can only go up

shellfirm determines the effective challenge type through a multi-layer escalation pipeline. Severity, per-group/per-check overrides, runtime context, and project policies all contribute — and each layer can only make challenges harder, never easier. This page explains the full logic.

The full escalation pipeline

The effective challenge is resolved through six layers, applied in order. Each layer can only raise the challenge, never lower it:

LayerSourceWhen it applies
1. Basesettings.challengeAlways — your configured default (e.g., Math)
2. SeverityCheck severity levelCritical → Yes, High → Enter (by default)
3. Groupgroup_escalation in settingsWhen the matched check's group has an override
4. Check IDcheck_escalation in settingsWhen the matched check's ID has an override
5. ContextRuntime environment signalsSSH → Enter, root/production → Yes
6. Policy.shellfirm.yaml overridesWhen the project policy overrides the check
effective = max(base, severity_floor, group_floor, check_floor, context_floor, policy_floor)

Risk level computation

Context signals (layer 5) are evaluated in priority order. The first Critical signal wins:

Signal Priority Order
1. Root user (EUID=0) -- Critical
2. Protected git branch -- Critical
3. Production k8s context -- Critical
4. Production environment vars -- Critical
5. SSH session -- Elevated
6. None of the above -- Normal

The algorithm short-circuits: if root is detected, the risk level is immediately Critical regardless of other signals. But all signals are still detected and recorded as labels for visibility.

Risk levels

LevelMeaningDefault challenge floor
NormalNo risk signalsMath (your configured default)
ElevatedModerate risk (SSH)Enter
CriticalHigh risk (root, production branch, production k8s, production env)Yes

Challenge escalation order

shellfirm has three challenge types, ordered by difficulty:

Math < Enter < Yes
  • Math -- solve a simple arithmetic problem (e.g., "7 + 3 = ?")
  • Enter -- press Enter to confirm
  • Yes -- type the word "yes" to confirm

How escalation works

Each layer applies the max() function — the stricter of the current challenge and the layer's floor:

effective = max(current, layer_floor)

Examples with the full pipeline

Check severityBaseAfter severityContextAfter contextResult
HighMathEnterNormalEnterEnter
HighMathEnterCriticalYesYes
CriticalMathYesNormalYesYes
MediumMathMathElevatedEnterEnter
CriticalMathYesElevatedYesYes
LowEnterEnterNormalEnterEnter

Notice that escalation never lowers the challenge. If severity already set the challenge to Yes, context escalation to Enter will not lower it.

The security invariant

This is the fundamental guarantee:

Escalation can only go up, never down.

This invariant applies everywhere in shellfirm:

  • Severity escalation -- check severity sets a challenge floor (Critical→Yes, High→Enter)
  • Group/check-id escalation -- user-configured overrides can only raise the floor
  • Context escalation -- risk level can only raise the challenge floor
  • Policy overrides -- team policies can only escalate challenge types, never downgrade them
  • LLM analysis -- LLM can only flag additional risks, never dismiss existing ones
  • Deny lists -- patterns can be added to deny lists but never removed by policies

Multiple signals stacking

When multiple signals are detected, they all contribute to the risk level. Since any Critical signal produces Critical risk, additional signals do not further increase the risk level (there is nothing above Critical). However, all signals are recorded as labels for transparency:

Stacked Context Labels
# SSH + root + production branch + production k8s
ssh=true
root=true
branch=production
k8s=prod-us-east-1
NODE_ENV=production

The risk level is Critical (same as with just one Critical signal), but the full context is visible in the challenge banner and audit log.

Configuring escalation

The mapping from risk levels to challenge types is configurable:

# In ~/.shellfirm/settings.yaml
context:
  escalation:
    elevated: Enter    # default: Enter
    critical: Yes      # default: Yes

You can make Elevated stricter:

context:
  escalation:
    elevated: Yes      # SSH sessions now require "yes" confirmation
    critical: Yes

Or make it more lenient (though this weakens protection):

context:
  escalation:
    elevated: Math     # SSH sessions use Math (no escalation)
    critical: Enter    # Root/production use Enter instead of Yes

Full example walkthrough

Consider this scenario: you SSH into a production server, switch to root, and run git push --force on the main branch with NODE_ENV=production.

Detected signals:

  • SSH session (SSH_CONNECTION present) -- would be Elevated
  • Root user (EUID=0) -- Critical
  • Protected branch (main) -- Critical
  • Production env (NODE_ENV=production) -- Critical

Risk level: Critical (first Critical signal from root short-circuits)

Labels: ssh=true, root=true, branch=main, NODE_ENV=production

Escalation pipeline for git push --force (High severity):

  1. Base: Math (your configured default)
  2. Severity: max(Math, Enter) = Enter (High severity → Enter)
  3. Group/Check-ID: no overrides configured → Enter
  4. Context: max(Enter, Yes) = Yes (Critical risk level → Yes)
  5. Policy: no overrides → Yes

Result: Yes — you must type "yes" to confirm. The challenge banner shows all four context labels so you understand exactly why shellfirm is being extra cautious.