@@ -36,7 +36,7 @@ https://github.com/organizations/OpenTouryoProject/settings/security_products
3636| Code scanning(CodeQL 既定セットアップ) | 有効 | ワークフローを自前で持たずに済む |
3737| Private vulnerability reporting | 有効 | ** 暗号・認証ライブラリを公開している** ため、非公開の報告窓口が要る |
3838| Secret scanning | 有効 | |
39- | ** Secret scanning: Push protection** | ** 無効 ** | ** 後述。棚卸し後に有効化する ** |
39+ | ** Secret scanning: Push protection** | ** 有効 ** | 2026-08-10 に有効化(後述) |
4040| ** Secret scanning: Non-provider patterns** | ** 無効** | ** 後述** |
4141| ** Secret scanning: Validity checks** | ** 無効** | ** 後述** |
4242| GitHub Advanced Security | 有効 | 公開リポジトリでは無料 |
@@ -52,12 +52,12 @@ https://github.com/organizations/OpenTouryoProject/settings/security_products
5252
5353#### Push protection
5454
55- ** 段階分けのため。** 有効にすると、検知した push をその場で止める。
55+ ** 段階分けのため、最初は無効にした 。** 有効にすると、検知した push をその場で止める。
5656
5757```
58- 1. Secret scanning だけ先に有効化 ← いまここ
59- 2. アラートを棚卸し(現在 0 件)
60- 3. Push protection を有効化
58+ 1. .github/secret_scanning.yml を develop へ入れる
59+ 2. Secret scanning だけ先に有効化 → アラート 0 件で立ち上がり、棚卸しは不要だった
60+ 3. Push protection を有効化 → 2026-08-10 実施
6161```
6262
6363いきなり両方入れると、** リリース作業の途中で不意に止まる** 。
@@ -123,7 +123,7 @@ CodeQL の内訳(上位)。
123123| ** ` requireSSL ` が ` true ` でない** | 4 | ** 対応済み。** ` requireSSL="true" ` の行を** コメントアウトで併記** し、「本番ではコメントアウトを外す」と明記。** サンプルは HTTP で動かす** ため、既定は ` false ` のまま |
124124| ** ` Encryption using ECB ` ** | 1 | ** 対応済み → 棄却。** ` EnumSymmetricAlgorithm.CipherMode_ECB ` に ** ` [Obsolete] ` を付与** した。利用者が指定したときだけ通る 5 択の 1 つで、** 既定ではない** (指定しなければ .NET 既定の CBC)。リポジトリ内に指定箇所は 0 件。** 削除は下位互換を壊す** ため、非推奨化に留めた。コードは残るのでアラートは消えず、` won't fix ` で棄却する |
125125| ** ` Cookie 'Secure' ` 未設定** | 2 | ** 対応済み → 棄却。** 指摘されたのは ` StdMigration/CookieExtensions.cs ` の ` Set ` 3 つのうち ** ` CookieOptions ` を受け取らない 2 つ** で、** リポジトリ内に呼び出しが 1 件も無い** (利用者向けに残している移植 API)。実際に Cookie を作るのは ` CookieOptions ` 版を呼ぶ ` FxCmnFunction ` で、そちらの ** 4 箇所** (net48 / netcore100 各 2)を修正した。** ` HttpOnly ` は設定済みだったが ` Secure ` が無かった** (前者は XSS 対策、後者は盗聴対策で別物) |
126- | ** ` DOM text reinterpreted as HTML ` ** | 2 | ** 対応済み。 ** サンプルの ` common.js ` (自作、CS / VB 同一)。 擬似ダイアログの ` iframe.src ` に URL を設定する前に、 ` Fx_IsSafeDialogUrl() ` でスキームを検証するようにした。** 既存の脆弱性を塞いだのではない** (URL はフレームワークの ` ShowModalScreen ` が組み立て、利用者入力は入らず、AppScan もクリア済み)。 ** サンプルを手本にしたコードが動的な URL を流し込む場合に備える多層防御 ** |
126+ | ** ` DOM text reinterpreted as HTML ` ** | 2 | ** 多層防御を追加したが、アラートは残る → 棄却。 ** 擬似ダイアログの ` iframe.src ` に URL を設定する前に ` Fx_IsSafeDialogUrl() ` でスキームを検証するようにした。** CodeQL はこの検証関数をサニタイザと認識しない ** ため、指摘は消えない。 ** 既存の脆弱性を塞いだのではない** (URL はフレームワークの ` ShowModalScreen ` が組み立て、利用者入力は入らず、AppScan もクリア済み) |
127127
128128** 棄却は削除ではない。** Security タブに ` Dismissed ` として残り、
129129棄却した人・日時・理由・コメントが記録される。復帰もできる。
@@ -173,6 +173,60 @@ newCookie.Secure = HttpContext.Current.Request.IsSecureConnection; // net48
173173> ` //Secure = CookieSecurePolicy.Always ` がコメントアウトで残っている。
174174> アプリ全体の Cookie ポリシーで一括指定する方法もある(未検討)。
175175
176+ ### CodeQL の挙動(踏んだので記録する)
177+
178+ #### ① 棄却は、コードが動くと外れる
179+
180+ ** アラートの同一性は「ルール + 位置」で判定される。**
181+ 棄却した箇所の周辺を編集して行番号がずれると、
182+ ** 別のアラートとして作り直され、` open ` に戻る。**
183+
184+ ` Encryption using ECB ` がこれに当たった。棄却済みだったが、
185+ ` #pragma warning disable ` とコメントを足したことで ** 287 行 → 291 行** にずれ、
186+ 新しい番号で ` open ` に戻ったため、** 同じ理由で棄却し直した。**
187+
188+ ** 棄却済みの箇所を触ったら、棄却が残っているかを確認すること。**
189+
190+ #### ② ` fixed ` は「直った」とは限らない
191+
192+ ` #537 ` のマージ後、` fixed ` が 12 件になったが、
193+ ** 本当に解消したのは ` Missing X-Frame-Options ` の 6 件だけ** だった。
194+
195+ ```
196+ fixed 12 件 ≠ 12 件が直った
197+ 6 件 解消(新しいアラートが出ていない)
198+ 6 件 位置が変わっただけ(同数が open に出ている)
199+ ```
200+
201+ ** ` fixed ` の件数だけを見て「減った」と判断してはならない。**
202+ ` open ` の中身とあわせて確認する。
203+
204+ #### ③ PR のチェックと、ブランチ全体のスキャンは別物
205+
206+ ** PR のチェックは差分中心で、ブランチ全体の状態とは一致しない。**
207+
208+ - ` #537 ` の ` CodeQL ` チェックは ` fail ` だったが、** 新しい脆弱性ではなかった** 。
209+ 行番号がずれた 2 件を「この PR で追加された」と数えたため
210+ - 逆に、PR 時点では ` 'requireSSL' ` が解消したように見えたが、
211+ ** ` develop ` の全体スキャンでは残っていた**
212+
213+ ** 判断は ` develop ` (既定ブランチ)のスキャン結果で行うこと。**
214+
215+ #### ④ 検証を足してもアラートは消えないことがある
216+
217+ ` DOM text reinterpreted as HTML ` に対して ` Fx_IsSafeDialogUrl() ` を追加したが、
218+ ** CodeQL はこの関数をサニタイザと認識せず、指摘は残った。**
219+
220+ ** 「アラートが消えること」と「安全になること」は別である。**
221+
222+ | | アラート | 実際の安全性 |
223+ | ---| ---| ---|
224+ | ` 'requireSSL' ` (コメントで併記) | ** 消えない** | ** 変わらない** (有効な設定は ` false ` のまま) |
225+ | ` DOM text ... ` (検証を追加) | ** 消えない** | ** 上がった** (危険なスキームを実際に弾く) |
226+ | ` X-Frame-Options ` (ヘッダ追加) | ** 消えた** | ** 上がった** |
227+
228+ ** アラート件数を目的にすると判断を誤る。** 何を守りたいかで決めること。
229+
176230---
177231
178232## 2. ブランチと保護
@@ -187,8 +241,9 @@ newCookie.Secure = HttpContext.Current.Request.IsSecureConnection; // net48
187241
188242### 現状の弱点
189243
190- - ** ` develop ` に必須ステータス チェックが無い。** CI は ` push: [deps] ` でしか動かないため、
191- ` develop ` へ入る変更は CI で検証されていない
244+ - ** ` develop ` に必須ステータス チェックは置かない** (意図的)。
245+ ` deps ` ⇔ ` develop ` ⇔ feature と往復が多く、毎回のマージが CI 待ちになるため。
246+ 代わりに ** ` master ` 宛の PR で CI を動かす** (7 節)
192247- ` master ` の ` enforce_admins ` は無効(少人数運用のため意図的)
193248- ` delete_branch_on_merge ` は無効。マージ済みブランチが残る
194249
@@ -199,17 +254,63 @@ newCookie.Secure = HttpContext.Current.Request.IsSecureConnection; // net48
199254
200255---
201256
202- ## 3. ワークフロー
257+ ## 3. GitHub Actions
203258
204- | ファイル | 発火 | 内容 |
259+ ### ` .github/ ` の中身
260+
261+ | ファイル | 役割 |
262+ | ---| ---|
263+ | [ ` workflows/build-windows.yml ` ] ( .github/workflows/build-windows.yml ) | 検証 3 本(ビルド・単体テスト・疎通)を windows-latest で |
264+ | [ ` workflows/dependabot-retarget.yml ` ] ( .github/workflows/dependabot-retarget.yml ) | Dependabot PR の向き先を ` deps ` へ変更 |
265+ | [ ` secret_scanning.yml ` ] ( .github/secret_scanning.yml ) | Secret scanning のアラートから除外するパス(1 節) |
266+
267+ ** ` dependabot.yml ` は置いていない** (4 節)。
268+
269+ ### ワークフロー
270+
271+ | ファイル | 発火 | ` permissions ` |
205272| ---| ---| ---|
206- | [ ` build-windows.yml ` ] ( .github/workflows/build-windows.yml ) | ` push: [deps] ` / 手動 | 検証 3 本(ビルド・単体テスト・疎通)を windows-latest で |
207- | [ ` dependabot-retarget.yml ` ] ( .github/workflows/dependabot-retarget.yml ) | ` pull_request_target ` | Dependabot PR の向き先を ` deps ` へ変更 |
208- | CodeQL(既定セットアップ) | GitHub 管理 | ワークフロー ファイルを持たない |
273+ | ` build-windows.yml ` | ` push: [deps] ` / ** ` pull_request: [master] ` ** / 手動 | ` contents: read ` |
274+ | ` dependabot-retarget.yml ` | ` pull_request_target ` ( ` opened ` ) | ` pull-requests: write ` |
275+ | CodeQL(既定セットアップ) | GitHub 管理。 ` push ` / ` pull_request ` / 週次 | ワークフロー ファイルを持たない |
209276
210277` build-windows.yml ` は既知の署名エラー(` MSB3482 ` / ` MSB3325 ` / ` MSB3321 ` )を除外する。
211278** ローカルと CI で出るコードが違う** 理由はファイル冒頭のコメントにある。
212279
280+ > ** ` dependabot-retarget.yml ` は ` pull_request_target ` を使う。**
281+ > これは** ベース ブランチ側の定義を、書き込み権限付きで動かす** トリガである。
282+ > ここで PR のコードを ` checkout ` して実行すると、
283+ > ** PR に任意のコードを書ける相手へ権限を渡すことになる** (pwn request)。
284+ >
285+ > 本ワークフローは ** ` checkout ` を行わず** 、` gh ` コマンドだけを実行し、
286+ > 権限も ` pull-requests: write ` の 1 つに絞っている。
287+ > ** この 2 点は変更しないこと。** 理由はファイル冒頭のコメントにある。
288+
289+ ### GitHub 側の設定
290+
291+ ```
292+ Actions enabled
293+ allowed_actions all … 使用できるアクションを制限していない
294+ sha_pinning_required false … アクションの SHA 固定を強制していない
295+ default_workflow_permissions read … 2026-08-10 に write から変更
296+ can_approve_pull_request_reviews false … 2026-08-10 に true から変更
297+ Secrets / Variables なし
298+ ```
299+
300+ ** 既定を ` read ` にした。** 各ワークフローは ` permissions: ` を明示して
301+ 最小権限にしているため、** 既定を下げても動く** (実測で確認)。
302+ ** ` permissions: ` を書き忘れた新しいワークフローが、
303+ 書き込み権限を持ってしまう状態を無くすため。**
304+
305+ > それでも** 新しいワークフローを足すときは ` permissions: ` を書くこと。**
306+ > 何を必要としているかが、ファイルを見て分かる方がよい。
307+
308+ ** ` can_approve_pull_request_reviews ` も無効にした。**
309+ Actions が PR を承認できると、` master ` のレビュー必須が形骸化するため。
310+
311+ ` Secrets ` は 1 つも登録していない。** 認証が要る操作は入れていない** ということであり、
312+ 足すときは「本当に必要か」を先に考える。
313+
213314---
214315
215316## 4. Dependabot
@@ -290,25 +391,72 @@ gh api orgs/OpenTouryoProject/code-security/configurations/265927/repositories \
290391 --jq ' .[] | {status, repo: .repository.full_name}'
291392```
292393
293- ### 次に Push protection を有効化するとき
394+ ### 2026-08-10 : Push protection の有効化
294395
295- ** 構成の値を変えるだけでよい** (リポジトリ個別の設定は触らない)。
396+ Secret scanning のアラートが 0 件で安定したことを確認した上で実施。
397+ ** 構成の値を変えるだけ** で、リポジトリ個別の設定は触らない。
296398
297399``` bash
298400gh api -X PATCH orgs/OpenTouryoProject/code-security/configurations/265927 \
299401 -f secret_scanning_push_protection=enabled
402+
403+ # リポジトリ側へ反映されたかを確認する
404+ gh api repos/OpenTouryoProject/OpenTouryo --jq ' .security_and_analysis'
405+ ```
406+
407+ ** 戻すとき** も同じ形(` -f secret_scanning_push_protection=disabled ` )。
408+
409+ ### 2026-08-10 : Actions の既定権限を絞る
410+
411+ ** リポジトリ設定** (組織の構成ではない)。` repo ` スコープで実行できる。
412+
413+ ``` bash
414+ gh api -X PUT repos/OpenTouryoProject/OpenTouryo/actions/permissions/workflow -f default_workflow_permissions=read -F can_approve_pull_request_reviews=false
415+
416+ # 確認
417+ gh api repos/OpenTouryoProject/OpenTouryo/actions/permissions/workflow
300418```
301419
420+ ** 下げる前に、各ワークフローが ` permissions: ` を明示しているかを確認すること。**
421+ 既定に頼っているワークフローがあると、権限不足で失敗する。
422+
423+ ``` bash
424+ grep -A3 ' ^permissions:' .github/workflows/* .yml
425+ ```
426+
427+ ### 有効化後の運用
428+
429+ ** 新しく push する内容だけ** が検査される。既存の履歴は対象外。
430+
431+ | | |
432+ | ---| ---|
433+ | 止まるもの | ** 発行元を特定できる形のキー** (NuGet の ` oy2... ` 、AWS、GitHub PAT など) |
434+ | 止まらないもの | ` .pfx ` / ` .cer ` 、接続文字列の ` Password= ` (` Non-provider patterns ` が無効のため) |
435+
436+ 止まった場合は理由を選んでバイパスできる(** 管理者に通知され、記録が残る** )。
437+
438+ > ** ` .github/secret_scanning.yml ` の除外は Push protection には効かない。**
439+ > あちらはアラート(Secret scanning)の抑制であり、push の判定は見ていない。
440+
302441---
303442
304443## 7. 未着手の提案
305444
306445| | 内容 |
307446| ---| ---|
308- | ** ` develop ` の CI 必須化** | ` build-windows.yml ` の ` on: push ` に ` develop ` を追加し、ブランチ保護で必須チェックにする |
309- | ** Push protection の有効化 ** | Secret scanning のアラートが 0 件で安定していることを確認してから |
447+ | ** ` master ` の CI 必須化** | ` build-windows.yml ` に ` on: pull_request: branches: [master] ` を足し、 ` master ` のブランチ保護で必須チェックにする。 ** リリースの関所 ** として働く(下記) |
448+ | ` allowed_actions ` を絞る / SHA 固定 | 現在 ` all ` / 強制なし。サプライ チェーン対策。 ** 運用が重くなる ** ので、必要性とあわせて判断する |
310449| ` delete_branch_on_merge ` | マージ済みブランチを自動削除する |
311450| ` SECURITY.md ` | Private vulnerability reporting は有効にしたが、文書は未整備 |
312451| Issue / PR テンプレート | 「調査 → 実装 → 検証」の型が定まっているのでテンプレート化できる |
313452| ` .github/dependabot.yml ` | #517 の決着後 |
314- | ** CodeQL の再スキャン待ち** | 修正した 10 件(` X-Frame-Options ` 6 / ` requireSSL ` 4)は ` develop ` で再スキャンされるまで ` open ` のまま。** ` requireSSL ` は有効な設定が ` false ` のままなので消えない見込み** で、その 4 件は結果を見てから棄却する |
453+
454+ > ** ` develop ` を必須チェックの対象にはしない。**
455+ > ` deps ` ⇔ ` develop ` ⇔ feature と** 往復が多いハブ** であり、
456+ > 毎回のマージが CI 待ちになる。
457+ > ` deps ` 由来の変更は** すでに ` deps ` で検証済み** なので、二度手間でもある。
458+ >
459+ > ** ` master ` は往復しない。** ` develop ` からのマージはリリース時だけで、
460+ > 1 回あたり 9 分程度の待ちは受け入れられる。
461+ > ` RELEASE.md ` フェーズ 1 の「検証 3 本」を、機械的に担保できる。
462+
0 commit comments