pwn 0.5.706 → 0.5.707

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/documentation/Reporting.md +1 -0
  4. data/etc/default_skills/pwn/reports/SKILL.md +4 -2
  5. data/etc/default_skills/pwn/reports/csv/SKILL.md +47 -0
  6. data/etc/default_skills/pwn/reports/html/SKILL.md +47 -0
  7. data/etc/default_skills/pwn/reports/json/SKILL.md +47 -0
  8. data/etc/default_skills/pwn/reports/markdown/SKILL.md +47 -0
  9. data/etc/default_skills/pwn/reports/pdf/SKILL.md +47 -0
  10. data/etc/default_skills/pwn/reports/xml/SKILL.md +47 -0
  11. data/lib/pwn/ai/agent/curriculum.rb +22 -20
  12. data/lib/pwn/ai/agent/learning.rb +3 -10
  13. data/lib/pwn/ai/agent/loop.rb +81 -44
  14. data/lib/pwn/ai/agent/policy.rb +23 -5
  15. data/lib/pwn/ai/agent/reward.rb +11 -23
  16. data/lib/pwn/ai/agent/turn_finalizer.rb +0 -1
  17. data/lib/pwn/reports/ai_red_team.rb +1 -1
  18. data/lib/pwn/reports/csv.rb +38 -0
  19. data/lib/pwn/reports/fuzz.rb +1 -1
  20. data/lib/pwn/reports/html.rb +58 -0
  21. data/lib/pwn/reports/json.rb +32 -0
  22. data/lib/pwn/reports/markdown.rb +40 -0
  23. data/lib/pwn/reports/pdf.rb +93 -0
  24. data/lib/pwn/reports/phone.rb +1 -1
  25. data/lib/pwn/reports/sast.rb +1 -1
  26. data/lib/pwn/reports/uri_buster.rb +1 -1
  27. data/lib/pwn/reports/xml.rb +44 -0
  28. data/lib/pwn/reports.rb +54 -6
  29. data/lib/pwn/version.rb +1 -1
  30. data/spec/integration/reinforced_feedback_loop_spec.rb +19 -4
  31. data/spec/lib/pwn/ai/agent/loop_spec.rb +61 -12
  32. data/spec/lib/pwn/ai/agent/policy_spec.rb +12 -2
  33. data/spec/lib/pwn/ai/agent/reward_spec.rb +72 -0
  34. data/spec/lib/pwn/reports/csv_spec.rb +19 -0
  35. data/spec/lib/pwn/reports/formats_spec.rb +90 -0
  36. data/spec/lib/pwn/reports/html_spec.rb +19 -0
  37. data/spec/lib/pwn/reports/json_spec.rb +19 -0
  38. data/spec/lib/pwn/reports/markdown_spec.rb +19 -0
  39. data/spec/lib/pwn/reports/pdf_spec.rb +19 -0
  40. data/spec/lib/pwn/reports/xml_spec.rb +19 -0
  41. data/third_party/pwn_rdoc.jsonl +35 -1
  42. metadata +22 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9233042c1c9518d8cfdcbfe6499eb521cb0348384554e03ab6641abdda253b9
4
- data.tar.gz: 51b81fd36f292bc54e762d4bb47f5641c9b263addf9f37997aaa4c2f6a00c49f
3
+ metadata.gz: 4c1b656d9d2cde2b5141da85cc87dbce6985f3f1f264d81c25fd5607121459d7
4
+ data.tar.gz: e84304c2573991adf9e99eeff5ec29d4dcad224d719016f81ce3c675b29cbb06
5
5
  SHA512:
6
- metadata.gz: 370d8b9305f7dcd2a692061b6708feca282546109533131d244cd97d207d1b216afac16baf44847a8a098c9847467208dd30e81084729f79ee7e3c16c050ae32
7
- data.tar.gz: 97725299f78baef12d73614b7258ebf5cc3664316c82d2bf53d6a82e3d9e0e0d2542a3143cbc686fc75b46b6883fb8edc0174cfd38ce74b88a0c908ab4864642
6
+ metadata.gz: d7367762d5ee2d30b01ef7b01ba51020a0532459c50d3cc4b327818ebfebae1491253ba0ce1edd264085832ca738f32a0a91d6c1c1fa22c40eada9df79009b91
7
+ data.tar.gz: 6ee7b63319321faeb9a62f94c5177ddf83d2ac4d2b3353e8a19ce96a8d3d67d6a090aacb7f976a75ccf7ce28e0ea698923ef044e1026d24c435f147fba1198e0
data/Gemfile CHANGED
@@ -49,7 +49,7 @@ gem 'jwt', '3.2.0'
49
49
  gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
