Skip to content

Commit b0dbe03

Browse files
authored
Merge pull request #20315 from cgranleese-r7/adds-rubocop-rule-to-detect-module-metadata-whitespace
Adds Rubocop rule to detect leading/trailing whitespace in module metadata
2 parents 6d897ea + 9eef0cf commit b0dbe03

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require:
2323
- ./lib/rubocop/cop/lint/deprecated_gem_version.rb
2424
- ./lib/rubocop/cop/lint/module_enforce_notes.rb
2525
- ./lib/rubocop/cop/lint/detect_invalid_pack_directives.rb
26+
- ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb
2627

2728
Layout/SpaceBeforeBrackets:
2829
Enabled: true
@@ -672,3 +673,6 @@ Style/UnpackFirst:
672673
Disabling to make it easier to copy/paste `unpack('h*')` expressions from code
673674
into a debugging REPL.
674675
Enabled: false
676+
677+
Lint/DetectMetadataTrailingLeadingWhitespace:
678+
Enabled: true
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: trueAdd commentMore actions
2+
3+
module RuboCop
4+
module Cop
5+
module Lint
6+
# Checks for leading or trailing whitespace in Metasploit module metadata keys/values
7+
# inside the initialize method. Recursively checks all hash and array values, except for
8+
# keys listed in EXEMPT_KEYS.
9+
#
10+
# EXEMPT_KEYS can be extended to skip additional metadata fields as needed.
11+
#
12+
# @example
13+
# # bad
14+
# 'Name' => ' value '
15+
# 'Author' => [' hd']
16+
#
17+
# # good
18+
# 'Name' => 'value'
19+
# 'Author' => ['hd']
20+
class DetectMetadataTrailingLeadingWhitespace < Base
21+
extend AutoCorrector
22+
MSG = 'Metadata key or value has leading or trailing whitespace.'
23+
EXEMPT_KEYS = %w[Description Payload].freeze
24+
25+
# Called for every method definition node
26+
# Only processes the initialize method
27+
# @param node [RuboCop::AST::DefNode]
28+
def on_def(node)
29+
return unless node.method_name == :initialize
30+
31+
node.each_descendant(:hash) do |hash_node|
32+
hash_node.pairs.each do |pair|
33+
key = extract_string(pair.key)
34+
next if key && EXEMPT_KEYS.any? { |exempt| key.casecmp?(exempt) }
35+
check_value(pair.value)
36+
if key && (key != key.strip)
37+
add_offense(pair.key, message: MSG) do |corrector|
38+
corrector.replace(pair.key.loc.expression, key.strip.inspect)
39+
end
40+
end
41+
end
42+
end
43+
end
44+
45+
private
46+
47+
# Recursively checks a value node for whitespace issues
48+
# @param node [RuboCop::AST::Node]
49+
def check_value(node)
50+
case node.type
51+
when :str, :dstr
52+
value = extract_string(node)
53+
if value && value != value.strip
54+
add_offense(node, message: MSG) do |corrector|
55+
replacement = node.sym_type? ? ":#{value.strip}" : value.strip.inspect
56+
corrector.replace(node.loc.expression, replacement)
57+
end
58+
end
59+
when :array
60+
node.children.each { |child| check_value(child) }
61+
when :hash
62+
node.pairs.each do |pair|
63+
key = extract_string(pair.key)
64+
next if key && EXEMPT_KEYS.any? { |exempt| key.casecmp?(exempt) }
65+
if key && key != key.strip
66+
add_offense(pair.key, message: MSG) do |corrector|
67+
corrector.replace(pair.key.loc.expression, key.strip.inspect)
68+
end
69+
end
70+
check_value(pair.value)
71+
end
72+
end
73+
end
74+
75+
# Extracts the string value from a node (handles str, sym, dstr)
76+
# @param node [RuboCop::AST::Node]
77+
# @return [String, nil]
78+
def extract_string(node)
79+
return unless node
80+
if node.str_type? || node.sym_type?
81+
node.value.to_s
82+
elsif node.dstr_type?
83+
# For dynamic strings, join all child string values
84+
node.children.map { |c| c.is_a?(Parser::AST::Node) ? extract_string(c) : c.to_s }.join
85+
end
86+
end
87+
end
88+
end
89+
end
90+
end
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# frozen_string_literal: trueAdd commentMore actions
2+
3+
require 'rubocop/cop/lint/detect_metadata_trailing_leading_whitespace'
4+
require 'rubocop/rspec/support'
5+
6+
RSpec.describe RuboCop::Cop::Lint::DetectMetadataTrailingLeadingWhitespace, :config do
7+
subject(:cop) { described_class.new(config) }
8+
9+
let(:config) { RuboCop::Config.new }
10+
11+
it 'registers an offense for leading/trailing whitespace in Name' do
12+
expect_offense(<<~RUBY)
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => ' value ',
16+
^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
17+
))
18+
end
19+
RUBY
20+
end
21+
22+
it 'registers an offense for leading/trailing whitespace in Author (array)' do
23+
expect_offense(<<~RUBY)
24+
def initialize(info = {})
25+
super(update_info(info,
26+
'Author' => [
27+
' author ',
28+
^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
29+
],
30+
))
31+
end
32+
RUBY
33+
end
34+
35+
it 'registers an offense for leading/trailing whitespace in License' do
36+
expect_offense(<<~RUBY)
37+
def initialize(info = {})
38+
super(update_info(info,
39+
'License' => ' MSF_LICENSE ',
40+
^^^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
41+
))
42+
end
43+
RUBY
44+
end
45+
46+
it 'registers an offense for leading/trailing whitespace in Privileged' do
47+
expect_offense(<<~RUBY)
48+
def initialize(info = {})
49+
super(update_info(info,
50+
'Privileged' => ' true ',
51+
^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
52+
))
53+
end
54+
RUBY
55+
end
56+
57+
it 'registers an offense for leading/trailing whitespace in DefaultOptions (hash)' do
58+
expect_offense(<<~RUBY)
59+
def initialize(info = {})
60+
super(update_info(info,
61+
'DefaultOptions' => {
62+
'WfsDelay' => ' 10 ',
63+
^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
64+
},
65+
))
66+
end
67+
RUBY
68+
end
69+
70+
it 'registers an offense for leading/trailing whitespace in References (array of arrays)' do
71+
expect_offense(<<~RUBY)
72+
def initialize(info = {})
73+
super(update_info(info,
74+
'References' => [
75+
[ ' CVE ', ' 1999-0504 ' ],
76+
^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
77+
^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
78+
],
79+
))
80+
end
81+
RUBY
82+
end
83+
84+
it 'registers an offense for leading/trailing whitespace in Platform' do
85+
expect_offense(<<~RUBY)
86+
def initialize(info = {})
87+
super(update_info(info,
88+
'Platform' => ' win ',
89+
^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
90+
))
91+
end
92+
RUBY
93+
end
94+
95+
it 'registers an offense for leading/trailing whitespace in Targets (array of arrays)' do
96+
expect_offense(<<~RUBY)
97+
def initialize(info = {})
98+
super(update_info(info,
99+
'Targets' => [
100+
[ ' Automatic ', { 'Arch' => [ ' ARCH_X86 ', ' ARCH_X64 ' ] } ],
101+
^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
102+
^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
103+
^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
104+
],
105+
))
106+
end
107+
RUBY
108+
end
109+
110+
it 'registers an offense for leading/trailing whitespace in DefaultTarget' do
111+
expect_offense(<<~RUBY)
112+
def initialize(info = {})
113+
super(update_info(info,
114+
'DefaultTarget' => ' 0 ',
115+
^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
116+
))
117+
end
118+
RUBY
119+
end
120+
121+
it 'registers an offense for leading/trailing whitespace in DisclosureDate' do
122+
expect_offense(<<~RUBY)
123+
def initialize(info = {})
124+
super(update_info(info,
125+
'DisclosureDate' => ' 1999-01-01 ',
126+
^^^^^^^^^^^^^^ Lint/DetectMetadataTrailingLeadingWhitespace: Metadata key or value has leading or trailing whitespace.
127+
))
128+
end
129+
RUBY
130+
end
131+
end

0 commit comments

Comments
 (0)