diff --git a/.github/workflows/brakeman.yml b/.github/workflows/brakeman.yml
new file mode 100644
index 000000000..d820e8c86
--- /dev/null
+++ b/.github/workflows/brakeman.yml
@@ -0,0 +1,25 @@
+name: Brakeman Security Scan
+
+on:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ brakeman:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: .ruby-version
+ bundler-cache: true
+
+ - name: Install dependencies
+ run: bundle install --jobs 4 --retry 3
+
+ - name: Run Brakeman
+ run: bundle exec brakeman --ignore-config config/brakeman.ignore --exit-on-warn --quiet
diff --git a/Gemfile b/Gemfile
index 8c0bf4a43..b56578b32 100644
--- a/Gemfile
+++ b/Gemfile
@@ -79,4 +79,6 @@ group :development, :test do
# NOTE: This enable GitHub Codespaces. Uncomment for YAGNI.
# https://github.com/coderdojo-japan/coderdojo.jp/pull/1526
#gem 'mini_racer'
+
+ gem 'brakeman', require: false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index d370cfb37..19ccb1e89 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -109,6 +109,8 @@ GEM
bootstrap-sass (3.4.1)
autoprefixer-rails (>= 5.2.1)
sassc (>= 2.0.0)
+ brakeman (7.1.0)
+ racc
builder (3.3.0)
capybara (3.40.0)
addressable
@@ -489,6 +491,7 @@ DEPENDENCIES
aws-sdk-s3 (~> 1)
bootsnap
bootstrap-sass
+ brakeman
capybara
connpass_api_v2
csv
diff --git a/app/views/pokemons/show.html.erb b/app/views/pokemons/show.html.erb
index eb4d26ed5..794fb34f3 100644
--- a/app/views/pokemons/show.html.erb
+++ b/app/views/pokemons/show.html.erb
@@ -12,7 +12,7 @@
ボタンをクリックして、
ポケモン素材をダウンロードしよう!
- <%=# link_to @presigned_url, class: "btn-blue", style: "max-width:320px; display:block; margin:20px auto 100px;" do %>
+ <% # link_to @presigned_url, class: "btn-blue", style: "max-width:320px; display:block; margin:20px auto 100px;" do %>
ポケモン素材をダウンロードする
<%# end %>
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
new file mode 100644
index 000000000..4b3ac0252
--- /dev/null
+++ b/config/brakeman.ignore
@@ -0,0 +1,273 @@
+{
+ "ignored_warnings": [
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "69b5a133fab8ea617d2581423cefaf077b9366e683c5fac715647bddeec7f50a",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/controllers/sotechsha_pages_controller.rb",
+ "line": 5,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => \"sotechsha_pages/#{params[:page]}\", {})",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "SotechshaPagesController",
+ "method": "show"
+ },
+ "user_input": "params[:page]",
+ "confidence": "Medium",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Command Injection",
+ "warning_code": 14,
+ "fingerprint": "7307f11036b1ab86f410d8d967d3972618705df73cafdd17f8e311c10c76c1f1",
+ "check_name": "Execute",
+ "message": "Possible command injection",
+ "file": "lib/statistics/aggregation.rb",
+ "line": 163,
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
+ "code": "`curl -X POST -H 'Content-type: application/json' --data '{\"text\":\"#{msg}\"}' #{slack_hook_url} -o /dev/null -w \"slack: %{http_code}\"`",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Statistics::Statistics::Aggregation::Notifier",
+ "method": "s(:self).notify"
+ },
+ "user_input": "msg",
+ "confidence": "Medium",
+ "cwe_id": [
+ 77
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Cross-Site Scripting",
+ "warning_code": 4,
+ "fingerprint": "8ba988098c444755698e4e65d38a94f4095948c1a9bc6220c7e2a4636c3c04d7",
+ "check_name": "LinkToHref",
+ "message": "Potentially unsafe model attribute in `link_to` href",
+ "file": "app/views/shared/_dojo.html.erb",
+ "line": 6,
+ "link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
+ "code": "link_to(\"#{Dojo.new.name} (#{Dojo.new.prefecture.name})\", Dojo.new.url, :target => \"_blank\", :rel => \"external noopener\")",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "HomeController",
+ "method": "show",
+ "line": 7,
+ "file": "app/controllers/home_controller.rb",
+ "rendered": {
+ "name": "home/show",
+ "file": "app/views/home/show.html.erb"
+ }
+ },
+ {
+ "type": "template",
+ "name": "home/show",
+ "line": 161,
+ "file": "app/views/home/show.html.erb",
+ "rendered": {
+ "name": "shared/_dojos",
+ "file": "app/views/shared/_dojos.html.erb"
+ }
+ },
+ {
+ "type": "template",
+ "name": "shared/_dojos",
+ "line": 2,
+ "file": "app/views/shared/_dojos.html.erb",
+ "rendered": {
+ "name": "shared/_dojo",
+ "file": "app/views/shared/_dojo.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "shared/_dojo"
+ },
+ "user_input": "Dojo.new.url",
+ "confidence": "Weak",
+ "cwe_id": [
+ 79
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Cross-Site Scripting",
+ "warning_code": 2,
+ "fingerprint": "b22a549fb14a7e6b3a9c34991ffcacd354dc768d74d50a8f6901e23c3ea19538",
+ "check_name": "CrossSiteScripting",
+ "message": "Unescaped model attribute",
+ "file": "app/views/podcasts/show.html.erb",
+ "line": 39,
+ "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
+ "code": "Rinku.auto_link(Kramdown::Document.new(self.convert_shownote(Podcast.find_by(:id => params[:id]).content), :input => \"GFM\").to_html)",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "PodcastsController",
+ "method": "show",
+ "line": 31,
+ "file": "app/controllers/podcasts_controller.rb",
+ "rendered": {
+ "name": "podcasts/show",
+ "file": "app/views/podcasts/show.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "podcasts/show"
+ },
+ "user_input": "Podcast.find_by(:id => params[:id]).content",
+ "confidence": "Weak",
+ "cwe_id": [
+ 79
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Cross-Site Scripting",
+ "warning_code": 4,
+ "fingerprint": "b29f98f4da690ffb7c663390c21db3a71174dae17d06234deab9d6655af6babe",
+ "check_name": "LinkToHref",
+ "message": "Potentially unsafe model attribute in `link_to` href",
+ "file": "app/views/shared/_dojo.html.erb",
+ "line": 3,
+ "link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
+ "code": "link_to(lazy_image_tag(Dojo.new.logo, :alt => (\"CoderDojo #{Dojo.new.name}\"), :class => \"dojo-picture\"), Dojo.new.url, :target => \"_blank\", :rel => \"external noopener\")",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "HomeController",
+ "method": "show",
+ "line": 7,
+ "file": "app/controllers/home_controller.rb",
+ "rendered": {
+ "name": "home/show",
+ "file": "app/views/home/show.html.erb"
+ }
+ },
+ {
+ "type": "template",
+ "name": "home/show",
+ "line": 161,
+ "file": "app/views/home/show.html.erb",
+ "rendered": {
+ "name": "shared/_dojos",
+ "file": "app/views/shared/_dojos.html.erb"
+ }
+ },
+ {
+ "type": "template",
+ "name": "shared/_dojos",
+ "line": 2,
+ "file": "app/views/shared/_dojos.html.erb",
+ "rendered": {
+ "name": "shared/_dojo",
+ "file": "app/views/shared/_dojo.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "shared/_dojo"
+ },
+ "user_input": "Dojo.new.url",
+ "confidence": "Weak",
+ "cwe_id": [
+ 79
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Dynamic Render Path",
+ "warning_code": 15,
+ "fingerprint": "c54623ebce2c2053b95088b9da8112aee962e7cadd79bd9b4b9afdedaddc15b1",
+ "check_name": "Render",
+ "message": "Render path contains parameter value",
+ "file": "app/controllers/sotechsha2_pages_controller.rb",
+ "line": 5,
+ "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
+ "code": "render(action => \"sotechsha2_pages/#{params[:page]}\", {})",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "Sotechsha2PagesController",
+ "method": "show"
+ },
+ "user_input": "params[:page]",
+ "confidence": "Medium",
+ "cwe_id": [
+ 22
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Cross-Site Scripting",
+ "warning_code": 2,
+ "fingerprint": "e4187193a881ef4e98b77f205c86fcafbef3d65d9269bba30e8612f6a59273ed",
+ "check_name": "CrossSiteScripting",
+ "message": "Unescaped model attribute",
+ "file": "app/views/docs/show.html.erb",
+ "line": 12,
+ "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
+ "code": "Kramdown::Document.new(Document.new(params[:id]).content, :input => \"GFM\").to_html",
+ "render_path": [
+ {
+ "type": "controller",
+ "class": "DocsController",
+ "method": "show",
+ "line": 42,
+ "file": "app/controllers/docs_controller.rb",
+ "rendered": {
+ "name": "docs/show",
+ "file": "app/views/docs/show.html.erb"
+ }
+ }
+ ],
+ "location": {
+ "type": "template",
+ "template": "docs/show"
+ },
+ "user_input": "Document.new(params[:id]).content",
+ "confidence": "Weak",
+ "cwe_id": [
+ 79
+ ],
+ "note": ""
+ },
+ {
+ "warning_type": "Command Injection",
+ "warning_code": 14,
+ "fingerprint": "e5394a11f2e9bb6bc213b7ebd34fbcead20048858592aa19e5ae2961f33c636d",
+ "check_name": "Execute",
+ "message": "Possible command injection",
+ "file": "lib/upcoming_events/aggregation.rb",
+ "line": 89,
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
+ "code": "`curl -X POST -H 'Content-type: application/json' --data '{\"text\":\"#{msg}\"}' #{slack_hook_url} -o /dev/null -w \"slack: %{http_code}\"`",
+ "render_path": null,
+ "location": {
+ "type": "method",
+ "class": "UpcomingEvents::UpcomingEvents::Aggregation::Notifier",
+ "method": "s(:self).notify"
+ },
+ "user_input": "msg",
+ "confidence": "Medium",
+ "cwe_id": [
+ 77
+ ],
+ "note": ""
+ }
+ ],
+ "brakeman_version": "7.1.0"
+}