- gem 'mcp', '1.3.0'
52
+ gem 'mcp', '1.4.0'
53
53
  gem 'meshtastic', '0.0.175'
54
54
  gem 'metasm', '1.0.6'
55
55
  gem 'mongo', '2.25.0'
@@ -9,6 +9,7 @@
9
9
 
10
10
  | Module | Consumes | Emits |
11
11
  |---|---|---|
12
+ | `Reports::PDF` / `HTML` / `Markdown` / `XML` / `CSV` / `JSON` | findings Hash | `.pdf` / `.html` / `.md` / `.xml` / `.csv` / `.json` |
12
13
  | `Reports::SAST` | `PWN::SAST::Factory` output | HTML (with `HTMLHeader`/`HTMLFooter`) + JSON |
13
14
  | `Reports::Fuzz` | `PWN::Plugins::Fuzz` crash log | HTML + JSON |
14
15
  | `Reports::URIBuster` | `pwn_www_uri_buster` output | HTML |
@@ -28,11 +28,13 @@ Class methods take `(opts = {})` and read `opts`.
28
28
 
29
29
  ```ruby
30
30
  PWN::Reports.help
31
- PWN::Reports.help(opts)
31
+ PWN::Reports.resolve_path(opts)
32
32
  ```
33
33
 
34
34
  ## Public methods
35
35
 
36
+ - `resolve_path`
37
+ - `report_payload`
36
38
  - `authors`
37
39
  - `help`
38
40
 
@@ -46,5 +48,5 @@ PWN::Reports.help(opts)
46
48
 
47
49
  ## Verification
48
50
 
49
- `PWN::Reports.respond_to?(:authors)` after the
51
+ `PWN::Reports.respond_to?(:resolve_path)` after the
50
52
  module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-csv
