Skip to content

Commit d88f3b4

Browse files
committed
Feature (STRINGS-1638) - Add Brakeman, Simplecov, Sonarqube
1 parent 0dbf77a commit d88f3b4

10 files changed

+151
-3
lines changed

.github/workflows/test.yml

+40-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,46 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v4
11-
- uses: ruby/setup-ruby@319066216501fbd5e2d568f14b7d68c19fb67a5d
11+
- uses: ruby/setup-ruby@v1
1212
with:
1313
ruby-version: ${{ matrix.ruby }}
1414
- run: bundle install
15-
- run: bundle exec rspec spec
15+
- name: Run Tests (RSpec)
16+
run: bundle exec rspec spec
17+
- name: Fix coverage.json Paths Before Upload
18+
run: sed -i 's|/work/|/_work/|g' coverage/coverage.json
19+
- name: Upload Coverage
20+
uses: actions/upload-artifact@v4
21+
with:
22+
name: simple-cov-result
23+
path: coverage
24+
- name: Brakeman Scan
25+
run: bundle exec rake brakeman:sonarqube
26+
env:
27+
RAILS_APP_PATH: ./phrase
28+
- name: Upload Brakeman Result
29+
uses: actions/upload-artifact@v4
30+
with:
31+
name: brakeman-result
32+
path: sonarqube.json
33+
scan:
34+
name: Sonarqube Scan
35+
needs: [test]
36+
runs-on: ["arc-runner-spot"] # Run Sonarqube in self-hosted
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v4
40+
with:
41+
fetch-depth: 0 # Ensures full commit history for better analysis
42+
- name: Download Brakeman result
43+
uses: actions/download-artifact@v4
44+
with:
45+
name: brakeman-result
46+
- name: Download SimpleCov result
47+
uses: actions/download-artifact@v4
48+
with:
49+
name: simple-cov-result
50+
- uses: sonarsource/sonarqube-scan-action@master
51+
env:
52+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
53+
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ test/version_tmp
2323
.rspec
2424
/locales
2525
node_modules
26+
/coverage/

.overcommit.yml

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Use this file to configure the Overcommit hooks you wish to use. This will
2+
# extend the default configuration defined in:
3+
# https://github.com/sds/overcommit/blob/master/config/default.yml
4+
#
5+
# At the topmost level of this YAML file is a key representing type of hook
6+
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
7+
# customize each hook, such as whether to only run it on certain files (via
8+
# `include`), whether to only display output if it fails (via `quiet`), etc.
9+
#
10+
# For a complete list of hooks, see:
11+
# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
12+
#
13+
# For a complete list of options that you can use to customize hooks, see:
14+
# https://github.com/sds/overcommit#configuration
15+
#
16+
# Uncomment the following lines to make the configuration take effect.
17+
18+
#PreCommit:
19+
# RuboCop:
20+
# enabled: true
21+
# on_warn: fail # Treat all warnings as failures
22+
#
23+
# TrailingWhitespace:
24+
# enabled: true
25+
# exclude:
26+
# - '**/db/structure.sql' # Ignore trailing whitespace in generated files
27+
#
28+
#PostCheckout:
29+
# ALL: # Special hook name that customizes all hooks of this type
30+
# quiet: true # Change all post-checkout hooks to only display output on failure
31+
#
32+
# IndexTags:
33+
# enabled: true # Generate a tags file with `ctags` each time HEAD changes

Gemfile

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
source "https://rubygems.org"
22

33
gemspec
4+
5+
group :development, :test do
6+
gem "brakeman", require: false
7+
end
8+
9+
group :test do
10+
gem "simplecov", require: false
11+
end

