Skip to content

REQ-C-019: Subprocess-Invoking Commands Declare Argument Schema

Tier: Command Contract | Priority: P1

Source: §34 Shell Injection via Agent-Constructed Commands

Addresses: Severity: Critical / Token Spend: High / Time: High / Context: Medium


Description

Any command that invokes a subprocess with arguments derived from user input MUST declare in its registration schema: the subprocess binary name, which of its arguments originate from user input (by field name), and which arguments are framework-hardcoded. The framework enforces this declaration at registration and uses it to apply REQ-F-044 metacharacter rejection to exactly the user-derived arguments. Commands MUST use the framework's subprocess() API rather than raw os.system(), subprocess.Popen(shell=True), or child_process.exec().

Acceptance Criteria

  • A command using the framework's subprocess() API with a user-supplied argument is automatically protected by REQ-F-044
  • A command that uses os.system() directly is flagged by the framework's registration linter
  • The --schema output for subprocess-invoking commands includes a subprocess section listing the binary and user-controlled arguments
  • An attempt to pass a shell metacharacter in a user-derived subprocess argument is rejected with exit code 2

Schema

Types: manifest-response.md

A subprocess section is added to CommandEntry in --schema output:

Field Type Description
subprocess.binary string Executable name invoked by this command
subprocess.user_controlled_args string[] Flag names whose values are passed as subprocess arguments
subprocess.hardcoded_args string[] Argument fragments always passed verbatim by the framework

Wire Format

$ tool run --schema
{
  "parameters": {
    "script": { "type": "string", "required": true,  "description": "Path to script file to execute" },
    "args":   { "type": "array",  "required": false, "description": "Arguments passed to the script" }
  },
  "subprocess": {
    "binary": "bash",
    "user_controlled_args": ["script", "args"],
    "hardcoded_args": ["--norc", "--noprofile"]
  },
  "exit_codes": {
    "0": { "name": "SUCCESS",   "description": "Script completed successfully", "retryable": false, "side_effects": "complete" },
    "3": { "name": "ARG_ERROR", "description": "Shell metacharacter in argument", "retryable": true, "side_effects": "none" }
  }
}

Example

register command "run":
  subprocess:
    binary: bash
    hardcoded_args: ["--norc", "--noprofile"]
    user_controlled_args: [script, args]
  parameters:
    script: type=string, required=true, description="Path to script file"
    args:   type=array,  required=false, description="Arguments passed to the script"

  # framework applies REQ-F-044 metacharacter rejection to 'script' and 'args' automatically
  # framework raises registration error if raw os.system() is detected instead of subprocess()

Requirement Tier Relationship
REQ-F-044 F Enforces: metacharacter rejection applied to all declared user_controlled_args
REQ-C-015 C Composes: subprocess section is part of the --schema output
REQ-C-020 C Composes: user_controlled_args that are resource IDs also declare validation patterns
REQ-F-015 F Enforces: subprocess argument validation happens in Phase 1 before any I/O