From a0661606d5a524a7dd4d4381993bdfd92bec2b75 Mon Sep 17 00:00:00 2001 From: Dean Welch Date: Thu, 6 Aug 2026 14:17:38 +0100 Subject: [PATCH 1/4] Enhance contributing documentation with module structure templates and guidelines --- .github/copilot-instructions.md | 71 +++- .../documentation.instructions.md | 24 ++ .github/instructions/library.instructions.md | 43 ++ .github/instructions/modules.instructions.md | 64 +++ .github/instructions/tests.instructions.md | 37 ++ AGENTS.md | 373 ++++++++++++++++-- CONTRIBUTING.md | 21 +- 7 files changed, 591 insertions(+), 42 deletions(-) create mode 100644 .github/instructions/documentation.instructions.md create mode 100644 .github/instructions/library.instructions.md create mode 100644 .github/instructions/modules.instructions.md create mode 100644 .github/instructions/tests.instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index db0a79d6d11aa..75a0a3abdfd7a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,3 +1,70 @@ -# Copilot Instructions +# Metasploit Framework — Copilot Instructions -Refer to [AGENTS.md](../AGENTS.md) in the repository root for all project conventions, coding standards, and AI agent guidelines. +## Project Overview + +Metasploit Framework is a Ruby penetration testing and exploitation framework. Modules (exploits, auxiliary, post, payloads, encoders, evasion) live in `modules/`. Core libraries live in `lib/msf/` and `lib/rex/`. Tests are in `spec/`. + +## Tech Stack + +- Ruby 3.1+ (see `.ruby-version`) +- RSpec for testing (`bundle exec rspec spec/path/to/spec.rb`) +- RuboCop for linting (custom cops in `lib/rubocop/cop/`) +- `tools/dev/msftidy.rb` for module-specific checks + +## Key Coding Rules + +- Add `# frozen_string_literal: true` to new files +- Use `%q{}` for multi-line module descriptions +- Don't use `get_`/`set_` prefixes for accessor methods +- All `print_*` calls start with a capital letter +- Use `Rex::Socket.to_authority(ip, port)` for host:port (IPv6 safe) +- Use `res.get_json_document` not `JSON.parse(res.body)` +- Use `fail_with(Failure::*, 'reason')` for error conditions in exploit/run methods +- Use `create_process(executable, args: [])` not `cmd_exec` with separate arguments + +## Module Structure (Exploit) + +```ruby +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + include Msf::Exploit::Remote::HttpClient # 1. Protocol mixins + include Msf::Exploit::FileDropper # 2. Utility mixins + prepend Msf::Exploit::Remote::AutoCheck # 3. ALWAYS LAST — prepend not include + + def initialize(info = {}) + super(update_info(info, 'Name' => ..., 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [REPEATABLE_SESSION] })) + end + + def check + CheckCode::Safe('Reason string required') # Never bare constants + end + + def exploit; end +end +``` + +## Check Methods + +- Must return `CheckCode` values only — never raise or call `fail_with` +- `CheckCode::Vulnerable` = vulnerability was exploited; `CheckCode::Appears` = version check +- Always include a reason string: `CheckCode::Safe("Patched version #{v}")` +- Use `Rex::Version` for version comparisons +- Prefer `prepend Msf::Exploit::Remote::AutoCheck` over manual check calls + +## Library Code (`lib/`) + +- Use specific error classes (`Rex::RuntimeError`, `Rex::ConnectionError`) — never `raise "string"` +- Use `rescue StandardError => e` — never bare `rescue` +- Add YARD `@param`/`@return` to public methods +- Write RSpec tests for all library changes + +## Before Submitting + +- Run `rubocop` and `msftidy` on changed files +- Run `ruby tools/dev/msftidy_docs.rb` on documentation files +- One module per PR; keep PRs focused +- Include verification steps and console output + +## Full Reference + +See [AGENTS.md](../AGENTS.md) for complete templates (auxiliary, post modules), payload selection guidance, Notes hash reference, legacy pattern migration table, and detailed subsection guidance. diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md new file mode 100644 index 0000000000000..0843d319a4f24 --- /dev/null +++ b/.github/instructions/documentation.instructions.md @@ -0,0 +1,24 @@ +--- +applyTo: "documentation/**/*.md" +--- + +# Module Documentation Instructions + +## Template + +Follow `documentation/modules/module_doc_template.md` for structure. + +## Required Sections + +1. **Introduction** — what the module does, what vulnerability it exploits +2. **Vulnerable Application** — affected versions, fixed version, setup instructions +3. **Verification Steps** — numbered steps to reproduce/verify +4. **Scenarios** — must be filled out by a human with real console output + +## Rules + +- Run `ruby tools/dev/msftidy_docs.rb ` before submitting +- Module descriptions should only use ASCII characters +- Include the range of vulnerable versions and the fixed version when known +- Do NOT include sensitive information (real IPs, credentials, API keys) +- Local/private IPs are acceptable in scenario examples diff --git a/.github/instructions/library.instructions.md b/.github/instructions/library.instructions.md new file mode 100644 index 0000000000000..be6adb610bb40 --- /dev/null +++ b/.github/instructions/library.instructions.md @@ -0,0 +1,43 @@ +--- +applyTo: "lib/**/*.rb" +--- + +# Library Code Instructions + +## Error Handling + +- Use specific error classes: `Rex::RuntimeError`, `Rex::ConnectionError`, `Rex::TimeoutError`, `ArgumentError` +- NEVER `raise "bare string"` — makes targeted rescue impossible +- NEVER bare `rescue` — catches `SignalException` and `SystemExit` (hides Ctrl-C) +- Always: `rescue StandardError => e` or more specific +- Propagate with context: `raise Rex::ConnectionError, "Failed to connect to #{host}: #{e.message}"` + +## Documentation + +- Add YARD `@param` and `@return` tags to ALL public methods +- Link to RFC/spec when implementing binary or protocol parsers +- Add `# frozen_string_literal: true` to new files + +## Naming + +- Do NOT use `get_`/`set_` prefixes for accessor-style methods (use `def version` not `def get_version`) +- Method parameter names must be at least 2 characters + +## Patterns + +- Use `Rex::Stopwatch.elapsed_time` for timing +- Use `Rex::MIME::Message` for MIME (not hardcoded XML) +- Use `Rex::RandomIdentifier::Generator` for random variable names (specify target language) +- Use `RubySMB` library for SMB operations + +## Testing + +- ALL library changes require RSpec tests in `spec/` mirroring `lib/` structure +- Follow [Better Specs](https://www.betterspecs.org/) conventions +- Run: `bundle exec rspec spec/path/to/spec.rb` or `:42` for single example + +## Quality + +- Keep PRs focused — small fixes are easier to review +- When overriding `cleanup`, always call `super` +- Hash cracking implementations require test hash in `tools/dev/hash_cracker_validator.rb` diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md new file mode 100644 index 0000000000000..d21c9c68ad717 --- /dev/null +++ b/.github/instructions/modules.instructions.md @@ -0,0 +1,64 @@ +--- +applyTo: "modules/**/*.rb" +--- + +# Module Development Instructions + +## Structure Order + +1. `# frozen_string_literal: true` +2. Header comment block +3. `class MetasploitModule < Msf::Exploit::Remote` (or `Msf::Auxiliary`, `Msf::Post`) +4. `Rank = ExcellentRanking` (exploits only) +5. Protocol mixins (`include Msf::Exploit::Remote::HttpClient`, etc.) +6. Utility mixins (`include Msf::Exploit::FileDropper`, etc.) +7. `prepend Msf::Exploit::Remote::AutoCheck` — ALWAYS LAST +8. `def initialize` with `update_info` +9. `def check` (when possible) +10. `def exploit` or `def run` + +## Required Metadata + +- `'Name'` — Vendor Product Vulnerability Type +- `'Description'` — use `%q{}` for multi-line +- `'Author'` — array with role comments +- `'License'` — `MSF_LICENSE` +- `'References'` — `[['CVE', '...'], ['URL', '...']]` +- `'DisclosureDate'` — required for exploits +- `'Notes'` — required with `Stability`, `SideEffects`, `Reliability` + +## Notes Hash Values + +- **Stability:** `CRASH_SAFE`, `CRASH_SERVICE_RESTARTS`, `CRASH_SERVICE_DOWN`, `CRASH_OS_RESTARTS`, `CRASH_OS_DOWN` +- **SideEffects:** `IOC_IN_LOGS`, `ARTIFACTS_ON_DISK`, `CONFIG_CHANGES`, `ACCOUNT_LOCKOUTS`, `SCREEN_EFFECTS` +- **Reliability:** `REPEATABLE_SESSION`, `FIRST_ATTEMPT_FAIL`, `UNRELIABLE_SESSION`, `EVENT_DEPENDENT` + +## Payload Selection + +- Command execution only → `ARCH_CMD` payloads +- Only HTTP outbound (curl/wget) → fetch payload +- File write possible → dropper/EXE (`Msf::Exploit::EXE`) +- Multi-step upload → `Msf::Exploit::CmdStager` (but prefer fetch when possible) + +## Do NOT + +- Set `DefaultOptions => { 'PAYLOAD' => '...' }` unless platform-locked +- Use `cmd_exec` with string interpolation — use `create_process(exe, args: [])` +- Use `HttpFingerprint` — implement a proper `check` method instead +- Use `include` for AutoCheck — must be `prepend` +- Return bare `CheckCode::Safe` without a reason string +- Use `JSON.parse(res.body)` — use `res.get_json_document` +- Print `"#{ip}:#{port}"` — use `Rex::Socket.to_authority(ip, port)` + +## Auxiliary Modules + +- Inherit from `Msf::Auxiliary` (not `Msf::Exploit::Remote`) +- Use `def run` (not `def exploit`) +- Use `report_service` / `report_vuln` for findings + +## Post Modules + +- Inherit from `Msf::Post` +- Declare `'SessionTypes' => ['meterpreter', 'shell']` +- Use `create_process` for command execution (not `cmd_exec` with args) +- Use `Msf::OptionalSession` for modules that work with or without sessions diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 0000000000000..69a1a9a0eed75 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,37 @@ +--- +applyTo: "spec/**/*_spec.rb" +--- + +# RSpec Test Instructions + +## Conventions + +- Follow [Better Specs](https://www.betterspecs.org/) +- Mirror `lib/` structure: `lib/msf/core/exploit/remote/http_client.rb` → `spec/lib/msf/core/exploit/remote/http_client_spec.rb` +- Use `described_class` instead of repeating the class name +- One expectation per example when practical +- Use `let` and `let!` for setup, `before` for side effects + +## Running Tests + +- Single file: `bundle exec rspec spec/path/to/spec.rb` +- Single example: `bundle exec rspec spec/path/to/spec.rb:42` +- Full suite: `bundle exec rake spec` (slow — avoid during development) + +## Test Data + +- Use TEST-NET-1 (`192.0.2.0/24`) for example IP addresses — never real IPs +- Use `Rex::Text.rand_text_alphanumeric` for random test data +- Use FAKER for usernames/accounts + +## Module Specs + +- Module functional tests live in `spec/modules/` +- Test end-to-end behaviour including `check` and `exploit`/`run` methods + +## What to Test + +- Public API methods — inputs, outputs, edge cases +- Error handling paths — verify correct exception classes raised +- Protocol parsing — round-trip encode/decode +- Version comparison logic — boundary conditions diff --git a/AGENTS.md b/AGENTS.md index 00eb277162429..361f646bb0234 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,40 +29,244 @@ Metasploit Framework is an open-source penetration testing and exploitation fram - Don't use `get_`/`set_` prefixes for accessor methods in new code - Method parameter names must be at least 2 characters (exception for well-known crypto abbreviations) +## Module Structure Templates + +### Exploit Module Template + +New exploit modules should follow this canonical structure and ordering: + +```ruby +# frozen_string_literal: true + +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + # 1. Protocol mixins first + include Msf::Exploit::Remote::HttpClient + # 2. Utility/feature mixins second + include Msf::Exploit::FileDropper + # 3. Reporting mixins (if needed) + # include Msf::Auxiliary::Report + # 4. AutoCheck ALWAYS LAST — must be prepend, not include + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Vendor Product Vulnerability Type', + 'Description' => %q{ + Description of the vulnerability and what this module does. + }, + 'Author' => [ + 'Discoverer Name', # Vulnerability discovery + 'Module Author' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-XXXXX'], + ['URL', 'https://example.com/advisory'] + ], + 'Targets' => [ + [ + 'Automatic', + { + 'Platform' => ['linux'], + 'Arch' => [ARCH_CMD], + 'Type' => :cmd + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-01-01', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [IOC_IN_LOGS], + 'Reliability' => [REPEATABLE_SESSION] + } + ) + ) + end + + def check + # Always return CheckCode with a reason string + CheckCode::Safe('Target is not vulnerable') + end + + def exploit + # Exploitation logic + end +end +``` + +### Auxiliary Module Template + +Auxiliary modules use `def run` (not `exploit`) and inherit from `Msf::Auxiliary`: + +```ruby +# frozen_string_literal: true + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Vendor Product Scanner/Gatherer', + 'Description' => %q{ + Description of what this module discovers or does. + }, + 'Author' => ['Author Name'], + 'License' => MSF_LICENSE, + 'References' => [['CVE', '2024-XXXXX']], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [] + } + ) + ) + + register_options([ + OptString.new('TARGETURI', [true, 'Base path', '/']) + ]) + end + + def check + CheckCode::Safe('Target is not affected') + end + + def run + # Main logic — use report_service, report_vuln, print_good, etc. + end +end +``` + +### Post Module Template + +Post modules inherit from `Msf::Post`, require a session, and declare compatible session types: + +```ruby +# frozen_string_literal: true + +class MetasploitModule < Msf::Post + include Msf::Post::File + include Msf::Post::Linux::System + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Platform Subsystem Gather/Action', + 'Description' => %q{ + Description of what this post module does on the target. + }, + 'Author' => ['Author Name'], + 'License' => MSF_LICENSE, + 'Platform' => ['linux'], + 'SessionTypes' => ['meterpreter', 'shell'], + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [] + } + ) + ) + end + + def run + # Use create_process, file_exist?, read_file, etc. + # Access session via `session` method + end +end +``` + +### Notes Hash Reference + +The `Notes` hash declares the module's operational characteristics: + +| Key | Values | Meaning | +|-----|--------|---------| +| `Stability` | `CRASH_SAFE`, `CRASH_SERVICE_RESTARTS`, `CRASH_SERVICE_DOWN`, `CRASH_OS_RESTARTS`, `CRASH_OS_DOWN` | Impact on target stability | +| `SideEffects` | `IOC_IN_LOGS`, `ARTIFACTS_ON_DISK`, `CONFIG_CHANGES`, `ACCOUNT_LOCKOUTS`, `SCREEN_EFFECTS`, `AUDIO_EFFECTS`, `PHYSICAL_EFFECTS` | Observable traces left on target | +| `Reliability` | `REPEATABLE_SESSION`, `FIRST_ATTEMPT_FAIL`, `UNRELIABLE_SESSION`, `EVENT_DEPENDENT` | How reliably the module succeeds | + +### Mixin Ordering + +Follow this order for includes and prepends in module classes: + +1. **Protocol mixins** — `Msf::Exploit::Remote::HttpClient`, `RubySMB`, `Msf::Exploit::Remote::Udp`, etc. +2. **Utility/feature mixins** — `Msf::Exploit::FileDropper`, `Msf::Exploit::CmdStager`, `Msf::Exploit::EXE`, etc. +3. **Reporting mixins** — `Msf::Auxiliary::Report` +4. **Post mixins** (if needed) — `Msf::Post::File`, `Msf::Post::Linux::Priv`, etc. +5. **`prepend Msf::Exploit::Remote::AutoCheck`** — always last, after all includes + +AutoCheck must use `prepend`, not `include` (the module raises `NotImplementedError` if included). It wraps the `exploit`/`run` method to automatically call `check` before exploitation. + ### Module Development +#### Metadata and Structure + - Prefer writing modules in Ruby. Go and Python modules are accepted, but their external runtimes don't support the full framework API (e.g. network pivoting). Ruby modules do not have this limitation - Prefer using hash over an array for return values, and use kwargs for reusable APIs for future extensions - Before writing a new module, check that there is not an existing module or open pull request that already covers the same functionality -- Each module should be in its own file under the appropriate `modules/` subdirectory. In some scenarios adding module actions or targets is preferred. +- Each module should be in its own file under the appropriate `modules/` subdirectory. In some scenarios adding module actions or targets is preferred - Exploits require a `DisclosureDate` field -- Exploits, auxiliary, and post modules require `Notes` with `SideEffects` -- Use the module mixin APIs — don't reinvent the wheel -- Use `create_process(executable, args: [], time_out: 15, opts: {})` instead of the deprecated `cmd_exec` with separate arguments +- Exploits, auxiliary, and post modules require `Notes` with `Stability`, `SideEffects`, and `Reliability` - License new code with `MSF_LICENSE` (the project default, defined in `lib/msf/core/constants.rb`) -- When overriding `cleanup`, always call `super` to ensure the parent mixin chain cleans up connections and sessions properly -- When possible don't set a default payload (`DefaultOptions` with `'PAYLOAD'`) in modules — let the framework choose the most appropriate payload automatically -- New modules require an associated markdown file in the `documentation/modules` folder with the same structure, including steps to set up the vulnerable environment for testing. The Scenarios section must be filled out by a human at all times. Follow `documentation/modules/module_doc_template.md` as a template. - Module descriptions or documentation should list the range of vulnerable versions and the fixed version of the affected software, when known - Module descriptions should only use ASCII characters -- `report_service` method called when a service can be reported -- `report_vuln` method called when a vuln can be reported +- New modules require an associated markdown file in the `documentation/modules` folder with the same structure, including steps to set up the vulnerable environment for testing. The Scenarios section must be filled out by a human at all times. Follow `documentation/modules/module_doc_template.md` as a template +- If there's only one `ACTION` in the exploit, it can likely be omitted + +#### Payloads and Targets + +- When possible don't set a default payload (`DefaultOptions` with `'PAYLOAD'`) in modules — let the framework choose the most appropriate payload automatically +- Define bad characters instead of explicitly base-64 encoding payloads +- Don't check the number of sessions at the end of an exploit and report success based on that — not all payloads open sessions +- Don't submit any kind of opaque binary blob — everything must include source code and build instructions + +**Payload selection guidance:** + +| Scenario | Approach | +|----------|----------| +| Only command execution available (no file write) | Use `ARCH_CMD` payloads | +| Only HTTP(S) outbound (curl/wget available) | Use fetch payload (`Msf::Exploit::Remote::HttpServer` + fetch handler) | +| File write possible on target | Use dropper/EXE payload (`Msf::Exploit::EXE`) | +| Full command stager needed (multi-step upload) | Use `Msf::Exploit::CmdStager` — but prefer fetch when only download mechanisms are available | + +#### File and Network Operations + +- When overriding `cleanup`, always call `super` to ensure the parent mixin chain cleans up connections and sessions properly +- When opening a file, make sure the file exists first +- Don't print host information like `#{ip}:#{port}` because it doesn't handle IPv6 addresses — use `#{Rex::Socket.to_authority(ip, port)}` +- Use the TEST-NET-1 range for example / non-routeable IP addresses in unit tests and spec files: `192.0.2.0`. Local/private IPs are fine in module documentation scenarios + +#### Output and Reporting + +- All `print_*` calls should start with a capital letter +- Call `report_service` when a service can be reported +- Call `report_vuln` when a vulnerability can be reported - When creating a fake account / username use FAKER not `rand_test_alphanumeric` -- Always use `res.get_json_document` to convert an HTTP response to a hash instead of calling `JSON.parse(res.body)` -- If there's only one `ACTION` in the exploit, it can likely be omitted. -- `Msf::Exploit::SQLi` should be used if it's exploiting an SQLi -- All `print_*` calls should start with a capital -- when opening a file, make sure the file exists first -- when checking for a string in a response - will it always be in english? + +#### Session and Post-Exploitation + +- Use `create_process(executable, args: [], time_out: 15, opts: {})` instead of the deprecated `cmd_exec` with separate arguments +- Use `Msf::OptionalSession` for modules that work both with and without an existing session (e.g. local exploits that can also run standalone) +- Use the module mixin APIs — don't reinvent the wheel + +#### Internationalisation Considerations + +- When checking for a string in a response — will it always be in English? - Ensure hardcoded strings being regex'ed will be consistent across multiple versions -- Use the TEST-NET-1 range for example / non-routeable IP addresses in unit tests and spec files: `192.0.2.0`. Local/private IPs are fine in module documentation scenarios. -- Use fetch payload instead of command stagers when only options that request the stage are available (i.e. don’t use a cmd stager and only allow curl/wget). -- Define bad characters instead of explicitly base-64 encoding payloads -- Use `ARCH_CMD` payloads instead of command stagers when only curl/wget and other download mechanisms would be available -- Don’t check the number of sessions at the end of an exploit and report success based on that, not all payloads open sessions -- Don’t submit any kind of opaque binary blob, everything must include source code and build instructions -- Don’t print host information like `#{ip}:#{port}` because it doesn’t handle IPv6 addresses, instead use `#{Rex::Socket.to_authority(ip, port)}` -- Implement a `check` method when possible to allow users to verify vulnerability before exploitation ### Check Methods @@ -72,49 +276,142 @@ Metasploit Framework is an open-source penetration testing and exploitation fram - Use `fail_with(Failure::UnexpectedReply, '...')` (and other `Failure::*` constants) to bail out of `exploit`/`run` methods — don't use `raise` or bare `return` for error conditions - `get_version` methods should return a REX version - `CheckCode::Vulnerable` is only used when the vulnerability has been exploited -- `CheckCode::Appears` is only used when the application's versions has been checked` +- `CheckCode::Appears` is only used when the application's version has been checked - Always provide a human-readable reason string when returning a CheckCode, e.g. `CheckCode::Safe("Target is running patched version #{version}")` — never return a bare constant or empty call -- Use specific regular expressions or `res.get_html_document` for version extraction with CSS selectors. Don't use a generic selectors like `href .*` dot star to grab the version, be more precise. -- Do catch exceptions that may be raised and ensure a valid Check Code is returned -- Do research and determine a minimum version where the application is vulnerable, mark prior versions as safe -- Check helper methods that are used by both `#check` and `#exploit` (or `#run`) and make sure there is no condition (exception, return, etc) where `#check` could return something else than CheckCode. +- Use specific regular expressions or `res.get_html_document` for version extraction with CSS selectors. Don't use generic selectors like `href .*` to grab the version — be more precise +- Catch exceptions that may be raised and ensure a valid CheckCode is returned +- Research and determine a minimum version where the application is vulnerable; mark prior versions as safe +- Check helper methods used by both `#check` and `#exploit` (or `#run`) — ensure there is no condition (exception, return, etc.) where `#check` could return something other than a CheckCode - Prefer `prepend Msf::Exploit::Remote::AutoCheck` over manually calling `check` inside `exploit` — this lets the framework handle check-before-exploit automatically ### Library Code -- When adding complex binary or protocol parsing (e.g. BinData, RASN1, Rex::Struct2), include a code comment linking to the specification or RFC that defines the format being implemented -- Write RSpec tests for any library changes -- Follow [Better Specs](http://www.betterspecs.org/) conventions -- Write YARD documentation for public methods +When writing or modifying code in `lib/`: + +#### Error Handling +- Use specific error classes (`Rex::RuntimeError`, `Rex::ConnectionError`, `ArgumentError`, `Rex::TimeoutError`) — never `raise "bare string"` which makes targeted rescue impossible +- Use `rescue StandardError => e` or a more specific class — never bare `rescue` (it catches `SignalException` and `SystemExit`, hiding Ctrl-C and kill signals) +- Propagate errors with context: `raise Rex::ConnectionError, "Failed to connect to #{host}: #{e.message}"` + +#### Documentation and Style +- Add YARD `@param` and `@return` tags to all public methods +- Add `# frozen_string_literal: true` to new library files +- Avoid `get_`/`set_` prefixes for accessor-style methods in new code (Ruby convention: use the attribute name directly, e.g. `def version` not `def get_version`) +- Link to the specification or RFC when implementing binary/protocol parsers + +#### Quality +- Write RSpec tests for any library changes — tests live in `spec/` mirroring the `lib/` structure +- Follow [Better Specs](https://www.betterspecs.org/) conventions - Keep PRs focused — small fixes are easier to review - Any new hash cracking implementations require adding a test hash to `tools/dev/hash_cracker_validator.rb` and ensuring that passes without error ### Testing - Tests live in `spec/` mirroring the `lib/` structure -- Run tests with: `bundle exec rspec spec/path/to/spec.rb` +- Run a single spec file: `bundle exec rspec spec/path/to/spec.rb` +- Run a single example by line: `bundle exec rspec spec/path/to/spec.rb:42` +- Run the full suite: `bundle exec rake spec` (slow — prefer targeted runs during development) +- Module functional tests live under `spec/modules/` and test end-to-end behaviour +- Always run specs relevant to your change before submitting ### Preferred Libraries - Use the `RubySMB` library for SMB modules - Use `Rex::Stopwatch.elapsed_time` to track elapsed time - Use the `Rex::MIME::Message` class for MIME messages instead of hardcoding XML -- When creating random variable names prefer `Rex::RandomIdentifier::Generator` and specify the runtime language used. This avoids generating langauge keywords that would break the script. +- When creating random variable names prefer `Rex::RandomIdentifier::Generator` and specify the runtime language used. This avoids generating language keywords that would break the script +- Use `Msf::Exploit::SQLi` when exploiting SQL injection vulnerabilities ## Common Patterns -- Register options with `register_options` and `register_advanced_options` -- Use `SCREAMING_SNAKE_CASE` option names and `CamelCase` advanced option names -- Use `datastore['OPTION_NAME']` to access module options +### Options Registration + +```ruby +register_options([ + OptString.new('TARGETURI', [true, 'Base path to the application', '/']), + OptInt.new('TIMEOUT', [true, 'Request timeout in seconds', 10]), + OptBool.new('SSL', [false, 'Use SSL/TLS', false]) +]) + +register_advanced_options([ + OptString.new('UserAgent', [false, 'Custom User-Agent header']) +]) +``` + +- Use `SCREAMING_SNAKE_CASE` for standard option names and `CamelCase` for advanced option names +- Access options via `datastore['OPTION_NAME']` + +### Console Output + - Use `print_status`, `print_good`, `print_error`, `print_warning` for console output -- Use `vprint_*` variants for verbose-only output +- Use `vprint_*` variants for verbose-only output (shown when user sets `VERBOSE true`) + +### HTTP Response Handling + +```ruby +res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'api', 'version') +) + +fail_with(Failure::Unreachable, 'Target did not respond') unless res +fail_with(Failure::UnexpectedReply, "Unexpected status: #{res.code}") unless res.code == 200 + +json = res.get_json_document +fail_with(Failure::UnexpectedReply, 'Response is not valid JSON') if json.empty? + +# For HTML parsing: +html = res.get_html_document +version = html.at_css('meta[name="version"]')&.[]('content') +``` + +- Always use `res.get_json_document` — never `JSON.parse(res.body)` +- Use `res.get_html_document` with CSS selectors for HTML parsing +- Check `res` for nil (target didn't respond) before accessing `.code` or `.body` +- Use `fail_with(Failure::*, 'reason')` for error conditions in `exploit`/`run` + +### Network Operations + - Use `send_request_cgi` for HTTP requests in modules - Use `connect` / `disconnect` for TCP socket operations +- Use the `srvhost` method to access the server host — don't use `datastore['SRVHOST']` directly (enforced by `Lint/DatastoreSrvhostUsage` cop) + +## Legacy Patterns (Migration Guidance) + +These patterns exist in older code but should not be used in new modules or library code. When touching existing code that uses these patterns, prefer modernizing it: + +| Legacy Pattern | Modern Replacement | Notes | +|---------------|-------------------|-------| +| `HttpFingerprint = { :pattern => [...] }` | Implement a `check` method + `prepend AutoCheck` | HttpFingerprint is a passive fingerprinting mechanism that predates the check API | +| `cmd_exec("command #{user_input}")` | `create_process("command", args: [user_input])` | String interpolation in cmd_exec is a command injection risk; create_process separates executable from arguments by design | +| `cmd_exec(cmd, args_string, timeout)` | `create_process(cmd, args: args_array, time_out: timeout)` | Enforced by `Lint/DetectOutdatedCmdExecApi` rubocop cop | +| `DefaultOptions => { 'PAYLOAD' => '...' }` | Remove — let the framework choose automatically | Only acceptable when the module genuinely only works with a single specific payload | +| `include Msf::Exploit::Remote::AutoCheck` | `prepend Msf::Exploit::Remote::AutoCheck` | Include raises NotImplementedError; prepend is required | +| Bare `rescue` in library code | `rescue StandardError => e` | Bare rescue catches SignalException/SystemExit | +| `raise "error message"` in library code | `raise Rex::RuntimeError, "message"` | Specific classes enable targeted error handling | +| Manual `check` call inside `exploit` | `prepend AutoCheck` + separate `check` method | Let the framework handle check-before-exploit | + +### Modernizing Existing Modules + +When updating an existing module, the lowest-effort improvement is adding AutoCheck: + +```ruby +# If the module already has a `def check` method, just add this line +# after the other includes: +prepend Msf::Exploit::Remote::AutoCheck +``` + +This single addition gives users the ability to verify vulnerability before exploitation, with automatic abort if the target is not vulnerable (overridable with `set ForceExploit true`). ## Before Submitting +- Work on a topic branch — don't commit directly to `master` +- Follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages (50 char subject, 72 char body wrap) - Ensure `rubocop` and `msftidy` pass on any changed files with no new offenses - Ensure `ruby tools/dev/msftidy_docs.rb ` passes on any changed documentation markdown docs with no new offenses +- Include console output (especially `msfconsole` demonstrations) in your pull request when the changes have observable effects +- Include verification steps so reviewers can test your changes +- Reference associated issues in your pull request description (e.g., `See #1234`) ## What NOT to Do diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62fe818d1c2ec..567f29caaa7a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,18 +58,36 @@ Keeping the following in mind gives your contribution the best chance of landing * **Do** check the issue tracker to see if there is a `suggestion-module` issue for the module you want to write, and assign yourself to it if there is. * **Do** license your code as BSD 3-clause, BSD 2-clause, or MIT. * **Do** stick to the [Ruby style guide] and use [Rubocop] to find common style issues. -* **Do** set up `msftidy` to fix any errors or warnings that come up as a [pre-commit hook]. +* **Do** set up `msftidy` as a [pre-commit hook] and ensure it passes with no errors or warnings before submitting — PRs that fail msftidy will not be accepted. * **Do** use the many module mixin [API]s. * **Do** include instructions on how to setup the vulnerable environment or software. * **Do** include [Module Documentation] showing sample run-throughs. +* **Do** run `ruby tools/dev/msftidy_docs.rb ` on any module documentation markdown files and ensure it passes with no errors. * **Do** ask cve@rapid7.com for a CVE ID if this describes a new vulnerability (remember to mention your PR number!) +* **Do** add `# frozen_string_literal: true` as the first line of new module files. +* **Do** use `prepend Msf::Exploit::Remote::AutoCheck` to let the framework handle vulnerability checking before exploitation — this is preferred over manually calling `check` in your exploit method. +* **Do** include a descriptive reason string when returning CheckCode values (e.g., `CheckCode::Safe("Patched version #{v}")`) — bare constants without reasons are not accepted. * **Don't** include more than one module per pull request. * **Don't** submit new [scripts]. Scripts are shipped as examples for automating local tasks, and anything "serious" can be done with post modules and local exploits. +#### Modernizing Existing Modules +We welcome PRs that bring older modules up to current conventions. High-value improvements include: + +* **Adding AutoCheck** — if a module has a `check` method but no `prepend Msf::Exploit::Remote::AutoCheck`, adding that single line is a welcome contribution. +* **Migrating cmd_exec to create_process** — replacing `cmd_exec("cmd #{input}")` with `create_process("cmd", args: [input])` eliminates command injection risks. +* **Removing HttpFingerprint** — replacing the deprecated `HttpFingerprint` constant with a proper `check` method gives users version-aware vulnerability verification. +* **Removing unnecessary DefaultOptions PAYLOAD** — letting the framework choose the best payload improves user experience. + +Keep modernization PRs focused: one pattern fix per PR, or one subsystem at a time. Include verification steps showing the module still works after the change. See [AGENTS.md](./AGENTS.md) for the full legacy patterns table with modern replacements and the canonical module structure template. + #### Library Code * **Do** write [RSpec] tests - even the smallest change in a library can break existing code. * **Do** follow [Better Specs] - it's like the style guide for specs. * **Do** write [YARD] documentation - this makes it easier for people to use your code. +* **Do** use specific error classes (`Rex::RuntimeError`, `ArgumentError`, etc.) — never `raise "bare string"`. +* **Do** use `rescue StandardError => e` — never bare `rescue` (it swallows Ctrl-C and kill signals). +* **Do** add `# frozen_string_literal: true` as the first line of new library files. +* **Don't** use `get_`/`set_` prefixes for accessor-style methods in new code. * **Don't** fix a lot of things in one pull request. Small fixes are easier to validate. #### Bug Fixes @@ -117,4 +135,3 @@ curve, so keep it up! [YARD]:http://yardoc.org [Issues]:https://github.com/rapid7/metasploit-framework/issues [Metasploit Slack]:https://www.metasploit.com/slack -[#metasploit on Freenode IRC]:http://webchat.freenode.net/?channels=%23metasploit&uio=d4 From 879de1c642ef604cecb0c07c36652258f1496c93 Mon Sep 17 00:00:00 2001 From: Dean Welch Date: Thu, 6 Aug 2026 14:57:26 +0100 Subject: [PATCH 2/4] Update contributing documentation for error handling and test data generation --- .github/instructions/library.instructions.md | 3 ++- .github/instructions/tests.instructions.md | 2 +- AGENTS.md | 6 +++--- CONTRIBUTING.md | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/instructions/library.instructions.md b/.github/instructions/library.instructions.md index be6adb610bb40..8f690ffe863b2 100644 --- a/.github/instructions/library.instructions.md +++ b/.github/instructions/library.instructions.md @@ -8,7 +8,8 @@ applyTo: "lib/**/*.rb" - Use specific error classes: `Rex::RuntimeError`, `Rex::ConnectionError`, `Rex::TimeoutError`, `ArgumentError` - NEVER `raise "bare string"` — makes targeted rescue impossible -- NEVER bare `rescue` — catches `SignalException` and `SystemExit` (hides Ctrl-C) +- NEVER bare `rescue` — it discards the exception object, making debugging impossible +- NEVER `rescue Exception` — it catches `SignalException` and `SystemExit` (hides Ctrl-C and kill signals) - Always: `rescue StandardError => e` or more specific - Propagate with context: `raise Rex::ConnectionError, "Failed to connect to #{host}: #{e.message}"` diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index 69a1a9a0eed75..51f2312f3f249 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -22,7 +22,7 @@ applyTo: "spec/**/*_spec.rb" - Use TEST-NET-1 (`192.0.2.0/24`) for example IP addresses — never real IPs - Use `Rex::Text.rand_text_alphanumeric` for random test data -- Use FAKER for usernames/accounts +- Use the `Faker` gem (e.g. `Faker::Internet.username`) for usernames/accounts ## Module Specs diff --git a/AGENTS.md b/AGENTS.md index 361f646bb0234..ee85037b313c9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -255,7 +255,7 @@ AutoCheck must use `prepend`, not `include` (the module raises `NotImplementedEr - All `print_*` calls should start with a capital letter - Call `report_service` when a service can be reported - Call `report_vuln` when a vulnerability can be reported -- When creating a fake account / username use FAKER not `rand_test_alphanumeric` +- When creating a fake account / username use the `Faker` gem (e.g. `Faker::Internet.username`) not `Rex::Text.rand_text_alphanumeric` #### Session and Post-Exploitation @@ -290,7 +290,7 @@ When writing or modifying code in `lib/`: #### Error Handling - Use specific error classes (`Rex::RuntimeError`, `Rex::ConnectionError`, `ArgumentError`, `Rex::TimeoutError`) — never `raise "bare string"` which makes targeted rescue impossible -- Use `rescue StandardError => e` or a more specific class — never bare `rescue` (it catches `SignalException` and `SystemExit`, hiding Ctrl-C and kill signals) +- Use `rescue StandardError => e` or a more specific class — never bare `rescue` (it discards the exception object, making debugging impossible) and never `rescue Exception` (it catches `SignalException` and `SystemExit`, hiding Ctrl-C and kill signals) - Propagate errors with context: `raise Rex::ConnectionError, "Failed to connect to #{host}: #{e.message}"` #### Documentation and Style @@ -387,7 +387,7 @@ These patterns exist in older code but should not be used in new modules or libr | `cmd_exec(cmd, args_string, timeout)` | `create_process(cmd, args: args_array, time_out: timeout)` | Enforced by `Lint/DetectOutdatedCmdExecApi` rubocop cop | | `DefaultOptions => { 'PAYLOAD' => '...' }` | Remove — let the framework choose automatically | Only acceptable when the module genuinely only works with a single specific payload | | `include Msf::Exploit::Remote::AutoCheck` | `prepend Msf::Exploit::Remote::AutoCheck` | Include raises NotImplementedError; prepend is required | -| Bare `rescue` in library code | `rescue StandardError => e` | Bare rescue catches SignalException/SystemExit | +| Bare `rescue` in library code | `rescue StandardError => e` | Bare rescue discards the exception object; `rescue Exception` is worse — it catches signals/exits | | `raise "error message"` in library code | `raise Rex::RuntimeError, "message"` | Specific classes enable targeted error handling | | Manual `check` call inside `exploit` | `prepend AutoCheck` + separate `check` method | Let the framework handle check-before-exploit | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 567f29caaa7a5..495fcc0ae2431 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ Keep modernization PRs focused: one pattern fix per PR, or one subsystem at a ti * **Do** follow [Better Specs] - it's like the style guide for specs. * **Do** write [YARD] documentation - this makes it easier for people to use your code. * **Do** use specific error classes (`Rex::RuntimeError`, `ArgumentError`, etc.) — never `raise "bare string"`. -* **Do** use `rescue StandardError => e` — never bare `rescue` (it swallows Ctrl-C and kill signals). +* **Do** use `rescue StandardError => e` — never bare `rescue` (it discards the exception object) and never `rescue Exception` (it swallows Ctrl-C and kill signals). * **Do** add `# frozen_string_literal: true` as the first line of new library files. * **Don't** use `get_`/`set_` prefixes for accessor-style methods in new code. * **Don't** fix a lot of things in one pull request. Small fixes are easier to validate. From e0a9207a0c4b1924364e7e8ba2e73955b7135226 Mon Sep 17 00:00:00 2001 From: Dean Welch Date: Thu, 6 Aug 2026 16:47:00 +0100 Subject: [PATCH 3/4] Refactor contributing documentation for agents: clarify stability, side effects, and reliability notes --- .github/copilot-instructions.md | 71 ++------------------------------- AGENTS.md | 8 ++-- 2 files changed, 8 insertions(+), 71 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 75a0a3abdfd7a..1510dfc2c6b2d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,70 +1,5 @@ -# Metasploit Framework — Copilot Instructions +# Copilot Instructions -## Project Overview +Refer to [AGENTS.md](../AGENTS.md) in the repository root for all project conventions, coding standards, and AI agent guidelines. -Metasploit Framework is a Ruby penetration testing and exploitation framework. Modules (exploits, auxiliary, post, payloads, encoders, evasion) live in `modules/`. Core libraries live in `lib/msf/` and `lib/rex/`. Tests are in `spec/`. - -## Tech Stack - -- Ruby 3.1+ (see `.ruby-version`) -- RSpec for testing (`bundle exec rspec spec/path/to/spec.rb`) -- RuboCop for linting (custom cops in `lib/rubocop/cop/`) -- `tools/dev/msftidy.rb` for module-specific checks - -## Key Coding Rules - -- Add `# frozen_string_literal: true` to new files -- Use `%q{}` for multi-line module descriptions -- Don't use `get_`/`set_` prefixes for accessor methods -- All `print_*` calls start with a capital letter -- Use `Rex::Socket.to_authority(ip, port)` for host:port (IPv6 safe) -- Use `res.get_json_document` not `JSON.parse(res.body)` -- Use `fail_with(Failure::*, 'reason')` for error conditions in exploit/run methods -- Use `create_process(executable, args: [])` not `cmd_exec` with separate arguments - -## Module Structure (Exploit) - -```ruby -class MetasploitModule < Msf::Exploit::Remote - Rank = ExcellentRanking - include Msf::Exploit::Remote::HttpClient # 1. Protocol mixins - include Msf::Exploit::FileDropper # 2. Utility mixins - prepend Msf::Exploit::Remote::AutoCheck # 3. ALWAYS LAST — prepend not include - - def initialize(info = {}) - super(update_info(info, 'Name' => ..., 'Notes' => { 'Stability' => [CRASH_SAFE], 'SideEffects' => [IOC_IN_LOGS], 'Reliability' => [REPEATABLE_SESSION] })) - end - - def check - CheckCode::Safe('Reason string required') # Never bare constants - end - - def exploit; end -end -``` - -## Check Methods - -- Must return `CheckCode` values only — never raise or call `fail_with` -- `CheckCode::Vulnerable` = vulnerability was exploited; `CheckCode::Appears` = version check -- Always include a reason string: `CheckCode::Safe("Patched version #{v}")` -- Use `Rex::Version` for version comparisons -- Prefer `prepend Msf::Exploit::Remote::AutoCheck` over manual check calls - -## Library Code (`lib/`) - -- Use specific error classes (`Rex::RuntimeError`, `Rex::ConnectionError`) — never `raise "string"` -- Use `rescue StandardError => e` — never bare `rescue` -- Add YARD `@param`/`@return` to public methods -- Write RSpec tests for all library changes - -## Before Submitting - -- Run `rubocop` and `msftidy` on changed files -- Run `ruby tools/dev/msftidy_docs.rb` on documentation files -- One module per PR; keep PRs focused -- Include verification steps and console output - -## Full Reference - -See [AGENTS.md](../AGENTS.md) for complete templates (auxiliary, post modules), payload selection guidance, Notes hash reference, legacy pattern migration table, and detailed subsection guidance. +Path-scoped instructions in `.github/instructions/` provide file-type-specific guidance for modules, library code, tests, and documentation. diff --git a/AGENTS.md b/AGENTS.md index ee85037b313c9..93e10aa4d305d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,9 +85,9 @@ class MetasploitModule < Msf::Exploit::Remote 'DefaultTarget' => 0, 'DisclosureDate' => '2024-01-01', 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'SideEffects' => [IOC_IN_LOGS], - 'Reliability' => [REPEATABLE_SESSION] + 'Stability' => [], # e.g. CRASH_SAFE, CRASH_SERVICE_RESTARTS + 'SideEffects' => [], # e.g. IOC_IN_LOGS, ARTIFACTS_ON_DISK + 'Reliability' => [] # e.g. REPEATABLE_SESSION } ) ) @@ -199,6 +199,8 @@ The `Notes` hash declares the module's operational characteristics: | `SideEffects` | `IOC_IN_LOGS`, `ARTIFACTS_ON_DISK`, `CONFIG_CHANGES`, `ACCOUNT_LOCKOUTS`, `SCREEN_EFFECTS`, `AUDIO_EFFECTS`, `PHYSICAL_EFFECTS` | Observable traces left on target | | `Reliability` | `REPEATABLE_SESSION`, `FIRST_ATTEMPT_FAIL`, `UNRELIABLE_SESSION`, `EVENT_DEPENDENT` | How reliably the module succeeds | +See also: [`lib/msf/core/constants.rb`](lib/msf/core/constants.rb) for the full list of valid values with descriptions. + ### Mixin Ordering Follow this order for includes and prepends in module classes: From f1e8c95b6cb41e0bdb02fdc6f67e5dcc64950eb2 Mon Sep 17 00:00:00 2001 From: Dean Welch Date: Thu, 6 Aug 2026 17:21:00 +0100 Subject: [PATCH 4/4] Update contributing documentation for agents: clarify Notes hash requirements and provide metadata source references --- .github/instructions/modules.instructions.md | 4 ++ AGENTS.md | 46 +++++++++++++++----- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.github/instructions/modules.instructions.md b/.github/instructions/modules.instructions.md index d21c9c68ad717..a60e0a4004e64 100644 --- a/.github/instructions/modules.instructions.md +++ b/.github/instructions/modules.instructions.md @@ -29,10 +29,14 @@ applyTo: "modules/**/*.rb" ## Notes Hash Values +Required for **exploits, auxiliary, and post** modules (enforced by rubocop). Not required for payloads, encoders, nops, or evasion. + - **Stability:** `CRASH_SAFE`, `CRASH_SERVICE_RESTARTS`, `CRASH_SERVICE_DOWN`, `CRASH_OS_RESTARTS`, `CRASH_OS_DOWN` - **SideEffects:** `IOC_IN_LOGS`, `ARTIFACTS_ON_DISK`, `CONFIG_CHANGES`, `ACCOUNT_LOCKOUTS`, `SCREEN_EFFECTS` - **Reliability:** `REPEATABLE_SESSION`, `FIRST_ATTEMPT_FAIL`, `UNRELIABLE_SESSION`, `EVENT_DEPENDENT` +Valid values with descriptions: [`lib/msf/core/constants.rb`](../../lib/msf/core/constants.rb). Platform classes: [`lib/msf/core/module/platform.rb`](../../lib/msf/core/module/platform.rb). + ## Payload Selection - Command execution only → `ARCH_CMD` payloads diff --git a/AGENTS.md b/AGENTS.md index 93e10aa4d305d..f474776c0a471 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,9 +76,9 @@ class MetasploitModule < Msf::Exploit::Remote [ 'Automatic', { - 'Platform' => ['linux'], - 'Arch' => [ARCH_CMD], - 'Type' => :cmd + 'Platform' => ['linux'], # or 'win', 'osx', 'unix', 'php', 'python', 'java' + 'Arch' => [ARCH_CMD], # or ARCH_X86, ARCH_X64, ARCH_PHP, ARCH_JAVA, ARCH_PYTHON + 'Type' => :cmd # or :dropper, :psh_stager — determines payload delivery } ] ], @@ -128,9 +128,9 @@ class MetasploitModule < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [['CVE', '2024-XXXXX']], 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'SideEffects' => [], - 'Reliability' => [] + 'Stability' => [], # e.g. CRASH_SAFE + 'SideEffects' => [], # e.g. IOC_IN_LOGS + 'Reliability' => [] # e.g. REPEATABLE_SESSION } ) ) @@ -171,11 +171,11 @@ class MetasploitModule < Msf::Post }, 'Author' => ['Author Name'], 'License' => MSF_LICENSE, - 'Platform' => ['linux'], - 'SessionTypes' => ['meterpreter', 'shell'], + 'Platform' => ['linux'], # or 'win', 'osx', 'unix', 'bsd', 'solaris' + 'SessionTypes' => ['meterpreter', 'shell'], # or just ['meterpreter'] if shell won't work 'Notes' => { - 'Stability' => [CRASH_SAFE], - 'SideEffects' => [], + 'Stability' => [], # e.g. CRASH_SAFE + 'SideEffects' => [], # e.g. ARTIFACTS_ON_DISK, CONFIG_CHANGES 'Reliability' => [] } ) @@ -201,6 +201,32 @@ The `Notes` hash declares the module's operational characteristics: See also: [`lib/msf/core/constants.rb`](lib/msf/core/constants.rb) for the full list of valid values with descriptions. +**Which module types require Notes:** + +| Module Type | Notes Required? | Enforced By | +|-------------|----------------|-------------| +| Exploit | **Yes** | msftidy + rubocop (`Lint/ModuleEnforceNotes`) | +| Auxiliary | **Yes** | rubocop (`Lint/ModuleEnforceNotes`) | +| Post | **Yes** | rubocop (`Lint/ModuleEnforceNotes`) | +| Evasion | No | — | +| Payload | No | — | +| Encoder | No | — | +| Nop | No | — | + +The same `Stability`, `SideEffects`, and `Reliability` constants apply uniformly — there are no type-specific values. Payloads, encoders, and nops don't use Notes because they don't independently interact with targets. + +### Metadata Source Reference + +The inline comments in the templates above list common values but are **not exhaustive**. Consult these source files for the full set: + +| Field | Source File | Notes | +|-------|------------|-------| +| Platform | [`lib/msf/core/module/platform.rb`](lib/msf/core/module/platform.rb) | Class hierarchy — use the lowercase short name (e.g. `'linux'`, `'win'`, `'osx'`) | +| Arch | [`rex-arch` gem](https://github.com/rapid7/rex-arch/blob/master/lib/rex/arch.rb) | Constants like `ARCH_CMD`, `ARCH_X86`, `ARCH_X64`, `ARCH_PHP` etc. | +| Stability / SideEffects / Reliability | [`lib/msf/core/constants.rb`](lib/msf/core/constants.rb) | All valid Notes hash values with descriptions | +| Rank | [`lib/msf/core/constants.rb`](lib/msf/core/constants.rb) | `ManualRanking` through `ExcellentRanking` | +| CheckCode | [`lib/msf/core/exploit.rb`](lib/msf/core/exploit.rb) (line ~52) | `Vulnerable`, `Appears`, `Safe`, `Detected`, `Unknown`, `Unsupported` | + ### Mixin Ordering Follow this order for includes and prepends in module classes: