Source code for jeevesagent.security.permissions
"""Permission decisions for tool calls.
Three modes mirror the Claude Agent SDK so users don't relearn:
- ``DEFAULT`` — allow non-destructive tools, ask on destructive
- ``ACCEPT_EDITS`` — auto-approve filesystem writes; otherwise like default
- ``BYPASS`` — allow everything (CI / sandbox use only)
Allow- and deny-lists win over modes; deny-list wins over allow-list.
The decision flow:
1. Tool in deny-list → deny
2. Allow-list set and tool not in it → deny
3. Mode == BYPASS → allow
4. Mode == ACCEPT_EDITS and call is a non-destructive edit → allow
5. Tool is destructive → ask
6. Otherwise → allow
"""
from __future__ import annotations
from collections.abc import Mapping
from enum import StrEnum
from typing import Any
from ..core.types import PermissionDecision, ToolCall
[docs]
class Mode(StrEnum):
DEFAULT = "default"
ACCEPT_EDITS = "acceptEdits"
BYPASS = "bypassPermissions"
[docs]
class AllowAll:
"""Trivial permission policy: every call is allowed.
The default for :class:`Agent` when no permissions are configured.
"""
[docs]
async def check(
self, call: ToolCall, *, context: Mapping[str, Any]
) -> PermissionDecision:
return PermissionDecision.allow_()
[docs]
class StandardPermissions:
"""Mode + allow/deny-list permission policy."""
def __init__(
self,
*,
mode: Mode = Mode.DEFAULT,
allowed_tools: list[str] | None = None,
denied_tools: list[str] | None = None,
) -> None:
self._mode = mode
self._allowed = set(allowed_tools) if allowed_tools is not None else None
self._denied = set(denied_tools or [])
[docs]
async def check(
self, call: ToolCall, *, context: Mapping[str, Any]
) -> PermissionDecision:
if call.tool in self._denied:
return PermissionDecision.deny_(f"{call.tool}: denied by policy")
if self._allowed is not None and call.tool not in self._allowed:
return PermissionDecision.deny_(f"{call.tool}: not in allow-list")
if self._mode == Mode.BYPASS:
return PermissionDecision.allow_()
if call.is_destructive() and self._mode != Mode.ACCEPT_EDITS:
return PermissionDecision.ask_(
f"{call.tool}: destructive call requires approval"
)
return PermissionDecision.allow_()
[docs]
@classmethod
def strict(cls) -> StandardPermissions:
"""Convenience: default-mode permissions with no overrides."""
return cls(mode=Mode.DEFAULT)