3
+ description: Drive PWN::Reports::CSV from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::CSV
10
+ source: pwn/reports/csv.rb
11
+ ---
12
+
13
+ # PWN::Reports::CSV
14
+
15
+ Generic CSV report writer for pentest / findings payloads.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::CSV` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/csv.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::CSV.help
31
+ PWN::Reports::CSV.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/csv.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::CSV.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-html
3
+ description: Drive PWN::Reports::HTML from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::HTML
10
+ source: pwn/reports/html.rb
11
+ ---
12
+
13
+ # PWN::Reports::HTML
14
+
15
+ Generic HTML report writer for pentest / findings payloads.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::HTML` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/html.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::HTML.help
31
+ PWN::Reports::HTML.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/html.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::HTML.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-json
3
+ description: Drive PWN::Reports::JSON from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::JSON
10
+ source: pwn/reports/json.rb
11
+ ---
12
+
13
+ # PWN::Reports::JSON
14
+
15
+ Generic JSON report writer for pentest / findings payloads.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::JSON` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/json.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::JSON.help
31
+ PWN::Reports::JSON.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/json.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::JSON.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-markdown
3
+ description: Drive PWN::Reports::Markdown from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::Markdown
10
+ source: pwn/reports/markdown.rb
11
+ ---
12
+
13
+ # PWN::Reports::Markdown
14
+
15
+ Generic Markdown report writer for pentest / findings payloads.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::Markdown` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/markdown.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::Markdown.help
31
+ PWN::Reports::Markdown.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/markdown.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::Markdown.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-pdf
3
+ description: Drive PWN::Reports::PDF from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::PDF
10
+ source: pwn/reports/pdf.rb
11
+ ---
12
+
13
+ # PWN::Reports::PDF
14
+
15
+ Generic PDF report writer for pentest / findings payloads. Emits a minimal PDF 1.4 document (no wkhtmltopdf).
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::PDF` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/pdf.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::PDF.help
31
+ PWN::Reports::PDF.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/pdf.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::PDF.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pwn-reports-xml
3
+ description: Drive PWN::Reports::XML from pwn_eval.
4
+ license: MIT
5
+ allowed-tools: [pwn, pwn_eval]
6
+ metadata:
7
+ bundled: true
8
+ generated: true
9
+ module: PWN::Reports::XML
10
+ source: pwn/reports/xml.rb
11
+ ---
12
+
13
+ # PWN::Reports::XML
14
+
15
+ Generic XML report writer for pentest / findings payloads.
16
+
17
+ ## When to use
18
+
19
+ Call `PWN::Reports::XML` from `pwn_eval` when the task needs this module.
20
+ Do not reimplement it in shell.
21
+
22
+ ## Methodologies
23
+
24
+ Generated from `pwn/reports/xml.rb`. Prefer the public class methods below.
25
+ Class methods take `(opts = {})` and read `opts`.
26
+
27
+ ## How to call
28
+
29
+ ```ruby
30
+ PWN::Reports::XML.help
31
+ PWN::Reports::XML.generate(opts)
32
+ ```
33
+
34
+ ## Public methods
35
+
36
+ - `generate`
37
+ - `authors`
38
+ - `help`
39
+
40
+ ## Source
41
+
42
+ `pwn/reports/xml.rb`
43
+
44
+ ## Verification
45
+
46
+ `PWN::Reports::XML.respond_to?(:generate)` after the
47
+ module is loaded. Read the source for parameter names.
@@ -94,15 +94,7 @@ module PWN
94
94
  []
95
95
  end
96
96
  cool = load_cooldown
97
- # P17 prefer budget-exhaustion fingerprints (agent_loop / critic)
98
- # so nightly self-play attacks the #1 live skill gap first.
99
- candidates = candidates.sort_by do |m|
100
- t = m[:tool].to_s
101
- e = m[:error].to_s.downcase
102
- budget = t == 'agent_loop' || t == 'assistant_answer' ||
103
- e.include?('budget exhausted') || e.include?('iteration budget')
104
- [budget ? 0 : 1, -m[:count].to_i]
105
- end
97
+ candidates = candidates.sort_by { |m| -m[:count].to_i }
106
98
  targets = candidates.reject { |m| practice_skip?(mistake: m, cooldown: cool) }.first(limit)
107
99
  results = []
108
100
 
@@ -112,7 +104,7 @@ module PWN
112
104
 
113
105
  prompts = generate_reproducers(mistake: m, count: [per, 2].max)
114
106
  runs = dry_run ? [] : prompts.map { |p| self_play(prompt: p, tag: "practice:#{m[:signature]}") }
115
- solved = runs.select { |r| r[:score].to_f >= 0.7 }
107
+ solved = runs.select { |r| r[:score].to_f >= 0.6 }
116
108
  mean = runs.empty? ? 0.0 : (runs.sum { |r| r[:score].to_f } / runs.length)
117
109
  resolved = false
118
110
  # 2.4 — auto-resolve only with N≥2 holdout successes + store trace
@@ -129,7 +121,12 @@ module PWN
129
121
  trace_ok = winning.length >= 20 && (
130
122
  !budgetish || winning.match?(/→|shell|pwn_eval|tool/i) || best[:final].to_s.length.between?(1, 800)
131
123
  )
132
- unless trace_ok
124
+ poc_ok = if budgetish || %w[agent_loop assistant_answer].include?(m[:tool].to_s)
125
+ practice_poc_ok?(run: best)
126
+ else
127
+ true
128
+ end
129
+ unless trace_ok && poc_ok
133
130
  bump_cooldown!(cooldown: cool, signature: m[:signature], mean: mean) unless dry_run
134
131
  results << {
135
132
  signature: m[:signature], tool: m[:tool], prompts: prompts,
@@ -895,15 +892,11 @@ module PWN
895
892
  'Return Dir.pwd from pwn_eval'
896
893
  ]
897
894
  when 'agent_loop', 'assistant_answer'
898
- # P17 — dominant live failure: iteration / critic budget exhaustion.
899
- # Practise finishing under a tight tool budget, not shell shapes.
900
895
  [
901
- 'Answer in one shell call: print kernel release with uname -r',
902
- 'In at most two tools, show cwd and ruby version then stop',
903
- 'Give a final answer with no tools: what is 7 times 8?',
904
- 'Finish under three iterations: list /tmp and report file count',
905
- 'Do not explore — one pwn_eval of Dir.pwd and return the path',
906
- 'Short plan then one command: show free disk with df -h /'
896
+ 'Write a working PoC to /tmp/pwn-practice-poc.txt that proves the issue, then stop',
897
+ 'Save a chained-impact PoC under /tmp/pwn-practice-chain.txt with reproduction steps',
898
+ 'Use pwn_eval to call PWN::Reports::JSON.generate with a findings hash to /tmp/pwn-practice-findings.json',
899
+ 'Produce evidence-backed severity in /tmp/pwn-practice-sev.md including a PoC path'
907
900
  ]
908
901
  else
909
902
  if err.include?('budget exhausted') || err.include?('iteration budget') ||
@@ -1000,7 +993,7 @@ module PWN
1000
993
  capped = true
1001
994
  end
1002
995
  begin
1003
- final = Loop.run(request: prompt, session_id: sid, enabled_toolsets: %w[terminal pwn memory learning])
996
+ final = Loop.run(request: prompt, session_id: sid)
1004
997
  ensure
1005
998
  if capped && defined?(PWN::Env) && PWN::Env[:ai].is_a?(Hash) && PWN::Env[:ai][:agent].is_a?(Hash) && !PWN::Env[:ai][:agent].frozen?
1006
999
  if prev_max == :__unset__
@@ -1083,6 +1076,15 @@ module PWN
1083
1076
  nil
1084
1077
  end
1085
1078
 
1079
+ private_class_method def self.practice_poc_ok?(opts = {})
1080
+ run = opts[:run] || {}
1081
+ blob = "#{run[:final]}\n#{run[:trace]}\n#{run[:prompt]}"
1082
+ paths = blob.scan(%r{(/tmp/[A-Za-z0-9._+-]+)})
1083
+ paths.flatten.any? { |path| File.file?(path) && File.size(path).positive? }
1084
+ rescue StandardError
1085
+ false
1086
+ end
1087
+
1086
1088
  private_class_method def self.ask_persona(opts = {})
1087
1089
  return nil unless defined?(Swarm)
1088
1090
 
@@ -568,9 +568,9 @@ module PWN
568
568
 
569
569
  # R1 judge — always attempt (heuristic is cheap; LLM gated inside)
570
570
  stages_run << :judge
571
- v = Reward.judge(request: opts[:request], final: opts[:final], session_id: session_id, proxy_ok: proxy_ok, predicted: opts[:predicted], plan: opts[:plan]) if defined?(Reward)
571
+ v = Reward.judge(request: opts[:request], final: opts[:final], session_id: session_id, proxy_ok: proxy_ok, predicted: opts[:predicted]) if defined?(Reward)
572
572
  v ||= { score: proxy_ok ? 1.0 : 0.0, success: proxy_ok, verdict: proxy_ok ? :solved : :wrong }
573
- v[:score] = [v[:score], 0.3].min if crit[:verdict] == :flaw
573
+ v[:score] = [v[:score], 0.3].min if crit[:verdict] == :flaw && v[:score].to_f < 0.6
574
574
  # P29 — critic floor used to leave stale verdict=:solved at score=0.3,
575
575
  # producing learning.jsonl rows tagged "solved" with success=false
576
576
  # (116+ rows). Always resync verdict/success from the final score.
@@ -598,14 +598,7 @@ module PWN
598
598
  if defined?(Reward) && Reward.respond_to?(:plan_coverage)
599
599
  begin
600
600
  plan_for_cov = opts[:plan]
601
- plan_for_cov = opts[:ts_state][:plan] if plan_for_cov.nil? && opts[:ts_state].is_a?(Hash)
602
- if plan_for_cov.nil? && defined?(TaskSummarizer)
603
- # Recover numbered tasks from the final/request only when caller
604
- # did not pass a plan — still keeps TaskSummarizer out of the
605
- # credit path (parse is pure text).
606
- plan_for_cov = nil
607
- end
608
- if !plan_for_cov.nil? || opts[:final].to_s.length.positive?
601
+ unless plan_for_cov.nil?
609
602
  plan_cov = Reward.plan_coverage(
610
603
  plan: plan_for_cov || [],
611
604
  final: opts[:final],