Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
charm.land/glamour/v2 v2.0.0
charm.land/lipgloss/v2 v2.0.2
github.com/adrg/xdg v0.5.3
github.com/cedar-policy/cedar-go v1.5.2
github.com/gofrs/flock v0.13.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
Expand Down Expand Up @@ -54,7 +55,6 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cedar-policy/cedar-go v1.5.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.2 // indirect
Expand Down
16 changes: 12 additions & 4 deletions internal/infra/mcp/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,16 @@ var observePolicies = []string{
// safeToolsPolicies extend observe with annotation-based tool call permits.
// Tools with readOnlyHint=true are allowed. Tools that are both non-destructive
// (destructiveHint=false) and closed-world (openWorldHint=false) are also allowed.
// Missing annotations cause the when clause to fail, resulting in Cedar's
// default-deny — matching MCP spec conservative defaults without custom code.
//
// Each when clause uses Cedar's `has` operator to guard attribute access.
// Without `has`, accessing a missing attribute is an evaluation error — Cedar
// treats errors as "policy not satisfied", but toolhive's pre-flight check
// treats ANY error as a hard deny for the tool. Many MCP servers (GitHub,
// context7) set only some annotations, so unguarded access would incorrectly
// block tools that have readOnlyHint=true but omit destructiveHint.
//
// Tools that omit all annotation attributes are still denied (none of the
// `has` guards pass), preserving the conservative default-deny posture.
var safeToolsPolicies = []string{
// All observe permits.
`permit(principal, action == Action::"list_tools", resource);`,
Expand All @@ -33,9 +41,9 @@ var safeToolsPolicies = []string{
`permit(principal, action == Action::"get_prompt", resource);`,
`permit(principal, action == Action::"read_resource", resource);`,
// Allow read-only tools.
`permit(principal, action == Action::"call_tool", resource) when { resource.readOnlyHint == true };`,
`permit(principal, action == Action::"call_tool", resource) when { resource has readOnlyHint && resource.readOnlyHint == true };`,
// Allow non-destructive AND closed-world tools.
`permit(principal, action == Action::"call_tool", resource) when { resource.destructiveHint == false && resource.openWorldHint == false };`,
`permit(principal, action == Action::"call_tool", resource) when { resource has destructiveHint && resource.destructiveHint == false && resource has openWorldHint && resource.openWorldHint == false };`,
}

// ResolveProfile returns Cedar policy strings for the given authz config.
Expand Down
Loading
Loading