diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 000000000..fccaeeae6 --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,6 @@ +--- +ignore: + # Sinatra ReDoS (ETag header) - sinatra 4.2.0で修正だが、 + # rack 3.2 + Sinatra 4.2.0でトークン汚染インシデント発生のため + # sinatra 4.1.1に固定中。docs/postmortem-2025-10-rack32.md 参照 + - GHSA-mr3q-g2mv-mr4q diff --git a/Gemfile b/Gemfile index 3add20c14..9bf9e270b 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'sentry-sidekiq' gem 'sidekiq', '~>8.1' gem 'sidekiq-scheduler', '~>6.0.1' group :development do + gem 'bundler-audit' gem 'ostruct' # gli < 2.22の未宣言依存 gem 'rack-test' gem 'rails-erb-lint' diff --git a/Gemfile.lock b/Gemfile.lock index 9b5870b02..f227a10c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,7 +42,7 @@ GIT GIT remote: https://github.com/pooza/ginseng-fediverse.git - revision: d780863b197b054aa91e15dfe9acacd7c022e41b + revision: 3a30745ecc8661a22fdf5cb5c4cc0bc1fe756cb5 branch: main specs: ginseng-fediverse (1.8.21) @@ -125,6 +125,9 @@ GEM base64 (0.3.0) bigdecimal (4.0.1) builder (3.3.0) + bundler-audit (0.9.3) + bundler (>= 1.2.0) + thor (~> 1.0) cgi (0.5.1) concurrent-ruby (1.3.6) connection_pool (3.0.2) @@ -205,7 +208,7 @@ GEM multi_xml (>= 0.5.2) i18n (1.14.8) concurrent-ruby (~> 1.0) - json (2.19.0) + json (2.19.1) json-schema (6.2.0) addressable (~> 2.8) bigdecimal (>= 3.1, < 5) @@ -346,7 +349,7 @@ GEM rubocop-ast (>= 1.49.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.49.0) + rubocop-ast (1.49.1) parser (>= 3.3.7.2) prism (~> 1.7) rubocop-minitest (0.39.1) @@ -417,11 +420,12 @@ GEM temple (0.10.4) test-unit (3.7.7) power_assert + thor (1.5.0) tilt (2.1.0) time (0.4.2) date timecop (0.9.10) - timeout (0.6.0) + timeout (0.6.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) @@ -449,6 +453,7 @@ PLATFORMS x86_64-linux DEPENDENCIES + bundler-audit concurrent-ruby dry-validation faye-websocket! diff --git a/app/lib/mulukhiya.rb b/app/lib/mulukhiya.rb index e41e11f8f..23288a3a8 100644 --- a/app/lib/mulukhiya.rb +++ b/app/lib/mulukhiya.rb @@ -38,11 +38,23 @@ def self.setup_sentry config.release = Package.version config.environment = Environment.type config.traces_sample_rate = Config.instance['/sentry/traces_sample_rate'] || 0 + config.before_send = method(:scrub_sentry_event) end rescue => e warn "Sentry initialization skipped: #{e.message}" end + def self.scrub_sentry_event(event, _hint) + patterns = (Config.instance['/sentry/scrub_patterns'] || []).map {|p| Regexp.new(p)} + return event if patterns.empty? + event.exception&.values&.each do |ex| # rubocop:disable Style/HashEachMethods + patterns.each do |pattern| + ex.value = ex.value&.gsub(pattern, '[FILTERED]') + end + end + return event + end + def self.setup_debug Ricecream.disable return unless Environment.development? diff --git a/app/lib/mulukhiya/config.rb b/app/lib/mulukhiya/config.rb index d9c2a1fac..5882f6d64 100644 --- a/app/lib/mulukhiya/config.rb +++ b/app/lib/mulukhiya/config.rb @@ -31,6 +31,7 @@ def about controller: self['/controller'], status: Environment.status_class.default.merge( label: controller.status_label, + reblog_label: controller.reblog_label, max_length: controller.max_length, ), capabilities: sub_hash("/#{name}/capabilities"), diff --git a/app/lib/mulukhiya/controller/mastodon_controller.rb b/app/lib/mulukhiya/controller/mastodon_controller.rb index 99bc82907..189ed7b0a 100644 --- a/app/lib/mulukhiya/controller/mastodon_controller.rb +++ b/app/lib/mulukhiya/controller/mastodon_controller.rb @@ -14,7 +14,7 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -33,7 +33,7 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -48,7 +48,35 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 + @renderer.message = {error: e.message} + @renderer.status = e.source_status + return @renderer.to_s + end + + put '/api/:version/statuses/:id' do + verify_token_integrity! + purpose = request.env['HTTP_X_MULUKHIYA_PURPOSE'] + body = case purpose + when nil, '', 'media_update' + source = sns.fetch_status_source(params[:id], {headers: @headers}) + {status: source['text'], media_attributes: params[:media_attributes]}.compact + when 'tag' + {status: params[:status], media_attributes: params[:media_attributes]}.compact + else + raise Ginseng::ValidateError, "unknown purpose: #{purpose}" + end + raise Ginseng::ValidateError, 'media_attributes is required' if body.empty? + reporter.response = sns.update_status(params[:id], body, {headers: @headers}) + @renderer.message = reporter.response.parsed_response + @renderer.status = reporter.response.code + return @renderer.to_s + rescue Ginseng::ValidateError => e + @renderer.message = {error: e.message} + @renderer.status = 422 + return @renderer.to_s + rescue Ginseng::GatewayError => e + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -63,7 +91,7 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -78,7 +106,7 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -93,7 +121,7 @@ class MastodonController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s diff --git a/app/lib/mulukhiya/controller/misskey_controller.rb b/app/lib/mulukhiya/controller/misskey_controller.rb index d024f03c3..83ab3b73d 100644 --- a/app/lib/mulukhiya/controller/misskey_controller.rb +++ b/app/lib/mulukhiya/controller/misskey_controller.rb @@ -14,7 +14,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -32,7 +32,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -56,7 +56,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -75,7 +75,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -89,7 +89,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s @@ -103,7 +103,7 @@ class MisskeyController < Controller @renderer.status = reporter.response.code return @renderer.to_s rescue Ginseng::GatewayError => e - e.alert + e.alert unless e.source_status == 401 @renderer.message = {error: e.message} @renderer.status = e.source_status return @renderer.to_s diff --git a/app/lib/mulukhiya/controller/ui_controller.rb b/app/lib/mulukhiya/controller/ui_controller.rb index 2c675fbe0..56aee1bd6 100644 --- a/app/lib/mulukhiya/controller/ui_controller.rb +++ b/app/lib/mulukhiya/controller/ui_controller.rb @@ -19,8 +19,13 @@ class UIController < Controller end get '/oauth/callback' do - raise Ginseng::AuthError, 'Missing state' unless params[:state] - raise Ginseng::AuthError, 'Missing code' unless params[:code] + unless params[:state] && params[:code] + @renderer = SlimRenderer.new + @renderer.template = 'token_error' + @renderer[:error] = 'Missing required OAuth parameters' + @renderer.status = 400 + return @renderer.to_s + end result = sns.auth_with_pkce(params[:code], params[:state]) raise Ginseng::AuthError, 'Token exchange failed' unless result parsed = result.parsed_response diff --git a/app/lib/mulukhiya/controller_methods.rb b/app/lib/mulukhiya/controller_methods.rb index 877af46f1..e80849403 100644 --- a/app/lib/mulukhiya/controller_methods.rb +++ b/app/lib/mulukhiya/controller_methods.rb @@ -175,6 +175,10 @@ def status_label return config["/#{name}/status/label"] end + def reblog_label + return config["/#{name}/status/reblog_label"] + end + def status_delete_limit return config["/#{name}/status/delete/limit"] rescue nil end diff --git a/app/task/bundler_audit.rb b/app/task/bundler_audit.rb new file mode 100644 index 000000000..166d340c7 --- /dev/null +++ b/app/task/bundler_audit.rb @@ -0,0 +1,11 @@ +module Mulukhiya + extend Rake::DSL + + namespace :bundler_audit do + desc 'bundler-audit' + task :check do + Dir.chdir(Environment.dir) + sh 'bundler-audit check --update' + end + end +end diff --git a/app/task/lint.rb b/app/task/lint.rb index f5ee27e24..d0638cd5a 100644 --- a/app/task/lint.rb +++ b/app/task/lint.rb @@ -2,5 +2,5 @@ module Mulukhiya extend Rake::DSL desc 'lint all' - task lint: ['erb:lint', 'slim:lint', 'rubocop:lint', 'yaml:lint'] + task lint: ['erb:lint', 'slim:lint', 'rubocop:lint', 'yaml:lint', 'bundler_audit:check'] end diff --git a/config/application.yaml b/config/application.yaml index 8e6852c56..25e44c51d 100644 --- a/config/application.yaml +++ b/config/application.yaml @@ -323,6 +323,7 @@ mastodon: limit: 30 key: id label: 投稿 + reblog_label: ブースト parser: toot spoiler_text: null streaming: @@ -377,6 +378,7 @@ misskey: default_max_length: 3000 key: noteId label: ノート + reblog_label: リノート parser: note spoiler_text: null streaming: @@ -404,7 +406,7 @@ package: - tkoishi@b-shock.co.jp license: MIT url: https://github.com/pooza/mulukhiya-toot-proxy - version: 5.7.0 + version: 5.8.0 parser: note: fields: @@ -449,6 +451,10 @@ redis: sentry: dsn: null traces_sample_rate: 0 + scrub_patterns: + - '[a-zA-Z0-9_\-]{20,}' + - '/home/[^\s]+' + - '/Users/[^\s]+' ruby: bundler: install: false diff --git a/config/sample/mastodon/mulukhiya.nginx b/config/sample/mastodon/mulukhiya.nginx index 8f0285c7b..b77914f15 100644 --- a/config/sample/mastodon/mulukhiya.nginx +++ b/config/sample/mastodon/mulukhiya.nginx @@ -8,7 +8,7 @@ map "${request_method}:${http_x_mulukhiya}" $media_put_backend { default http://localhost:3000; } map "${request_method}:${http_x_mulukhiya}" $status_put_backend { - "~^PUT:$" reject; + "~^PUT:$" http://localhost:3008; default http://localhost:3000; } @@ -28,12 +28,6 @@ location ~ ^/api/v[0-9]+/media/[0-9]+$ { } location ~ ^/api/v[0-9]+/statuses/[0-9]+$ { include /path/to/mulukhiya_proxy.conf; - if ($http_x_mulukhiya_purpose != '') { - proxy_pass http://localhost:3008; - } - if ($status_put_backend = reject) { - return 405; - } proxy_pass $status_put_backend; } location = /api/v1/timelines/public { diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md index b5f67cffb..1bcdf950c 100644 --- a/docs/CLAUDE.md +++ b/docs/CLAUDE.md @@ -88,19 +88,36 @@ git diff Gemfile.lock # 5. 問題なければコミット ``` -## 予定: 5.8.0 +## 予定: 5.8.0 — セキュリティレビュー対応(リリース待ち) + +全Issue クローズ済み。ステージング全4台(dev04/dev15/dev22/dev23)デプロイ・動作確認済み。リリース作業のみ残。 + +- ~~#4155 Wiki: Sentry.ioの設定項目をドキュメントに追加~~ — クローズ済み +- ~~#4157 Sentry: before_sendフィルタによる秘匿情報スクラビング~~ — クローズ済み +- ~~#4158 bundler-auditの導入とCI統合~~ — クローズ済み。sinatra CVE-2025-61921 は rack 3.2問題のため ignore 設定 +- ~~#4156 セキュリティレビュー~~ — クローズ済み +- ~~#4159 フロントJSテストのアサーション修正~~ — クローズ済み +- ~~#4161 about APIでブースト/リノートのカスタムラベルを返す~~ — クローズ済み + +## 予定: 5.9.0 — アーキテクチャ整理 -- #4117 WebUI: 複雑なハンドラーパラメータ編集(CRUD一覧管理) — オブジェクト配列や深いネスト構造を持つパラメータの一覧管理UI - #4144 カスタムAPI/カスタムフィードを独立デーモンに分離 — Bundler二重管理・Open3.capture3の不安定さを解消。元の独立デーモン形式に戻す - #4146 PieFed対応をginseng-piefedに切り出し — tomato-shriekerとの共有が動機。#4145 完了が前提 -## 予定: 5.7.0 +## 予定: 5.10.0 — WebUI拡張 + +- #4117 WebUI: 複雑なハンドラーパラメータ編集(CRUD一覧管理) — オブジェクト配列や深いネスト構造を持つパラメータの一覧管理UI +- #4118 WebUI: サービス連携・システム設定の編集と不要設定の自動検出 + +## リリース済み: 5.7.0(2026-03-14) -- **#4152 docs/api.md: Annict連携セクションの認証要件を実装に合わせて修正** — Codexレビュー(PR #4149)の指摘に対応。P4セクションとの重複記述を解消 -- **#4153 Misskey: 内部DMにlocalOnlyフラグを設定する** — コマンドトゥート、お知らせボット通知DM、ボットメンション時のDM強制変更でlocalOnly: trueを設定。capabilities/local_postで判定 -- **#4154 Sentry.ioによるエラートラッキングの導入** — sentry-ruby + sentry-sidekiq。既存のalertメソッドにSentry.capture_exceptionを統合。DSNはconfig/local.yamlの/sentry/dsnで設定(デプロイ時にSentryプロジェクト作成 → chubo2#13) -- #4140 config.slim のフォーム処理ロジックを外部 JS に抽出 — #4131 後続。データ変換ロジックの抽出とブラウザテスト追加 -- #4141 テンプレート内 JS の段階的なモジュール抽出 — 各テンプレートから純粋関数を外部モジュールへ段階的に移行 +全5 Issue クローズ。Sentry エラートラッキング導入、Misskey localOnly フラグ、フロントエンド JS モジュール抽出。セキュリティレビュー(#4156)実施済み。 + +- **#4154 Sentry.ioによるエラートラッキングの導入** — sentry-ruby + sentry-sidekiq。既存のalertメソッドにSentry.capture_exceptionを統合。DSNはconfig/local.yamlの`/sentry/dsn`で設定 +- **#4153 Misskey: 内部DMにlocalOnlyフラグを設定する** — コマンドトゥート、お知らせボット通知DM、ボットメンション時のDM強制変更でlocalOnly: trueを設定 +- **#4140 config.slimのフォーム処理ロジックを外部JSに抽出** — config_form.jsに10個の純粋関数を抽出、27テストケース追加 +- **#4141 テンプレート内JSの段階的なモジュール抽出** — webui_utils.jsに6個の純粋関数を抽出、18テストケース追加 +- #4152 Annict連携セクションの認証要件をドキュメントで修正 ## リリース済み: 5.6.0(2026-03-11) @@ -177,6 +194,53 @@ git diff Gemfile.lock - #4082 Sidekiqワーカーテスト、#4099 Worker個別コンテキストログ、#4103 テストの外部API依存解消 - #4105 FreeBSD rc.d 起動ブロック原因切り分け(Mastodon streaming が主犯 → [pooza/mastodon#900](https://github.com/pooza/mastodon/issues/900)) +## セッション開始時の同期手順 + +会話の最初に「進捗を同期してください」等の指示があった場合、以下の手順を実行する。 + +### 1. プロジェクトガイドの読み込み + +- `docs/CLAUDE.md` を読む(プロジェクトのルール・構造・履歴の正本) +- `MEMORY.md` は自動ロードされるので、両者の整合性を意識する + +### 2. リモートとの同期・状態確認 + +- `git fetch origin` — **最初に必ず実行**。リモートが正本であり、ローカルの状態を信用しない +- `git log HEAD..origin/develop --oneline` — リモートに未取り込みのコミットがないか確認。差分があればpullを検討 +- `git log --oneline -10` — 直近のコミット履歴 +- `gh issue list --state open` — open Issue一覧 +- `gh pr list --state open` — open PR一覧 + +### 3. Dependabotセキュリティアラート + +- `gh api repos/pooza/mulukhiya-toot-proxy/dependabot/alerts` で open アラートを確認 +- 0件なら対応不要、あれば提案 + +### 4. Codexレビューコメントの確認 + +- 最近マージされたPR(`gh pr list --state merged --limit 5`)を取得 +- 各PRに対して `gh api repos/pooza/mulukhiya-toot-proxy/pulls/{number}/comments` でCodex(`chatgpt-codex-connector[bot]`)のコメントを確認 +- 未返信のコメントがあれば内容を確認し、対応が必要か判断 + +### 5. 外部リポジトリの同期確認(chubo2) + +- `cd ~/repos/chubo2 && git fetch origin` + `git log HEAD..origin/main --oneline` でリモートとの差分を確認 +- `docs/infra-note.md` に変更があれば MEMORY.md のインフラセクションに反映が必要か判断 +- `gh issue list --state open` で open Issue の変動を確認 + +### 6. マイルストーンの状態確認 + +- `docs/CLAUDE.md` と MEMORY.md に記載された次期マイルストーンの Issue が、実際の GitHub 上の状態(open/closed)と一致しているか確認 +- クローズ済みの Issue があれば MEMORY.md から除外し、`docs/CLAUDE.md` も必要に応じて更新 + +### 7. MEMORY.md の更新 + +- 上記で検出した差分(Issue 状態、リリース日の誤り、件数のズレ等)を反映 + +### 8. 同期結果の報告 + +- 現在のブランチ・状態、マイルストーンの状況、各確認項目の結果をまとめて報告する + ## 情報の記載先ルール - **課題・タスク** → GitHub Issue で管理(インフラ面の課題は `pooza/chubo2` の Issue として起票) @@ -258,7 +322,7 @@ test/ ### マイルストーン管理 -- 1マイルストーンあたり10件前後で区切る +- 1マイルストーンあたり10件前後で区切る(平均的なボリュームのIssueを基準とし、粒度の大きなIssueがある場合は件数を減らして調整する) - 優先度の低いIssueは次のマイナーバージョンへ送る - 計画書は作成せず、Issue+マイルストーンで管理する - セキュリティレビュー([#4156](https://github.com/pooza/mulukhiya-toot-proxy/issues/4156))は各マイルストーンの Issue をすべて消化した後、リリース直前に毎度実施する @@ -277,6 +341,7 @@ test/ - 未対応 → PRをマージ - セキュリティアラートはリリース時の Gemfile.lock 更新で自動クローズされる - `target-branch`: v4(4.x向け)と develop(5.x向け)の2エントリ +- **bundler-audit**: `rake lint` に統合済み。RubyGems ソースの gem の既知脆弱性を自動スキャンする。`ginseng-*` 系 gem は git ソースのため対象外。`ginseng-*` の依存 gem に脆弱性がある場合は、該当 gem のリポジトリで `bundle update` して対応する ### Codexレビュー確認 diff --git a/docs/api.md b/docs/api.md index 8d0891156..6efb4b2d3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -159,6 +159,22 @@ HTTP ステータス 200 なら全サービス正常、503 なら一部サービ - モロヘイヤが SNS に転送する際に自動的に `X-Mulukhiya: {パッケージ名}` を付与する - クライアントがこのヘッダを送信する必要はない +### X-Mulukhiya-Purpose ヘッダ + +`PUT /api/v{version}/statuses/{id}` で本文更新の可否を制御するヘッダ。 +SNS における「発言の責任」の観点から、本文の自由な編集はデフォルトで禁止している。 + +| X-Mulukhiya-Purpose | 許可されるパラメータ | 用途 | +| --- | --- | --- | +| (なし / 空) | `media_attributes` のみ | メディア説明(ALT)の編集(直接アクセス時) | +| `media_update` | `media_attributes` のみ | メディア説明(ALT)の編集(クライアントから) | +| `tag` | `status`, `media_attributes` | ハッシュタグの付け替え(モロヘイヤ内部用) | + +- Purpose ヘッダなし: 直接アクセス時。`media_attributes` 以外は除去される +- Purpose が `media_update`: クライアント(capsicum 等)からの ALT 編集リクエスト。nginx が Purpose ヘッダの有無でルーティングするため、クライアントはこの値を指定する +- Purpose が `tag`: モロヘイヤ自身がハッシュタグを書き換える際に使用(将来の #3877 対応) +- 不明な Purpose: 422 Unprocessable Entity を返す + ### パイプライン処理 以下の処理が**透過的に**適用される。クライアントは標準 API を呼ぶだけでよい。 @@ -182,6 +198,7 @@ URL 正規化、短縮 URL 展開、NowPlaying 検出(iTunes/Spotify/YouTube | エンドポイント | メソッド | 処理内容 | |---------------|---------|---------| | `/api/v{version}/statuses` | POST | 投稿作成(pre_toot → SNS → post_toot) | +| `/api/v{version}/statuses/{id}` | PUT | 投稿更新(`X-Mulukhiya-Purpose` ヘッダで許可範囲を制御、下記参照) | | `/api/v{version}/media` | POST | メディアアップロード(pre_upload → SNS → post_upload) | | `/api/v{version}/media/{id}` | PUT | メディア更新(サムネイル変換) | | `/api/v{version}/statuses/{id}/favourite` | POST | お気に入り(SNS → post_fav) | @@ -228,12 +245,13 @@ URL 正規化、短縮 URL 展開、NowPlaying 検出(iTunes/Spotify/YouTube "email": ["author@example.com"], "license": "MIT", "url": "https://github.com/pooza/mulukhiya-toot-proxy", - "version": "5.2.1" + "version": "5.8.0" }, "config": { "controller": "mastodon", "status": { "label": "投稿", + "reblog_label": "ブースト", "max_length": 2400, "spoiler": {"text": null, "emoji": null, "shortcode": null}, "default_hashtag": "precure_fun" diff --git a/public/mulukhiya/script/test/config_form_test.js b/public/mulukhiya/script/test/config_form_test.js index d8795b251..c14bb4da3 100644 --- a/public/mulukhiya/script/test/config_form_test.js +++ b/public/mulukhiya/script/test/config_form_test.js @@ -82,7 +82,7 @@ describe('config_form', () => { const options = extractVisibilityOptions(data) assert.equal(options.length, 2) assert.equal(options[0].value, 'public') - assert.equal(options[0].label, '公開 (public)') + assert.equal(options[0].label, 'public (公開)') assert.equal(options[1].value, 'unlisted') }) @@ -177,7 +177,7 @@ describe('config_form', () => { const form = {tags: 'tag1', program: {minutes: null}, decoration: {id: null}} const cmd = buildTagsCommand(form) assert.deepEqual(cmd.tagging.user_tags, ['tag1']) - assert.isUndefined(cmd.tagging.minutes) + assert.isNull(cmd.tagging.minutes) }) }) diff --git a/test/unit/lib/sentry_scrub.rb b/test/unit/lib/sentry_scrub.rb new file mode 100644 index 000000000..d0e1900df --- /dev/null +++ b/test/unit/lib/sentry_scrub.rb @@ -0,0 +1,43 @@ +module Mulukhiya + class SentryScrubTest < TestCase + def build_event(message) + config = Sentry::Configuration.new + event = Sentry::ErrorEvent.new(configuration: config) + exc = RuntimeError.new(message) + exc.set_backtrace(caller) + mechanism = Sentry::Mechanism.new(type: 'generic', handled: true) + event.add_exception_interface(exc, mechanism: mechanism) + return event + end + + def test_scrub_token + event = build_event('Token abc123XYZ456defGHI789jkl failed') + result = Mulukhiya.scrub_sentry_event(event, {}) + + assert_not_match(/abc123XYZ456defGHI789jkl/, result.exception.values.first.value) + assert_match(/\[FILTERED\]/, result.exception.values.first.value) + end + + def test_scrub_path + event = build_event('Error at /home/mastodon/mulukhiya-toot-proxy/app/lib/foo.rb') + result = Mulukhiya.scrub_sentry_event(event, {}) + + assert_not_match(%r{/home/mastodon}, result.exception.values.first.value) + end + + def test_scrub_preserves_short_strings + event = build_event('Something went wrong') + result = Mulukhiya.scrub_sentry_event(event, {}) + + assert_match(/Something went wrong/, result.exception.values.first.value) + end + + def test_scrub_no_exception + config = Sentry::Configuration.new + event = Sentry::ErrorEvent.new(configuration: config) + result = Mulukhiya.scrub_sentry_event(event, {}) + + assert_not_nil(result) + end + end +end