feat(dap): Introduce command validators for tool calls
What does this MR do and why?
Resolves #602124
Adds program-specific command validators for pattern-based tool call approvals. The existing metacharacter guard (merged in !233672 (merged)) catches shell chaining attacks, but it cannot catch argument injection via program-specific flags that contain no shell metacharacters. This MR adds a second validation layer to close that gap.
The implementation introduces a CommandValidators framework with a GitValidator that enforces three layers of defense before allowing a pattern match:
- Subcommand allowlist (deny-by-default) -- only known-safe git subcommands are eligible for pattern approval
- Global option allowlist (deny-by-default) -- pre-subcommand flags must be in an explicit safe set
- Dangerous flag denylist (defense-in-depth) -- blocks known code-execution flags as an interim measure until per-subcommand flag allowlists are implemented (#602494)
Programs without a registered validator fail closed -- they require exact-match approval. This prevents argument injection in non-git programs that have no shell metacharacters.
Exact-match approvals are unaffected by all of the above -- the user explicitly approved that specific command.
References
- Parent MR: !233672 (merged) (pattern approval with wildcards)
- Parent issue: #602124
- Follow-up -- per-subcommand flag allowlists: #602494
- Follow-up -- documentation: #602496 (closed)
- Related epic: gitlab-org#20519
Screenshots or screen recordings
N/A -- backend-only change, no UI impact.
How to set up and validate locally
Test via the GraphQL explorer (/-/graphql-explorer) or Rails console. Replace <ID> with your workflow's numeric ID.
1. Pattern approval for a safe git command (should approve)
mutation {
updateDuoWorkflowToolCallApprovals(input: {
workflowId: "gid://gitlab/Ai::DuoWorkflows::Workflow/<ID>"
toolName: "run_command"
pattern: "git *"
}) {
workflow { id toolCallApprovals }
errors
}
}Then verify git checkout feature-branch is approved:
workflow = Ai::DuoWorkflows::Workflow.find(<ID>)
workflow.tool_call_approvals.approved?(
tool_name: 'run_command',
call_args: '{"command": "git checkout feature-branch"}'
) # => true2. Argument injection is blocked (should reject)
workflow.tool_call_approvals.approved?(
tool_name: 'run_command',
call_args: '{"command": "git -c core.sshCommand=evil fetch"}'
) # => false (dangerous global flag)3. Unregistered programs fail closed (should reject)
workflow.tool_call_approvals.add_pattern_approval(
tool_name: 'run_command', pattern: 'curl *'
)
workflow.tool_call_approvals.approved?(
tool_name: 'run_command',
call_args: '{"command": "curl http://example.com"}'
) # => false (no registered validator for curl)4. Run the specs
bundle exec rspec ee/spec/models/ai/duo_workflows/command_validators/git_validator_spec.rb
bundle exec rspec ee/spec/models/ai/duo_workflows/workflow_spec.rbMR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.