Gemfile.lock

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
phraseapp-in-context-editor-ruby (2.1.0)
4+
phraseapp-in-context-editor-ruby (3.1.0)
55
i18n (~> 1.0)
66
json (~> 2.0)
77
request_store (~> 1.2)
@@ -96,6 +96,8 @@ GEM
9696
async
9797
async-pool (0.4.0)
9898
async (>= 1.25)
99+
brakeman (7.0.0)
100+
racc
99101
builder (3.2.4)
100102
concurrent-ruby (1.2.2)
101103
console (1.23.2)
@@ -104,6 +106,7 @@ GEM
104106
crass (1.0.6)
105107
date (3.3.3)
106108
diff-lcs (1.5.0)
109+
docile (1.4.1)
107110
erubi (1.12.0)
108111
faraday (2.7.10)
109112
faraday-net_http (>= 2.0, < 3.1)
@@ -153,6 +156,8 @@ GEM
153156
nio4r (2.5.9)
154157
nokogiri (1.15.4-arm64-darwin)
155158
racc (~> 1.4)
159+
nokogiri (1.15.4-x86_64-linux)
160+
racc (~> 1.4)
156161
octokit (4.25.1)
157162
faraday (>= 1, < 3)
158163
sawyer (~> 0.9)
@@ -217,6 +222,12 @@ GEM
217222
sawyer (0.9.2)
218223
addressable (>= 2.3.5)
219224
faraday (>= 0.17.3, < 3)
225+
simplecov (0.22.0)
226+
docile (~> 1.1)
227+
simplecov-html (~> 0.11)
228+
simplecov_json_formatter (~> 0.1)
229+
simplecov-html (0.13.1)
230+
simplecov_json_formatter (0.1.4)
220231
thor (1.2.2)
221232
timeout (0.4.0)
222233
timers (4.3.5)
@@ -230,12 +241,15 @@ GEM
230241

231242
PLATFORMS
232243
arm64-darwin-22
244+
x86_64-linux
233245

234246
DEPENDENCIES
247+
brakeman
235248
github_changelog_generator (~> 1.16)
236249
phraseapp-in-context-editor-ruby!
237250
rails (~> 7.0)
238251
rspec (~> 3.0)
252+
simplecov
239253

240254
BUNDLED WITH
241255
2.4.19

Rakefile

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
require "bundler/gem_tasks"
2+
require "rspec/core/rake_task"
3+
4+
RSpec::Core::RakeTask.new(:spec)
5+
6+
# Load custom tasks
7+
Dir.glob("lib/tasks/**/*.rake").each { |r| load r }
8+
9+
task default: :spec

lib/tasks/brakeman.rake

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
require "json"
2+
namespace :brakeman do
3+
desc "Starts static code anaylsis with brakeman and output as json"
4+
task :sonarqube do
5+
rails_app_path = ENV.fetch("RAILS_APP_PATH", "../phrase")
6+
brakeman_output = `bundle exec brakeman #{rails_app_path} --format json --quiet`
7+
raise "Brakeman scan failed!" if brakeman_output.strip.empty?
8+
9+
json = JSON.parse(brakeman_output)
10+
11+
severity = Hash.new("INFO").merge!(
12+
"High" => "CRITICAL",
13+
"Medium" => "MAJOR",
14+
"Weak" => "MINOR"
15+
)
16+
17+
output = json["warnings"].each_with_object("issues" => []) { |warning, memo|
18+
memo["issues"] << {
19+
"engineId" => "brakeman",
20+
"ruleId" => warning["fingerprint"],
21+
"type" => "VULNERABILITY",
22+
"severity" => severity[warning["confidence"]],
23+
"primaryLocation" => {
24+
"message" => "#{warning["warning_type"]} - #{warning["check_name"]} - #{warning["message"]}",
25+
"filePath" => warning["file"],
26+
"textRange" => {
27+
"startLine" => warning["line"]
28+
}
29+
}
30+
}
31+
}.to_json
32+
File.write("sonarqube.json", output)
33+
end
34+
end

