06.3 policy.yml - samerfarida/mcp-ssh-orchestrator GitHub Wiki

6.3 policy.yml

Purpose: Define security policies, access controls, and execution limits for mcp-ssh-orchestrator using a deny-by-default model.

Security Note: Host key verification is always enforced (CWE-295). Unsafe policies (host_key_auto_add: true, require_known_host: false) are deprecated and ignored. All SSH connections require a known_hosts entry.

Overview

The policy.yml file implements a deny-by-default security model where commands must explicitly match an "allow" rule to execute. It provides multiple layers of security controls:

  1. Command Substring Blocking - Hard blocks commands containing dangerous substrings
  2. Command Substitution Blocking - Hard blocks all command substitution ($(), backticks) to prevent bypasses
  3. Argument-Aware Rules - Version 2 schema with simple_binaries and structured rules for precise control
  4. Network Controls - IP/CIDR allowlists and blocklists
  5. Execution Limits - Timeouts, output size caps, and host key requirements
  6. Per-host/Tag Overrides - Granular control for specific hosts or host groups

Security Note: Command substitution ($(...) and backticks) is hard-banned to prevent policy bypasses. Path-based binaries (e.g., /usr/bin/cat, ./script.sh) are blocked by default for security.

File Structure

# policy.yml
known_hosts_path: "/app/keys/known_hosts"

limits:
  max_seconds: 60
  max_output_bytes: 1048576
  host_key_auto_add: false
  require_known_host: true
  deny_substrings:
    - "rm -rf /"
    - "shutdown*"
    - "reboot*"

network:
  allow_cidrs:
    - "10.0.0.0/8"
    - "192.168.0.0/16"
  block_ips:
    - "0.0.0.0"
    - "255.255.255.255"
  require_known_host: true

rules:
  # Simple binaries: bulk allow for read-only inspection commands
  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    simple_binaries:
      - uptime
      - whoami
      - hostname
      - date
    simple_max_args: 6

  # Structured rule: specific binary with argument requirements
  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "df"
    arg_prefix: ["-h"]
    allow_extra_args: false

  # Structured rule: binary with path argument restrictions
  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "systemctl"
    arg_prefix: ["status"]
    allow_extra_args: true

overrides:
  aliases:
    prod-db-1:
      max_seconds: 20
      max_output_bytes: 131072
  tags:
    production:
      max_seconds: 30
      require_known_host: true

Configuration Sections

Section Purpose Required
known_hosts_path Path to SSH known_hosts file No
limits Global execution limits and security settings No
network Network access controls and IP filtering No
rules Command allow/deny rules with pattern matching No
overrides Per-host and per-tag limit overrides No

Root Level Settings

Field Type Required Default Description Example
known_hosts_path string No None Path to SSH known_hosts file for host key verification "/app/keys/known_hosts"

Limits Section

The limits section defines global execution limits and security settings that apply to all hosts unless overridden.

Field Type Required Default Description Example
max_seconds integer No 60 Maximum command execution time in seconds 30
max_output_bytes integer No 1048576 Maximum combined stdout/stderr output size in bytes 524288
host_key_auto_add boolean No false Deprecated: Ignored for security (CWE-295). Always use require_known_host: true true
require_known_host boolean No true Security: Always enforced. Require host to exist in known_hosts before connection. Prevents MITM attacks false
task_result_ttl integer No 300 Async task result retention time in seconds (5 minutes). Results are kept for this duration after completion 600
task_progress_interval integer No 5 Interval in seconds between progress notifications for async tasks 10
deny_substrings array No See below List of substrings that will block any command containing them ["rm -rf", "shutdown"]

Command Denial Bypass Prevention

Commands are normalized before checking against deny_substrings to prevent bypass attempts:

  • Quote Removal: Single and double quotes are stripped (e.g., 'rm -rf /'rm -rf /)
  • Escape Handling: Escaped characters are normalized (e.g., rm\ -rf\ /rm -rf /)
  • Whitespace Normalization: Multiple spaces/tabs are collapsed (e.g., rm -rf /rm -rf /)
  • Dual Checking: Both original and normalized commands are checked

Security: Bypass attempts detected via normalization are logged as security events.

Note: Complex obfuscation (encoding, variable substitution) may still bypass. Focus is on common techniques.

Command Chaining Behavior

The policy engine validates command chains to prevent policy bypass via chaining operators.

