Adds Rubocop rule to detect leading/trailing whitespace in module metadata

This commit is contained in:
cgranleese-r7
2025-06-16 11:55:07 +01:00
parent a02dff9bb5
commit 9eef0cf13f
3 changed files with 225 additions and 0 deletions
+4
View File
@@ -23,6 +23,7 @@ require:
- ./lib/rubocop/cop/lint/deprecated_gem_version.rb
- ./lib/rubocop/cop/lint/module_enforce_notes.rb
- ./lib/rubocop/cop/lint/detect_invalid_pack_directives.rb
- ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb
Layout/SpaceBeforeBrackets:
Enabled: true
@@ -672,3 +673,6 @@ Style/UnpackFirst:
Disabling to make it easier to copy/paste `unpack('h*')` expressions from code
into a debugging REPL.
Enabled: false
Lint/DetectMetadataTrailingLeadingWhitespace:
Enabled: true
@@ -0,0 +1,90 @@
# frozen_string_literal: trueAdd commentMore actions
module RuboCop
module Cop
module Lint
# Checks for leading or trailing whitespace in Metasploit module metadata keys/values
# inside the initialize method. Recursively checks all hash and array values, except for
# keys listed in EXEMPT_KEYS.
#
# EXEMPT_KEYS can be extended to skip additional metadata fields as needed.
#
# @example
# # bad
# 'Name' => ' value '
# 'Author' => [' hd']
#
# # good
# 'Name' => 'value'
# 'Author' => ['hd']
class DetectMetadataTrailingLeadingWhitespace < Base
extend AutoCorrector
MSG = 'Metadata key or value has leading or trailing whitespace.'
EXEMPT_KEYS = %w[Description Payload].freeze
# Called for every method definition node
# Only processes the initialize method
# @param node [RuboCop::AST::DefNode]
def on_def(node)
return unless node.method_name == :initialize
node.each_descendant(:hash) do |hash_node|
hash_node.pairs.each do |pair|
key = extract_string(pair.key)
next if key && EXEMPT_KEYS.any? { |exempt| key.casecmp?(exempt) }
check_value(pair.value)
if key && (key != key.strip)
add_offense(pair.key, message: MSG) do |corrector|
corrector.replace(pair.key.loc.expression, key.strip.inspect)
end
end
end
end
end
private
# Recursively checks a value node for whitespace issues
# @param node [RuboCop::AST::Node]
def check_value(node)
case node.type
when :str, :dstr
value = extract_string(node)
if value && value != value.strip
add_offense(node, message: MSG) do |corrector|
replacement = node.sym_type? ? ":#{value.strip}" : value.strip.inspect
corrector.replace(node.loc.expression, replacement)
end
end
when :array
node.children.each { |child| check_value(child) }
when :hash
node.pairs.each do |pair|
key = extract_string(pair.key)
next if key && EXEMPT_KEYS.any? { |exempt| key.casecmp?(exempt) }
if key && key != key.strip
add_offense(pair.key, message: MSG) do |corrector|
corrector.replace(pair.key.loc.expression, key.strip.inspect)
end
end
check_value(pair.value)
end
end
end
# Extracts the string value from a node (handles str, sym, dstr)
# @param node [RuboCop::AST::Node]
# @return [String, nil]
def extract_string(node)
return unless node
if node.str_type? || node.sym_type?
node.value.to_s
elsif node.dstr_type?
# For dynamic strings, join all child string values
node.children.map { |c| c.is_a?(Parser::AST::Node) ? extract_string(c) : c.to_s }.join
end
end
end
end
end
end
@@ -0,0 +1,131 @@
# frozen_string_literal: trueAdd commentMore actions
require 'rubocop/cop/lint/detect_metadata_trailing_leading_whitespace'
require 'rubocop/rspec/support'
RSpec.describe RuboCop::Cop::Lint::DetectMetadataTrailingLeadingWhitespace, :config do
subject(:cop) { described_class.new(config) }
let(:config) { RuboCop::Config.new }
it 'registers an offense for leading/trailing whitespace in Name' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'Name' => ' value ',
^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in Author (array)' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'Author' => [
' author ',
^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
],
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in License' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'License' => ' MSF_LICENSE ',
^^^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in Privileged' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'Privileged' => ' true ',
^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in DefaultOptions (hash)' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'DefaultOptions' => {
'WfsDelay' => ' 10 ',
^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
},
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in References (array of arrays)' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'References' => [
[ ' CVE ', ' 1999-0504 ' ],
^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
],
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in Platform' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'Platform' => ' win ',
^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in Targets (array of arrays)' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'Targets' => [
[ ' Automatic ', { 'Arch' => [ ' ARCH_X86 ', ' ARCH_X64 ' ] } ],
^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
],
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in DefaultTarget' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'DefaultTarget' => ' 0 ',
^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
it 'registers an offense for leading/trailing whitespace in DisclosureDate' do
expect_offense(<<~RUBY)
def initialize(info = {})
super(update_info(info,
'DisclosureDate' => ' 1999-01-01 ',
^^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
))
end
RUBY
end
end