各ツールが内部でどう動くかを開発者向けに解説する。README のツール一覧は「何ができるか」、本ドキュメントは「どう動くか」を扱う。ライブラリの採用理由や設計判断の経緯は docs/decisions.md を参照。
各ツールは原則 3 小節(仕組み・アルゴリズム / 準拠仕様・RFC / 制限・エッジケース)で構成する。該当しない小節は省略する。src/data/tools.ts に登録された全ツールを網羅することを原則とし、新規ツール追加時は本ドキュメントに節を追加する(.agents/rules/common.md 4 章のドキュメント更新ルール参照)。
ULID の生成は ulidx パッケージの ulid() に委譲する。ULID は 26 文字の Crockford Base32 文字列で、前半 10 文字(48 bit)がミリ秒タイムスタンプ、後半 16 文字(80 bit)がランダム成分。先頭にタイムスタンプを持つため辞書順ソートで生成時刻順に並ぶ。表示では先頭 10 文字(タイムスタンプ部)を色分けする。
各行の「タイムスタンプ」列は、ULID をデコードした値ではなく、生成時に new Date().toISOString() で取得した発行時刻を ISO 8601 で表示している(タイムスタンプ部と同一ミリ秒)。
- ULID 仕様(48 bit タイムスタンプ + 80 bit ランダム、Crockford Base32、大文字小文字を区別しない、辞書順ソート可能)。
- RFC で定義された仕様ではない。
- 生成には素の
ulid()を使うため、同一ミリ秒内に複数生成した場合の単調増加(monotonicity)は保証しない(ulidx のmonotonicFactoryは使用していない)。同一ミリ秒内ではランダム成分のみで区別される。
UUID v7 の生成は uuid パッケージの v7() に委譲する。生成した UUID は 128 bit を以下の 5 フィールドに分解して色分け表示する。
| フィールド | ビット数 | 内容 |
|---|---|---|
unix_ts_ms |
48 bit | ミリ秒単位の Unix タイムスタンプ |
ver |
4 bit | バージョン番号(常に 7) |
rand_a |
12 bit | ランダムビット |
var |
2 bit | バリアント(RFC 4122 = 8〜b の nibble 上位) |
rand_b |
62 bit | ランダムビット |
タイムスタンプ表示は、UUID 先頭 12 桁の 16 進(= 48 bit の unix_ts_ms)を数値に変換し、ISO 8601 形式に整形して得る。先頭にタイムスタンプを持つため、UUID v7 は文字列としてソートするとおおむね生成時刻順に並ぶ。
- RFC 9562(UUID v7 を定義。RFC 4122 を更新)。
- ランダムビットは
uuidパッケージ経由でcrypto.getRandomValues(CSPRNG)から供給される。
- 同一ミリ秒内に複数生成した場合の単調増加(monotonicity)の保証は
uuidパッケージのv7()実装に依存する。 - タイムスタンプはミリ秒精度のため、1 ミリ秒未満の生成順序はランダムビットの差で区別される。
文字種・文字数を指定してダミーテキストを生成する。文字種は 5 種類。
- ひらがな / カタカナ / 英数字: それぞれの文字集合から 1 文字ずつ
Math.randomで等確率に選ぶ。 - 漢字混じり日本語: 1 文字ごとに重み付き抽選する(漢字 55% / ひらがな 25% / 助詞 20%)。文字集合は固定の代表的な漢字・助詞セット。
- Lorem Ipsum: 固定の Lorem Ipsum 文字列を必要長まで繰り返し、指定文字数に切り詰めて末尾空白を除去する(ランダム化はしない)。
改行を「あり」にすると、指定した間隔(文字数)ごとに改行を挿入する。
- 文字数は 1〜5000、改行間隔は 1〜1000 に丸められる(範囲外入力は自動でクランプ)。
- 乱数は
Math.randomを使用する(ダミーテキスト用途のため暗号学的乱数は不要)。 - Lorem Ipsum は固定文の繰り返しであり、語順はランダムにならない。
ワンタイムパスワードの生成・検証を Web Crypto API(crypto.subtle の HMAC)で行う。シークレット鍵はブラウザ外に送信しない。
- シークレット: Base32 文字列として扱う。ランダム生成時は
crypto.getRandomValuesで 160 bit(20 バイト)を生成し、Base32(32 文字・パディングなし)で表現する。 - HOTP: シークレットを HMAC 鍵としてインポートし、8 バイトのビッグエンディアンのカウンタに対して HMAC を計算する。MAC の末尾 nibble をオフセットとして 4 バイトを動的に切り出し(dynamic truncation)、最上位ビットをマスクした 31 bit 値を
10^digitsで剰余して指定桁数のコードにする。 - TOTP: カウンタを
floor(現在時刻ms / (period × 1000))として算出し、あとは HOTP と同じ。 - 検証: 現在カウンタの前後 ±window(既定 ±1)を走査して一致を探す。比較は定数時間(
timingSafeEqual)で行い、早期 return せず window 全件を走査する(タイミング攻撃耐性)。 otpauth://URI: 認証アプリ取り込み用の URI を組み立てる(issuer に:を含むと throw、secret は defensive に URL エンコード)。
- RFC 4226(HOTP)/ RFC 6238(TOTP)。
- Base32 は RFC 4648 §6。padding 除去後の長さ
mod 8が 0/2/4/5/7 のみ有効(1/3/6 は末尾ビットが中途半端で無効)。 - ハッシュは SHA-1 / SHA-256 / SHA-512、桁数は 6 / 7 / 8、周期は 30 / 60 秒から選択。
- 検証の時刻ずれ許容は前後 ±1 ステップ(既定)。大きな時計ずれは検証に失敗する。
- 不正な Base32 文字・長さはデコード時に throw する。
- HMAC・乱数生成は Web Crypto API に依存するため、
crypto.subtleが利用可能なセキュアコンテキスト(HTTPS / localhost)が前提。
日本人向け架空個人データを自前辞書+Math.random() で生成する(faker の ja ロケールはバンドル過大のため不採用)。
- 氏名↔フリガナ整合: 姓・名それぞれの辞書(
{ kanji, yomi, romaji })から 1 件ずつランダム選択し、漢字と読みが同一辞書エントリの組み合わせになることを保証する。辞書には姓 30 件・男名 20 件・女名 20 件を収録(src/utils/dummy-personal-data/dictionaries.ts)。辞書の読み(yomi)はひらがなで保持し、「フリガナ」列はカタカナに変換して出力する(ラベルとの整合)。 - 生年月日↔年齢整合: 指定年齢範囲
[ageMin, ageMax]から乱数で年齢を決定 → 基準日(生成時の現在日付)からage 年前の日付を基点に 0〜364 日さかのぼって生年月日を生成 →computeAge(date, today)で満年齢を再計算して age カラムに格納(端数日による 1 歳ずれを防ぐ往復確認)。 - 住所↔郵便番号↔固定電話整合: 住所辞書(
{ zip, pref, city, areaCode })から 1 件選択すると郵便番号・都道府県・市区町村・市外局番が同時確定。固定電話はareaCode-middle-last形式で全 10 桁(先頭0含む)に整形。 - 携帯電話番号の実在回避: 先頭
090/070+ 第 4 桁を0に固定した 11 桁を生成する。総務省「電気通信番号の種別」では音声携帯番号0X0CDEFGHJKの C は「0 を除く」と規定されているため、第 4 桁 C=0 の帯域は音声携帯の割当対象外であり実在しない確度が高い。0800(フリーダイヤル)・0600(FMC 電話番号)は割当済みのため除外している。 - メールアドレス: ローマ字姓名ベースのローカル部 + RFC 2606 予約ドメイン(
example.com/example.jp/example.net/example.org)でランダム生成。実在ドメインは使わない。 - 一意性オプション: 「メール・電話番号を一意化」を有効にすると、メール(ローカル部への連番付与
sato1@...)・固定電話(市外局番を保持し加入者番号のみ再生成 → 住所整合を維持)・携帯(pickMobile()再生成で非実在帯を維持)が生成件数内で重複しないよう後処理される。氏名・フリガナは辞書規模の制約(姓 30 × 名 40 ≒ 1,200 通り・最大 3,000 件)から一意化対象外。 - 連番ID列: 「連番ID列 (No.)」を有効にすると、出力(CSV/JSON)の先頭列/キーに 1 始まりの連番が付与される(JSON では数値型で出力)。主キー用途に使いやすいよう設計。
- 反映タイミングの統一: トグルを「生成条件」(人数・年齢範囲・氏名区切り・一意化=変更後に再生成が必要)と「出力の見せ方」(出力項目・連番ID列=プレビューに即時反映)の 2 セクションに分離して表示する。生成後に生成条件を変更すると「生成条件が変更されました。再生成してください」の注意表示(
aria-liveでスクリーンリーダーへも通知)を生成ボタン近傍に出し、再生成で消える。一意化は件数全体に作用する破壊的処理のため即時反映ではなく生成条件側に置く。
シリアライズ: CSV は既存 papaparse で生成し先頭に UTF-8 BOM(U+FEFF)を付加することで Excel の文字化けを防ぐ。各セルに escapeCsvFormula(既存 @/utils/json-csv)で CSV 数式インジェクション(CWE-1236)対策を適用。JSON は選択フィールドを日本語ラベルキーに変換して JSON.stringify で出力。
- 携帯番号帯域方針: 総務省「電話番号に関する情報」の「電気通信番号の種別」表(音声携帯は
0X0CDEFGHJK、C は 0 を除く)を根拠とする。 - CSV 数式インジェクション対策: OWASP CSVX CWE-1236 の先頭文字エスケープ方式(
=,+,-,@,\t,\rの前にシングルクォートを付加)。 - 予約ドメイン: RFC 2606 §2「Reserved Example Second Level Domain Names」(
example.com/example.net/example.org)および IANA に登録されたexample.jp。
- 辞書はサンプルセットのため多件数生成では同一データが繰り返し出やすい
- 郵便番号の実在性・住所の正確性は保証しない(ダミー辞書)
- クレジットカード番号・マイナンバー・血液型・会社名は対象外
- Excel(.xlsx)出力は非対応(
xlsx/exceljsのバンドル増を避けるため。CSV の BOM 付き出力で Excel 互換を確保) - 全処理はブラウザ内で完結し、生成データを外部サーバーに送信しない
テキスト / URL から QR コードを生成する。生成は qrcode-generator を使うが、直接 import せず @/utils/qrcode のパッチ済みモジュールを介す。このパッチは qrcode.stringToBytes を TextEncoder(UTF-8)で上書きし、ライブラリ既定の ISO-8859-1 相当では壊れる日本語(マルチバイト文字)を正しくエンコードできるようにしている。型番(version)は 0 を渡してデータ量から自動決定する。
出力は SVG。アクセシビリティのため role="img" と <title>QRコード: …</title> を SVG 先頭に注入する(aria-label は付けない。付けると ARIA の名前計算で <title> が除外され URL 等の本文が読まれなくなるため)。<title> に入れる本文は escapeXml で実体参照化し、XSS の二次防衛線も兼ねる。
- QR Code(ISO/IEC 18004 / JIS X 0510)。
- 誤り訂正レベルは L(約 7%)/ M(15%)/ Q(25%)/ H(30%)から選択。
- データが長すぎて QR の容量上限を超えると生成に失敗し、「テキストが長すぎる可能性があります」と表示する。
- 誤り訂正レベルを上げるほど 1 シンボルに格納できるデータ量は減る。
入力桁からチェックディジットを計算し、バーコードを描画する。チェックディジットは モジュラス 10 ウェイト 3-1(calcJan)で算出する。
- JAN-13: 12 桁入力。奇数位(1,3,5…)× 1、偶数位(2,4,6…)× 3 を合計し、
(10 − 合計 mod 10) mod 10がチェックディジット。 - JAN-8: 7 桁入力。奇数位 × 3、偶数位 × 1。
計算過程(重み別の桁・小計・合計・剰余)を表示する。バーコード描画は jsbarcode(EAN-13 / EAN-8)。サンプル値は先頭 2 桁を日本の国コード「49」固定で生成する。
- JAN(= EAN-13 / EAN-8、JIS X 0501)。チェックディジットは GS1 のモジュラス 10 方式。
- 入力は数字のみ。桁数は JAN-13 が 12 桁、JAN-8 が 7 桁(チェックディジットを除いた桁数)に固定。範囲外は入力時にエラー表示する。
GTIN-14 と任意のアプリケーション識別子(AI)から GS1 DataBar Limited 合成シンボルを生成する。
- チェックディジット: 13 桁から GTIN-14 のチェックディジットを計算する(
calcGtin14CheckDigit、モジュラス 10 ウェイト 3-1。左端が奇数位 × 3)。 - シンボル生成:
bwip-jsのdatabarlimitedcomposite(合成シンボル)で描画する。対応 AI は 17(賞味/消費期限)/ 10(ロット番号)/ 11(製造日)/ 15(best-before)/ 21(シリアル)。可変長 AI に後続 AI が続く場合の FNC1 区切りは bwip-js のgs1process()が自動挿入する。 - AI テキスト注入: bwip-js の
includetextはリニア部(01)GTINしか描画しないため、AI 値((17)YYMMDD等)は SVG の<text>として合成シンボル上に手動で 1 行注入する(injectCompositeText)。注入文字列はescapeHtmlでエスケープする。
- GS1 General Specifications(GS1 DataBar Limited / GS1 Composite Component、アプリケーション識別子)。
- GTIN-14。
有効なバーコード(svg と gtin が揃ったカード)を A4 用紙に印刷する。
- 実寸変換: bwip-js は
scale: 3(1 モジュール = 3px)で SVG を生成する。印刷時はsetSvgPrintSize(svg, xdimMm)がfactor = xdimMm / 3を計算し、SVG ルート要素のwidth/height属性を mm 値に置換する。これにより X-dimension(モジュール幅)が xdimMm に一致した mm 実寸で印刷される(プリンタ DPI 非依存)。 - CSP 両立:
style-src 'unsafe-inline'を撤去済みのため、SVG の presentation attribute(width="52.14mm")で実寸を指定する。CSS inline style やel.style.setPropertyは使用しない。詳細はdocs/decisions.md[098] を参照。 - レイアウト: 列数(1/2/3、デフォルト 2)と X-dimension プリセット(小=0.330mm / 中=0.495mm / 大=0.660mm、デフォルト 中)を ToggleGroup で切替。
@page { size: A4; margin: 12mm }+.print-cell { border: 1px dashed #000 }で破線カット線付きグリッドを構成する。 - 印刷起動: in-page
window.print()を呼ぶ。ブラウザ印刷ダイアログから「PDF に保存」も利用可能。 - 複数ページ対応: 印刷コンテナは
createPortalでdocument.body直下へ出し、通常フロー配置で複数ページ印刷に対応する(position: absolute配置は Chrome 等で 2 ページ目以降がクリップされる既知挙動があるため)。10 件 × 大サイズ等で A4 高さを超えてもページ送りされる。詳細はdocs/decisions.md[098] を参照。
- GS1 DataBar Limited の入力は 13 桁、先頭桁は 0 または 1 のみ(仕様上の制約)。
- PNG ダウンロード時は白背景での描画が必須(透明背景だと一部リーダーでデコードできない)。合成シンボル上の AI テキスト描画の経緯・トレードオフは
docs/decisions.md[067] / [082] / [083] を参照。 - 大サイズ × 3 列は A4 印刷可能幅(約 186mm)を超過し得る。UI にヒント注記を表示するが、ハードな組合せ制限は設けていない。印刷プレビューで確認すること。
- 印刷用 SVG(
setSvgPrintSize出力)は mm 値の width/height を持つため、svgContentToPngBlobに渡すと canvas サイズ抽出が失敗する。両者は別経路で使用すること。
ECDSA 署名付きチケットを生成し、公開鍵でオフライン検証する。暗号処理はすべてブラウザ組み込みの Web Crypto API(crypto.subtle)で行う。
- 鍵ペア生成:
crypto.subtle.generateKeyで ECDSA P-256 の鍵ペアを生成し、JWK 形式でエクスポートする。既存の秘密鍵 JWK(dフィールドを含む)をインポートして再利用することもできる。 - 署名: チケットデータをパイプ区切り文字列
eventId|ticketId|timestamp|name|categoryにシリアライズし、これを署名対象としてcrypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' })で署名する。署名(P-256 で 64 バイト)は Base64URL(パディングなし 86 文字)にエンコードする。 - QR 文字列:
payload|signatureの形式で連結し、qrcode-generator(誤り訂正レベル M)で SVG を生成する。 - 検証: 読み取った文字列を
lastIndexOf('|')で payload と signature に分離する(signature は Base64URL のため文字集合に|を含まず境界が一意に定まる)。同じ payload 文字列を再構築してcrypto.subtle.verifyで署名を照合し、さらにtimestampを現在時刻と比較して有効期限切れを判定する。公開鍵 JWK さえあればサーバー無しで検証できる。
- 署名方式: ECDSA(曲線 P-256 / secp256r1)+ SHA-256。
- 鍵フォーマット: JWK(JSON Web Key)。
- エンコード: 署名は Base64URL(
A-Za-z0-9_-)。
- QR 容量: 署名込みの全データを最大 250 バイトに制限している。署名は約 86 バイト固定のため、ペイロード(イベント ID・チケット ID・名前・料金区分等)がこれを圧迫する。
- 一括生成上限: 一度に生成できるチケットは最大 20 枚。
- フィールド制約: 区切り文字のため、各フィールド値に
|を含めることはできない(含む場合は生成時に throw)。 - オフライン検証の前提: 検証側は発行者の公開鍵 JWK を別途入手している必要がある(公開鍵の配布手段はツールのスコープ外)。
- 失効の仕組みは無い: 検証は署名の正当性と
timestampによる有効期限のみ。発行済みチケットの個別失効(revocation)はできない。
カメラまたは画像ファイルから QR コードを読み取る。デコードは jsQR。
- 画像ファイル:
URL.createObjectURL→Image→<canvas>に描画 →getImageData→jsQRでデコードする(decodeQrFromFile)。長辺がmaxDim(既定 1600px)を超える画像はアスペクト比を保ってダウンスケールしてから処理する。各awaitポイントでAbortSignalを確認し、処理中のキャンセルに対応する。 - ファイル検証: アップロード前に
validateFile(@/utils/file-validation)で検証する。画像判定はfile.type(OS / browser 由来の advisory 値で拡張子偽装を検知できない)に依存せず、先頭バイトの magic number(PNG / JPEG / GIF / WebP)で行う。SVG はバイナリ magic を持たないため先頭テキストを sniff(<svg>始まり、または<?xml>始まり かつ<svg>出現)する。拡張子だけ画像に偽装した非画像ファイルは canvas 到達前に拒否される。 - カメラ:
useQrCameraフックでライブ映像から読み取る。 - 結果判定: デコード文字列を
detectQrContentで解析し、http:/https:の URL ならホスト名付きの URL として、それ以外はテキストとして表示する。
- QR Code(ISO/IEC 18004)の読み取り。
jsQRは QR コード専用。JAN/EAN などの 1 次元バーコードや他の 2 次元コードは読み取れない。- 画像・映像はすべてブラウザ内で処理し、サーバーへアップロードしない。
- カメラ利用はセキュアコンテキスト(HTTPS / localhost)とユーザーのカメラ許可が前提。
JavaScript 標準の encodeURIComponent / decodeURIComponent でテキストとパーセントエンコード形式を相互変換する。デコードは不正なエスケープシーケンスで例外になるため、デコードモードでは事前に decodeURIComponent を試行してバリデーション(不正なら「不正なURLエンコード文字列です」)する。
- パーセントエンコーディング(RFC 3986)。
encodeURIComponentは英数字と- _ . ! ~ * ' ( )以外をエスケープする(JavaScript の仕様に準拠)。
- 不正な
%シーケンス(例:%ZZ、孤立した%)はデコードに失敗する。 - 文字列の相互変換のみで、URL 全体の構造(スキーム・クエリ等)の解析はしない。
ブラウザ組み込みの btoa / atob を使う。これらはバイナリ文字列しか扱えないため、UTF-8 のマルチバイト文字を正しく往復させるよう TextEncoder / TextDecoder を挟む。
- エンコード: テキスト →
TextEncoder(UTF-8 バイト列)→ バイナリ文字列 →btoa。URL-safe 指定時は+→-、/→_に置換し末尾パディング=を除去する。 - デコード: URL-safe 入力は標準 Base64 に正規化(
-→+、_→/、パディング補完)してからatob→Uint8Array→TextDecoder('utf-8', { fatal: true })。fatalにより UTF-8 として不正なバイト列を検出する。
- Base64 / base64url(RFC 4648 §4・§5)。
- Base64 として不正な文字列は「有効なBase64文字列ではありません」、デコード結果が UTF-8 テキストにならない場合は「テキストとして表示できないデータです」とエラーになる(テキスト変換が前提でバイナリファイルは扱わない)。
JWT を . で 3 分割し、Header・Payload を base64url デコードして JSON パースする(parseJwt)。Payload に exp(数値)があれば現在時刻と比較し、有効 / 期限切れ / exp なし を判定し、有効な場合は残り時間を表示する。
署名検証は任意で、Web Crypto API(crypto.subtle.verify)で行う。署名入力は rawHeader.rawPayload。対応アルゴリズム:
- HS256 / HS384 / HS512: HMAC。共有シークレット文字列を鍵としてインポート。
- RS256 / RS384 / RS512: RSASSA-PKCS1-v1_5。公開鍵 PEM(SPKI)を使用。
- ES256 / ES384 / ES512: ECDSA(P-256 / P-384 / P-521)。公開鍵 PEM(SPKI)を使用。
- 上記マップにないアルゴリズム(
none/ EdDSA / PS* 等)は「unsupported」となり検証できない。 - デコード(Header/Payload の表示)は署名検証なしでも行える。改竄の検出には署名検証が必要で、検証せずに Payload を信用してはならない。
- RS* / ES* の検証には対応する公開鍵 PEM(
-----BEGIN PUBLIC KEY-----)が必要。
- 入力種別を
detect.tsで判定する。PEM は-----BEGIN CERTIFICATE-----/-----BEGIN PKCS7-----ブロックを正規表現で全抽出し Base64 を DER 化、生 DER(先頭0x30)・Base64 単体も受け付ける。PKCS12/PFX/ 証明書を含まないENCRYPTED PRIVATE KEYは PKCS#12 として識別し、パスワード入力 UI へ誘導する - 各 DER を
asn1js.fromBERでデコードしpkijsのCertificateに変換、parse.tsで表示用フィールドへ正規化する。DN は OID を短縮名(CN/O/OU/C/L/ST 等)へマップ、SAN・KeyUsage・ExtKeyUsage・BasicConstraints・SKI/AKI は拡張 OID から取得する。フィンガープリントはcrypto.subtle.digest('SHA-256', der) - PKCS#7 は
ContentInfo→SignedDataから証明書を展開する - SCT 拡張(OID
1.3.6.1.4.1.11129.2.4.2)は ASN.1 ではなく RFC 6962 の TLS シリアライズ構造のため、OCTET STRING 内のバイト列をsct.tsで手動デコードする(version / logId / timestamp、best-effort) - チェーンは
chain.tsが subject/issuer DN(必要に応じて AKI/SKI)で親子関係を構築し issuer→subject 順に並べ替える。各リンクの署名は DER から再構築したCertificate.verify(Web Crypto)で検証し、改ざん・issuer 不一致を検出する。有効期限は現在時刻と NotBefore/NotAfter の比較で判定する - 1 枚のパース失敗は
error付きで保持し、他証明書の表示を継続する - PKCS#12(.pfx/.p12): pkijs の
PFX → AuthenticatedSafe → SafeContents → SafeBagを辿って証明書 DER と PKCS#8 秘密鍵を抽出する(src/utils/cert/pkcs12.ts)- パスワード: UI で入力 →
TextEncoder().encode(password).buffer(UTF-8 ArrayBuffer)を pkijs に渡す。pkijs が内部で BMPString 変換する(makePKCS12B2Key) - 証明書抽出:
CertBag(OID1.2.840.113549.1.12.10.1.3)から DER を取り出し、既存のparseDerCertificates → buildChainパイプラインに流す - 秘密鍵抽出:
PKCS8ShroudedKeyBag(OID1.2.840.113549.1.12.10.1.2)をparseInternalValuesで復号しPrivateKeyInfoから PKCS#8 PEM を生成。アルゴリズム・鍵長・曲線名は常時表示、PEM 本体は<details>トグル開示 - 暗号方式制限: PBES2(PBKDF2 + AES-CBC)のみ復号可能。レガシー RC2-40/3DES は Web Crypto 非対応のため
unsupported-encryptionエラーで案内する - 誤パスワード検出:
pfx.parseInternalValues({ checkIntegrity: true })が "Integrity for the PKCS#12 data is broken!" を throw →wrong-passwordとして UI に表示
- パスワード: UI で入力 →
- PKCS#12 は PBES2/AES のみ対応。レガシー暗号(RC2-40/3DES)保護の .pfx は
openssl pkcs12 -keypbe AES-256-CBC -certpbe AES-256-CBCで再エクスポートが必要 - 鍵フォーマット変換(PEM/DER/JWK)は key-converter ツールで対応
- 失効確認(CRL / OCSP)は行わない。署名検証はチェーン内の隣接ペアに対してのみで、信頼ストアとの照合(ルート CA の信頼性確認)は行わない
- SCT はタイムスタンプ・ログ ID の表示のみで、署名の暗号検証はしない(best-effort)
- 全処理はブラウザ内で完結し、入力(社内 CA・本番証明書・秘密鍵を含む)は外部に送信しない
- 入力形式を
decode.tsのdecodeSamlInputで自動判定する。URL 全体ならSAMLResponse/SAMLRequestクエリパラメータを抽出(URLSearchParamsは+を空白に変換し base64 を破壊するため、生クエリ文字列から自前パースし percent エンコードのまま+を保持する。クエリキー名が percent エンコードされている場合も比較前にdecodeURIComponentを試みる)→ 生 XML 判定 → URL デコード → base64 デコード(-/_を含む base64url 表記は標準 base64 へ変換しパディングを補完してから decode)→ UTF-8 として XML と解釈できれば HTTP-POST binding、できなければfflateのDecompress(ストリーミング API。raw deflate/zlib/gzip 自動判定)で展開し HTTP-Redirect binding と判定する。展開後サイズが 32MB を超えた場合は zip bomb 対策としてエラーにする(圧縮データを 64KB 単位のチャンクに分けて渡すことで、上限超過を検知した時点で残りの展開処理を打ち切る)。適用した変換ステップは UI に表示する parse.tsがDOMParserで XML をパースし、getElementsByTagNameNS等の名前空間 URI ベースの解決で prefix(saml:/samlp:等)非依存に構造化する。Response は Issuer/Status/Destination と Assertion ごとの NameID・属性・Conditions・AuthnStatement・SubjectConfirmationData、AuthnRequest は Issuer/Destination/AssertionConsumerServiceURL/ProtocolBinding/NameIDPolicy/RequestedAuthnContext、LogoutRequest は Issuer/Destination/NotOnOrAfter/Reason/NameID(EncryptedID は存在検出のみ)/SessionIndex(複数可)、LogoutResponse は Issuer/Status/Destination/InResponseTo を抽出する。ds:Signatureの有無・EncryptedAssertionの件数も検出する(存在表示のみ、検証・復号はしない)- 二段階ステータス(外側 StatusCode の子にネストした内側 StatusCode)の内側コードも
statusSubCodeとして抽出し、UI では Status 行の直後に表示する AudienceRestrictionは要素ごとにstring[](AND される制約)として保持し、各制約内のAudienceは OR 列挙として扱う
- 二段階ステータス(外側 StatusCode の子にネストした内側 StatusCode)の内側コードも
checks.tsのrunResponseChecksが Response の定番チェック(Status / 有効期間 / Audience・Recipient / NameID)を現在時刻基準で実行する。NotOnOrAfterは SAML 仕様どおりその時刻自体を含まない排他境界(now >= notOnOrAfterで期限切れ)として判定する- Status が失敗の場合、内側 StatusCode があれば
外側 / 内側(例:Responder / RequestDenied)の形式で併記する - タイムゾーン指定(
Z/±hh(:mm))のないNotBefore/NotOnOrAfterはこの端末のローカル時刻として解釈されるため実行環境依存になる旨を警告として注記しつつ、判定自体は継続する。日付のみ形式(YYYY-MM-DD)は ES 仕様上 UTC (00:00Z) 解釈が確定するため、ローカル時刻の注記ではなく専用の注記を表示する Dateでパース不能な日時は「有効期間内」と誤って表示しないよう warning 扱いにする- SP entityID 入力時の Audience 照合は、複数の
AudienceRestrictionがある場合、空でないすべての制約に entityID が含まれる場合のみ一致とする(AND 判定) runLogoutRequestChecksは LogoutRequest の NotOnOrAfter(任意属性のため未指定は info、期限切れは error)と NameID の存在(EncryptedID は復号非対応のため warning、いずれもなしは仕様違反として error)を、runLogoutResponseChecksは Status を同じ規則で判定する
- Status が失敗の場合、内側 StatusCode があれば
format.tsが表示用に生 XML を簡易整形する(要素・属性・テキストのみを再構成するため、タグ間に混在するテキスト(mixed content)は表示されない場合がある旨を UI に注記)mask.tsのmaskSamlXmlが「共有用マスク XML」トグル用の 2 フェーズマスクを行う。フェーズ1(構造ベース)は再パースした DOM 上のsaml:NameID/saml:AttributeValueのテキストを値ベース一貫トークン[REDACTED:PII_n](同一値は同一トークン)に置換し、フェーズ2 は再シリアライズ後の文字列にsecret-scrubberのscrubTextをHIGH_ENTROPYカテゴリ除外で適用して URL クエリ埋め込みメール等の構造で拾えない残余を救済する。HIGH_ENTROPYを除外するのはds:SignatureValue/ds:X509Certificate等の base64(非 PII・公開情報)を over-mask しないため。署名値・証明書・タイムスタンプ・ID・要素名は構造情報としてそのまま残す
- SAML 2.0 Core / Bindings(HTTP-POST・HTTP-Redirect binding)
- XMLDSig 署名検証・EncryptedAssertion / EncryptedID の復号・ArtifactResolve 等のその他メッセージ型は非対応(署名・暗号化は存在の有無のみ表示。第2版候補)
- LogoutRequest の主体識別子は
NameID/EncryptedIDのみ対応(SAML 2.0 Core 3.7.1 で許容されるBaseIDは非対応。実運用例がほぼ皆無なため。BaseIDのみのリクエストは NameID チェックが error 表示になる) - ブラウザの
DOMParserは外部エンティティを解決しないため XXE は発生しない - 全処理はブラウザ内で完結し、入力(Assertion に含まれる氏名・メール等の PII を含む)は外部に送信しない
- deflate 展開後のサイズが 32MB を超える入力はエラーにする(zip bomb 対策)
- 「共有用マスク XML」は構造上の PII フィールド(NameID・AttributeValue)と
secret-scrubberの既知パターンの除去であり、完全な匿名化を保証するものではない。共有前に必ず目視で確認すること
fast-xml-parser の XMLParser / XMLBuilder で相互変換する。
- JSON → XML:
JSON.parse後、XMLBuilderで構築する。ルート要素はroot固定({ root: parsed }でラップ)。出力に XML 宣言<?xml version="1.0" encoding="UTF-8"?>を付与し、2 スペースインデントで整形する。 - XML → JSON:
XMLParserでパースし、JSON.stringify(_, null, 2)で整形出力する。
属性は @_ プレフィックス、テキストノードは #text というキーで表現する(parseAttributeValue で属性値を型変換)。
- JSON → XML のルートタグは
root固定で変更できない。 - 属性・テキストの表現は
@_/#text規約に従うため、これらを含む元データは往復で表現が変わりうる。 - 不正な JSON / XML はそれぞれ「有効なJSONではありません」「有効なXMLではありません」とエラーになる。
papaparse で相互変換する。
- JSON → CSV: オブジェクト(またはオブジェクト配列)を受け取り、ネストはドット記法でフラット化(
flattenObject)してからPapa.unparse。配列値はJSON.stringifyで文字列化する。 - CSV → JSON:
Papa.parse(header: true/dynamicTyping: true/skipEmptyLines: true)。列数不一致(FieldMismatch)は警告として無視し、それ以外の致命的エラーのみ例外にする。
- CSV フォーミュラインジェクション(CWE-1236)対策: セルの先頭が
= + - @ \t \rの場合にシングルクォートを前置し、Excel 等で数式として解釈されないようにする(既定 ON)。 - プロトタイプ汚染(CWE-1321)対策: フラット化の結果は
Object.create(null)で構築し、__proto__/constructor/prototypeキーを明示的にスキップする。
- 入力はオブジェクトまたはオブジェクトの配列のみ(スカラーやスカラー配列は不可)。
- ネストは出力時にドット記法へフラット化されるため、CSV → JSON で元のネスト構造には戻らない。
encoding-japanese でファイル / テキストの文字コードを判定・変換する。
- 判定: 先頭バイトの BOM(UTF-8 / UTF-16 LE / UTF-16 BE)を検出しつつ、
Encoding.detectの結果を正規化する。対応エンコーディングは UTF-8 / Shift_JIS (CP932) / EUC-JP / ISO-2022-JP / UTF-16 LE/BE / ASCII。 - 変換:
Encoding.convertでバイト列を変換する。BOM 付与オプション(UTF-8 は手動プリペンド、UTF-16 はbom: 'LE'/'BE')と、改行コード正規化(そのまま / LF / CRLF)に対応する。改行正規化は UTF-16 を除きバイト単位で行う。
- ファイルサイズ上限は 10 MB。
- 自動判定が確定できない場合は「不明(UNKNOWN)」となる。
- 改行正規化のバイト単位処理は UTF-16 を対象外とする(呼び出し側で除外)。
YAML・JSON・TOML・.env を相互変換する。各フォーマットを中間表現(JS の値)にパースし、目的フォーマットへ直列化する(parseFrom → stringifyTo)。ライブラリは YAML = yaml、TOML = smol-toml、JSON = ネイティブ JSON.parse / JSON.stringify。
- 同一フォーマットの整形: YAML は
parseDocumentベースの整形でコメントを保持する。 - フォーマット検出: 入力テキストからヒューリスティックに判定する(
[section]→ TOML、{/[→ JSON、---やkey:→ YAML、KEY=VALUE→ dotenv)。 - JSON Schema 検証: スキーマを与えて入力を検証できる(
schema-validator)。
- コメントは YAML の同一フォーマット整形でのみ保持される。異フォーマット変換や TOML の整形ではコメントが失われる(警告表示)。
- .env への変換は値がすべて文字列になり、.env からの読み込みも値はすべて文字列として扱われる(警告表示)。
入力テキストを多角的に集計する(count)。
- 文字数: UTF-16 コード単位長・コードポイント数・書記素(grapheme)数(改行除外 / 空白除外の variant も)・全角を 2 とする加重幅。
- エンコーディング互換性: UTF-8 / UTF-8 BMP のみ / UTF-16 / Shift_JIS / EUC-JP で表現可能かを判定する。UTF-8 BMP 判定は、4 バイト文字(絵文字等)が BMP 外であることを検出し、
utf8(≠utf8mb4)列への DB 投入エラーを予測する用途。 - 行数:
analyzeLinesで集計。 - SNS 文字数制限: Twitter の加重カウント・Bluesky のカウント。
- 原稿換算: 原稿用紙枚数・段落数・推定読了時間・英単語数。
- 入力長が 100 万文字を超えると
meta.largeフラグが立つ(大入力ガード)。 - 書記素分割・加重幅は Intl/実装依存の規則に従うため、特殊な結合文字列では見た目の字数と一致しない場合がある。
- 整形:
sql-formatterで方言別に整形する。方言は MySQL / PostgreSQL / SQLite / SQL Server(transactsql)。キーワードは大文字・2 スペースインデント固定。 - カンマ位置: 行末(既定)/ 先頭を切り替えられる。
sql-formatterv15 でcommaPositionオプションが廃止された(指定すると例外)ため、先頭カンマは整形結果に対する後処理で実現する。後処理は文字列リテラル('...'/"..."/`...`、同記号 2 連はエスケープ)・行コメント(--/#)・区間コメント(/* */、複数行文字列とともに行をまたいで状態を持ち越す)の内側を読み飛ばし、コード上の列区切りカンマだけを次行の先頭へ移動する(インデントは保持)。これにより行末コメント(-- memo,)や複数行文字列内のカンマを誤って動かして SQL を壊すことがない。移動先が無い最終行のカンマは欠落を防ぐため保持する。先頭カンマ時は縦の区切りを揃えるため文末セミコロンも単独行にする(newlineBeforeSemicolon: true)。 - パラメータ埋め込み: プレースホルダ付き SQL に JSON パラメータを埋め込む(デバッグ用途)。SQL を走査し、文字列リテラル(
'...')・識別子クォート("..."/`...`)・コメント(--行 //* */)の内側を読み飛ばして「外側」のプレースホルダのみ収集する('why?'の?を誤検出しない)。記法は位置(?)・番号($n)・名前(:name)の 3 種で、混在はエラー。値は方言に応じて SQL リテラル化する(文字列は'を''にエスケープ、真偽値は PostgreSQL ではTRUE/FALSE・他は1/0)。
- 文字列内エスケープは標準 SQL の
''(クォート二重化)のみ対応。MySQL 既定のバックスラッシュエスケープ('can\'t')は未解釈。 - PostgreSQL の dollar-quoted string(
$tag$...$tag$)は未対応($+ 数字は番号プレースホルダ扱い)。識別子内の$(col$1等)を番号プレースホルダと誤検出しうる。 - 配列・オブジェクトの値は埋め込めない。プレースホルダ数とパラメータ数の不一致はエラー。
入力された正規表現パターンとフラグを 3 系統で処理する。
- 構文解析(AST): まず native
new RegExp(pattern, flags)で構文・フラグを検証し(不正ならSyntaxError)、regexp-tree で位置情報付き AST を得る。各ノードを日本語ラベル(「キャプチャグループ #1」「1 回以上の繰り返し」「選択肢 (|)」等)に変換して構造ツリーとして描画する。regexp-tree は/pattern/リテラル基準で位置を返すため、先頭/の分だけオフセットを −1 補正して pattern 文字列基準に揃える。不正な正規表現のエラーは、JS エンジンの英語メッセージをそのまま出さず「正規表現が不正です: 〈英語の詳細〉」という日本語見出し付きに整形して表示する(V8 系の重複するInvalid regular expression:接頭辞は除去し、不正箇所・理由の詳細は残す)。 - 鉄道図(railroad diagram): 同じ AST から
buildRailroadでレイアウトを構築し、SVG の鉄道図として可視化する。ノードは種別ごとに色/形で区別する: 文字(リテラル)は白ボックス、文字クラス・メタ文字([..]\s\d.等)は青ボックス、アンカー(^ $ \b \B)は紫の円/pill。量指定子はループ弧(上・反復方向の矢印付き)とスキップ弧(下・バイパス)で表し、ラベルは「0回以上」「1回以上」「2〜5回」等の日本語で表示する(lazy は「(最短)」を付す)。図の下部に種別の凡例を表示する。 - ReDoS 検出: recheck の
checkSync(pattern, flags, { timeout: 1000 })で壊滅的バックトラッキングを静的解析する。結果は 安全 / 脆弱 / 不明 の 3 状態に正規化する。脆弱と判定された場合は攻撃文字列・複雑度(指数時間 / 多項式時間の次数)・パターン内の危険箇所(hotspot のオフセット範囲)を提示する。
テスト文字列に対するマッチ機能は runMatch が担い、マッチ範囲のハイライトとキャプチャグループを表示する。
- 解析対象は JavaScript の
RegExp構文(nativenew RegExpでの検証を前提とするため、JS エンジンが受理するパターン・フラグに準拠)。 - 特定の RFC に準拠する仕様ではない。
- ReDoS 検出は静的解析であり完全ではない。1000ms の timeout を超えると判定は「不明(unknown)」になり、recheck が想定外に throw した場合も「不明」に倒す。「不明」は「安全」ではないため UI 上も区別して表示する。
- ReDoS 解析はメインスレッドを占有するため timeout の上限を 1000ms に設定している。
- 実装上の制約として、ReDoS / AST 解析モジュール(
regexp-tree/recheck)は CommonJS 依存のため、マッチ機能runMatchを barrel 経由で値として import すると兄弟モジュールの CJS が SSR グラフに巻き込まれ dev SSR が落ちる。クライアントコンポーネントからはmatch.tsを直接 import している。
jsonc-parser の parseTree で strict JSON(コメント・末尾カンマ・空入力を不許可)としてパースし、AST と構文エラー(行・列付き)を得る。
- 整形 / 最小化: AST を走査して直列化するが、プリミティブは 元ソースのテキスト span をそのまま出力するため、大きな数値の精度・数値表記(
1.0,1e3)・文字列エスケープを失わない(lossless)。インデントは 2 / 4 / タブ、最小化は空白除去。 - ツリービュー: 折りたたみツリーを遅延構築(
makeTree)。全展開換算 2,000 行超は仮想スクロールに自動切替(後述)。 - JMESPath クエリ:
jmespathのsearchで抽出。 - マスク: 機密データを 6 カテゴリで伏字化する。SECRET(
password/token/secret等のキー名部分一致で値全体)・EMAIL・JWT・IP(IPv4 妥当性チェック)・CREDIT_CARD(Luhn チェック)・PHONE_JP。カテゴリごとに ON/OFF と検出件数を表示。 - TypeScript 型生成: JSON から型定義を生成する。
全展開換算 2,000 行超のツリーは可視範囲のみを DOM 化する仮想スクロールに自動で切り替わる(自前 windowing・依存なし)。仮想表示では入れ子の罫線(インデントガイド)は省略され、深さはインデント幅で表現される。画面外の行はブラウザのページ内検索にヒットしない。また、フラット構造のためリストのネスト(深さ)情報はスクリーンリーダーに伝わらず、フォーカス中の行が画面外へスクロールアウトするとフォーカスが外れる。2,000 行以下は従来どおり全行を描画する。
- strict JSON のみ受け付ける(コメント・末尾カンマは構文エラー)。
- 整形・ツリー構築は再帰実装のため、極端に深いネストは
RangeErrorになり「ネストが深すぎて処理できません」と表示する(大入力ガード)。 - マスクのクレジットカード/電話番号等はパターン + チェックディジットによる推定であり、誤検出・検出漏れの可能性がある。
- 計算モード: CIDR 文字列を入力すると、ネットワークアドレス・ブロードキャスト・ホスト範囲・サブネットマスク等を一覧表示。
- 分割モード: 元の CIDR と分割先 prefix 長を指定すると、等分割されたサブネット一覧をテーブル表示。IPv4/IPv6 両対応、最大 1024 分割。
- 重複検出モード: 複数の CIDR を 1 行 1 つ形式で入力すると、重複するペアを検出してテーブル表示。関係は「完全一致」「A が B を包含」「B が A を包含」「部分重複」の 4 種で表示。IPv4/IPv6 混在入力可(バージョンが異なるペアは重複なしとして扱う)。
外部ライブラリを使用せず、BigInt で IPv4(32bit)と IPv6(128bit)を統一的に扱う純関数群で実装している。
- IPv4 パース: 4 オクテット・各 0–255 を厳密検証。先頭ゼロ(
01.0.0.0等の octal 表記)・空オクテット・非数字を拒否。 - IPv6 パース:
::展開(1 回のみ許可)、hextet 0–ffff 検証、8 グループ整合チェック、IPv4-mapped 末尾記法(::ffff:192.168.1.1)に対応。 - ネットワーク計算:
network = addr & mask、broadcast (IPv4) = network | ~mask。BigInt の bit 演算で 128bit 幅でも精度を損わない。 - 特殊ケース: /32 はホスト自身(usableHostCount=1)、/31 は RFC 3021 P2P(network/broadcast 控除なし、usableHostCount=2)、/30 以下は total-2。
- IPv6 フォーマット: RFC 5952 準拠(小文字・最長連続ゼロを
::圧縮・先頭ゼロ省略)。圧縮対象は 2 グループ以上の連続ゼロのみ(1 グループは::化しない)。 - サブネット分割:
parseCidrを再利用して分割元を検証し、step = 2^(maxBits - newPrefix)単位で BigInt 加算して各サブネットを生成。分割数2^(newPrefix - basePrefixLength)が 1024 超の場合はエラーを返す。 - 重複検出: 各 CIDR をネットワークアドレスと末尾アドレス(
start + totalCount - 1)に変換し、全ペアをO(n²)で比較。[aStart,aEnd]と[bStart,bEnd]の包含・一致・交差を BigInt 比較で判定。バージョン混在ペアはスキップ。解析失敗行は行単位でエラー収集し、有効行の重複判定は継続する。有効 CIDR が 256 件を超える場合はO(n²)ループを実行せずエラーを返す(自己 DoS 防止)。検出ペアは先頭 1000 件で打ち切る(描画 DoS 防止)。
- RFC 4632: CIDR(Classless Inter-Domain Routing)記法の定義。
- RFC 3021: /31 サブネットの P2P リンク向け利用(network/broadcast アドレス控除なし)。
- RFC 5952: IPv6 アドレスの文字列表現推奨(:: 圧縮・小文字・先頭ゼロ省略)。
- IPv4-mapped IPv6(
::ffff:x.x.x.x)はパースして BigInt に変換するが、結果は純 IPv6 アドレスとして表示される(IPv4 形式には戻さない)。 - IPv6 の 2 進表記は prefix 部のビット列のみ表示(128bit 全体を表示すると冗長なため)。
- /0 は全アドレス空間を表し、総アドレス数が
2^32(IPv4)または2^128(IPv6)となる。 - 分割モードで分割数が 1024 を超える場合(例: /8 を /24 へ = 65536 件)はエラーメッセージを表示する。
- 重複検出モードで有効 CIDR が 256 件を超える場合はエラーメッセージを表示し、重複判定をスキップする。検出ペアが 1000 件を超える場合は先頭 1000 件のみ表示し、打ち切り旨を通知する。
src/utils/secret-scrubber/ に独立モジュールとして実装した純関数エンジン(外部ライブラリなし)。
- ルールベース検出: カテゴリ別の正規表現ルール群(
rules.ts)でテキストを走査し、マッチした範囲を収集する。API キーはプロバイダ別パターン(AWSAKIA/ASIA/ABIA/ACCA・GitHubghp_/ghs_・Anthropicsk-ant-・OpenAIsk-・Stripe・Google API・SendGrid・npm・GitLab・Slack)で高精度に検出する。 - maskGroup:
CREDENTIAL_ASSIGN(代入式。password等の英語キーに加えパスワード・トークン等の日本語キー名・全角コロンに対応)・CREDENTIAL_URL(URL 認証)・CREDENTIAL_AUTH_HEADER(Authorization ヘッダ)はキャプチャグループを使い、キー名や URL のホスト部を残して値部分のみをマスクする。 - バリデーション: IPv4 は各オクテット 0〜255 検証、クレジットカードは Luhn アルゴリズム、HIGH_ENTROPY は Shannon エントロピー閾値チェックで誤検出を抑制する。
- 重複解決: マッチを start 昇順でソートし、重なる場合は
priority高い方(同値なら長い方)を勝者とする。負けた側が勝者のフルマッチ範囲(maskGroup ルールが意図的に残すキー名・ホスト等を含む「考慮済み領域」)に完全に含まれる場合は破棄する(例: Authorization ヘッダ内 JWT は 1 つのプレースホルダになる)。はみ出す場合は範囲を union にマージし、負けたマッチの断片(例: 高エントロピー文字列の内側だけが AWS キーにマッチしたときの前後)が素通しになる漏えいを防ぐ(over-masking 側に倒す。PR #631 レビュー指摘)。 - 一貫トークン化:
Map<カテゴリ:値, プレースホルダ>を持ち、同一値に対して常に同一プレースホルダ([REDACTED:EMAIL_1]等)を割り当てる。カテゴリごとに初出順で連番を振る。 - 後ろから順に置換: オフセット保護のため、解決済みマッチを末尾から処理して前方の位置が変化しないようにする。
- Shannon エントロピー:
entropy.tsで実装。文字ごとの出現頻度から- Σ p * log2(p)を計算する(bits/char)。base64 風文字列は ≥ 4.0、hex 文字列は ≥ 3.0 を閾値とする。
- Shannon エントロピー(Claude E. Shannon, 1948)による情報エントロピー計算
- Luhn アルゴリズム(ISO/IEC 7812)によるクレジットカード番号検証
- 各プロバイダ公式ドキュメントのシークレット形式仕様
- IPv6 未対応: IPv6 アドレスは検出しない(今後の拡張候補)。
- UUID は HIGH_ENTROPY から除外:
8-4-4-4-12の hex 形式は識別子の可能性が高いため HIGH_ENTROPY 検出対象外。ただし UUID がプロバイダ特有パターンに合致する場合は別ルールで検出される。 - 代入式の値は 6 文字以上のみ検出:
password=abc12のような 6 文字未満の値は誤検出抑制のため検出しない。 - 既知の誤検出(over-masking 側):
06-11-2026のようなハイフン区切り日付が電話番号として、10.2.3.4のようなバージョン表記が IP アドレスとして検出されることがある。不要ならカテゴリのトグルを OFF にする。 - 検出は完全ではない: 未知の形式のシークレット・プロバイダ固有の非標準形式は検出されない場合がある。共有前に必ず目視確認すること。
- 高エントロピー検出は誤検出が発生しうる: 長いランダムに見える文字列(Base64 エンコードされた非機密データ等)も HIGH_ENTROPY で検出される場合がある。不要なカテゴリはトグルで OFF にすることを推奨する。
- json-formatter/mask.ts との関係: JSON 構造の値を走査するマスク機能(
json-formatter)とは独立したモジュール。テキスト全文を正規表現で走査するため、JSON 以外のログ・設定ファイルにも対応する。将来的な共通基盤化(S2-3)は別 PR で判断する。
src/utils/dataTransferSnapshot.ts と src/utils/sanitizeHtml.ts を組み合わせて実装。
- DataTransfer 取得:
pasteイベント(document全体で捕捉)とdropイベントのDataTransferを受け取り、DataTransferItemListを同期パスで列挙する。getAsStringの呼び出しはイベントハンドラの同期スコープ内で行う必要があり(ハンドラ終了後はDataTransferItemListが無効化される)、Promise で非同期解決する設計を採っている。 - 受付領域は contenteditable(モバイル対応): モバイルの OS ペーストメニューは編集可能要素の長押しでしか出ないため、受付領域を
contenteditable化している(issue #636)。inputMode="none"でフォーカス時のソフトキーボード表示を抑制する。paste 自体は従来どおりdocumentレベルの listener が捕捉するため、ページ内のどこでも Ctrl+V / Cmd+V で貼り付けできる。 - contenteditable の編集阻止(二段ガード): ①
beforeinputのpreventDefault(React のonBeforeInputは native beforeinput ではなく textInput / keypress 等から合成されるため、native と React 合成の両系統に登録して全編集経路を阻止)。② IME のinsertCompositionTextは W3C Input Events 仕様で non-cancelable のため beforeinput では阻止できず、貫通した編集はinputイベント時にマウント時に保存した deep clone から案内文言を復元する(実 IME は既存テキストノード内部を直接変異させるため同一ノード参照の保存では復元が no-op になる。復元のたびに再クローンして装着し、master の clone 汚染も防止する)。 - フレーバー分類:
DataTransferItem.kind === 'string'のものをStringFlavor(type・content・byteSize)、kind === 'file'のものをFileFlavor(type・name・size・lastModified・File オブジェクト)として分離して収集する。 - HTML サニタイズ + sandbox:
text/htmlフレーバーのプレビュー表示時は、sanitizeHtml(許可リスト方式のサニタイザ。script・iframe・on*イベント属性・javascript:URL・style・remote 画像 URL(img の src は data:image の raster 形式 png/jpeg/gif/webp/avif/bmp のみ許可。svg+xml は script を内包し得るため除外)を除去。a の href は http/https/mailto のみ許可)でスクリプト・危険属性を除去したうえでsandbox=""属性付き<iframe>(スクリプト実行・フォーム送信・同一オリジン不許可)にsrcdocとして渡す二重防御を実施する。 - 画像プレビュー:
image/*型のファイルフレーバーはURL.createObjectURLでブラウザ内 blob URL を生成して<img>に渡す。コンポーネントアンマウント時にURL.revokeObjectURLでメモリを解放する。
- W3C Clipboard API および
DataTransferインターフェース仕様 - W3C HTML Living Standard
<iframe sandbox>属性仕様
- ブラウザ非公開フレーバーは表示不可: OS のクリップボードに存在しても、ブラウザが Web ページへ公開しないフレーバー(独自アプリ形式等)は列挙されない。
- プレビューにインラインスタイルが反映されない:
srcdocの iframe は親ドキュメントの CSP(style-srcstrict)を継承するため、サニタイズ後プレビューは構造・テキスト中心の表示になる。 - Async Clipboard API 非対応: ボタンクリックでの読み取り(
navigator.clipboard.read())には対応しない。権限プロンプトが必要で取得できる型も限定的なため、初版のスコープ外とした。 - サニタイズで除去された要素・属性はプレビューに現れない: 除去内容を確認したい場合は「生ソース」表示に切り替えれば原文をそのまま確認できる。
- style 属性付き HTML 貼り付け時の CSP 違反ログ: style 属性を含む HTML を貼り付けると、Chromium のクリップボード内部処理(
getAsStringの HTML サニタイズ)が inline style を評価するため、本番 CSP 環境(style-srcstrict)のコンソールに style-src 違反ログが数件記録されることがある。アプリの実装・表示には影響しない(E2Etests/e2e/clipboard-inspector.spec.tsの本番 CSP テスト参照)。 - プレビューでは remote 画像は表示されない: http/https の img src は外部リクエスト防止(tracking pixel 対策)と CSP 違反ノイズ回避のためサニタイズで src を除去する(alt テキストは保持)。img の src として表示されるのは data:image の raster 形式(png/jpeg/gif/webp/avif/bmp)のみ。
- ハイドレーション完了前は貼り付けを捕捉できない:
pastelistener は React コンポーネントのマウント時にdocumentへ登録されるため、ページ表示直後の数百 ms(ハイドレーション完了前)の貼り付けは捕捉されない。
scheme://[userinfo@]authority[/path][?query]を自前パーサで分解する。URLAPI は mongodb のカンマ区切り複数ホスト(host1:27017,host2:27018)を解釈できないため使用しない- userinfo・パス・クエリは percent-decode してフォームに表示し、URI 生成時に
encodeURIComponentで再エンコードする(パスワード中の@ : /等の手動エンコード不要) - スキーム方言辞書(
src/utils/dsn-builder/dialects.ts)が既定ポート・複数ホスト可否・ パス部の意味(DB 名 / DB 番号 / vhost)・SRV 制約・JDBC 形式可否を定義する - パスワードを
****に置換した共有用 URI を常時導出する(同期不要の純粋関数) - JDBC(
jdbc:postgresql/jdbc:mysql)は credential を userinfo でなく?user=&password=クエリプロパティに置く JDBC 標準の流儀に従う。パース時はプロパティを ユーザー名・パスワードのフォーム欄へ移し、シリアライズ時にプロパティ列の先頭へ戻す - JDBC URL に userinfo(
jdbc:postgresql://user:pass@host/db)を含めて貼り付けた場合は、 userinfo を専用フィールドへ取り込み、再シリアライズ時に?user=&password=プロパティ形式へ 正規化する(JDBC ドライバは userinfo を解釈しないため。専用フィールドが空のときのみ プロパティ側から引き取る)
- RFC 3986(URI 構文・percent-encoding)
- libpq 接続 URI(PostgreSQL 複数ホスト)・MongoDB Connection String・RabbitMQ URI Specification
- JDBC URL(PostgreSQL / MySQL ドライバの
jdbc:postgresql/jdbc:mysql形式)
- 実接続テストは不可(ブラウザの制約)
- クエリパラメータの意味的妥当性(sslmode の値等)は検証しない
- 過剰エンコードされた入力(例:
%41=A)は decode → 再 encode で正規化される - JDBC は PostgreSQL / MySQL のみ対応。SQL Server(
;区切り)・Oracle(@host:port:SID)・ ADO.NET(Server=...;)形式は文法が大きく異なるため対象外
- 入力種別を
key/detect.tsで判定する。テキストが{始まりで JSON parse 可能かつktyを持つ → JWK、-----BEGIN ... -----マッチ → PEM、Uint8Array または base64-only テキスト(先頭0x30DER SEQUENCE)→ DER の優先順で判別する - DER / PEM の場合は
asn1js.fromBERでトップレベル SEQUENCE を解析し、第1要素が INTEGER(version=0)→ PKCS#8 秘密鍵、第1要素が SEQUENCE(AlgorithmIdentifier)→ SPKI 公開鍵と判定する。AlgorithmIdentifier の OID で RSA(1.2.840.113549.1.1.1)/ EC(1.2.840.10045.2.1)を識別し、EC の場合は params の named curve OID(P-256=1.2.840.10045.3.1.7/ P-384=1.3.132.0.34/ P-521=1.3.132.0.35)から曲線名を取得する - JWK の場合は
kty/crvフィールドとプライベートキーフィールド(dの有無)で鍵種別を判定する - JWK の import は鍵素材(RSA:
n/e/d…、EC:x/y/d)のみを取り込み、入力 JWK のalg/key_ops/use/extは import 前に除去する。これによりRS384/RS512/PS256を宣言した署名鍵や用途宣言付きの鍵も鍵素材の形式変換として扱える(hash・用途は変換結果に影響しないため) - 出力 JWK は Web Crypto が付与する
ext/key_ops/algを除去したうえで、入力が JWK の場合のみ元のkid/use/alg/key_opsを復元する。PEM / DER 入力では鍵素材から導けないalgを付与せず、アルゴリズムを詐称しない - 変換は
crypto.subtle.importKey(extractable: true)→exportKeyの流れで全形式を生成する。RSA はRSASSA-PKCS1-v1_5 / SHA-256、EC はECDSA / namedCurveをアルゴリズムパラメータとして使用する(hash は変換用の便宜値で実際の署名/検証には使用しない) - PEM は DER を base64 化し 64 文字折返しで構築する。JWK は
JSON.stringify(jwk, null, 2)でインデント付き出力する - PKCS#1(RSA PUBLIC KEY / RSA PRIVATE KEY)/ SEC1(EC PRIVATE KEY)/ ENCRYPTED PRIVATE KEY などの未対応形式は
detectKeyInputがunsupportedを返し、UI で openssl 変換コマンドを案内する importKey失敗(壊れた DER/JWK)は catch してerrorフィールド付きの結果を返す(throw しない設計)
- RFC 5958(非対称鍵パッケージ、PKCS#8 Private-Key Information Syntax)
- RFC 5480(楕円曲線暗号 SubjectPublicKeyInfo)
- RFC 7517(JSON Web Key)/ RFC 7518(JSON Web Algorithms、鍵パラメータ定義)
- Web Cryptography API(W3C)
- PKCS#1 形式(RSA PUBLIC KEY / RSA PRIVATE KEY)・SEC1 形式(EC PRIVATE KEY)のレガシー PEM は非対応。
openssl pkcs8 -topk8 -nocryptで PKCS#8 に変換してから使用する - 暗号化秘密鍵(ENCRYPTED PRIVATE KEY・パスフレーズ付き PEM)は非対応。
openssl pkcs8 -in key.pem -nocrypt -out key_plain.pemで復号してから変換する - Ed25519 / Ed448(EdDSA、
kty: OKP)は非対応 - 秘密鍵からの公開鍵抽出は非対応
- JWK 出力で復元する入力メタデータは allowlist 限定(
kid/use/alg/key_ops)。x5c/x5t/x5t#S256/x5uなどの X.509 連携フィールドは v1 スコープ外で、JWK→JWK の往復では脱落する。ktyが RSA / EC 以外(OKP等)は引き続き非対応 - 全処理はブラウザ内で完結し、秘密鍵データは外部に送信しない
- HAR(HTTP Archive)は JSON 形式のため
JSON.parseでパースし、log.entriesが配列であることを最小スキーマ検証する(src/utils/har/parse.ts) - サニタイズは二段階。①構造的 redact(
src/utils/har/sanitize.ts): フィールド名辞書で確実に処理。②scrubText(src/utils/secret-scrubber/scrub.ts): 本文の取りこぼしを自由テキスト走査で補完 - 構造的 redact の対象:
request.cookies[].value/response.cookies[].value/ Cookie・Set-Cookie ヘッダ値(COOKIE カテゴリ)/ Authorization 等の認証ヘッダ値(AUTH_HEADER)/request.queryString[]の機密名エントリ(QUERY)/request.urlのクエリパラメータと basic-auth パスワード(QUERY)/ URL を運ぶヘッダReferer・Origin・Location・Content-Locationへの URL redact 適用(QUERY。URL から消した値が他ヘッダに残る漏洩を防ぐ)/postData.params[]の機密名エントリとpostData.textへの scrubText(BODY)/response.content.textへの scrubText(BODY_SCAN) - カバレッジ拡張(#687/#689/#690/#694/#695):
- 辞書外ヘッダ値にも
scrubTextフォールバックを適用(HEADER_SCAN カテゴリ)。x-amz-security-token等の認証ヘッダ辞書も拡充。認証ヘッダ(AUTH_HEADER)は辞書ベースの確実な redact、ヘッダ走査(HEADER_SCAN)は辞書外ヘッダへの自由テキスト走査と役割が分離されており、それぞれ独立トグルで制御できる(#694) - URL のパス以降(path?query#fragment)に
scrubTextを適用(PATH_SCAN カテゴリ)。scheme://authority(host・port・basic-auth)は保持し、パス内トークン(/reset/<jwt>)や辞書外クエリ名の JWT/API キーを redact する。クエリ/フラグメントは&越えの飲み込みを防ぐため param value 単位で走査する。機密クエリ(QUERY)は辞書一致クエリ/POST param + basic-auth の構造的 redact、URL走査(PATH_SCAN)はパス以降への自由テキスト走査と役割が分離されている(#694) response.redirectURLにも URL redact を適用(QUERY / PATH_SCAN)data:URL は scrubText を適用しない(#695)。data:image/png;base64,...のようなペイロードにHIGH_ENTROPY_BASE64が誤マッチして base64 を[REDACTED]に置換しデコード不能にする破壊を防ぐ- over-masking 方針: パス/ヘッダ内の IP・メール・高エントロピー文字列も redact されうるが、host は保持され漏えい方向ではなく安全側。詳細は
docs/decisions.md - 本文スキャンのスキップ拡張:
encoding === 'base64'に加え、content.mimeTypeがバイナリ系(image/*・audio/*・video/*・font/*・application/octet-stream・pdf・zip等)なら encoding 欄が無くてもスキップし、HIGH_ENTROPY_BASE64による本文破壊を防ぐ
- 辞書外ヘッダ値にも
- 防御的処理: JSON として妥当でも
request/responseを欠く壊れた entry は例外を投げずスキップする(sanitizeHarはレンダリング中のuseMemoで走るため、throw すると画面が落ちる)。一覧(HarEntryList)では壊れた行を「(壊れたエントリ)」プレースホルダのクリック可能 button として描画し、クリックすると詳細パネルにプレースホルダを表示する(正常 entry 選択後に壊れた行を再クリックしても詳細が切り替わる、issue #701) - 一貫トークン化:
makeTokenizerがカテゴリ × 値 →[REDACTED:COOKIE_1]等のプレースホルダを発行する。同一値には同一プレースホルダを割り当て、HAR 全体で値の同一性が保たれる。ただしこの HAR 全体一貫性は構造的 redact(tokenize 由来)に限る。scrubText由来の redaction(本文・辞書外ヘッダ・URLパス等)は呼び出しごとに採番されるため、異なるフィールドに跨る同一秘密値の一貫トークン化は保証されない(安全側であり漏えいはしない) - 純関数・入力非破壊:
structuredCloneでディープコピーしてから処理するため元オブジェクトを mutate しない - parse + sanitize は Web Worker(
src/workers/harSanitizer.worker.ts)で実行する。sanitizeHarはstructuredClone+ 全 response body の正規表現スキャンで中規模 HAR でも数秒かかり、メインスレッド同期実行だと「ページが応答しません」になる(issue #677)。worker に逃がしメインスレッドを固めない。worker は parse 済み HAR を保持し、redact トグル時は再 parse せず sanitize のみ再実行する - フック
useHarSanitizer(src/hooks/useHarSanitizer.ts)が worker のライフサイクルとメッセージングを担う。各 load / sanitize にrequestIdを振り、最新リクエストの結果のみ反映(トグル連打時の stale result を破棄)。sanitizeHarのonProgressコールバックで処理済みエントリ数を逐次受け取り、ProgressBarで進捗表示する - カテゴリ別の redact 件数は worker が返す
countsをToggleChipsのバッジに表示する - エントリ一覧(
HarEntryList)は全件描画する。フリーズの主因は描画ではなく sanitize であり Worker 化で解消したため、ページングは導入しない(実 HAR の検証でエントリ数は数百件程度で、その規模の<tr>描画は問題にならないことを確認。loadSeqは新規読込時の選択リセット判定にのみ使い、トグル時は選択を保持する) - 出力 HAR の
JSON.stringify(.., null, 2)はコピー/ダウンロード押下時のみ遅延生成する(CopyButtonのtextprop はstring | (() => string)を受け付け、関数はクリック時に評価される)。毎レンダリングでの数 MB 直列化を避ける sanitize.tsは worker の依存グラフに含まれるため@/ではなく相対 import を使う(Vite の worker Rollup サブビルドに tsconfig paths が伝播しないため。詳細はファイル冒頭コメント参照)- ウォーターフォール(タイミング可視化):
computeWaterfall(src/utils/har/waterfall.ts)がHarEntry[]から全体タイムライン基準の配置モデルを計算する。各エントリのstartedDateTime(ISO 文字列)を epoch ms に変換して起点を求め、timings(blocked / dns / connect / ssl / send / wait / receive)をフェーズ別セグメントに分解する。HAR 1.2 仕様に従いsslはconnectの部分時間として扱うため、connectセグメント ms =connect - ssl(下限 0)に補正し、ssl を別セグメントとして並べる(二重計上防止)。値が-1・未定義・0のフェーズはセグメント化しない。WaterfallRow.offsetRatioは(start - t0) / totalMs、widthRatioはdurationMs / totalMsで全体タイムライン基準の相対配置を表す。一覧テーブルの「タイミング」列(スマホではhidden md:table-cellで非表示)にHarWaterfallBarが横棒を描画し、詳細パネル(HarEntryDetail内TimingBreakdown)にフェーズ別内訳テーブルとミニバーを表示する。動的な幅・オフセットはuseDynamicStyleSheet(Constructable Stylesheets)で CSS カスタムプロパティ(--bar-left/--bar-width/--seg-width/--mini-width)として注入する(CSPstyle-src制約により inline style は使用しない、decisions [067])。computeWaterfallはHarViewerでuseMemo化してentries変化時のみ再計算する
- HAR 1.2 仕様(http://www.softwareishard.com/blog/har-12-spec/)の必要サブセットを型定義(完全検証は不要なため
log.entries配列の存在のみを確認) - ウォーターフォールの
ssl/connect処理は HAR 1.2 spec の「ssl timings are included in the connect timings」に準拠
- ウォーターフォール:
timingsを持たないエントリはバーを非表示にして degrade する(—を表示)。startedDateTimeの欠落時も同様。スマホ(390px)では一覧のタイミング列を非表示にし、詳細パネルで内訳を確認できる - ファイルサイズ上限 25MB(メモリ防御ガード)。読み込み時のフリーズは sanitize の Web Worker 化で解消済みのため、バイト数は処理能力の指標ではなくメモリ確保の上限として残す。大きな HAR は worker 上で時間がかかる(実測 ~6MB/5000 エントリで約 2.6 秒、~18MB/10000 エントリで約 17 秒)が、メインスレッドは固まらず進捗バーを表示する。redact トグルのたびに全エントリを再 sanitize する(worker 上のため非ブロッキング。差分 sanitize は将来課題)
- 辞書に無い独自ヘッダ名・独自名のクエリ/フォームパラメータは
scrubTextが拾える範囲のみ redact される(任意名のセッショントークン等は残りうる。完全な網羅は保証せず、出力は共有前の目視確認が前提) - レスポンスボディが base64 エンコード(
content.encoding: "base64")の場合はscrubTextをスキップする。HIGH_ENTROPY_BASE64ルールが base64 ブロック自体にマッチして本文を破壊し、デコード不能な HAR を出力するのを防ぐため(その代わり base64 本文内の秘密は検出されない) - 全処理はブラウザ内で完結し、HAR データは外部に送信しない
- 生成モード:
crypto.subtle.generateKeyで RSA(RSASSA-PKCS1-v1_5)または ECDSA(P-256 / P-384 / P-521)の鍵ペアを生成し、pkijs のCertificationRequestに Subject DN と SAN 拡張を設定して PKCS#10 CSR を構築する- Subject DN フィールドの文字種は OID ごとに制御: countryName は
PrintableString、emailAddress はIA5String、その他はUTF8String - SAN(Subject Alternative Name)は
pkcs-9-at-extensionRequest(OID1.2.840.113549.1.9.14)属性内のid-ce-subjectAltName(OID2.5.29.17)拡張として設定する pkcs10.sign(privateKey, hashAlg)で自己署名。RSA は SHA-256 固定、ECDSA は P-256=SHA-256 / P-384=SHA-384 / P-521=SHA-512- CSR は
pkcs10.toSchema(true).toBER()→ DER → 64 文字折返し PEM 化(-----BEGIN CERTIFICATE REQUEST-----) - 秘密鍵は
crypto.subtle.exportKey('pkcs8', ...)でエクスポートし PEM 化(-----BEGIN PRIVATE KEY-----)。平文 PKCS#8 のみ対応
- Subject DN フィールドの文字種は OID ごとに制御: countryName は
- 解析モード: PEM(
-----BEGIN CERTIFICATE REQUEST-----ヘッダを抽出)または Base64(DER 直接)を受け取り、asn1js.fromBER+pkijs.CertificationRequestでパース。Subject のtypesAndValuesから OID→ラベル変換し、extensionRequest属性から SAN を抽出する。pkcs10.verify()で署名自己整合性を検証する(改竄検出) - pkijs の Web Crypto エンジン初期化には既存
src/utils/cert/engine.tsのensureCryptoEngine()を再利用する
- RFC 2986(PKCS#10 Certification Request Syntax Specification)
- RFC 5280(X.509 SAN 拡張 id-ce-subjectAltName = 2.5.29.17)
- PKCS#8(秘密鍵のエクスポート形式)
- Ed25519 / Ed448(EdDSA)は非対応(Web Crypto のブラウザサポート差・pkijs の追加検証が必要)
- 暗号化 PKCS#8(PBES2 / パスフレーズ付き秘密鍵)でのエクスポートは非対応
- SAN の IP アドレスは IPv4(4 オクテット)のみ対応。IPv6 は DNS SAN での代替を推奨
- challengePassword 属性・KeyUsage / ExtendedKeyUsage 等のカスタム拡張編集は非対応
- 全処理はブラウザ内で完結し、秘密鍵は外部サーバーに送信しない
- markdown パース:
markedライブラリのmarked.parse(md, { gfm: true, breaks: true, async: false })で GFM 準拠の HTML 文字列を生成する。gfm: trueで表・取り消し線・コードブロックを有効化、breaks: trueで改行を<br>に変換する(一般的なエディタ体験に合わせる)。 - XSS 対策: 生成 HTML は必ず既存の
sanitizeHtml(html)に通してから返す(src/utils/sanitizeHtml.ts)。許可リスト方式(<script>/ style / 危険属性 /javascript:URL を除去)でガード。新規サニタイザは導入せず既存資産を再利用。 - 描画:
sanitizeHtml済みの HTML 文字列を<div className="markdown-preview" dangerouslySetInnerHTML={{ __html: sanitized }}>でインライン描画する。sanitizeHtmlが許可外要素・属性を全除去した後の文字列のみを渡すため XSS リスクはない。 - パフォーマンス:
useMemo(() => renderMarkdown(input), [input])で入力単位に memo 化し、毎レンダーでのパース再実行を回避する。 - スタイリング:
sanitizeHtmlはclass属性を除去するため、生成要素にクラスを付けられない。global.cssの@layer componentsに.markdown-previewを定義し、子孫要素セレクタ(.markdown-preview h1、.markdown-preview table等)で整形する。
- CommonMark:
marked(本プロジェクトでは v18 を使用)は CommonMark に準拠する基盤を持つ - GitHub Flavored Markdown (GFM):
gfm: trueで GFM 拡張(表・取り消し線・コードブロックの言語記法)を有効化。GFM は CommonMark のスーパーセット仕様(https://github.github.com/gfm/)
- GFM タスクリストの
<input type=checkbox>:sanitizeHtmlのDROP_WITH_CHILDRENリストにinputが含まれるため、チェックボックス要素が除去される。テキスト部分([ ] TODO等)は<li>のテキストとして残る。 - コードブロックの
class="language-xxx":sanitizeHtmlがclass属性を許可しないため除去される。シンタックスハイライトはスコープ外のため影響なし。 - 見出しの
idアンカー:id属性は許可リストに含まれないため除去される。見出しリンクは機能しない。 imgの外部 URL:sanitizeHtmlはimg srcにdata:image/の raster 形式のみを許可し、https://等の外部 URL は除去する(本番 CSPimg-src 'self' data: blob:との整合、および「外部送信なし」建前の維持)。- 全処理はブラウザ内で完結し、入力 markdown を外部サーバーに送信しない
- 色パース(
parseColor): HEX#rgb(展開)/#rrggbbおよびrgb(r, g, b)形式の文字列を{ r, g, b }オブジェクトに変換する。大文字小文字・前後空白を許容。アルファ付き形式(#rrggbbaa/rgba())は v1 非対応でnullを返す。 - WCAG 相対輝度(
relativeLuminance): IEC 61966-2-1(sRGB)のガンマ展開c <= 0.03928 ? c/12.92 : ((c+0.055)/1.055)^2.4で各チャネルを線形化し、L = 0.2126*R + 0.7152*G + 0.0722*Bで輝度を算出する。 - WCAG コントラスト比(
contrastRatio):(max(L1,L2)+0.05) / (min(L1,L2)+0.05)。1:1(同色)〜 21:1(黒×白)の範囲を取る対称指標。 - 合否判定(
wcagLevels): AA 通常テキスト ≥ 4.5 / AA 大きいテキスト ≥ 3.0 / AAA 通常テキスト ≥ 7.0 / AAA 大きいテキスト ≥ 4.5。 - APCA Lc(
apcaLc): APCA-W3 0.1.9 の公式アルゴリズムを自前実装。W3C ベータライセンスのライブラリ採用を避け依存追加なしで実現(docs/decisions.md参照)。前景・背景それぞれのY = 0.2126729*R^2.4 + 0.7151522*G^2.4 + 0.0721750*B^2.4を算出後、ソフトクランプ(閾値 0.022、指数 1.414)を適用。bgY > txtY(明背景)なら(bgY^0.56 - txtY^0.57) * 1.14、逆(暗背景)なら(bgY^0.65 - txtY^0.62) * 1.14で SAPC を計算し、lo-clip(0.1)と offset(0.027)を適用して Lc = SAPC × 100 を返す。符号は極性(明背景=正、暗背景=負)を表す非対称指標。 - マトリクス生成(
buildMatrix):ColorEntry[]の全ペアに対してcontrastRatio/wcagLevels/apcaLcを一括計算し N×N のMatrixCell[][]を返す。行=前景色、列=背景色。対角(fg.id === bg.id)はsameColor: trueフラグでグレーアウト対象とする。
- WCAG 2.x コントラスト比: W3C WCAG 2.1/2.2 の Success Criterion 1.4.3(AA)/ 1.4.6(AAA)の算出式に準拠
- APCA-W3 0.1.9: W3C WCAG 3 のコントラスト研究プロジェクトの公開アルゴリズム(https://www.w3.org/community/apca/)を実装。ベータ仕様のため将来的に定数が変更される可能性がある
- 不透明色のみ対応: アルファ付き(半透明)色は v1 で非対応。背景色との合成計算が必要なため意図的に除外
- 入力形式: HEX(
#rgb/#rrggbb)とrgb()のみ。HSL・OKLCH・名前付き色(red等)は非対応 - 最低 2 色: マトリクス描画には有効な色が 2 つ以上必要。2 色未満の場合は案内文を表示
- 全処理はブラウザ内で完結し、入力した色を外部サーバーに送信しない
CREATE TABLE 文(MySQL / PostgreSQL)を node-sql-parser でパースし、テーブル・カラム・FK 制約の中間モデルを構築する。中間モデルを 2 系統の出力に変換する。
- DDL パース(
parse.ts):node-sql-parserのParserを dynamic import で遅延ロードし、astify(ddl, { database: dialect })で AST を得る。AST からテーブル定義(カラム名・型・NOT NULL)と FK 制約(FOREIGN KEY ... REFERENCES・列定義内REFERENCES)を正規化して中間モデル(ErdTable[])に変換する。 - Mermaid 記法生成(
mermaid.ts、toMermaid): 中間モデルからerDiagram記法のテキストを生成する(mermaid ライブラリ不使用、テキスト出力のみ)。FK があるテーブル間に||--o{等の関係線を出力する。 - 自前 SVG レンダラ(
svg.ts、toSvg): 中間モデルから直接 SVG を生成する純関数。等幅フォント幅推定で DOM 非依存。カラム行は<rect>+<text>の presentation 属性(fill/stroke/font-size等)のみで描画し、style=属性・<style>タグを一切使用しない(CSPstyle-src 'unsafe-inline'不要)。FK リレーション線は<line>/<path>で描画。 - 表示: 生成した SVG を
URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }))で blob URL 化し、<img src={blobUrl}>で表示する。blob URL 経由の<img>はスクリプト実行の心配がなく、CSP 隔離も保たれる。 - PNG 変換: blob URL を
<canvas>で描画しtoBlob('image/png')でダウンロード。
- MySQL / PostgreSQL の
CREATE TABLE構文(方言はnode-sql-parserが吸収)。 - FK 対象:
CONSTRAINT ... FOREIGN KEY (col) REFERENCES tbl(col)および列定義のcol TYPE REFERENCES tbl(col)記法。 - Mermaid ER 記法(https://mermaid.js.org/syntax/entityRelationshipDiagram.html)。
ALTER TABLEFK 非対応:CREATE TABLE内の制約のみを対象とする。ALTER TABLE ... ADD CONSTRAINT FOREIGN KEYは未対応。- 命名規則推測なし:
user_id → users.idのような名前ベースの関係推測は行わない。明示的な FK 定義のみからリレーション線を生成する。 - mermaid ライブラリ不使用: mermaid の
render()は描画時に一時 DOM へインラインスタイルを挿入するため、本プロジェクトの strict CSP(style-src 'unsafe-inline'なし)と根本衝突する(詳細はdocs/decisions.md[122])。コピー用の Mermaid テキスト生成は自前実装で行い、描画は自前 SVG レンダラが担う。 - 全処理はブラウザ内で完結し、入力 DDL を外部サーバーに送信しない。