How It Works:

  • Commands containing chaining operators (&&, ||, ;, |) are parsed into individual commands
  • Each command in the chain is validated separately against policy rules
  • All commands in the chain must be allowed for the entire chain to execute
  • If any command is denied, the entire chain is blocked

Supported Operators:

  • && - Logical AND (run next command only if previous succeeds)
  • || - Logical OR (run next command only if previous fails)
  • ; - Sequential execution (run commands in order)
  • | - Pipe (pass output of first command to second)

Command Substitution (BLOCKED):

  • Hard-banned: All command substitution is blocked for security
  • Backtick substitution: `command` - BLOCKED
  • Dollar-paren substitution: $(command) - BLOCKED
  • Arithmetic expansion: $((...)) - BLOCKED
  • This prevents policy bypasses where denied commands could be executed via substitution

Examples:

# Policy allows: uptime*, whoami
# Policy denies: apt list --upgradable*

# ✅ ALLOWED: Both commands are allowed
uptime && whoami

# ❌ DENIED: Second command is denied
uptime && apt list --upgradable

# ❌ DENIED: First command is denied
apt list --upgradable && uptime

# ✅ ALLOWED: All three commands are allowed
uptime && whoami && hostname

# ❌ DENIED: Middle command is denied
uptime && apt list --upgradable && whoami

Security Guarantees:

  • Command chaining cannot bypass policy restrictions
  • Each command in a chain is validated individually
  • Order-independent: cmd1 && cmd2 and cmd2 && cmd1 are both validated the same way
  • Operators inside quotes are ignored (e.g., echo "hello && world" is treated as a single command)

Error Messages: When a chained command is denied, the error message identifies which specific command in the chain caused the denial:

  • Policy blocked command in chain: 'apt list --upgradable'

Default deny_substrings

The following dangerous command substrings are blocked by default:

deny_substrings:
  # Destructive commands
  - "rm -rf /"
  - ":(){ :|:& };:"  # Fork bomb
  - "mkfs "
  - "dd if=/dev/zero"
  - "shutdown -h"
  - "reboot"
  - "userdel "
  - "passwd "
  # Lateral movement / egress tools
  - "ssh "
  - "scp "
  - "rsync -e ssh"
  - "curl "
  - "wget "
  - "nc "
  - "nmap "
  - "telnet "
  - "kubectl "
  - "aws "
  - "gcloud "
  - "az "

Network Section

The network section controls which IP addresses and networks are allowed for SSH connections.

Field Type Required Default Description Example
allow_ips array No [] List of specific IP addresses to allow ["10.0.0.1", "192.168.1.100"]
allow_cidrs array No [] List of CIDR networks to allow ["10.0.0.0/8", "192.168.0.0/16"]
block_ips array No [] List of specific IP addresses to block ["0.0.0.0", "255.255.255.255"]
block_cidrs array No [] List of CIDR networks to block ["169.254.0.0/16", "224.0.0.0/4"]
require_known_host boolean No true Override for host key verification (overrides limits setting) false

Network Policy Evaluation

  1. Block Check: If IP is in block_ips or block_cidrs, deny connection
  2. Allow Check: If allow_ips or allow_cidrs are configured, IP must be in one of them
  3. Default: If no allow lists are configured, allow all (after block checks)

Rules Section

The rules section defines command allow/deny rules using version 2 schema with argument-aware matching. Legacy commands fnmatch patterns are no longer supported for security reasons.

Rule Types

Rules support two types of command matching:

  1. Simple Binaries - Bulk allow for read-only inspection commands (exact binary name match)
  2. Structured Rules - Precise control for specific binaries with argument and path restrictions

Rule Fields

Field Type Required Default Description Example
action string Yes "deny" Rule action: "allow" or "deny" "allow"
aliases array No [] List of host aliases to match (glob patterns) ["prod-*", "web1"]
tags array No [] List of host tags to match (glob patterns) ["production", "web"]
simple_binaries array No [] List of binary names to allow (exact match) ["uptime", "whoami"]
simple_max_args integer No None Maximum number of arguments allowed for simple_binaries 6
binary string No None Exact binary name for structured rule "df"
arg_prefix array No [] Exact argument prefix sequence ["-h"]
allow_extra_args boolean No true Allow additional arguments after prefix false
path_args object No {} Path argument restrictions See below

Simple Binaries

