Skip to content
Kward Search API index

Class: Kward::Permissions::Policy

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/permissions/policy.rb

Overview

Evaluates the opt-in permission policy for model-requested tool calls.

The policy is intentionally a decision layer, not a process sandbox. It can prevent Kward from starting a tool, but it cannot constrain a shell command after it has begun. OS-enforced sandboxing belongs at a lower process boundary.

Defined Under Namespace

Classes: Decision

Constant Summary collapse

MODES =
%w[ask read-only workspace-write deny-by-default].freeze
NETWORK_TOOLS =
%w[web_search fetch_content fetch_raw].freeze
FILE_CHANGE_TOOLS =
%w[write_file edit_file].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(enabled: false, mode: "ask", allow: [], ask: [], deny: [], write_scopes: nil) ⇒ Policy

Returns a new instance of Policy.



43
44
45
46
47
48
49
50
51
# File 'lib/kward/permissions/policy.rb', line 43

def initialize(enabled: false, mode: "ask", allow: [], ask: [], deny: [], write_scopes: nil)
  @enabled = enabled == true
  @mode = normalize_mode(mode)
  @allow = normalize_rules(allow)
  @ask = normalize_rules(ask)
  @deny = normalize_rules(deny)
  @write_scopes = normalize_scopes(write_scopes)
  @session_allowed_tools = {}
end

Class Method Details

.from_config(config) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/kward/permissions/policy.rb', line 31

def self.from_config(config)
  permissions = config["permissions"].is_a?(Hash) ? config["permissions"] : {}
  new(
    enabled: permissions["enabled"] == true,
    mode: permissions["mode"],
    allow: permissions["allow"],
    ask: permissions["ask"],
    deny: permissions["deny"],
    write_scopes: permissions.key?("write_scopes") ? permissions["write_scopes"] : nil
  )
end

Instance Method Details

#allow_for_session!(tool_name) ⇒ Object

Allows this model tool for the remaining lifetime of this policy object. A matching deny or ask rule still takes precedence over this temporary grant.



59
60
61
# File 'lib/kward/permissions/policy.rb', line 59

def allow_for_session!(tool_name)
  @session_allowed_tools[tool_name.to_s] = true
end

#decision_for(tool_name, arguments, source: nil) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/kward/permissions/policy.rb', line 63

def decision_for(tool_name, arguments, source: nil)
  return Decision.new(action: :allow, reason: "permissions disabled") unless enabled?

  request = request_for(tool_name, arguments, source: source)
  return Decision.new(action: :deny, reason: "matched deny rule") if matches?(@deny, request)
  return Decision.new(action: :ask, reason: "matched ask rule") if matches?(@ask, request)
  return Decision.new(action: :allow, reason: "matched allow rule") if matches?(@allow, request)
  return Decision.new(action: :allow, reason: "allowed for this session") if @session_allowed_tools[request.fetch("tool")]

  default_decision(request)
end

#enabled?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/kward/permissions/policy.rb', line 53

def enabled?
  @enabled
end