Module: PWN::SessionTrace
- Defined in:
- lib/pwn/session_trace.rb
Overview
Recorded, redacted evidence, not a promise of deterministic LLM output.
Constant Summary collapse
- EVENTS =
%w[request response tool_call tool_result].freeze
Class Method Summary collapse
- .append(opts = {}) ⇒ Object
- .authors ⇒ Object
- .help ⇒ Object
- .read(opts = {}) ⇒ Object
-
.replay(opts = {}) ⇒ Object
Read-only rendering; never dispatches a recorded call.
-
.rerun(opts = {}) ⇒ Object
Explicit opt-in; no host dispatch, home mount, network or inherited env.
Class Method Details
.append(opts = {}) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/pwn/session_trace.rb', line 16 public_class_method def self.append(opts = {}) event = opts[:event].to_s raise ArgumentError, 'Invalid trace event' unless EVENTS.include?(event) path = trace_path(session_id: opts[:session_id]) FileUtils.mkdir_p(File.dirname(path), mode: 0o700) row = PWN::Redaction.redact(value: { version: 1, event: event, data: opts[:data], model: opts[:model], params: opts[:params] || {} }) row[:seed] = opts[:seed] if opts.key?(:seed) && !opts[:seed].nil? File.open(path, File::RDWR | File::CREAT | File::APPEND | File::NOFOLLOW, 0o600) do |file| file.flock(File::LOCK_EX) file.rewind last = nil file.each_line { |line| last = JSON.parse(line) } now = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) row[:monotonic_ns] = [now, last ? last.fetch('monotonic_ns') + 1 : 0].max row[:sequence] = last ? last.fetch('sequence') + 1 : 1 file.puts(JSON.generate(row)) file.flush end row end |
.authors ⇒ Object
128 129 130 |
# File 'lib/pwn/session_trace.rb', line 128 public_class_method def self. "AUTHOR(S):\n 0day Inc. <[email protected]>\n" end |
.help ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/pwn/session_trace.rb', line 132 public_class_method def self.help puts "USAGE: # Persist one redacted event with monotonic ordering. #{self}.append( session_id: 'required - safe session identifier', event: 'required - request, response, tool_call or tool_result', data: 'optional - event payload with call IDs', model: 'optional - provider model identifier', params: 'optional - model invocation parameters', seed: 'optional - provider-supported seed' ) # Render recorded evidence without executing tools. #{self}.replay( session_id: 'required - recorded session identifier', io: 'optional - output IO, defaults to stdout' ) # Read recorded evidence with defensive redaction. #{self}.read( session_id: 'required - recorded session identifier' ) # Rerun shell/Ruby calls in a fresh no-network Bubblewrap namespace. #{self}.rerun( session_id: 'required - recorded session identifier', environment: 'required - explicit bubblewrap opt-in; no host fallback', timeout: 'optional - positive deadline seconds, default 30, maximum 300' ) # Print the author information. #{self}.authors " end |
.read(opts = {}) ⇒ Object
48 49 50 51 52 53 54 |
# File 'lib/pwn/session_trace.rb', line 48 public_class_method def self.read(opts = {}) path = trace_path(session_id: opts[:session_id]) File.open(path, File::RDONLY | File::NOFOLLOW) do |file| file.flock(File::LOCK_SH) file.each_line.map { |line| PWN::Redaction.redact(value: JSON.parse(line)) } end end |
.replay(opts = {}) ⇒ Object
Read-only rendering; never dispatches a recorded call.
41 42 43 44 45 46 |
# File 'lib/pwn/session_trace.rb', line 41 public_class_method def self.replay(opts = {}) rows = read(session_id: opts[:session_id]) io = opts[:io] || $stdout rows.each { |row| io.puts(JSON.generate(row)) } rows end |
.rerun(opts = {}) ⇒ Object
Explicit opt-in; no host dispatch, home mount, network or inherited env. Ruby is the isolated system Ruby, not the original provider/runtime.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/pwn/session_trace.rb', line 58 public_class_method def self.rerun(opts = {}) raise ArgumentError, 'Select an isolated environment: bubblewrap' unless opts[:environment].to_s == 'bubblewrap' calls = read(session_id: opts[:session_id]).select { |row| row['event'] == 'tool_call' }.map { |row| row['data'] } calls.each do |call| raise ArgumentError, 'Unsupported rerun tool' unless call.is_a?(Hash) && %w[shell pwn_eval].include?(call['name']) raise ArgumentError, 'Redacted calls require fresh fixture inputs' if JSON.generate(call).include?('[REDACTED:') raise ArgumentError, 'Tool arguments required' unless call['arguments'].is_a?(Hash) end runner = <<~RUBY require 'json' require 'open3' calls = JSON.parse(STDIN.read) results = calls.map do |call| args = call.fetch('arguments') argv = if call['name'] == 'shell' ['/bin/sh', '-c', args.fetch('command')] else ['/usr/bin/ruby', '-e', "result = eval(ARGV.fetch(0)); puts(result) unless result.nil?", args.fetch('code')] end out, err, status = Open3.capture3(*argv) { id: call['id'], name: call['name'], stdout: out, stderr: err, exit_status: status.exitstatus } end STDOUT.write(JSON.generate(results)) RUBY argv = ['/usr/bin/bwrap', '--unshare-all', '--die-with-parent', '--new-session', '--clearenv', '--setenv', 'PATH', '/usr/bin:/bin', '--setenv', 'HOME', '/work', '--ro-bind', '/usr', '/usr', '--symlink', 'usr/bin', '/bin', '--symlink', 'usr/lib', '/lib', '--symlink', 'usr/lib64', '/lib64', '--proc', '/proc', '--dev', '/dev', '--tmpfs', '/tmp', '--dir', '/work', '--chdir', '/work', '/usr/bin/ruby', '-e', runner] out = +'' status = nil timeout = Float(opts.fetch(:timeout, 30)) raise ArgumentError, 'Invalid rerun deadline' unless timeout.positive? && timeout <= 300 Open3.popen3(*argv, unsetenv_others: true, pgroup: true) do |stdin, stdout, stderr, wait| readers = [Thread.new { stdout.read }, Thread.new { stderr.read }] begin Timeout.timeout(timeout) do stdin.write(JSON.generate(calls)) stdin.close out = readers.first.value readers.last.value status = wait.value end rescue Timeout::Error Process.kill('KILL', -wait.pid) raise 'Isolated rerun deadline exceeded; no host fallback' ensure readers.each(&:join) end end raise 'Isolated rerun failed; no host fallback' unless status.success? { run_id: SecureRandom.hex(12), environment: 'bubblewrap', results: PWN::Redaction.redact(value: JSON.parse(out)) } rescue Errno::ENOENT raise 'Isolated environment unavailable; no host fallback' end |