Simple binaries allow bulk authorization of read-only inspection commands with argument limits.

Example:

rules:
  - action: "allow"
    aliases: ["*"]
    tags: []
    simple_binaries:
      - uptime
      - whoami
      - hostname
      - date
      - ls
      - echo
    simple_max_args: 6

Matching:

  • Binary name must match exactly (case-sensitive)
  • Number of arguments must not exceed simple_max_args
  • No shell meta characters allowed in arguments (;, &&, ||, |, `, $()

Structured Rules

Structured rules provide precise control for specific binaries with argument and path restrictions.

Important: Structured rules must have at least one restriction:

  • arg_prefix - Required argument sequence
  • path_args - Path restrictions on specific arguments
  • Or both

A structured rule with only binary and no restrictions will not match any commands. Use simple_binaries if you want to allow a binary with any arguments.

Path Arguments:

The path_args object restricts which paths can be used in specific argument positions:

Field Type Required Description Example
indices array Yes Argument positions (0-indexed: 0=binary, 1=first arg, 2=second arg, etc.) [1], [3]
patterns array Yes fnmatch patterns for allowed paths ["/etc/os-release", "/var/log/*"]

Important: Indices are 0-indexed from the full command:

  • 0 = binary name
  • 1 = first argument
  • 2 = second argument
  • etc.
path_args:
  indices: [1]              # First argument position (after binary)
  patterns:                 # fnmatch patterns for allowed paths
    - "/etc/os-release"
    - "/etc/*release"

Example - Allow cat only for specific files:

rules:
  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "cat"
    allow_extra_args: false
    path_args:
      indices: [1]          # First argument (after binary)
      patterns:
        - "/etc/os-release"
        - "/etc/*release"

Example - Allow tail with specific arguments:

rules:
  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "tail"
    arg_prefix: ["-n", "200"]
    allow_extra_args: false
    path_args:
      indices: [3]          # Position 3: tail(0) -n(1) 200(2) <path>(3)
      patterns:
        - "/var/log/*"

Example - Multiple path arguments:

For commands with multiple path arguments, specify all positions:

rules:
  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "cp"
    allow_extra_args: false
    path_args:
      indices: [1, 2]       # cp(0) <source>(1) <dest>(2)
      patterns:
        - "/tmp/*"          # Source must be in /tmp/
        - "/backup/*"       # Destination must be in /backup/

Rule Matching Logic

A rule matches when ALL specified conditions are met:

  • aliases: If specified, host alias must match at least one pattern
  • tags: If specified, at least one host tag must match at least one pattern
  • simple_binaries OR structured rule (binary + arg_prefix + path_args): Command must match the rule pattern

If any condition is empty ([]), it matches all values.

Evaluation Order:

  1. Check alias/tag matching
  2. Check simple_binaries first (if present)
  3. Check structured rule (if present)
  4. First matching rule wins (rules evaluated top-to-bottom)
  5. Default deny if no rule matches

Path-Based Binaries Blocked

For security, path-based binaries are always blocked:

  • /usr/bin/cat - Blocked
  • /tmp/evil.sh - Blocked
  • ./script.sh - Blocked

Only binaries in $PATH are allowed (matched by exact name in rules).

Overrides Section

The overrides section allows per-host and per-tag customization of limits.

Aliases Subsection

Override limits for specific host aliases.

Field Type Required Default Description Example
{alias_name} object No N/A Host alias name (exact match) "prod-web-1"
max_seconds integer No From limits Override max execution time 30
max_output_bytes integer No From limits Override max output size 524288
host_key_auto_add boolean No From limits Override host key auto-add (deprecated, ignored) false
require_known_host boolean No From limits Override host key requirement true
task_result_ttl integer No From limits Override async result retention time 600
task_progress_interval integer No From limits Override progress notification interval 10
deny_substrings array No From limits Override deny substrings list ["rm -rf", "shutdown"]

Tags Subsection

Override limits for hosts with specific tags.

Field Type Required Default Description Example
{tag_name} object No N/A Tag name (exact match) "production"
max_seconds integer No From limits Override max execution time 30
max_output_bytes integer No From limits Override max output size 524288
host_key_auto_add boolean No From limits Override host key auto-add (deprecated, ignored) false
require_known_host boolean No From limits Override host key requirement true
task_result_ttl integer No From limits Override async result retention time 600
task_progress_interval integer No From limits Override progress notification interval 10
deny_substrings array No From limits Override deny substrings list ["rm -rf", "shutdown"]

Glob Pattern Matching

The policy engine uses Python's fnmatch module for pattern matching, supporting:

Pattern Description Matches Doesn't Match
* Matches any characters uptime, systemctl status None
? Matches single character cat, cut cat, cats
[seq] Matches any char in seq [abc] matches a, b, c d, ab
[!seq] Matches any char not in seq [!abc] matches d, e a, b, c

Common Patterns

Pattern Purpose Example Matches
* Match all commands Any command
uptime* Commands starting with "uptime" uptime, uptime -s
systemctl status * systemctl status with any service systemctl status nginx, systemctl status apache2
prod-* Hosts starting with "prod-" prod-web-1, prod-db-1
*prod* Hosts containing "prod" prod-web-1, staging-prod-1

Rule Evaluation Order

  1. Deny Substrings Check: Commands containing any substring in deny_substrings are blocked
  2. Rule Matching: Rules are evaluated in order until a match is found
  3. Default Deny: If no rule matches, command is denied

Rule Resolution Details

  • Rules are processed top-to-bottom and the last matching rule wins. Each match updates the pending decision, so place broad deny rules after their paired allows if you intend them to override.
  • Because matches overwrite previous decisions, keep conflicting rules close together and comment them for future reviewers.
  • deny_substrings are evaluated before any rule logic. If a command is blocked there (for example, it contains sudo), later allow rules will never run. To permit that operation, remove or override the substring entry for the specific hosts.
  • Always test with ssh_plan to verify the final decision after ordering changes.
rules:
  - action: "allow"
    aliases: ["prod-*"]
    binary: "systemctl"
    arg_prefix: ["status"]
    allow_extra_args: true

  - action: "deny"
    aliases: ["prod-*"]
    simple_binaries: ["systemctl"]  # Denies all systemctl commands

In the example, systemctl status nginx is denied because the later deny rule matches the same alias and binary name.

Override Hierarchy

When multiple overrides apply, precedence is (highest to lowest):

  1. Alias Overrides - Specific host alias settings
  2. Tag Overrides - Host tag settings (only if not set by alias)
  3. Global Limits - Settings in the limits section
  4. Default Values - Hardcoded defaults in the policy engine

Deny Substring Overrides in Practice

Alias/tag overrides can redefine deny_substrings, which is useful when you want a strict global block list but still need an escape hatch for a small set of hosts:

limits:
  deny_substrings:
    - "sudo "
    - "rm -rf /"
    - "kubectl "

overrides:
  aliases:
    docker-prod-manager1:
      deny_substrings:  # Clone the list without sudo
        - "rm -rf /"
        - "kubectl "

In this pattern, sudo stays blocked for every host except docker-prod-manager1. Pair the override with explicit allow rules and higher max_seconds values so privileged operations are both auditable and predictable.

Default Values

Setting Default Value Description
max_seconds 60 Maximum command execution time
max_output_bytes 1048576 Maximum output size (1 MiB)
host_key_auto_add false Deprecated: Ignored for security (CWE-295)
require_known_host true Security: Always enforced. Prevents MITM attacks
deny_substrings 14+ patterns Dangerous command substrings
allow_ips [] No IP allowlist (allow all)
allow_cidrs [] No CIDR allowlist (allow all)
block_ips [] No IP blocklist
block_cidrs [] No CIDR blocklist
known_hosts_path None Use system default

Policy Examples

Basic Read-Only Policy

# Basic read-only policy
known_hosts_path: "/app/keys/known_hosts"

limits:
  max_seconds: 30
  max_output_bytes: 262144
  host_key_auto_add: false
  require_known_host: true

network:
  allow_cidrs:
    - "10.0.0.0/8"
    - "192.168.0.0/16"
  require_known_host: true

rules:
  # Basic system information (simple binaries)
  - action: "allow"
    aliases: ["*"]
    tags: []
    simple_binaries:
      - uname
      - uptime
      - whoami
      - hostname
      - date
      - id
    simple_max_args: 6

  # Disk and memory usage (structured rules)
  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "df"
    arg_prefix: ["-h"]
    allow_extra_args: false

  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "free"
    arg_prefix: ["-h"]
    allow_extra_args: false

  - action: "allow"
    aliases: ["*"]
    tags: []
    simple_binaries:
      - lsblk
    simple_max_args: 6

  # Process information
  - action: "allow"
    aliases: ["*"]
    tags: []
    simple_binaries:
      - ps
    simple_max_args: 6

  # Service status (read-only) - structured rules
  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "systemctl"
    arg_prefix: ["status"]
    allow_extra_args: true

  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "systemctl"
    arg_prefix: ["is-active"]
    allow_extra_args: true

  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "systemctl"
    arg_prefix: ["is-enabled"]
    allow_extra_args: true

Production Environment Policy

# Production environment policy
known_hosts_path: "/app/keys/known_hosts"

limits:
  max_seconds: 20
  max_output_bytes: 131072
  host_key_auto_add: false
  require_known_host: true
  deny_substrings:
    - "rm -rf /"
    - "shutdown*"
    - "reboot*"
    - "systemctl restart*"
    - "systemctl stop*"
    - "systemctl start*"
    - "apt *"
    - "yum *"
    - "docker run*"
    - "kubectl *"

network:
  allow_cidrs:
    - "10.0.0.0/8"
  block_cidrs:
    - "0.0.0.0/0"  # Block all public internet
  require_known_host: true

rules:
  # Minimal read-only commands for production
  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    simple_binaries:
      - uptime
    simple_max_args: 6

  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "df"
    arg_prefix: ["-h"]
    allow_extra_args: false

  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "systemctl"
    arg_prefix: ["status"]
    allow_extra_args: true

  - action: "allow"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "journalctl"
    arg_prefix: ["--no-pager", "-n", "20"]
    allow_extra_args: true

  # Explicit deny for production
  - action: "deny"
    aliases: ["prod-*"]
    tags: ["production"]
    simple_binaries:
      - apt
      - yum
      - docker
      - kubectl

  - action: "deny"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "systemctl"
    arg_prefix: ["restart"]
    allow_extra_args: true

  - action: "deny"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "systemctl"
    arg_prefix: ["stop"]
    allow_extra_args: true

  - action: "deny"
    aliases: ["prod-*"]
    tags: ["production"]
    binary: "systemctl"
    arg_prefix: ["start"]
    allow_extra_args: true

overrides:
  aliases:
    prod-db-1:
      max_seconds: 10
      max_output_bytes: 65536
    prod-web-1:
      max_seconds: 15
      max_output_bytes: 131072

Development/Staging Policy

# Development/staging policy
known_hosts_path: "/app/keys/known_hosts"

limits:
  max_seconds: 60
  max_output_bytes: 1048576
  require_known_host: true  # Always enforced (CWE-295)

network:
  allow_cidrs:
    - "10.0.0.0/8"
    - "192.168.0.0/16"
    - "172.16.0.0/12"
  require_known_host: true  # Always enforced (CWE-295)

rules:
  # Read-only commands
  - action: "allow"
    aliases: ["*"]
    tags: []
    simple_binaries:
      - uname
      - uptime
      - ps
    simple_max_args: 6

  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "df"
    arg_prefix: ["-h"]
    allow_extra_args: false

  - action: "allow"
    aliases: ["*"]
    tags: []
    binary: "systemctl"
    arg_prefix: ["status"]
    allow_extra_args: true

  # Development-specific commands
  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "systemctl"
    arg_prefix: ["restart"]
    allow_extra_args: true

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "systemctl"
    arg_prefix: ["stop"]
    allow_extra_args: true

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "systemctl"
    arg_prefix: ["start"]
    allow_extra_args: true

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "docker"
    arg_prefix: ["ps"]
    allow_extra_args: false

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "docker"
    arg_prefix: ["logs"]
    allow_extra_args: true

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "kubectl"
    arg_prefix: ["get"]
    allow_extra_args: true

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "kubectl"
    arg_prefix: ["describe"]
    allow_extra_args: true

  # Network diagnostics for dev/staging
  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    simple_binaries:
      - ping
      - traceroute
      - netstat
    simple_max_args: 8

  - action: "allow"
    aliases:
      - "dev-*"
      - "stg-*"
    tags:
      - "development"
      - "staging"
    binary: "ss"
    arg_prefix: ["-tulpn"]
    allow_extra_args: false

overrides:
  tags:
    development:
      max_seconds: 120
      # Note: host_key_auto_add and require_known_host=false are deprecated (CWE-295)
    staging:
      max_seconds: 90
      # Note: host_key_auto_add and require_known_host=false are deprecated (CWE-295)

Privileged Maintenance (Non-Interactive Upgrades)

Long-running maintenance commands (like apt upgrades) often require sudo, additional flags, and longer timeouts. Combine explicit allow rules with per-alias overrides:

rules:
  - action: "allow"
    aliases:
      - "docker-prod-manager1"
      - "docker-prod-manager2"
      - "docker-prod-manager3"
    binary: "sudo"
    arg_prefix: ["apt-get", "update"]
    allow_extra_args: false

  - action: "allow"
    aliases:
      - "docker-prod-manager1"
      - "docker-prod-manager2"
      - "docker-prod-manager3"
    binary: "sudo"
    arg_prefix: ["apt-get", "upgrade", "-y"]
    allow_extra_args: false

overrides:
  aliases:
    docker-prod-manager1:
      max_seconds: 300
      task_result_ttl: 1800
    docker-prod-manager2:
      max_seconds: 300
      task_result_ttl: 1800
    docker-prod-manager3:
      max_seconds: 300
      task_result_ttl: 1800

Workflow:

  1. Remove sudo from the global deny_substrings list or override it for these hosts.
  2. Allow only the exact command patterns required (non-interactive apt commands in this case).
  3. Increase per-host max_seconds so dpkg has enough time, and extend task_result_ttl so you can retrieve async results later.
  4. Test with ssh_plan, run via ssh_run_async, then monitor with ssh_get_task_status/result and revert overrides if they were temporary.

YAML Style Guide

For consistency and readability, follow these YAML array formatting guidelines:

When to Use Inline Arrays []

  • Empty arrays: allow_ips: []
  • Single-item lists: aliases: ["*"], tags: ["production"]

When to Use Multi-line Dash Syntax

  • Two or more items (always use multi-line for readability):

    deny_substrings:
      - "rm -rf /"
      - "shutdown -h"
      - "reboot"
  • Simple binaries in rules (always multi-line for readability):

    simple_binaries:
      - uptime
      - whoami
      - hostname
  • Network blocks/lists with comments:

    block_ips:
      - "0.0.0.0"           # Block all zeros
      - "255.255.255.255"   # Block broadcast

General Principles

  • Empty arrays → Inline: tags: []
  • Single item → Inline: aliases: ["*"]
  • Two or more items → Multi-line for readability
  • Simple binaries → Always multi-line
  • Network blocks/lists → Multi-line (allows comments)
  • Consistency → When in doubt, use multi-line for clarity

Validation and Testing

Policy Validation

# Validate policy.yml syntax
python -c "import yaml; yaml.safe_load(open('config/policy.yml'))"

# Validate policy rules
python -c "
from mcp_ssh.policy import Policy
policy = Policy('config/policy.yml')
print('Policy validation:', policy.validate())
"

Policy Testing

# Test policy rules with dry-run
ssh_plan --alias "web1" --command "uptime"
ssh_plan --alias "prod-web-1" --command "systemctl restart nginx"

# Test network policies
ssh_plan --alias "web1" --command "ping 8.8.8.8"

Policy Debugging

# Enable debug logging
export MCP_SSH_DEBUG=1
ssh_plan --alias "web1" --command "uptime"

# Check policy evaluation
python -c "
from mcp_ssh.policy import Policy
policy = Policy('config/policy.yml')
result = policy.evaluate('web1', 'uptime', ['production'])
print('Policy result:', result)
"

Troubleshooting

Common Issues

  1. Invalid YAML syntax

    # Check syntax
    python -c "import yaml; yaml.safe_load(open('config/policy.yml'))"
  2. Policy rule conflicts

    # Test specific rules
    ssh_plan --alias "web1" --command "uptime"
  3. Network policy issues

    # Check network configuration
    python -c "
    from mcp_ssh.policy import Policy
    policy = Policy('config/policy.yml')
    print('Network config:', policy.network_config)
    "

Policy Debugging

  1. Rule not matching

    # Enable debug mode
    export MCP_SSH_DEBUG=1
    ssh_plan --alias "web1" --command "uptime"
  2. Override not applying

    # Check override hierarchy
    python -c "
    from mcp_ssh.policy import Policy
    policy = Policy('config/policy.yml')
    print('Overrides:', policy.overrides)
    "

Next Steps

⚠️ **GitHub.com Fallback** ⚠️