Skip to content

feat(security): add approval gate for /copilot commands#168

Open
peteroden wants to merge 4 commits intomainfrom
feat/156-approval-gate
Open

feat(security): add approval gate for /copilot commands#168
peteroden wants to merge 4 commits intomainfrom
feat/156-approval-gate

Conversation

@peteroden
Copy link
Owner

What

Implements opt-in approval gate for /copilot commands (Issue #156). When COPILOT_REQUIRE_APPROVAL=true, users must approve commands before execution.

Closes #156

How it works

  1. User posts /copilot fix the bug on an MR
  2. Agent responds: "⏳ Approval required. React with 👍 or reply /copilot approve to proceed."
  3. Only the original requester can approve — prevents hijacking
  4. On approval, the stored command executes normally
  5. Pending approvals expire silently after timeout (default: 1 hour)

Configuration

Variable Default Description
COPILOT_REQUIRE_APPROVAL false Enable approval gate (opt-in)
COPILOT_APPROVAL_TIMEOUT 3600 Timeout in seconds

Security

  • Fail-closed: If approval is required but store unavailable, command is refused (not executed)
  • Single approver: Only the user who posted the command can approve
  • Both paths covered: Webhook and GitLab poller both enforce the gate
  • Silent timeout: No spam comment on expiry

Files changed

New

  • src/gitlab_copilot_agent/approval_store.py — Protocol + Memory + Redis implementations
  • tests/test_approval_store.py — 7 tests for store behavior

Modified

  • config.py — 2 new settings
  • models.pyPendingApproval Pydantic model
  • mr_comment_handler.py — approval flow + extracted _execute_copilot_task()
  • webhook.py — route /copilot approve
  • main.py — wire approval_store in lifespan
  • gitlab_poller.py — thread approval_store (fixes HIGH: poller bypass)
  • tests/conftest.py — approval_store fixture
  • tests/test_mr_comment_handler.py — 5 new approval flow tests

Code Review

GPT-5.3-Codex review found HIGH: GitLab poller bypassed approval gate — fixed in second commit (fail-closed + threaded store through poller).

Testing

  • All 50 tests pass (12 new)
  • Lint + format clean
  • Coverage gate: pre-existing project-wide 46% (not a regression)

Diff size

599 lines (534 insertions in new files/tests). Exceeds 200-line guideline but feature is atomic — splitting would break functionality.

Developer Agent and others added 3 commits February 22, 2026 13:11
Implements GitHub Issue #156: Add review/approval gate for /copilot commands.

When COPILOT_REQUIRE_APPROVAL=true, /copilot commands require explicit
approval before execution. User posts /copilot <prompt>, agent posts
confirmation comment, user approves with 👍 or /copilot approve, then
agent executes.

Key changes:
- New approval_store.py: Protocol + Memory + Redis implementations
- Config: copilot_require_approval (default: false), copilot_approval_timeout
- Models: PendingApproval for storing pending commands
- mr_comment_handler: Refactored to handle approval flow, extracted _execute_copilot_task
- webhook: Pass approval_store to handler, recognize /copilot approve
- main: Wire approval_store into lifespan

Security:
- Only the original requester can approve (prevents hijacking)
- Silent timeout expiry (no spam on timeout)
- Backward compatible: disabled by default

Testing:
- 7 new tests for approval_store (basic flow, TTL, isolation)
- 5 new tests for approval flow (require, approve, wrong user, no pending)
- All 351 tests pass

Note: 519-line diff (225 prod, 294 tests). Exceeds 200-line guideline for
feat commits, but justified:
1. Feature is cohesive and atomic
2. Majority is test coverage (required for 90% target)
3. Proper Protocol abstraction (reuses state_backend pattern)
4. Zero impact when disabled (backward compat)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sed gate

- Pass approval_store to GitLabPoller and handle_copilot_comment from poller path
- Recognize /copilot approve notes in poller (not just webhook)
- Make approval gate fail-closed: refuse execution when store unavailable
- Fixes HIGH: poller path bypassed approval gate entirely

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@peteroden peteroden force-pushed the feat/156-approval-gate branch from 1c0e37d to 83ca2a2 Compare February 22, 2026 18:12
Replace get()+delete() with atomic pop() to prevent duplicate execution
when concurrent workers process the same /copilot approve command.
Memory store uses dict.pop(); Redis store uses GETDEL.
Wrong-user rejections re-store the approval after pop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(copilot): Add review gate before auto-push on /copilot commands

1 participant