Class: Vangrail::Profile
- Inherits:
-
Object
- Object
- Vangrail::Profile
- 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.
Extra allow: / deny: on a named profile is a conflict, not a
merge: resolve raises. Deny-wins merge applies when composing
from hashes alone.
Constant Summary collapse
- NAMES =
%i[off read_only workspace strict].freeze
- SECRET =
/key|secret|token|password|passwd|authorization/i
Instance Attribute Summary collapse
-
#allow ⇒ Object
readonly
Returns the value of attribute allow.
-
#deny ⇒ Object
readonly
Returns the value of attribute deny.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#readonly ⇒ Object
readonly
Returns the value of attribute readonly.
-
#strip_secrets ⇒ Object
readonly
Returns the value of attribute strip_secrets.
Class Method Summary collapse
- .from_allow(allow, deny: []) ⇒ Object
- .glob_denied?(name, rules) ⇒ Boolean
- .named(value) ⇒ Object
- .off ⇒ Object
- .read_only ⇒ Object
- .resolve(value, allow: {}, deny: []) ⇒ Object
- .strict ⇒ Object
- .strip_secrets(env) ⇒ Object
- .workspace ⇒ Object
Instance Method Summary collapse
- #denied?(tool) ⇒ Boolean
-
#initialize(name:, allow: {}, deny: [], readonly: false, strip_secrets: true) ⇒ Profile
constructor
A new instance of Profile.
- #readonly? ⇒ Boolean
- #strip_secrets? ⇒ Boolean
Constructor Details
#initialize(name:, allow: {}, deny: [], readonly: false, strip_secrets: true) ⇒ Profile
Returns a new instance of Profile.
30 31 32 33 34 35 36 37 38 39 40 |
# File 'lib/vangrail/profile.rb', line 30 def initialize(name:, allow: {}, deny: [], readonly: false, strip_secrets: true) @name = name.to_sym deny_list = Array(deny).map(&:to_s) @deny = deny_list.map(&:freeze).freeze @allow = allow.transform_keys(&:to_sym) .transform_values { |kinds| Array(kinds).map(&:to_sym) } .reject { |tool, _| self.class.glob_denied?(tool, deny_list) } .freeze @readonly = readonly @strip_secrets = strip_secrets end |
Instance Attribute Details
#allow ⇒ Object (readonly)
Returns the value of attribute allow.
28 29 30 |
# File 'lib/vangrail/profile.rb', line 28 def allow @allow end |
#deny ⇒ Object (readonly)
Returns the value of attribute deny.
28 29 30 |
# File 'lib/vangrail/profile.rb', line 28 def deny @deny end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
28 29 30 |
# File 'lib/vangrail/profile.rb', line 28 def name @name end |
#readonly ⇒ Object (readonly)
Returns the value of attribute readonly.
28 29 30 |
# File 'lib/vangrail/profile.rb', line 28 def readonly @readonly end |
#strip_secrets ⇒ Object (readonly)
Returns the value of attribute strip_secrets.
28 29 30 |
# File 'lib/vangrail/profile.rb', line 28 def strip_secrets @strip_secrets end |
Class Method Details
.from_allow(allow, deny: []) ⇒ Object
101 102 103 104 105 106 |
# File 'lib/vangrail/profile.rb', line 101 def self.from_allow(allow, deny: []) deny_list = Array(deny).map(&:to_s) cleaned = allow.transform_keys(&:to_sym) .reject { |name, _| glob_denied?(name, deny_list) } new(name: :custom, allow: cleaned, deny: deny, readonly: false, strip_secrets: true) end |
.glob_denied?(name, rules) ⇒ Boolean
96 97 98 99 |
# File 'lib/vangrail/profile.rb', line 96 def self.glob_denied?(name, rules) needle = name.to_s rules.any? { |rule| File.fnmatch?(rule, needle, File::FNM_EXTGLOB) } end |
.named(value) ⇒ Object
86 87 88 89 90 91 92 93 94 |
# File 'lib/vangrail/profile.rb', line 86 def self.named(value) 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 |
.off ⇒ Object
55 56 57 |
# File 'lib/vangrail/profile.rb', line 55 def self.off new(name: :off, allow: {}, deny: [], readonly: true) end |
.read_only ⇒ Object
59 60 61 |
# File 'lib/vangrail/profile.rb', line 59 def self.read_only new(name: :read_only, allow: {}, deny: [], readonly: true) end |
.resolve(value, allow: {}, deny: []) ⇒ Object
77 78 79 80 81 82 83 84 |
# File 'lib/vangrail/profile.rb', line 77 def self.resolve(value, allow: {}, deny: []) extra = !allow.empty? || !Array(deny).empty? return from_allow(allow, deny: deny) if value.nil? raise ArgumentError, 'pass profile: or allow:/deny:, not both' if extra return value if value.is_a?(self) named(value) end |
.strict ⇒ Object
70 71 72 73 74 75 |
# File 'lib/vangrail/profile.rb', line 70 def self.strict new(name: :strict, allow: { cite: %i[data] }, deny: %w[delete_* dump_* shell], readonly: true) end |
.strip_secrets(env) ⇒ Object
108 109 110 111 112 113 114 |
# File 'lib/vangrail/profile.rb', line 108 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 |
.workspace ⇒ Object
63 64 65 66 67 68 |
# File 'lib/vangrail/profile.rb', line 63 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
50 51 52 53 |
# File 'lib/vangrail/profile.rb', line 50 def denied?(tool) needle = tool.to_s deny.any? { |rule| File.fnmatch?(rule, needle, File::FNM_EXTGLOB) } end |
#readonly? ⇒ Boolean
42 43 44 |
# File 'lib/vangrail/profile.rb', line 42 def readonly? @readonly end |
#strip_secrets? ⇒ Boolean
46 47 48 |
# File 'lib/vangrail/profile.rb', line 46 def strip_secrets? @strip_secrets end |