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, 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

Class Method Summary collapse

Class Method Details

.authorsObject



97
98
99
# File 'lib/pwn/ai/agent/tool_guard.rb', line 97

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.bashism?(opts = {}) ⇒ Boolean

Returns:

  • (Boolean)


42
43
44
45
46
# File 'lib/pwn/ai/agent/tool_guard.rb', line 42

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.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/pwn/ai/agent/tool_guard.rb', line 61

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

.helpObject



101
102
103
104
105
106
107
108
# File 'lib/pwn/ai/agent/tool_guard.rb', line 101

public_class_method def self.help
  puts <<~USAGE
    USAGE:
      PWN::AI::Agent::ToolGuard.placeholder?(text: '...')
      PWN::AI::Agent::ToolGuard.coerce_args(args: { value: 'id' }, required: %w[command])
      #{self}.authors
  USAGE
end

.invalid_payload(opts = {}) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pwn/ai/agent/tool_guard.rb', line 85

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

Returns:

  • (Boolean)


36
37
38
39
40
# File 'lib/pwn/ai/agent/tool_guard.rb', line 36

public_class_method def self.placeholder?(opts = {})
  PLACEHOLDER_RX.match?(opts[:text].to_s)
rescue StandardError
  false
end

.present?(opts = {}) ⇒ Boolean

Returns:

  • (Boolean)


31
32
33
34
# File 'lib/pwn/ai/agent/tool_guard.rb', line 31

public_class_method def self.present?(opts = {})
  value = opts.is_a?(Hash) ? opts[:value] : opts
  !value.nil? && !value.to_s.strip.empty?
end

.shell_bash?Boolean

Returns:

  • (Boolean)


48
49
50
51
52
53
# File 'lib/pwn/ai/agent/tool_guard.rb', line 48

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_nameObject



55
56
57
# File 'lib/pwn/ai/agent/tool_guard.rb', line 55

public_class_method def self.shell_name
  shell_bash? ? 'bash -lc' : '/bin/sh'
end