Class: SecurityPentestPlanner::ContractAnalyzer

Inherits:
Object
  • Object
show all
Defined in:
lib/security_pentest_planner/contract_analyzer.rb

Defined Under Namespace

Classes: RedFlag

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parser, scope_paths: nil, include_datalake: false) ⇒ ContractAnalyzer

Returns a new instance of ContractAnalyzer.



9
10
11
12
13
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 9

def initialize(parser, scope_paths: nil, include_datalake: false)
  @parser = parser
  @scope_paths = scope_paths
  @include_datalake = include_datalake
end

Instance Attribute Details

#include_datalakeObject (readonly)

Returns the value of attribute include_datalake.



7
8
9
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 7

def include_datalake
  @include_datalake
end

#parserObject (readonly)

Returns the value of attribute parser.



7
8
9
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 7

def parser
  @parser
end

#scope_pathsObject (readonly)

Returns the value of attribute scope_paths.



7
8
9
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 7

def scope_paths
  @scope_paths
end

Instance Method Details

#analytics_endpoint?Boolean

Returns:

  • (Boolean)


59
60
61
62
63
64
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 59

def analytics_endpoint?
  scoped_endpoints.any? do |ep|
    ep.path.match?(/metric|analytic|report|channel|insight|dashboard|aggregate/i) ||
      ep.summary.to_s.match?(/metric|analytic|report|aggregate/i)
  end
end

#date_param_namesObject



134
135
136
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 134

def date_param_names
  date_params.map(&:name)
end

#date_paramsObject



28
29
30
31
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 28

def date_params
  scoped_endpoints.flat_map { |ep| parameters_for(ep).select(&:date_param?) }
                  .uniq { |p| p.name }
end

#enum_paramObject



138
139
140
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 138

def enum_param
  enum_params.first
end

#enum_paramsObject



33
34
35
36
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 33

def enum_params
  scoped_endpoints.flat_map { |ep| parameters_for(ep).select(&:enum_param?) }
                  .uniq { |p| p.name }
end

#has_date_range?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 47

def has_date_range?
  date_params.any?
end

#has_enum_params?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 51

def has_enum_params?
  enum_params.any?
end

#has_free_text_headers?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 55

def has_free_text_headers?
  timezone_headers.any? || tenant_headers.any? { |h| !h.enum_values.any? }
end

#has_tenant_scoping?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 43

def has_tenant_scoping?
  tenant_headers.any?
end

#hostObject



126
127
128
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 126

def host
  parser.servers.first || "api.example.com"
end

#primary_endpointObject



122
123
124
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 122

def primary_endpoint
  scoped_endpoints.first
end

#red_flagsObject



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
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 66

def red_flags
  flags = []

  if parser.security_schemes.empty? && scoped_endpoints.any?
    flags << RedFlag.new(
      message: "Ausência de `securitySchemes` documentado no contrato OpenAPI",
      severity: :high
    )
  end

  tenant_headers.each do |header|
    next if header.required

    flags << RedFlag.new(
      message: "Header `#{header.name}` documentado como opcional (`required: false`)",
      severity: :high
    )
  end

  if tenant_headers.map(&:name).uniq.size > 1
    flags << RedFlag.new(
      message: "Divergência de nomes de header de tenant: #{tenant_headers.map(&:name).uniq.join(' vs ')}",
      severity: :medium
    )
  end

  timezone_headers.each do |header|
    next if header.enum_values.any?

    flags << RedFlag.new(
      message: "Header `#{header.name}` como string livre (sem enum/whitelist IANA)",
      severity: :medium
    )
  end

  if analytics_endpoint?
    flags << RedFlag.new(
      message: "Nenhum rate limiting documentado em endpoint analítico",
      severity: :medium
    )
  end

  flags
end

#scoped_endpointsObject



15
16
17
18
19
20
21
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 15

def scoped_endpoints
  endpoints = parser.endpoints
  return endpoints if scope_paths.nil? || scope_paths.empty?

  paths = Array(scope_paths)
  endpoints.select { |ep| paths.any? { |p| ep.path == p || ep.path.start_with?(p) } }
end

#signalsObject



111
112
113
114
115
116
117
118
119
120
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 111

def signals
  {
    tenant_scoping: has_tenant_scoping?,
    date_range: has_date_range?,
    enum_params: has_enum_params?,
    free_text_headers: has_free_text_headers?,
    analytics: analytics_endpoint?,
    datalake: include_datalake
  }
end

#tenant_header_nameObject



130
131
132
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 130

def tenant_header_name
  tenant_headers.first&.name || "X-Tenant-ID"
end

#tenant_headersObject



23
24
25
26
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 23

def tenant_headers
  scoped_endpoints.flat_map { |ep| parameters_for(ep).select(&:tenant_header?) }
                  .uniq { |p| p.name }
end

#timezone_headersObject



38
39
40
41
# File 'lib/security_pentest_planner/contract_analyzer.rb', line 38

def timezone_headers
  scoped_endpoints.flat_map { |ep| parameters_for(ep).select(&:timezone_header?) }
                  .uniq { |p| p.name }
end