@codacy/tools-brakeman-4 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +973 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Codacy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @codacy/tools-brakeman-4
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Updating patterns](#updating-patterns)
|
|
7
|
+
- [Updating the Brakeman version](#updating-the-brakeman-version)
|
|
8
|
+
- [Development](#development)
|
|
9
|
+
- [Notes for maintainers](#notes-for-maintainers)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
Ruby on Rails security scanner using the [Brakeman](https://brakemanscanner.org/) gem. Uses the CLI execution strategy -- spawns `brakeman -f json` via `spawnTool()` and parses its JSON output.
|
|
16
|
+
|
|
17
|
+
| Property | Value |
|
|
18
|
+
| ------------- | ----------------------------------------------- |
|
|
19
|
+
| Tool ID | `Brakeman` |
|
|
20
|
+
| Codacy UUID | `c6273c22-5248-11e5-885d-feff819cdc9f` |
|
|
21
|
+
| Strategy | CLI |
|
|
22
|
+
| Languages | Ruby |
|
|
23
|
+
| Binary | `brakeman` (installed via `gem install`) |
|
|
24
|
+
| File patterns | `**/*.rb`, `**/*.erb`, `**/*.haml`, `**/*.slim` |
|
|
25
|
+
|
|
26
|
+
## Updating patterns
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm prefetch
|
|
30
|
+
git add src/patterns.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Pattern IDs are bare check names (no prefix): `SQL`, `CrossSiteScripting`, `Execute`, etc.
|
|
34
|
+
|
|
35
|
+
## Updating the Brakeman version
|
|
36
|
+
|
|
37
|
+
1. Update `BRAKEMAN_VERSION` in `src/adapter.ts`
|
|
38
|
+
2. Run `pnpm prefetch` to check for new/removed checks
|
|
39
|
+
3. Run `pnpm test` to verify compatibility
|
|
40
|
+
4. If the major version changes, create a new adapter package (`brakeman-5/`)
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm build # Build with tsup
|
|
46
|
+
pnpm test # Run tests (requires ruby + brakeman in PATH for integration tests)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
To install brakeman locally for testing:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gem install brakeman -v 4.3.1
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Notes for maintainers
|
|
56
|
+
|
|
57
|
+
- Brakeman is **Rails-only**. The adapter checks for `config/environment.rb` before running and returns empty results (not an error) for non-Rails projects.
|
|
58
|
+
- **Whole-project scanning**: Brakeman cannot target individual files. It scans the entire Rails app via `-p <path>`. Results are filtered by `ctx.targetFiles` after parsing.
|
|
59
|
+
- `--no-exit-on-warn --no-exit-on-error` ensures Brakeman always produces JSON output.
|
|
60
|
+
- `-q` suppresses informational messages so stdout contains only JSON.
|
|
61
|
+
- Pattern IDs match `check_name` from Brakeman's JSON output (not `warning_code`).
|
|
62
|
+
- Brakeman's `confidence` field (`High`/`Medium`/`Weak`) is mapped to `Issue.confidence` (1/2/3).
|
|
63
|
+
- The `errors` array in Brakeman's output contains parse errors (files Brakeman couldn't read), mapped to `AnalysisError` warnings.
|
|
64
|
+
- Config file detection supports `config/brakeman.ignore` (warning suppression file) and `config/brakeman.yml`.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
default: () => index_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_tooling2 = require("@codacy/tooling");
|
|
37
|
+
|
|
38
|
+
// src/adapter.ts
|
|
39
|
+
var import_tooling = require("@codacy/tooling");
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
42
|
+
|
|
43
|
+
// src/patterns.json
|
|
44
|
+
var patterns_default = [
|
|
45
|
+
{
|
|
46
|
+
id: "BasicAuth",
|
|
47
|
+
category: "Security",
|
|
48
|
+
severityLevel: "High",
|
|
49
|
+
title: "Avoid Using http_basic_authenticate_with with Hardcoded Passwords",
|
|
50
|
+
enabled: true,
|
|
51
|
+
parameters: []
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "BasicAuthTimingAttack",
|
|
55
|
+
category: "Security",
|
|
56
|
+
severityLevel: "Error",
|
|
57
|
+
title: "Detect Timing Attack Vulnerability in Basic Authentication",
|
|
58
|
+
enabled: false,
|
|
59
|
+
parameters: []
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "ContentTag",
|
|
63
|
+
category: "Security",
|
|
64
|
+
severityLevel: "High",
|
|
65
|
+
title: "Avoid Unescaped XSS in Calls to content_tag",
|
|
66
|
+
enabled: true,
|
|
67
|
+
parameters: []
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "CreateWith",
|
|
71
|
+
category: "Security",
|
|
72
|
+
severityLevel: "Error",
|
|
73
|
+
title: "Avoid Strong Parameters Bypass via create_with",
|
|
74
|
+
enabled: true,
|
|
75
|
+
parameters: []
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "CrossSiteScripting",
|
|
79
|
+
category: "Security",
|
|
80
|
+
severityLevel: "Error",
|
|
81
|
+
title: "Avoid Unescaped Output in Views to Prevent Cross-Site Scripting",
|
|
82
|
+
enabled: true,
|
|
83
|
+
parameters: []
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "DefaultRoutes",
|
|
87
|
+
category: "Security",
|
|
88
|
+
severityLevel: "High",
|
|
89
|
+
title: "Avoid Default Routes",
|
|
90
|
+
enabled: true,
|
|
91
|
+
parameters: []
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "Deserialize",
|
|
95
|
+
category: "Security",
|
|
96
|
+
severityLevel: "Error",
|
|
97
|
+
title: "Detect Unsafe Deserialization of Objects",
|
|
98
|
+
enabled: true,
|
|
99
|
+
parameters: []
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "DetailedExceptions",
|
|
103
|
+
category: "Security",
|
|
104
|
+
severityLevel: "High",
|
|
105
|
+
title: "Avoid Information Disclosure via Detailed Exceptions",
|
|
106
|
+
enabled: true,
|
|
107
|
+
parameters: []
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "DigestDoS",
|
|
111
|
+
category: "Security",
|
|
112
|
+
severityLevel: "Error",
|
|
113
|
+
title: "Detect Digest Authentication DoS Vulnerability",
|
|
114
|
+
enabled: true,
|
|
115
|
+
parameters: []
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "DivideByZero",
|
|
119
|
+
category: "ErrorProne",
|
|
120
|
+
severityLevel: "Error",
|
|
121
|
+
title: "Avoid Potential Division by Zero",
|
|
122
|
+
enabled: false,
|
|
123
|
+
parameters: []
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "DynamicFinders",
|
|
127
|
+
category: "Security",
|
|
128
|
+
severityLevel: "High",
|
|
129
|
+
title: "Avoid Unsafe Usage of find_by_* Dynamic Finders",
|
|
130
|
+
enabled: false,
|
|
131
|
+
parameters: []
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "EscapeFunction",
|
|
135
|
+
category: "Security",
|
|
136
|
+
severityLevel: "Error",
|
|
137
|
+
title: "Avoid Vulnerable Escape Method in Ruby on Rails Versions Before 2.3.14",
|
|
138
|
+
enabled: true,
|
|
139
|
+
parameters: []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "Evaluation",
|
|
143
|
+
category: "Security",
|
|
144
|
+
severityLevel: "Error",
|
|
145
|
+
title: "Avoid Evaluation of User Input",
|
|
146
|
+
enabled: true,
|
|
147
|
+
parameters: []
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "Execute",
|
|
151
|
+
category: "Security",
|
|
152
|
+
severityLevel: "Error",
|
|
153
|
+
title: "Detect Possible Command Injection",
|
|
154
|
+
enabled: true,
|
|
155
|
+
parameters: []
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "FileAccess",
|
|
159
|
+
category: "Security",
|
|
160
|
+
severityLevel: "Error",
|
|
161
|
+
title: "Avoid File Access Using User Input",
|
|
162
|
+
enabled: true,
|
|
163
|
+
parameters: []
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: "FileDisclosure",
|
|
167
|
+
category: "Security",
|
|
168
|
+
severityLevel: "Error",
|
|
169
|
+
title: "Detect File Existence Disclosure Vulnerability",
|
|
170
|
+
enabled: true,
|
|
171
|
+
parameters: []
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "FilterSkipping",
|
|
175
|
+
category: "Security",
|
|
176
|
+
severityLevel: "Error",
|
|
177
|
+
title: "Detect Filter Skipping Vulnerability in Ruby on Rails 3.0-3.0.9",
|
|
178
|
+
enabled: true,
|
|
179
|
+
parameters: []
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "ForgerySetting",
|
|
183
|
+
category: "Security",
|
|
184
|
+
severityLevel: "Error",
|
|
185
|
+
title: "Enforce protect_from_forgery Enabled in ActionController Subclasses",
|
|
186
|
+
enabled: true,
|
|
187
|
+
parameters: []
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "HeaderDoS",
|
|
191
|
+
category: "Security",
|
|
192
|
+
severityLevel: "Error",
|
|
193
|
+
title: "Detect Header DoS Vulnerability (CVE-2013-6414)",
|
|
194
|
+
enabled: true,
|
|
195
|
+
parameters: []
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "I18nXSS",
|
|
199
|
+
category: "Security",
|
|
200
|
+
severityLevel: "Error",
|
|
201
|
+
title: "Detect i18n Reflective XSS Vulnerability (CVE-2013-4491)",
|
|
202
|
+
enabled: true,
|
|
203
|
+
parameters: []
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "JRubyXML",
|
|
207
|
+
category: "Security",
|
|
208
|
+
severityLevel: "Error",
|
|
209
|
+
title: "Detect Vulnerable JRuby XML Parsing Backend Usage",
|
|
210
|
+
enabled: true,
|
|
211
|
+
parameters: []
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "JSONEncoding",
|
|
215
|
+
category: "Security",
|
|
216
|
+
severityLevel: "Error",
|
|
217
|
+
title: "Detect Missing JSON Encoding to Prevent XSS Vulnerability",
|
|
218
|
+
enabled: true,
|
|
219
|
+
parameters: []
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "JSONParsing",
|
|
223
|
+
category: "Security",
|
|
224
|
+
severityLevel: "Error",
|
|
225
|
+
title: "Detect JSON Parsing Vulnerabilities CVE-2013-0333 and CVE-2013-0269",
|
|
226
|
+
enabled: true,
|
|
227
|
+
parameters: []
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: "LinkTo",
|
|
231
|
+
category: "Security",
|
|
232
|
+
severityLevel: "Error",
|
|
233
|
+
title: "Warn for XSS Vulnerabilities in link_to Before Rails 3.0",
|
|
234
|
+
enabled: true,
|
|
235
|
+
parameters: []
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: "LinkToHref",
|
|
239
|
+
category: "Security",
|
|
240
|
+
severityLevel: "High",
|
|
241
|
+
title: "Ensure href values in link_to are sanitized with a url_safe_method",
|
|
242
|
+
enabled: true,
|
|
243
|
+
parameters: []
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "MailTo",
|
|
247
|
+
category: "Security",
|
|
248
|
+
severityLevel: "Error",
|
|
249
|
+
title: "Avoid mail_to XSS Vulnerability in Certain Versions",
|
|
250
|
+
enabled: true,
|
|
251
|
+
parameters: []
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "MassAssignment",
|
|
255
|
+
category: "Security",
|
|
256
|
+
severityLevel: "Error",
|
|
257
|
+
title: "Avoid Unprotected Mass Assignment",
|
|
258
|
+
enabled: true,
|
|
259
|
+
parameters: []
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "MimeTypeDoS",
|
|
263
|
+
category: "Security",
|
|
264
|
+
severityLevel: "Error",
|
|
265
|
+
title: "Detect Mime Type Denial of Service (CVE-2016-0751)",
|
|
266
|
+
enabled: false,
|
|
267
|
+
parameters: []
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: "ModelAttrAccessible",
|
|
271
|
+
category: "Security",
|
|
272
|
+
severityLevel: "High",
|
|
273
|
+
title: "Avoid Dangerous Attributes in attr_accessible Whitelist",
|
|
274
|
+
enabled: true,
|
|
275
|
+
parameters: []
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
id: "ModelAttributes",
|
|
279
|
+
category: "Security",
|
|
280
|
+
severityLevel: "High",
|
|
281
|
+
title: "Enforce use of attr_restricted and avoid attr_protected in models",
|
|
282
|
+
enabled: true,
|
|
283
|
+
parameters: []
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "ModelSerialize",
|
|
287
|
+
category: "Security",
|
|
288
|
+
severityLevel: "Error",
|
|
289
|
+
title: "Avoid Using serialize in Vulnerable Rails Versions",
|
|
290
|
+
enabled: true,
|
|
291
|
+
parameters: []
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "NestedAttributes",
|
|
295
|
+
category: "Security",
|
|
296
|
+
severityLevel: "Error",
|
|
297
|
+
title: "Detect Nested Attributes Vulnerability in Rails 2.3.9 and 3.0.0",
|
|
298
|
+
enabled: true,
|
|
299
|
+
parameters: []
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "NestedAttributesBypass",
|
|
303
|
+
category: "Security",
|
|
304
|
+
severityLevel: "Error",
|
|
305
|
+
title: "Detect Nested Attributes Vulnerability (CVE-2015-7577)",
|
|
306
|
+
enabled: false,
|
|
307
|
+
parameters: []
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "NumberToCurrency",
|
|
311
|
+
category: "Security",
|
|
312
|
+
severityLevel: "High",
|
|
313
|
+
title: "Detect Number Helpers XSS Vulnerabilities",
|
|
314
|
+
enabled: false,
|
|
315
|
+
parameters: []
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "PermitAttributes",
|
|
319
|
+
category: "Security",
|
|
320
|
+
severityLevel: "High",
|
|
321
|
+
title: "Warn on Potentially Dangerous Attributes Whitelisted via Permit",
|
|
322
|
+
enabled: false,
|
|
323
|
+
parameters: []
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: "QuoteTableName",
|
|
327
|
+
category: "Security",
|
|
328
|
+
severityLevel: "Error",
|
|
329
|
+
title: "Detect quote_table_name SQL Injection Vulnerability",
|
|
330
|
+
enabled: true,
|
|
331
|
+
parameters: []
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: "Redirect",
|
|
335
|
+
category: "Security",
|
|
336
|
+
severityLevel: "High",
|
|
337
|
+
title: "Avoid unvalidated redirects with user input in redirect_to",
|
|
338
|
+
enabled: true,
|
|
339
|
+
parameters: []
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: "RegexDoS",
|
|
343
|
+
category: "Security",
|
|
344
|
+
severityLevel: "High",
|
|
345
|
+
title: "Avoid Converting User Input to Symbol to Prevent DoS",
|
|
346
|
+
enabled: true,
|
|
347
|
+
parameters: []
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: "Render",
|
|
351
|
+
category: "Security",
|
|
352
|
+
severityLevel: "High",
|
|
353
|
+
title: "Avoid Dynamic Render Paths",
|
|
354
|
+
enabled: true,
|
|
355
|
+
parameters: []
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: "RenderDoS",
|
|
359
|
+
category: "Security",
|
|
360
|
+
severityLevel: "High",
|
|
361
|
+
title: "Warn about Denial of Service with render :text",
|
|
362
|
+
enabled: true,
|
|
363
|
+
parameters: []
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "RenderInline",
|
|
367
|
+
category: "Security",
|
|
368
|
+
severityLevel: "Error",
|
|
369
|
+
title: "Detect Cross-Site Scripting in render Calls",
|
|
370
|
+
enabled: true,
|
|
371
|
+
parameters: []
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: "ResponseSplitting",
|
|
375
|
+
category: "Security",
|
|
376
|
+
severityLevel: "Error",
|
|
377
|
+
title: "Detect Response Splitting Vulnerability in Rails 2.3.0 - 2.3.13",
|
|
378
|
+
enabled: true,
|
|
379
|
+
parameters: []
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: "RouteDoS",
|
|
383
|
+
category: "Security",
|
|
384
|
+
severityLevel: "Error",
|
|
385
|
+
title: "Detect Route Denial of Service (DoS) Vulnerability",
|
|
386
|
+
enabled: false,
|
|
387
|
+
parameters: []
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: "SafeBufferManipulation",
|
|
391
|
+
category: "Security",
|
|
392
|
+
severityLevel: "High",
|
|
393
|
+
title: "Detect Unsafe SafeBuffer Manipulation in Rails",
|
|
394
|
+
enabled: true,
|
|
395
|
+
parameters: []
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "SanitizeMethods",
|
|
399
|
+
category: "Security",
|
|
400
|
+
severityLevel: "Error",
|
|
401
|
+
title: "Detect Vulnerable sanitize and sanitize_css Methods",
|
|
402
|
+
enabled: true,
|
|
403
|
+
parameters: []
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "Secrets",
|
|
407
|
+
category: "Security",
|
|
408
|
+
severityLevel: "Error",
|
|
409
|
+
title: "Detect Secrets Stored in Source Code",
|
|
410
|
+
enabled: false,
|
|
411
|
+
parameters: []
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "SelectTag",
|
|
415
|
+
category: "Security",
|
|
416
|
+
severityLevel: "High",
|
|
417
|
+
title: "Avoid Unsafe Uses of select_tag() with Prompt in Rails 3.x",
|
|
418
|
+
enabled: true,
|
|
419
|
+
parameters: []
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: "SelectVulnerability",
|
|
423
|
+
category: "Security",
|
|
424
|
+
severityLevel: "High",
|
|
425
|
+
title: "Avoid Unsafe Uses of select() Helper",
|
|
426
|
+
enabled: true,
|
|
427
|
+
parameters: []
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: "Send",
|
|
431
|
+
category: "Security",
|
|
432
|
+
severityLevel: "Error",
|
|
433
|
+
title: "Avoid Unsafe Use of Object#send",
|
|
434
|
+
enabled: true,
|
|
435
|
+
parameters: []
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
id: "SendFile",
|
|
439
|
+
category: "Security",
|
|
440
|
+
severityLevel: "Error",
|
|
441
|
+
title: "Avoid Using User Input Directly in send_file",
|
|
442
|
+
enabled: false,
|
|
443
|
+
parameters: []
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: "SessionManipulation",
|
|
447
|
+
category: "Security",
|
|
448
|
+
severityLevel: "High",
|
|
449
|
+
title: "Avoid User Input in Session Keys",
|
|
450
|
+
enabled: false,
|
|
451
|
+
parameters: []
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "SessionSettings",
|
|
455
|
+
category: "Security",
|
|
456
|
+
severityLevel: "High",
|
|
457
|
+
title: "Enforce Secure Session Settings",
|
|
458
|
+
enabled: true,
|
|
459
|
+
parameters: []
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "SimpleFormat",
|
|
463
|
+
category: "Security",
|
|
464
|
+
severityLevel: "Error",
|
|
465
|
+
title: "Detect simple_format XSS Vulnerability (CVE-2013-6416)",
|
|
466
|
+
enabled: true,
|
|
467
|
+
parameters: []
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "SingleQuotes",
|
|
471
|
+
category: "Security",
|
|
472
|
+
severityLevel: "Error",
|
|
473
|
+
title: "Check for Versions That Do Not Escape Single Quotes",
|
|
474
|
+
enabled: true,
|
|
475
|
+
parameters: []
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "SkipBeforeFilter",
|
|
479
|
+
category: "Security",
|
|
480
|
+
severityLevel: "Error",
|
|
481
|
+
title: "Warn When Skipping CSRF or Authentication Checks by Default",
|
|
482
|
+
enabled: true,
|
|
483
|
+
parameters: []
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: "SQL",
|
|
487
|
+
category: "Security",
|
|
488
|
+
severityLevel: "Error",
|
|
489
|
+
title: "Check for SQL Injection",
|
|
490
|
+
enabled: true,
|
|
491
|
+
parameters: []
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "SQLCVEs",
|
|
495
|
+
category: "Security",
|
|
496
|
+
severityLevel: "Error",
|
|
497
|
+
title: "Detect SQL Injection Vulnerabilities in Active Record",
|
|
498
|
+
enabled: true,
|
|
499
|
+
parameters: []
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: "SSLVerify",
|
|
503
|
+
category: "Security",
|
|
504
|
+
severityLevel: "Error",
|
|
505
|
+
title: "Avoid OpenSSL::SSL::VERIFY_NONE to Enforce SSL Certificate Verification",
|
|
506
|
+
enabled: true,
|
|
507
|
+
parameters: []
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: "StripTags",
|
|
511
|
+
category: "Security",
|
|
512
|
+
severityLevel: "Error",
|
|
513
|
+
title: "Detect Vulnerabilities in strip_tags Usage",
|
|
514
|
+
enabled: true,
|
|
515
|
+
parameters: []
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "SymbolDoS",
|
|
519
|
+
category: "Security",
|
|
520
|
+
severityLevel: "High",
|
|
521
|
+
title: "Avoid Symbol Denial of Service",
|
|
522
|
+
enabled: true,
|
|
523
|
+
parameters: []
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
id: "SymbolDoSCVE",
|
|
527
|
+
category: "Security",
|
|
528
|
+
severityLevel: "Error",
|
|
529
|
+
title: "Detect ActiveRecord Symbol Denial of Service Vulnerability",
|
|
530
|
+
enabled: true,
|
|
531
|
+
parameters: []
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
id: "TranslateBug",
|
|
535
|
+
category: "Security",
|
|
536
|
+
severityLevel: "Error",
|
|
537
|
+
title: "Detect XSS Vulnerability in Translate Helper",
|
|
538
|
+
enabled: true,
|
|
539
|
+
parameters: []
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "UnsafeReflection",
|
|
543
|
+
category: "Security",
|
|
544
|
+
severityLevel: "Error",
|
|
545
|
+
title: "Detect Unsafe Reflection Usage",
|
|
546
|
+
enabled: true,
|
|
547
|
+
parameters: []
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: "UnscopedFind",
|
|
551
|
+
category: "Security",
|
|
552
|
+
severityLevel: "High",
|
|
553
|
+
title: "Avoid Unscoped ActiveRecord Queries",
|
|
554
|
+
enabled: true,
|
|
555
|
+
parameters: []
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: "ValidationRegex",
|
|
559
|
+
category: "Security",
|
|
560
|
+
severityLevel: "High",
|
|
561
|
+
title: "Enforce Proper Anchors in validates_format_of",
|
|
562
|
+
enabled: true,
|
|
563
|
+
parameters: []
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: "WeakHash",
|
|
567
|
+
category: "Security",
|
|
568
|
+
severityLevel: "High",
|
|
569
|
+
title: "Avoid Use of Weak Hashes like MD5",
|
|
570
|
+
enabled: false,
|
|
571
|
+
parameters: []
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "WithoutProtection",
|
|
575
|
+
category: "Security",
|
|
576
|
+
severityLevel: "Error",
|
|
577
|
+
title: "Avoid mass assignment using without_protection",
|
|
578
|
+
enabled: true,
|
|
579
|
+
parameters: []
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
id: "XMLDoS",
|
|
583
|
+
category: "Security",
|
|
584
|
+
severityLevel: "Error",
|
|
585
|
+
title: "Detect XML Denial of Service Vulnerability (CVE-2015-3227)",
|
|
586
|
+
enabled: true,
|
|
587
|
+
parameters: []
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
id: "YAMLParsing",
|
|
591
|
+
category: "Security",
|
|
592
|
+
severityLevel: "Error",
|
|
593
|
+
title: "Detect YAML Parsing Vulnerabilities (CVE-2013-0156)",
|
|
594
|
+
enabled: true,
|
|
595
|
+
parameters: []
|
|
596
|
+
}
|
|
597
|
+
];
|
|
598
|
+
|
|
599
|
+
// src/adapter.ts
|
|
600
|
+
var TOOL_ID = "Brakeman";
|
|
601
|
+
var ADAPTER_ID = "brakeman-4";
|
|
602
|
+
var BRAKEMAN_VERSION = "4.3.1";
|
|
603
|
+
var RUBY_VERSION_RANGE = ">=2.7.0";
|
|
604
|
+
var CONFIG_FILE_CANDIDATES = ["config/brakeman.ignore", "config/brakeman.yml"];
|
|
605
|
+
var PATTERN_META = new Map(
|
|
606
|
+
patterns_default.map((p) => [
|
|
607
|
+
p.id,
|
|
608
|
+
{
|
|
609
|
+
category: p.category,
|
|
610
|
+
severity: p.severityLevel ?? "Warning"
|
|
611
|
+
}
|
|
612
|
+
])
|
|
613
|
+
);
|
|
614
|
+
function ruleMetadata(checkName) {
|
|
615
|
+
return PATTERN_META.get(checkName) ?? { category: "Security", severity: "Warning" };
|
|
616
|
+
}
|
|
617
|
+
function mapConfidence(confidence) {
|
|
618
|
+
switch (confidence) {
|
|
619
|
+
case "High":
|
|
620
|
+
return 1;
|
|
621
|
+
case "Medium":
|
|
622
|
+
return 2;
|
|
623
|
+
default:
|
|
624
|
+
return 3;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function getLineContent(lines, lineNumber) {
|
|
628
|
+
return lines[lineNumber - 1] ?? "";
|
|
629
|
+
}
|
|
630
|
+
async function readFileLines(filePath) {
|
|
631
|
+
try {
|
|
632
|
+
const content = await import_promises.default.readFile(filePath, "utf-8");
|
|
633
|
+
return content.split("\n");
|
|
634
|
+
} catch {
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function buildResult(startedAt, completedAt, issues, errors, filesAnalyzed) {
|
|
639
|
+
const durationMs = completedAt.getTime() - startedAt.getTime();
|
|
640
|
+
return {
|
|
641
|
+
metadata: {
|
|
642
|
+
startedAt: startedAt.toISOString(),
|
|
643
|
+
completedAt: completedAt.toISOString(),
|
|
644
|
+
durationMs
|
|
645
|
+
},
|
|
646
|
+
issues,
|
|
647
|
+
errors,
|
|
648
|
+
summary: {
|
|
649
|
+
toolId: TOOL_ID,
|
|
650
|
+
status: errors.some((e) => e.level === "error") ? "partial" : "success",
|
|
651
|
+
issueCount: issues.length,
|
|
652
|
+
errorCount: errors.length,
|
|
653
|
+
durationMs,
|
|
654
|
+
filesAnalyzed
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
async function resolveToolInvocation(ctx) {
|
|
659
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
660
|
+
const scriptPath = (0, import_tooling.resolveGemBinary)(gemPaths, "brakeman");
|
|
661
|
+
const portableRubyBin = (0, import_tooling.resolvePortableRubyBinary)(ctx.globalDir, "3.2.11");
|
|
662
|
+
try {
|
|
663
|
+
await import_promises.default.access(portableRubyBin);
|
|
664
|
+
await import_promises.default.access(scriptPath);
|
|
665
|
+
return { binary: portableRubyBin, prefixArgs: [scriptPath], rubyPath: portableRubyBin };
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
const rubyInfo = await (0, import_tooling.findRuby)(RUBY_VERSION_RANGE).catch(() => null);
|
|
669
|
+
try {
|
|
670
|
+
await import_promises.default.access(scriptPath);
|
|
671
|
+
return { binary: scriptPath, prefixArgs: [], rubyPath: rubyInfo?.path };
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
return { binary: "brakeman", prefixArgs: [], rubyPath: rubyInfo?.path };
|
|
675
|
+
}
|
|
676
|
+
var adapter = {
|
|
677
|
+
id: TOOL_ID,
|
|
678
|
+
displayName: "Brakeman",
|
|
679
|
+
preferredVersion: BRAKEMAN_VERSION,
|
|
680
|
+
compatibleVersions: ">=4.0.0",
|
|
681
|
+
website: "https://brakemanscanner.org/",
|
|
682
|
+
languages: ["Ruby"],
|
|
683
|
+
filePatterns: ["**/*.rb", "**/*.erb", "**/*.haml", "**/*.slim"],
|
|
684
|
+
supportedOS: ["linux", "darwin", "win32"],
|
|
685
|
+
supportedArch: ["x64", "arm64"],
|
|
686
|
+
supportedRunners: ["local", "container"],
|
|
687
|
+
prerequisites: [
|
|
688
|
+
{
|
|
689
|
+
name: "Ruby",
|
|
690
|
+
versionRange: RUBY_VERSION_RANGE,
|
|
691
|
+
preferredVersion: "3.2",
|
|
692
|
+
checkCommand: "ruby --version"
|
|
693
|
+
}
|
|
694
|
+
],
|
|
695
|
+
/**
|
|
696
|
+
* Checks whether Brakeman is available (Ruby runtime + brakeman gem).
|
|
697
|
+
*/
|
|
698
|
+
async checkAvailability(ctx) {
|
|
699
|
+
ctx.logger.debug("Checking availability", { toolId: TOOL_ID });
|
|
700
|
+
const rubyInfo = await (0, import_tooling.findRuby)(RUBY_VERSION_RANGE, ctx.logger).catch(() => null);
|
|
701
|
+
let resolvedRubyPath = rubyInfo?.path;
|
|
702
|
+
const dependencies = rubyInfo ? [{ name: "Ruby", available: true, version: rubyInfo.version, installation: "global" }] : [{ name: "Ruby", available: false, reason: `Ruby (${RUBY_VERSION_RANGE}) not found` }];
|
|
703
|
+
if (!rubyInfo) {
|
|
704
|
+
const rubyBin = (0, import_tooling.resolvePortableRubyBinary)(ctx.globalDir, "3.2.11");
|
|
705
|
+
try {
|
|
706
|
+
await import_promises.default.access(rubyBin);
|
|
707
|
+
resolvedRubyPath = rubyBin;
|
|
708
|
+
dependencies[0] = {
|
|
709
|
+
name: "Ruby",
|
|
710
|
+
available: true,
|
|
711
|
+
version: "3.2.11",
|
|
712
|
+
installation: "codacy"
|
|
713
|
+
};
|
|
714
|
+
} catch {
|
|
715
|
+
return {
|
|
716
|
+
available: false,
|
|
717
|
+
reason: "Ruby runtime not found (required for Brakeman)",
|
|
718
|
+
installHint: "Install Ruby 2.7+, or run with --install-dependencies for auto-download",
|
|
719
|
+
dependencies
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
724
|
+
const brakemanVersion = await (0, import_tooling.checkGemTool)(
|
|
725
|
+
gemPaths,
|
|
726
|
+
"brakeman",
|
|
727
|
+
"--version",
|
|
728
|
+
/(\d+\.\d+\.\d+)/,
|
|
729
|
+
resolvedRubyPath,
|
|
730
|
+
ctx.logger
|
|
731
|
+
);
|
|
732
|
+
if (brakemanVersion) {
|
|
733
|
+
return {
|
|
734
|
+
available: true,
|
|
735
|
+
version: brakemanVersion,
|
|
736
|
+
path: (0, import_tooling.resolveGemBinary)(gemPaths, "brakeman"),
|
|
737
|
+
installation: "codacy",
|
|
738
|
+
dependencies
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
const { stdout, exitCode } = await (0, import_tooling.spawnTool)("brakeman", ["--version"], {
|
|
743
|
+
cwd: ctx.repositoryRoot,
|
|
744
|
+
logger: ctx.logger
|
|
745
|
+
});
|
|
746
|
+
if (exitCode === 0) {
|
|
747
|
+
const match = stdout.match(/(\d+\.\d+\.\d+)/);
|
|
748
|
+
if (match) {
|
|
749
|
+
return {
|
|
750
|
+
available: true,
|
|
751
|
+
version: match[1],
|
|
752
|
+
installation: "global",
|
|
753
|
+
dependencies
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
available: false,
|
|
761
|
+
reason: "Brakeman not found in Codacy gem directory or on PATH",
|
|
762
|
+
installHint: "Run with --install-dependencies to auto-install, or run: gem install brakeman",
|
|
763
|
+
dependencies
|
|
764
|
+
};
|
|
765
|
+
},
|
|
766
|
+
/**
|
|
767
|
+
* Installs Brakeman by ensuring a Ruby runtime is available and installing
|
|
768
|
+
* the brakeman gem into an isolated gem directory.
|
|
769
|
+
*/
|
|
770
|
+
async install(ctx) {
|
|
771
|
+
ctx.logger.info("Starting install", { toolId: TOOL_ID });
|
|
772
|
+
const ruby = await (0, import_tooling.ensureRuby)(ctx.globalDir, RUBY_VERSION_RANGE, ctx.logger);
|
|
773
|
+
if (!ruby) {
|
|
774
|
+
return {
|
|
775
|
+
success: false,
|
|
776
|
+
error: {
|
|
777
|
+
toolId: TOOL_ID,
|
|
778
|
+
phase: "requirementCheck",
|
|
779
|
+
kind: "MissingPrerequisite",
|
|
780
|
+
message: `Ruby (${RUBY_VERSION_RANGE}) could not be found or downloaded. Brakeman requires a Ruby runtime.`,
|
|
781
|
+
level: "error"
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
787
|
+
ctx.logger.info(`Installing Brakeman ${BRAKEMAN_VERSION} to ${gemPaths.gemHome}...`);
|
|
788
|
+
await (0, import_tooling.gemInstall)(gemPaths, ruby.path, [`brakeman:${BRAKEMAN_VERSION}`], ctx.logger);
|
|
789
|
+
const installedPath = (0, import_tooling.resolveGemBinary)(gemPaths, "brakeman");
|
|
790
|
+
ctx.logger.info(`Installed Brakeman to ${installedPath}`);
|
|
791
|
+
return { success: true, installedPath, installedVersion: BRAKEMAN_VERSION };
|
|
792
|
+
} catch (err) {
|
|
793
|
+
return {
|
|
794
|
+
success: false,
|
|
795
|
+
error: {
|
|
796
|
+
toolId: TOOL_ID,
|
|
797
|
+
phase: "toolInstall",
|
|
798
|
+
kind: "InstallError",
|
|
799
|
+
message: `Failed to install Brakeman: ${err instanceof Error ? err.message : String(err)}`,
|
|
800
|
+
level: "error"
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
},
|
|
805
|
+
/**
|
|
806
|
+
* Checks for Brakeman configuration files in the repository.
|
|
807
|
+
* Looks for `config/brakeman.ignore` or `config/brakeman.yml`.
|
|
808
|
+
*/
|
|
809
|
+
async checkLocalConfigurationFile(ctx) {
|
|
810
|
+
ctx.logger.debug("Checking for local config", { toolId: TOOL_ID });
|
|
811
|
+
for (const candidate of CONFIG_FILE_CANDIDATES) {
|
|
812
|
+
const candidatePath = import_path.default.join(ctx.repositoryRoot, candidate);
|
|
813
|
+
try {
|
|
814
|
+
await import_promises.default.access(candidatePath);
|
|
815
|
+
ctx.logger.debug("Local config found", { toolId: TOOL_ID, path: candidatePath });
|
|
816
|
+
return { found: true, path: candidatePath };
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return { found: false };
|
|
821
|
+
},
|
|
822
|
+
/**
|
|
823
|
+
* Runs Brakeman against the repository and converts the JSON output
|
|
824
|
+
* into Codacy Issue objects.
|
|
825
|
+
*
|
|
826
|
+
* Key details:
|
|
827
|
+
* - Rails detection: checks for `config/environment.rb`. Returns empty
|
|
828
|
+
* results (not an error) for non-Rails projects.
|
|
829
|
+
* - Whole-project scanning: Brakeman analyzes the entire Rails app via
|
|
830
|
+
* `-p <repoRoot>`. Individual files cannot be targeted — results are
|
|
831
|
+
* filtered by `ctx.targetFiles` after parsing.
|
|
832
|
+
* - `--no-exit-on-warn --no-exit-on-error` ensures Brakeman always
|
|
833
|
+
* produces JSON output regardless of findings.
|
|
834
|
+
* - Pattern IDs match `check_name` from Brakeman's JSON output
|
|
835
|
+
* (e.g. `SQL`, `CrossSiteScripting`).
|
|
836
|
+
* - Confidence: `High` → 1, `Medium` → 2, `Weak` → 3.
|
|
837
|
+
*
|
|
838
|
+
* Never throws — all errors are captured in the returned `errors` array.
|
|
839
|
+
*/
|
|
840
|
+
async analyze(ctx, codacyConfig) {
|
|
841
|
+
const startMs = Date.now();
|
|
842
|
+
ctx.logger.info("Starting analysis", { toolId: TOOL_ID, fileCount: ctx.targetFiles.length });
|
|
843
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
844
|
+
const issues = [];
|
|
845
|
+
const errors = [];
|
|
846
|
+
if (ctx.targetFiles.length === 0) {
|
|
847
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
await import_promises.default.access(import_path.default.join(ctx.repositoryRoot, "config", "environment.rb"));
|
|
851
|
+
} catch {
|
|
852
|
+
ctx.logger.info("Not a Rails project (config/environment.rb not found), skipping Brakeman");
|
|
853
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, 0);
|
|
854
|
+
}
|
|
855
|
+
const invocation = await resolveToolInvocation(ctx);
|
|
856
|
+
const args = [
|
|
857
|
+
...invocation.prefixArgs,
|
|
858
|
+
"-f",
|
|
859
|
+
"json",
|
|
860
|
+
"-q",
|
|
861
|
+
"--no-exit-on-warn",
|
|
862
|
+
"--no-exit-on-error",
|
|
863
|
+
"-p",
|
|
864
|
+
ctx.repositoryRoot
|
|
865
|
+
];
|
|
866
|
+
const useLocal = codacyConfig.useLocalConfigurationFile && codacyConfig.localConfigurationFile;
|
|
867
|
+
if (useLocal && codacyConfig.localConfigurationFile.includes("brakeman.ignore")) {
|
|
868
|
+
args.push("-i", codacyConfig.localConfigurationFile);
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
const gemPaths = (0, import_tooling.getGemPaths)(ctx.globalDir, ADAPTER_ID);
|
|
872
|
+
const env = (0, import_tooling.buildGemEnv)(gemPaths, invocation.rubyPath);
|
|
873
|
+
const { stdout, stderr, exitCode } = await (0, import_tooling.spawnTool)(invocation.binary, args, {
|
|
874
|
+
cwd: ctx.repositoryRoot,
|
|
875
|
+
env,
|
|
876
|
+
logger: ctx.logger
|
|
877
|
+
});
|
|
878
|
+
if (exitCode !== 0) {
|
|
879
|
+
errors.push({
|
|
880
|
+
toolId: TOOL_ID,
|
|
881
|
+
phase: "toolInvoke",
|
|
882
|
+
kind: "NonZeroExit",
|
|
883
|
+
message: `Brakeman exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`,
|
|
884
|
+
level: "error"
|
|
885
|
+
});
|
|
886
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
887
|
+
}
|
|
888
|
+
if (!stdout.trim()) {
|
|
889
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
890
|
+
}
|
|
891
|
+
let output;
|
|
892
|
+
try {
|
|
893
|
+
output = JSON.parse(stdout);
|
|
894
|
+
} catch (err) {
|
|
895
|
+
errors.push({
|
|
896
|
+
toolId: TOOL_ID,
|
|
897
|
+
phase: "outputParse",
|
|
898
|
+
kind: "MalformedOutput",
|
|
899
|
+
message: `Failed to parse Brakeman JSON output: ${err instanceof Error ? err.message : String(err)}`,
|
|
900
|
+
level: "error"
|
|
901
|
+
});
|
|
902
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
903
|
+
}
|
|
904
|
+
const enabledPatterns = new Set(codacyConfig.patterns.map((p) => p.patternId));
|
|
905
|
+
const targetSet = new Set(ctx.targetFiles);
|
|
906
|
+
const fileLineCache = /* @__PURE__ */ new Map();
|
|
907
|
+
for (const warning of output.warnings ?? []) {
|
|
908
|
+
const patternId = warning.check_name;
|
|
909
|
+
if (!patternId) continue;
|
|
910
|
+
if (enabledPatterns.size > 0 && !enabledPatterns.has(patternId)) continue;
|
|
911
|
+
const filePath = warning.file;
|
|
912
|
+
if (!filePath) continue;
|
|
913
|
+
if (targetSet.size > 0 && !targetSet.has(filePath)) continue;
|
|
914
|
+
const line = Math.max(warning.line ?? 1, 1);
|
|
915
|
+
const meta = ruleMetadata(patternId);
|
|
916
|
+
const absPath = import_path.default.resolve(ctx.repositoryRoot, filePath);
|
|
917
|
+
if (!fileLineCache.has(absPath)) {
|
|
918
|
+
fileLineCache.set(absPath, await readFileLines(absPath));
|
|
919
|
+
}
|
|
920
|
+
const fileLines = fileLineCache.get(absPath);
|
|
921
|
+
issues.push({
|
|
922
|
+
filePath,
|
|
923
|
+
patternId,
|
|
924
|
+
toolId: TOOL_ID,
|
|
925
|
+
line,
|
|
926
|
+
message: warning.message,
|
|
927
|
+
lineContent: getLineContent(fileLines, line),
|
|
928
|
+
category: meta.category,
|
|
929
|
+
severity: meta.severity,
|
|
930
|
+
confidence: mapConfidence(warning.confidence)
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
for (const brakemanError of output.errors ?? []) {
|
|
934
|
+
errors.push({
|
|
935
|
+
toolId: TOOL_ID,
|
|
936
|
+
phase: "toolInvoke",
|
|
937
|
+
kind: "ParseWarning",
|
|
938
|
+
message: `Brakeman: ${brakemanError.error}`,
|
|
939
|
+
level: "warning"
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
const stderrFiltered = stderr.split("\n").filter((l) => l.trim()).join("\n").trim();
|
|
943
|
+
if (stderrFiltered) {
|
|
944
|
+
errors.push({
|
|
945
|
+
toolId: TOOL_ID,
|
|
946
|
+
phase: "outputParse",
|
|
947
|
+
kind: "ParseWarning",
|
|
948
|
+
message: `Brakeman stderr: ${stderrFiltered}`,
|
|
949
|
+
level: "warning"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
} catch (invokeErr) {
|
|
953
|
+
errors.push({
|
|
954
|
+
toolId: TOOL_ID,
|
|
955
|
+
phase: "toolInvoke",
|
|
956
|
+
kind: "InvocationError",
|
|
957
|
+
message: `Failed to run Brakeman: ${invokeErr instanceof Error ? invokeErr.message : String(invokeErr)}`,
|
|
958
|
+
level: "error"
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
const durationMs = Date.now() - startMs;
|
|
962
|
+
ctx.logger.info("Analysis complete", {
|
|
963
|
+
toolId: TOOL_ID,
|
|
964
|
+
issueCount: issues.length,
|
|
965
|
+
errorCount: errors.length,
|
|
966
|
+
durationMs
|
|
967
|
+
});
|
|
968
|
+
return buildResult(startedAt, /* @__PURE__ */ new Date(), issues, errors, ctx.targetFiles.length);
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// src/index.ts
|
|
973
|
+
var index_default = (0, import_tooling2.defineToolAdapter)(adapter);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codacy/tools-brakeman-4",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Brakeman adapter — CLI-mode Ruby on Rails security scanner",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@codacy/tooling": "0.2.0"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Codacy <support@codacy.com> (https://www.codacy.com)",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/codacy/analysis-cli.git",
|
|
18
|
+
"directory": "packages/tools/brakeman-4"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/codacy/analysis-cli",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup src/index.ts --format cjs --dts --clean",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"lint": "eslint src/",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"prefetch": "node ../scripts/fetch-patterns.mjs c6273c22-5248-11e5-885d-feff819cdc9f src/patterns.json"
|
|
30
|
+
}
|
|
31
|
+
}
|