Skip to content

feat: SAML デコーダに共有用マスク出力を追加 - #755

Merged
fumtas1k merged 8 commits into
developfrom
claude/issue-745-m0e3kx
Jul 20, 2026
Merged

feat: SAML デコーダに共有用マスク出力を追加#755
fumtas1k merged 8 commits into
developfrom
claude/issue-745-m0e3kx

Conversation

@fumtas1k

Copy link
Copy Markdown
Owner

概要

issue #745「SAMLデコーダ第2版」の残機能候補のうち 共有用マスク出力(secret-scrubber との連携) を実装しました。デコード結果 XML から社員 PII(NameID・属性値)と機密文字列を除去した「共有用マスク XML」を生成し、既存の整形 XML 表示ブロック内でトグル切替・コピーできます。SSO トラブルシュートで「PII を伏せた状態なら同僚・チケットに共有したい」というニーズに応え、ツールの存在意義(PII を外部サービスに貼れない現場向け)を出力面から完成させます。

  • 設計: docs/superpowers/specs/2026-07-20-saml-mask-output-design.md
  • 実装計画: docs/superpowers/plans/2026-07-20-saml-mask-output.md

残る第2版候補(XMLDSig 署名検証・EncryptedAssertion 復号)は本 PR スコープ外で、別 PR とします(issue #745 は引き続きオープン)。

マスク戦略(構造ベース+scrubber 併用)

  • フェーズ1(構造ベース): DOM を再パースし saml:NameID / saml:AttributeValue のテキストを値ベース一貫トークン [REDACTED:PII_n] に置換。同一値は同一トークンにすることで NameID とメール属性の相関を保つ。パターンでは拾えない日本語氏名(displayName の「山田 太郎」等)も構造的に確実に除去。
  • フェーズ2(scrubber 併用): 再シリアライズ後の文字列に secret-scrubberscrubTextHIGH_ENTROPY カテゴリ除外で適用し、URL クエリ埋め込みメール等の構造で拾えない残余を救済。HIGH_ENTROPY を除外するのは ds:SignatureValue / ds:X509Certificate の base64(非 PII・公開情報)を over-mask しないため。
  • 署名値・証明書・タイムスタンプ・ID・要素名は構造情報としてそのまま保持。

判断理由の詳細は docs/decisions.md [125] に記載。

変更点

  • src/utils/saml/ns.ts(新規): 名前空間定数を切り出し parse/mask で共有
  • src/utils/saml/mask.ts(新規): maskSamlXml(xml): SamlMaskResult
  • src/components/tools/SamlDecoder.tsx: 整形 XML ブロックに ToggleGroup(生 XML / マスク XML)・件数バッジ・注記を追加
  • ドキュメント(docs/tools.md / docs/decisions.md / SPEC.md)更新

テスト

test-gates 準拠でマスク(除去機構)の陽性対照を必須化。

  • 陽性対照: NameID メール・日本語氏名・複数属性値のマスク/同一値→同一トークンの相関/Destination URL 埋め込みメールの scrubber 救済/LogoutRequest の NameID マスク
  • 陰性対照(over-mask 防止): X509Certificate/SignatureValue の base64 が残る/タイムスタンプ・要素名・ID の保持
  • 不変条件: マスク後も valid XML

E2E は整形 XML details 内のトグル切替(PII トークン化・件数バッジ・コピー対象切替)を追加。

検証結果

コマンド 結果
npm run format:check PASS
npm run test PASS(2717 tests)
node_modules/.bin/astro check PASS(0 errors)
npm run lint PASS
npm run build PASS
npm run test:e2e -- saml-decoder PASS(12/12)

補足

  • 新規ツール追加ではなく既存ツールの挙動追加のため、ツールアイコン/VRT ページの新規登録は不要。details は初期折りたたみのため既存 /tools/saml-decoder の VRT baseline への影響は無い見込み。
  • マスクは「完全な匿名化」を保証しないため、UI に共有前の目視確認を促す注記を配置。

Closes #745 の一部(共有用マスク出力)。

🤖 Generated with Claude Code


Generated by Claude Code

claude added 7 commits July 20, 2026 07:40
issue #745 の機能候補「共有用マスク出力(secret-scrubber との連携)」の
設計をブレインストーミングで確定。構造ベースマスク+scrubber 併用方針。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzXQvK6mSGKEtAHRki75Ds
構造ベース(NameID/AttributeValue)+secret-scrubber 併用(HIGH_ENTROPY 除外)で
PII・機密を除去。値ベース一貫トークンで相関を保つ。陽性/陰性対照テスト付き。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzXQvK6mSGKEtAHRki75Ds
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 20, 2026

Copy link
Copy Markdown

Deploying devtools with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7e618d6
Status: ✅  Deploy successful!
Preview URL: https://d407c1ba.devtools-d9w.pages.dev
Branch Preview URL: https://claude-issue-745-m0e3kx.devtools-d9w.pages.dev

View logs

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Test 結果

  • Status: ✅ 全 74 件 pass
  • Workflow run: 29728425459
  • Artifact (diff 画像 / playwright-report): 上記 workflow run の Artifacts セクションから download

diff が 意図的な visual 変更の場合: Update Visual Regression Baseline workflow を本 PR ブランチで workflow_dispatch trigger して baseline を更新。
diff が 意図しない regression の場合: 該当変更を fix。
本 check は required ではないため fail のままでも merge は可能(reviewer 判断)。

@fumtas1k fumtas1k self-assigned this Jul 20, 2026

@fumtas1k fumtas1k left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

レビュー: SAML デコーダ共有用マスク出力(#755

セキュリティ / フロントエンド / ロジック / アーキテクチャ / テストの各観点で確認しました。全体として設計・実装・テストの質が高く、CI も全 green(test / e2e / VRT 74 件 / build)。ローカルで saml-mask ユニット 11 件の pass も再現できました。

良い点

  • セキュリティ: 全処理がブラウザ内完結、dangerouslySetInnerHTML 不使用、DOMParser は外部エンティティ非解決で XXE 安全。HIGH_ENTROPY を除外して ds:SignatureValue / ds:X509Certificate(公開情報)の over-mask を避け、それを裏付ける陰性対照テストまで用意しているのは堅実。マスクの限界(完全な匿名化を保証しない)を UI・docs・SPEC に明記し過信を防ぐ設計も良い。
  • アーキテクチャ: ns.ts への名前空間定数切り出しは parse/mask の SoT 化として自然で、リファクタと機能追加をコミット単位で分離できている。maskSamlXml は純関数+パース失敗時フォールバックで責務が明快。
  • ロジック: 値ベース一貫トークン化(同一値→同一トークン)で NameID とメール属性の相関をマスク後も追える点はトラブルシュート実務に即しており良い判断。フェーズ1(構造)+フェーズ2(scrubber 残余救済)の二段構えと、[REDACTED:PII_n] が有効カテゴリの正規表現に再マッチしない冪等性の考慮も spec で押さえられている。
  • フロントエンド: ToggleGroup / StatusBadge / CopyButton の既存共通コンポーネント再利用、semantic class のみ使用、Clear 時の xmlView リセット、E2E の waitForReactHydration 呼び出し(#750 対策)まで規約準拠。

指摘(対応推奨)

  1. [テスト品質] LogoutRequest の陽性対照が空振りsaml-mask.test.ts:62 にインラインコメント)
    expect(xml).not.toContain('taro@example.com') だが、フィクスチャの NameID は taro.yamada@example.com で、taro@example.com は入力に存在しません。マスクが何もしなくても通る vacuous なアサーションで、test-gates の「機構を無効化したら fail する陽性対照」を満たしていません。実在値 taro.yamada@example.com で検証するよう修正を推奨。

任意(低優先・ブロッカーではない)

  1. [効率] マスクの eager 計算: masked / maskedXml[ok] 依存でデコード成功のたびに実行されます。details は初期折りたたみ・初期 raw のため、ユーザーがマスク表示を一度も開かなくても scrubText が全 XML を走査します。通常サイズの SAML では無視できますが、xmlView === 'masked' で遅延評価にすれば大きな入力での無駄な計算を避けられます。
  2. [網羅性] 属性値の PII: フェーズ1 は要素テキスト(NameID / AttributeValue)のみが対象のため、XML 属性に載る PII(例: SubjectConfirmationData@Address の IP、独自スキーマの属性に入った日本語氏名)はフェーズ2 のパターン依存で漏れる余地があります。既に「完全な匿名化を保証しない・共有前に目視確認」と明記済みなので許容範囲ですが、認識として。
  3. [nit] E2E ロケータ: page.locator('pre').last()getByRole/getByText/getByLabel 方針(ui-conventions §3.3)からの逸脱です。<pre> に role が無いための妥協は plan にも注記済みで許容できますが、対象 details 内にスコープを絞る書き方だとより堅牢です。

結論: 指摘1(陽性対照の空振り)だけ本 PR で直すと test-gates の趣旨に沿います。他は任意対応で問題ありません。機能・型・CI いずれも問題は見当たりませんでした。

🤖 Generated with Claude Code


Generated by Claude Code

Comment thread src/utils/__tests__/saml-mask.test.ts Outdated

it('LogoutRequest の NameID もマスクされる', () => {
const { xml, piiCount } = maskSamlXml(LOGOUT_REQUEST_XML);
expect(xml).not.toContain('taro@example.com');

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このアサーションは空振り(vacuous)しています。フィクスチャ LOGOUT_REQUEST_XML の NameID は taro.yamada@example.com で、taro@example.com という部分文字列は入力に存在しません。そのため not.toContain('taro@example.com')マスクが何もしなくても通り、NameID が実際に除去されたことを実証できていません(陽性対照として機能していない)。除去の裏付けは下の piiCount >= 1 のみに依存しています。

test-gates の「機構を無効化したら fail する陽性対照」を満たすには、入力に実在する値で検証してください:

expect(xml).not.toContain('taro.yamada@example.com');
expect(xml).toContain('[REDACTED:PII_');
expect(piiCount).toBeGreaterThanOrEqual(1);

Generated by Claude Code

フィクスチャに存在しない taro@example.com での not.toContain は空振り
していたため、実在値 taro.yamada@example.com とトークン出現で検証する
真の陽性対照に修正(レビュー指摘・test-gates 準拠)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzXQvK6mSGKEtAHRki75Ds

Copy link
Copy Markdown
Owner Author

レビューありがとうございます。指摘1(LogoutRequest 陽性対照の空振り)を修正しました(7e618d6)。

  • LOGOUT_REQUEST_XML に存在しない taro@example.com での not.toContain を、実在値 taro.yamada@example.com の除去 + [REDACTED:PII_ の出現で検証する形に変更。マスクを無効化すると fail する真の陽性対照になっています(test-gates 準拠)。
  • saml-mask ユニット 11 件 pass・format:check green を確認済み。

任意項目(低優先・ブロッカーではない旨了解)の扱い:

  • 2 マスクの遅延評価: 妥当なご提案ですが本 PR スコープ外とし据え置きます(通常サイズでは無視できる範囲)。
  • 3 属性値外の PII / 4 E2E ロケータ: いずれも plan・docs で認識済みの許容範囲として据え置きます。

据え置き分のうちご希望のものがあれば別 issue 化して対応します。


Generated by Claude Code

@fumtas1k fumtas1k left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再レビュー: 指摘1 修正確認 ✅

7e618d6 を確認しました。指摘1(LogoutRequest 陽性対照の空振り)は解消されています。

expect(xml).not.toContain('taro.yamada@example.com'); // 実在値で検証(空振り解消)
expect(xml).toContain('[REDACTED:PII_');              // 除去→トークン化を実証
expect(piiCount).toBeGreaterThanOrEqual(1);
  • フィクスチャ LOGOUT_REQUEST_XML の NameID 実在値 taro.yamada@example.com を対象にしたため、マスクを無効化すれば fail する真の陽性対照になりました(test-gates 準拠)。
  • ローカルで saml-mask ユニット 11 件 pass を再確認。CI も test / VRT green(e2e 実行中)。

残りの任意項目(#2 eager 計算 / #3 属性値 PII / #4 E2E ロケータ)はブロッカーではなく、本 PR で対応しなくても問題ありません。マージ可能な状態と判断します。LGTM 👍

🤖 Generated with Claude Code


Generated by Claude Code

@fumtas1k
fumtas1k merged commit 71b7dfc into develop Jul 20, 2026
5 checks passed
@fumtas1k
fumtas1k deleted the claude/issue-745-m0e3kx branch July 20, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SAMLデコーダ第2版: 署名検証・復号・他メッセージ型・レビュー残余改善

2 participants