$ shellfirm

Custom Check Patterns

Add your own check patterns to shellfirm's pattern database

You can add custom check patterns in your settings file or in project policies. Custom patterns go through the same matching pipeline as built-in patterns.

Adding patterns in settings

Add custom checks to ~/.shellfirm/settings.yaml:

# Note: custom checks are typically added via project policies (.shellfirm.yaml)
# rather than global settings, but both approaches work.

The recommended approach is to add custom patterns in project-level .shellfirm.yaml files, which are version-controlled and shared across the team:

# .shellfirm.yaml
version: 1
checks:
  - id: "team:deploy_prod"
    from: base
    test: "deploy\\s+--env\\s+prod"
    severity: High
    description: "Production deployment detected"
    alternative: "deploy --env staging"
    alternative_info: "Deploy to staging first"

Pattern format

Each custom check requires these fields:

- id: "group:pattern_name"     # Unique ID
  from: base                    # Check group (for enable/disable filtering)
  test: "regex_pattern"         # Regex to match against commands
  severity: High                # Info | Low | Medium | High | Critical
  description: "Why it's risky" # Shown when the pattern matches

Optional fields:

  alternative: "safer command"            # Suggested safer command
  alternative_info: "Why it's safer"      # Explanation
  filters:                                # Post-match filters
    - type: NotContains
      value: "--dry-run"

Writing effective regex patterns

Basic patterns

# Match a specific command with arguments
test: "deploy\\s+--env\\s+production"
# Matches: deploy --env production
# Matches: deploy   --env  production (multiple spaces)

# Match a command with optional arguments
test: "npm\\s+publish"
# Matches: npm publish
# Matches: npm publish --tag beta

Using alternation

# Match multiple variants
test: "deploy\\s+--(env|environment)\\s+(prod|production)"
# Matches: deploy --env prod
# Matches: deploy --environment production

Negative lookahead

# Match --force but NOT --force-with-lease
test: "--force(?!-with-lease)"
# Matches: git push --force
# Does NOT match: git push --force-with-lease

Anchored patterns

# Match only at the start of a command
test: "^sudo\\s+rm"
# Matches: sudo rm -rf /
# Does NOT match: echo "sudo rm" (in quoted context)

Filter types

Filters are applied after the regex matches. They reduce false positives.

NotContains

The command must NOT contain this substring:

filters:
  - type: NotContains
    value: "--dry-run"

Use case: Allow deploy --dry-run but flag deploy without it.

Contains

The command MUST contain this substring:

filters:
  - type: Contains
    value: "--privileged"

Use case: Only flag docker run when --privileged is present.

PathExists

The match only triggers if a specific path exists on the filesystem:

filters:
  - type: PathExists
    value: "/data/production"

Use case: Only flag deletion commands when the production data directory exists.

ID naming conventions

Pattern IDs follow the group:name convention:

  • team:deploy_prod -- team-specific deployment check
  • custom:db_migrate -- custom database migration check
  • infra:delete_lb -- infrastructure-specific check

The group prefix is informational. Use something that identifies the source of the pattern.

Examples

Deployment safety

checks:
  - id: "team:deploy_prod_force"
    from: base
    test: "deploy.*--force.*production|deploy.*production.*--force"
    severity: Critical
    description: "Forced production deployment bypasses safety checks"
    filters:
      - type: NotContains
        value: "--dry-run"
    alternative: "deploy --env production"
    alternative_info: "Deploy without --force to use normal safety checks"

Custom tool protection

checks:
  - id: "team:admin_reset"
    from: base
    test: "admin-tool\\s+reset\\s+--all"
    severity: Critical
    description: "Resets all data in the admin tool"
    alternative: "admin-tool reset --scope limited"
    alternative_info: "Reset only the specific scope you need"

  - id: "team:cache_purge"
    from: base
    test: "cache-manager\\s+purge\\s+--global"
    severity: High
    description: "Global cache purge affects all users"
    alternative: "cache-manager purge --scope user"
    alternative_info: "Purge only user-scoped cache"

Preventing accidental data exposure

checks:
  - id: "team:env_dump"
    from: base
    test: "env\\s*\\|.*curl|printenv.*\\|.*curl"
    severity: Critical
    description: "Piping environment variables to an external URL"

  - id: "team:cat_secrets"
    from: base
    test: "cat.*(credentials|secrets|passwords)"
    severity: Medium
    description: "Reading files that may contain secrets"