sonar-project.properties

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sonar.projectKey=phrase_phraseapp-in-context-editor-ruby_bb7b7e1c-eaab-4088-a420-1ecdf59d9c3c
2+
sonar.externalIssuesReportPaths=sonarqube.json
3+
sonar.exclusions=**/*.java
4+
sonar.ruby.coverage.reportPaths=coverage.json
5+
sonar.sourceEncoding=UTF-8

sonarqube.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"issues":[{"engineId":"brakeman","ruleId":"08e25268ae8e35e855590a35a27fd4013cd9f9f67cfb4cbb382f1a18f8be8341","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/distributions/show.html.slim","textRange":{"startLine":107}}},{"engineId":"brakeman","ruleId":"0db3ef74774162cc4d79fcb5a7935cef5e294f5da5dfd72389add5deae743e65","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped parameter value","filePath":"app/views/job_templates/show.html.slim","textRange":{"startLine":23}}},{"engineId":"brakeman","ruleId":"0e5269ad8b5b9e6f1d75b1ee605dfeb73400cffc2990aae9c42e4df7dabfd05d","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/glossaries/show.html.slim","textRange":{"startLine":28}}},{"engineId":"brakeman","ruleId":"0f816fa8cda89e9d63d41785020e20b89dc37f63b4f32d1cef38aea30045c223","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"Mass Assignment - PermitAttributes - Potentially dangerous key allowed for mass assignment","filePath":"app/controllers/api/v2/invitations_controller.rb","textRange":{"startLine":127}}},{"engineId":"brakeman","ruleId":"21dea1e31be35b194d2932d512a2827c1e6fe01bd9c62b3b88a9ad59ed55a0a8","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/jobs/show.html.slim","textRange":{"startLine":12}}},{"engineId":"brakeman","ruleId":"23450bbd513dfb9aa7fcd3e35da90619e18cabc650b112acd5f42eb16b8d77a2","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/distributions/index.html.slim","textRange":{"startLine":33}}},{"engineId":"brakeman","ruleId":"2ab62f173251b441dc075b463895ac5dddc9f59059593f9e49bda1fee8feced2","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/teams/_list.html.slim","textRange":{"startLine":4}}},{"engineId":"brakeman","ruleId":"2ea3847c3582686c978b2c8cb419316bf01869d7783daf840b38eabcc998ee18","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/glossaries/index.html.slim","textRange":{"startLine":45}}},{"engineId":"brakeman","ruleId":"3ccb435732be1b6ba63995df445a5b4ab8feb30597780581051bbc69db936a68","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/services/accounts/account_level_search_service.rb","textRange":{"startLine":96}}},{"engineId":"brakeman","ruleId":"4706108b689e3f300499de09dbb3dbd0098a4ea4ba9fca85b84fd483e5ec58d4","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/glossaries/show.html.slim","textRange":{"startLine":64}}},{"engineId":"brakeman","ruleId":"47e42d5bf6a3c0276bab1cd14a9f20bbab837b0422bc05e753ad5838ea68596c","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/notifications/notification_modals/_role_changed_body.html.slim","textRange":{"startLine":6}}},{"engineId":"brakeman","ruleId":"4d99d6ac5864219aa7543e4054cdd8a77886b368f9a18edc5fba307c0d2fa872","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/models/custom_metadata_labels_translation_key.rb","textRange":{"startLine":45}}},{"engineId":"brakeman","ruleId":"552426947c63ce6245f20254e1f76b44c3468a3ee9ecd85a7141199c921ff538","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/integrations/webhooks/update.js.erb","textRange":{"startLine":5}}},{"engineId":"brakeman","ruleId":"5a8461bee1d8392bd62d312b10bfdd5bcaf71eceb7804ec50e8ec324b6a8e5c8","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/models/translation_key_repository.rb","textRange":{"startLine":107}}},{"engineId":"brakeman","ruleId":"5bf4f8abbe33879b732531cca4acc7191bcb8159c3c5066ebbdd5e785212ee0e","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/invitations/show.html.slim","textRange":{"startLine":17}}},{"engineId":"brakeman","ruleId":"6628bd98f6f2165ad6819298214580a2525573da0fdff3cf0828c03336119d8f","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"lib/migrators/translation_history_events_migrator.rb","textRange":{"startLine":125}}},{"engineId":"brakeman","ruleId":"6b737e919335d69666724fc05d47f446a2485d2d0d28f71ab38dca98bea1bf2c","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/models/key_search/api_query_parser.rb","textRange":{"startLine":69}}},{"engineId":"brakeman","ruleId":"7247696621bc12bc2d04a93e845eb9f69caffba545cfb9c6360c0ddfb569a2a9","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"Mass Assignment - PermitAttributes - Potentially dangerous key allowed for mass assignment","filePath":"app/controllers/account_memberships_controller.rb","textRange":{"startLine":189}}},{"engineId":"brakeman","ruleId":"73259845d06eea91ca3efe18cdc2de6fca2be05720b2bc22dfa2aef7ca4296d1","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"lib/migrators/translation_history_events_migrator.rb","textRange":{"startLine":122}}},{"engineId":"brakeman","ruleId":"76962d9ebb5449c19d0489ac61dda14847954ffe91d2086e2e0ebba3b37ec380","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/translation_orders/show.html.slim","textRange":{"startLine":11}}},{"engineId":"brakeman","ruleId":"7874a767d0541e941f845ee61f3332d54ea0044fc7d870f78a99376ea0cff022","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/accounts/show.html.slim","textRange":{"startLine":70}}},{"engineId":"brakeman","ruleId":"807a5c202b510ea856df45cbb30908deda5d8ecb3a0b728e82ef979adf31d65e","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped parameter value","filePath":"app/views/global_job_templates/show.html.slim","textRange":{"startLine":26}}},{"engineId":"brakeman","ruleId":"81d91e783ed4b12bbaf6d6152893b53250b72325c1d0abe0b503047099fc4ddc","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/uploads/index.html.slim","textRange":{"startLine":34}}},{"engineId":"brakeman","ruleId":"85ee0495888c95cad837a9857ceca6dd75ebe3782c7c201e2b4caa9481881514","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Cross-Site Scripting - CrossSiteScripting - Unescaped model attribute","filePath":"app/views/jobs/briefing.html.slim","textRange":{"startLine":5}}},{"engineId":"brakeman","ruleId":"8caa40d647debf1a725b5d6736fef4f2c0ccf32041a2b178871d34e85f45d8af","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Remote Code Execution - Deserialize - Use of `Marshal.load` may be dangerous","filePath":"app/jobs/notifications/event_dispatcher_job.rb","textRange":{"startLine":17}}},{"engineId":"brakeman","ruleId":"90b357989c7ef724ecaf9e74dc2e8d764847934be13f4fa4eea46e3e3c57158e","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"config/initializers/008_detect_table_charsets.rb","textRange":{"startLine":8}}},{"engineId":"brakeman","ruleId":"a20dd627eac6b86fb13b519210b0c0398ba8904e8a9e569787ae8afe0c7757f6","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/job_templates/_list.html.slim","textRange":{"startLine":3}}},{"engineId":"brakeman","ruleId":"b1931d956da1e2831beb5affbdf60b697faa103f9af4fbefa2859faa8e16294d","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/use_cases/projects/merge_branch_into_main.rb","textRange":{"startLine":492}}},{"engineId":"brakeman","ruleId":"d3e6df834a9420c432b7bfe01126edad612f3dbf1019714c1acd15abd7b41316","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/use_cases/projects/merge_translation_version.rb","textRange":{"startLine":82}}},{"engineId":"brakeman","ruleId":"dc5fcbce6e057e769417eb3b2e27eb228332318b3be11dd9bd30d0a577558dff","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"Dynamic Render Path - Render - Render path contains parameter value","filePath":"app/views/oauth_access_tokens/index.html.slim","textRange":{"startLine":24}}},{"engineId":"brakeman","ruleId":"e1959a3705a29a1204c705dda94ccf892d3260f1439604231ba7eed6ed8e8701","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"lib/migrators/translation_history_events_migrator.rb","textRange":{"startLine":130}}},{"engineId":"brakeman","ruleId":"e4f6c9170e1cc37531ac7331aaf0e4b7dafa2c9a44975e63e9460d402efdb80f","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"Mass Assignment - MassAssignment - Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys","filePath":"app/controllers/documents_controller.rb","textRange":{"startLine":125}}},{"engineId":"brakeman","ruleId":"f4d337ab6acc33a7ffbf877040b328e46b05cdef9fb4890e765b2ea129a666ba","type":"VULNERABILITY","severity":"MINOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/services/export/locale_export.rb","textRange":{"startLine":214}}},{"engineId":"brakeman","ruleId":"f715f05a0b9a90df4a7a8905a78f294f244581d1d71a51f44b31225e05d47eb8","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"app/services/accounts/account_level_search_service.rb","textRange":{"startLine":127}}},{"engineId":"brakeman","ruleId":"f7b684e56922760b5e958b3eacddf008d4553383bb890de640fb8d3861bafc38","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"lib/migrators/translation_history_events_migrator.rb","textRange":{"startLine":135}}},{"engineId":"brakeman","ruleId":"f7b684e56922760b5e958b3eacddf008d4553383bb890de640fb8d3861bafc38","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"SQL Injection - SQL - Possible SQL injection","filePath":"lib/migrators/translation_history_events_migrator.rb","textRange":{"startLine":66}}},{"engineId":"brakeman","ruleId":"fed7dd6d2439ea47674346cb6379ca7b162303609709bea2ac022f2d3ebabbdd","type":"VULNERABILITY","severity":"MAJOR","primaryLocation":{"message":"Mass Assignment - PermitAttributes - Potentially dangerous key allowed for mass assignment","filePath":"app/controllers/api/v2/members_controller.rb","textRange":{"startLine":111}}}]}

spec/spec_helper.rb

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require "rspec"
22
require "rails/engine"
33
require "rails/generators"
4+
require "simplecov"
5+
require "simplecov_json_formatter"
46

57
require "phraseapp-in-context-editor-ruby"
68

@@ -13,3 +15,6 @@
1315
RequestStore.store[:phraseapp_config] = nil
1416
end
1517
end
18+
19+
SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
20+
SimpleCov.start

0 commit comments

Comments
 (0)