Module: PWN::AI::Agent::ToolGuard
- Defined in:
- lib/pwn/ai/agent/tool_guard.rb
Overview
Shared pre-dispatch guards for the two high-volume runtime tools (shell / pwn_eval). Rejects placeholder payloads, aliases wrong schema keys, shares the recon authorization gate, and names the shell that will actually run the command.
Constant Summary collapse
- ALIASES =
{ 'command' => %w[value cmd input], 'code' => %w[value source ruby input], 'query' => %w[value q text] }.freeze
- PLACEHOLDER_RX =
Token-level junk the model keeps emitting instead of a real command.
/ \A\s*(?:\.{3}|…|\{\s*\.{3}\s*\}|\{\s*…\s*\}|<\.{3}>)\s*\z |(?:^|[\s;|&])(?:\.{3}|…|\{\s*\.{3}\s*\}|\{\s*…\s*\})(?:$|[\s;|&]) /x- BASHISM_RX =
Conservative bash-only constructs. POSIX
$(())is allowed. / \bPIPESTATUS\b |\[\[(?:\s|\z) |(?:^|[\s;|&])source\s+\S |<\([^)] |&> /x- RECON_RX =
%r{ (?:^|[;&|\s])(?:sudo\s+)?hping3?(?:\s|$).*(?:-1|--icmp|--flood|/[0-9]{1,2}|seq\s+|for\s+) |(?:^|[;&|\s])(?:sudo\s+)?nmap\b[^\n]*(?:-sn|-sP|-PE|-PP|-PM)[^\n]*(?:/[0-9]{1,2}|\d+\.\d+\.\d+\.\d+) |(?:^|[;&|\s])(?:sudo\s+)?masscan\b |(?:^|[;&|\s])(?:sudo\s+)?nping\b |(?:^|[;&|\s])(?:sudo\s+)?fping\b[^\n]*/ |for\s+\w+\s+in\s+\$?\(?seq[^)]*\)?[^;]*;\s*do\s[^;]*(?:hping|ping\s+-c|nmap) |PWN::Plugins::NmapIt |\bNmapIt\.(?:scan|new|help) }ix
Class Method Summary collapse
- .authors ⇒ Object
- .bashism?(opts = {}) ⇒ Boolean
-
.coerce_args(opts = {}) ⇒ Object
Coerce common wrong keys onto the first required schema field.
- .help ⇒ Object
- .invalid_payload(opts = {}) ⇒ Object
- .placeholder?(opts = {}) ⇒ Boolean
- .present?(opts = {}) ⇒ Boolean
- .recon_authorized?(opts = {}) ⇒ Boolean
- .recon_blocked(opts = {}) ⇒ Object
- .recon_text?(opts = {}) ⇒ Boolean
- .shell_bash? ⇒ Boolean
- .shell_name ⇒ Object
Class Method Details
.authors ⇒ Object
141 142 143 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 141 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.bashism?(opts = {}) ⇒ Boolean
54 55 56 57 58 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 54 public_class_method def self.bashism?(opts = {}) BASHISM_RX.match?(opts[:text].to_s) rescue StandardError false end |
.coerce_args(opts = {}) ⇒ Object
Coerce common wrong keys onto the first required schema field. Returns the args hash; sets :__schema_error when still missing.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 90 public_class_method def self.coerce_args(opts = {}) args = (opts[:args] || {}).dup args = args.each_with_object({}) { |(k, v), m| m[k.to_sym] = v } unless args.empty? req = Array(opts[:required]).map(&:to_s) req.each do |key| next if present?(value: args[key.to_sym]) hit = Array(ALIASES[key]).find { |a| present?(value: args[a.to_sym]) } args[key.to_sym] = args[hit.to_sym] if hit end missing = req.reject { |k| present?(value: args[k.to_sym]) } unless missing.empty? args[:__schema_error] = "missing required #{missing.join(', ')}" args[:__expected] = req args[:__schema_hint] = "Expected keys: #{req.join(', ')}. " \ 'Do not send value/placeholder/ellipsis. ' \ 'Example: shell(command="uname -r") or pwn_eval(code="1+1").' end args rescue StandardError opts[:args] || {} end |
.help ⇒ Object
145 146 147 148 149 150 151 152 153 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 145 public_class_method def self.help puts <<~USAGE USAGE: PWN::AI::Agent::ToolGuard.placeholder?(text: '...') PWN::AI::Agent::ToolGuard.recon_authorized? PWN::AI::Agent::ToolGuard.coerce_args(args: { value: 'id' }, required: %w[command]) #{self}.authors USAGE end |
.invalid_payload(opts = {}) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 114 public_class_method def self.invalid_payload(opts = {}) hint = opts[:hint].to_s { stdout: '', stderr: hint, exit: 2, error: 'invalid_payload', hint: hint, shell: opts[:shell] || shell_name } end |
.placeholder?(opts = {}) ⇒ Boolean
48 49 50 51 52 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 48 public_class_method def self.placeholder?(opts = {}) PLACEHOLDER_RX.match?(opts[:text].to_s) rescue StandardError false end |
.present?(opts = {}) ⇒ Boolean
43 44 45 46 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 43 public_class_method def self.present?(opts = {}) value = opts.is_a?(Hash) ? opts[:value] : opts !value.nil? && !value.to_s.strip.empty? end |
.recon_authorized?(opts = {}) ⇒ Boolean
66 67 68 69 70 71 72 73 74 75 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 66 public_class_method def self.(opts = {}) return true if Thread.current[:pwn_recon_authorized] == true return true if defined?(Loop) && Loop.respond_to?(:recon_authorized?) && Loop.(request: opts[:request].to_s) v = (PWN::Env.dig(:ai, :agent, :recon_authorized) if defined?(PWN::Env)) v == true || v.to_s.match?(/\A(1|true|yes|on)\z/i) rescue StandardError false end |
.recon_blocked(opts = {}) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 126 public_class_method def self.recon_blocked(opts = {}) refused = opts[:text].to_s[0, 200] msg = '[pwn-ai/guard] blocked unauthorized recon/sweep. ' \ 'Need explicit in-scope authorization on the user request or ' \ 'PWN::Env[:ai][:agent][:recon_authorized]=true. ' \ "Refused: #{refused}" { stdout: '', stderr: msg, exit: 126, error: 'unauthorized_recon_blocked', hint: msg } end |
.recon_text?(opts = {}) ⇒ Boolean
60 61 62 63 64 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 60 public_class_method def self.recon_text?(opts = {}) RECON_RX.match?(opts[:text].to_s) rescue StandardError false end |
.shell_bash? ⇒ Boolean
77 78 79 80 81 82 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 77 public_class_method def self.shell_bash? v = (PWN::Env.dig(:ai, :agent, :shell_bash) if defined?(PWN::Env)) v == true || v.to_s.match?(/\A(1|true|yes|on)\z/i) rescue StandardError false end |
.shell_name ⇒ Object
84 85 86 |
# File 'lib/pwn/ai/agent/tool_guard.rb', line 84 public_class_method def self.shell_name shell_bash? ? 'bash -lc' : '/bin/sh' end |