$ shellfirm

Monorepo Support

Use multiple .shellfirm.yaml files for different services in a monorepo

In a monorepo, different services may have different safety requirements. shellfirm supports multiple .shellfirm.yaml files placed at different directory levels, so each service can have its own policy.

How discovery works

When shellfirm evaluates a command, it walks up from the current working directory to find the nearest .shellfirm.yaml. The first one found is used.

monorepo/
  .shellfirm.yaml              ← root policy
  services/
    api/
      .shellfirm.yaml          ← API-specific policy
      src/
    web/
      .shellfirm.yaml          ← web-specific policy
      src/
    worker/
      src/                     ← no local policy, uses root policy
  • Working in services/api/src/ -- uses services/api/.shellfirm.yaml
  • Working in services/web/src/ -- uses services/web/.shellfirm.yaml
  • Working in services/worker/src/ -- uses root .shellfirm.yaml

Example monorepo layout

Root policy (baseline)

# monorepo/.shellfirm.yaml
version: 1
deny:
  - "git:force_push"
  - "fs:format_filesystem"

overrides:
  - id: "fs:recursively_delete"
    challenge: Yes

context:
  protected_branches:
    - main
    - production

API service (stricter database rules)

# monorepo/services/api/.shellfirm.yaml
version: 1
deny:
  - "database:drop_database"
  - "database:truncate_table"

checks:
  - id: "api:migrate_prod"
    from: base
    test: "migrate.*--env\\s+production"
    severity: Critical
    description: "Production database migration for API service"

overrides:
  - id: "database:drop_database"
    challenge: Yes

Web frontend (fewer restrictions)

# monorepo/services/web/.shellfirm.yaml
version: 1
checks:
  - id: "web:deploy_cdn"
    from: base
    test: "deploy.*--cdn.*production"
    severity: High
    description: "CDN deployment affects all users immediately"

Infrastructure service (maximum protection)

# monorepo/infrastructure/.shellfirm.yaml
version: 1
deny:
  - "terraform:destroy"
  - "kubernetes:delete_namespace"
  - "aws:delete_stack"

overrides:
  - id: "terraform:apply"
    challenge: Yes
  - id: "kubernetes:scale_to_zero"
    challenge: Yes

checks:
  - id: "infra:delete_rds"
    from: base
    test: "aws\\s+rds\\s+delete"
    severity: Critical
    description: "Deleting an RDS instance is irreversible"

Policy interaction with global settings

The found .shellfirm.yaml is merged with the user's global ~/.shellfirm/settings.yaml:

  1. Global settings provide the baseline (challenge type, enabled groups, etc.)
  2. Project policy adds restrictions on top (deny lists, overrides, custom checks)

The project policy cannot weaken global settings. It can only add to them.

Best practices for monorepos

Use a root-level baseline policy

Place a .shellfirm.yaml at the monorepo root with rules that apply to all services. Service-specific policies then add to this baseline.

Keep service policies focused

Each service policy should only contain rules specific to that service. Shared rules belong in the root policy.

Document your policy structure

Add a note in your repository README or contributing guide explaining where policies are and what they cover:

# Safety Policies
- `/.shellfirm.yaml` - Baseline rules (git, filesystem)
- `/services/api/.shellfirm.yaml` - Database and migration rules
- `/infrastructure/.shellfirm.yaml` - Cloud and Kubernetes rules

Validate all policies in CI

Run shellfirm policy validate from each directory that has a policy:

# In CI
for policy in $(find . -name ".shellfirm.yaml"); do
  dir=$(dirname "$policy")
  cd "$dir"
  shellfirm policy validate
  cd -
done