Class: Vangrail::Profile

Inherits:
Object
  • Object
show all
Defined in:
lib/vangrail/profile.rb

Overview

A named, session-pinned posture. Copied from Grok Build's sandbox and permission model, not from a detector paper.

Grok Build pins the sandbox profile for the life of a session and refuses to change it on resume. Deny rules still apply under always-approve. Child processes do not inherit KEY/SECRET/TOKEN. The same four facts live here:

Profile.workspace   cite and search; mutating names are denied
Profile.strict      cite only; only read-only tools
Profile.read_only   no invoke of a mutating tool
Profile.off         no grants

The profile is chosen at Conversation construction and cannot be widened later. Deny always wins over allow and over the plan.

Constant Summary collapse

NAMES =
%i[off read_only workspace strict].freeze
SECRET =
/key|secret|token|password|passwd|authorization/i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, allow: {}, deny: [], readonly: false, strip_secrets: true) ⇒ Profile

Returns a new instance of Profile.



27
28
29
30
31
32
33
34
35
# File 'lib/vangrail/profile.rb', line 27

def initialize(name:, allow: {}, deny: [], readonly: false, strip_secrets: true)
  @name = name.to_sym
  @allow = allow.transform_keys(&:to_sym)
                .transform_values { |kinds| Array(kinds).map(&:to_sym) }
                .freeze
  @deny = Array(deny).map { |rule| rule.to_s.freeze }.freeze
  @readonly = readonly
  @strip_secrets = strip_secrets
end

Instance Attribute Details

#allowObject (readonly)

Returns the value of attribute allow.



25
26
27
# File 'lib/vangrail/profile.rb', line 25

def allow
  @allow
end

#denyObject (readonly)

Returns the value of attribute deny.



25
26
27
# File 'lib/vangrail/profile.rb', line 25

def deny
  @deny
end

#nameObject (readonly)

Returns the value of attribute name.



25
26
27
# File 'lib/vangrail/profile.rb', line 25

def name
  @name
end

#readonlyObject (readonly)

Returns the value of attribute readonly.



25
26
27
# File 'lib/vangrail/profile.rb', line 25

def readonly
  @readonly
end

#strip_secretsObject (readonly)

Returns the value of attribute strip_secrets.



25
26
27
# File 'lib/vangrail/profile.rb', line 25

def strip_secrets
  @strip_secrets
end

Class Method Details

.from_allow(allow, deny: []) ⇒ Object



85
86
87
# File 'lib/vangrail/profile.rb', line 85

def self.from_allow(allow, deny: [])
  new(name: :custom, allow: allow, deny: deny, readonly: false, strip_secrets: true)
end

.offObject



50
51
52
# File 'lib/vangrail/profile.rb', line 50

def self.off
  new(name: :off, allow: {}, deny: [], readonly: true)
end

.read_onlyObject



54
55
56
# File 'lib/vangrail/profile.rb', line 54

def self.read_only
  new(name: :read_only, allow: {}, deny: [], readonly: true)
end

.resolve(value, allow: {}, deny: []) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/vangrail/profile.rb', line 72

def self.resolve(value, allow: {}, deny: [])
  return value if value.is_a?(self)
  return from_allow(allow, deny: deny) if value.nil?

  case value.to_sym
  when :off then off
  when :read_only then read_only
  when :workspace then workspace
  when :strict then strict
  else raise ArgumentError, "unknown profile #{value.inspect}"
  end
end

.strictObject



65
66
67
68
69
70
# File 'lib/vangrail/profile.rb', line 65

def self.strict
  new(name: :strict,
      allow: { cite: %i[data] },
      deny: %w[delete_* dump_* shell],
      readonly: true)
end

.strip_secrets(env) ⇒ Object



89
90
91
92
93
94
95
# File 'lib/vangrail/profile.rb', line 89

def self.strip_secrets(env)
  env.each_with_object({}) do |(key, value), kept|
    next if key.to_s.match?(SECRET)

    kept[key] = value
  end
end

.workspaceObject



58
59
60
61
62
63
# File 'lib/vangrail/profile.rb', line 58

def self.workspace
  new(name: :workspace,
      allow: { cite: %i[data], search: [] },
      deny: %w[delete_* dump_* shell],
      readonly: false)
end

Instance Method Details

#denied?(tool) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
# File 'lib/vangrail/profile.rb', line 45

def denied?(tool)
  needle = tool.to_s
  deny.any? { |rule| File.fnmatch?(rule, needle, File::FNM_EXTGLOB) }
end

#readonly?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/vangrail/profile.rb', line 37

def readonly?
  @readonly
end

#strip_secrets?Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/vangrail/profile.rb', line 41

def strip_secrets?
  @strip_secrets
end