Persistent Approvals Across Sessions
**Note: This work is currently on pause, see **[**this update**](https://gitlab.com/groups/gitlab-org/-/work_items/21877#note_3547608932)**.** ## Summary The first iterations of tool approvals built session-scoped approvals. decisions a user makes during a workflow that reset when the session ends. This iteration persists those decisions to disk so users don't start from scratch every session. Pre-approvals are file-based and operate at the user level via `~/.gitlab/duo`. File entries are read at Duo session start and converted into session approvals using the existing `tool_call_pattern_approval` infrastructure. The file supports a three-tier trust model: `allow` (pre-approved), `ask` (always prompt), and `deny` (auto-rejected). --- ## Motivation Users currently lose all approval decisions when a session ends. Recurring workflows, a developer who always approves `git` commands, or always trusts a set of read-only tools, require re-approval on every new session. This creates unnecessary friction in agentic workflows without any security benefit for tools the user has already made a deliberate trust decision about. --- ## Proposed Change Introduce a user-level YAML permissions file at `~/.gitlab/duo`: ```yaml # ~/.gitlab/duo version: 1 tool_approvals: allow: - tool: run_command patterns: - "npm *" # trust npm broadly - tool: read_file # no patterns = all uses pre-approved - tool: grep ask: - tool: run_command patterns: - "npm publish *" # always prompt, overrides the allow above - "npm run deploy" deny: - tool: run_command patterns: - "rm -rf *" - "sudo *" - tool: delete_file # no patterns = all uses denied ``` ### How it works Precedence order (most restrictive wins): governance → `deny` → `ask` → `allow` → default (prompt). | Entry type | v1 enforcement | Future | |------------|----------------|--------| | `allow` (with pattern) | Converted to pattern session approval via `UpdateToolCallApprovals` mutation at session start | — | | `allow` (no pattern) | Converted to wildcard session approval at session start | — | | `ask` | Client-side: LSP always fires the normal approval prompt, even if a broader `allow` would have matched | Client-side only, no backend work needed | | `deny` | Client-side: LSP auto-rejects matching tool calls without prompting | Wired to backend once GraphQL mutation supports deny | `ask` exists to carve out exceptions from broad `allow` rules. For example, `allow: npm *` with `ask: npm publish *` trusts npm broadly but always prompts for publish — without enumerating every other npm sub-command explicitly. File is read on each Duo session start via the existing `/direct_access` path. Gated behind the `tool_call_pattern_approval` capability. Governance rules from the monolith (instance → group → project) override user file entries. > **Note:** Project-level file (`.gitlab/duo`) is explicitly out of scope for this iteration. Governance at the namespace/group/project level already covers this use case. The architecture remains open to adding it in a future iteration. --- :white_check_mark: - Done :construction: - In Progress :white_circle: - Not Started ### :sparkles: Core Foundational Elements <table> <tr> <th></th> <th>Description</th> <th>Sub Epic / Issues</th> <th>Status</th> <th>Technical Lead</th> </tr> <tr> <td>Determine persistent tool approval storage and security model</td> <td> * Spike to decide storage location, file format, and security model for persistent approvals. </td> <td> https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/work_items/2114+ </td> <td> :white_check_mark: </td> <td> @aspringfield / @dbernardi </td> </tr> <tr> <td>Finalize YAML file format for user tool permission settings</td> <td> * Define the canonical schema for `~/.gitlab/duo` including the three-tier allow/ask/deny structure, pattern format, and version field. </td> <td> https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/work_items/2361+ </td> <td> :white_check_mark: </td> <td> @dbernardi </td> </tr> <tr> <td>User file pre-approval POC for CLI</td> <td> * Proof of concept: read `~/.gitlab/duo` at session start, enforce ask/deny entries client-side, and convert allow entries to session approvals via the existing `UpdateToolCallApprovals` mutation. </td> <td> https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/work_items/1647+ </td> <td> :white_circle: </td> <td> @dbernardi </td> </tr> <tr> <td> Read from user tool permissions file (\`\~/.gitlab/duo\`) </td> <td> * Full LSP implementation: parse the user file, enforce deny and ask entries client-side, and wire allow entries to the session approval store on session init. </td> <td> https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/work_items/2362+ </td> <td> :white_circle: </td> <td> @dbernardi </td> </tr> </table> ### :sparkles: Followup <table> <tr> <th></th> <th>Description</th> <th>Sub Epic / Issues</th> <th>Status</th> <th>Technical Lead</th> </tr> <tr> <td> Add deny support to `updateDuoWorkflowToolCallApprovals` GraphQL mutation </td> <td> * Extend the mutation to accept deny entries so they can be enforced at the backend level, not just client-side. * No schema change to `~/.gitlab/duo` required when this lands. </td> <td> https://gitlab.com/gitlab-org/gitlab/-/work_items/603232+ </td> <td> :white_circle: </td> <td> @dbernardi </td> </tr> </table> ## Dependencies - Blocked by [&21850](https://gitlab.com/groups/gitlab-org/-/work_items/21850) — wildcard/pattern approvals must be stable before the file format can be finalized and the `tool_call_pattern_approval` capability gate is meaningful - GraphQL endpoint for exposing merged tool permissions to the client (SSCS:Compliance — `@jeanvdw` / `@nrosandich`) — not a hard dependency for v1 ## References - Parent epic: https://gitlab.com/groups/gitlab-org/-/work_items/20519 - Spike async update: https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/work_items/2114#note_3284384611 - Wildcard/pattern approvals sub-epic: https://gitlab.com/groups/gitlab-org/-/work_items/21850 - Layer 3 project-scoped policy overrides (control plane): https://gitlab.com/groups/gitlab-org/-/work_items/21390
epic