Repository navigation
fix: 全領域監査で検出した表示・検索・サーバーの不具合を修正 #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| name: Test | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| node: ['20.19.0', '22'] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: ${{ matrix.node }} | ||
| cache: npm | ||
| cache-dependency-path: npm-shrinkwrap.json | ||
| - run: npm ci | ||
| - run: npm run test:unit | ||
| - run: npm run check:contrast | ||
| - run: npx playwright install --with-deps chromium | ||
| - run: npm run test:e2e -- --workers=2 | ||
| - uses: actions/upload-artifact@v4 | ||
| if: failure() | ||
| with: | ||
| name: test-results-node-${{ matrix.node }} | ||
| path: test-results/ | ||
| retention-days: 7 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| # DocView 不具合監査・修正記録 | ||
|
|
||
| 対象: `main` の `563094060805aa542075a25c9e857a5a6e611689`。 | ||
| サーバー、CLI、全フロントエンドモジュール、画像解析、既存テスト、テーマ、依存関係、配布設定をレビューした。 | ||
| 以下は今回確認・修正した問題の一覧であり、未知の不具合が存在しないことを保証するものではない。 | ||
|
|
||
| ## サーバー・CLI | ||
|
|
||
| | 問題 | 対応 | | ||
| |---|---| | ||
| | 不正なパーセントエンコードの raw URL でサーバーが終了 | 400 応答に変換 | | ||
| | diagram API の `null`・不正な source 型でサーバーが終了 | JSON 形状・文字列型を検証 | | ||
| | 外部描画 CLI の stdin エラー・stderr 詰まり | エラーハンドラーと出力処理を追加 | | ||
| | 検索・backlinks・tree・gallery・custom CSS の外部 symlink 漏洩 | 公開ルート内への realpath 解決を検証 | | ||
| | IPv6 URL の角括弧によりリモート取得失敗 | 接続ホストを正規化 | | ||
| | IPv4-mapped IPv6 と link-local 範囲の private 判定漏れ | 対象表現と範囲を修正 | | ||
| | OS opener 不在でプロセスが終了 | 非同期起動失敗を API エラーへ変換 | | ||
| | TCP チャンク境界で日本語 JSON が破損 | バイト列結合後に UTF-8 復号 | | ||
| | JSON 本文のサイズ上限が文字数依存 | バイト数で制限 | | ||
| | ZIP の生成連番と実ファイル名が衝突 | 全エントリーを一意化 | | ||
| | `.docviewignore` の `?` による不正な正規表現 | 1 文字ワイルドカードへ変換 | | ||
| | `.env` の変更がライブ更新されない | 対象の隠しファイルを監視 | | ||
| | 巨大な remote サイズ指定が上限を無効化 | 安全な整数を検証 | | ||
| | stdout 内の URL による誤ったブラウザー起動・重複起動 | IPC の ready 通知を使用 | | ||
| | Windows の `start` を実行可能ファイルとして起動 | `explorer.exe` を使用 | | ||
| | CLI 終了時の子プロセス残存 | SIGINT / SIGTERM を伝搬 | | ||
| | 病的な正規表現が HTTP イベントループを停止 | Worker へ隔離し 1 ファイル 1 秒で打ち切り | | ||
|
|
||
| 主な回帰テスト: `tests/server-audit.test.mjs`。 | ||
|
|
||
| ## ナビゲーション・検索 | ||
|
|
||
| | 問題 | 対応 | | ||
| |---|---| | ||
| | 古い file / SVG / PDF / Office / remote 応答が新画面を上書き | 読み込み世代を検証 | | ||
| | 閉じた分割ペインの応答が左ペインへ描画 | 世代・元 DOM・ペイン状態を検証 | | ||
| | 検索を閉じる・消す・モード変更後に旧結果が復活 | 入力変更時点で検索を失効 | | ||
| | 遅いファイル一覧取得後に現在の検索が更新されない | 現在の入力を再評価 | | ||
| | URL バーで後から作成されたファイルを開けない | 送信時に存在確認を再実行 | | ||
| | キャンセルした URL バーの存在確認完了後に遷移 | 入力・表示状態を検証 | | ||
| | 遅延 import 失敗が永続キャッシュされる | 失敗時に Promise キャッシュを解放 | | ||
| | 相対 URI・`..`・remote 文書内の画像やリンクが誤解決 | 文書の実 URL / パスを基準に解決 | | ||
| | 巨大な `line=1-999999999999` でブラウザーが停止 | 実在する DOM 行だけを走査 | | ||
| | ゼロ・安全な整数を超える行指定 | 不正な行指定を除外 | | ||
| | TOC の ID 内 `&` が実体参照へ変換 | 属性値を適切にエスケープ | | ||
| | 破損した recent localStorage で起動失敗 | 配列・文字列の型を検証 | | ||
| | 比較終了後のスケルトン・タイトルの不整合 | 元ファイルを再描画 | | ||
| | 分割表示・File 入力で動画をテキスト扱い | 動画プレーヤーへ接続 | | ||
| | ファイル選択の accept が新形式を含まない | 対応拡張子と整合 | | ||
| | Marp 遅延生成・重複・相対画像の不整合 | 世代、オーバーレイ、画像 URL を補正 | | ||
|
|
||
| 主な回帰テスト: `tests/navigation-audit.spec.ts`。 | ||
|
|
||
| ## 文書・データ表示 | ||
|
|
||
| | 問題 | 対応 | | ||
| |---|---| | ||
| | HTML の画像保存時にスクリプトが親 DOM / storage へ到達 | 通常プレビューから DOM を受け取り、スクリプト禁止 iframe で撮影 | | ||
| | scripts off の HTML でも保存時にスクリプトが実行 | 撮影側では常に scripts を禁止 | | ||
| | HTML リソース URL の `%20` / `%25` 二重エンコード | 一度デコードし正規化後に再エンコード | | ||
| | Marp のコメント付き true を見落とし、nested true を誤認 | YAML ルートの boolean を検証 | | ||
| | Wiki link の fragment / 親ディレクトリのドットを誤処理 | ファイル名だけで拡張子を判定 | | ||
| | Mermaid 構文エラーの一時 SVG が画面に残る | 内部エラー描画を無効化 | | ||
| | 空 CSV のパスが HTML として挿入 | エスケープ | | ||
| | CSV の `00123`・ISO 日時・指数表記が変形 | 原文の文字列を保持 | | ||
| | 空欄・指数表記のある数値列のソート不正 | 数値判定と比較を補正 | | ||
| | JSONL の空行・不正行後の行番号ずれ | 元の行番号を保持 | | ||
| | JSONL の欠落キーから継承プロパティが表示 | own property のみ使用 | | ||
| | JSON / YAML の空文字キーが消える | ルート判定と分離 | | ||
| | 循環 YAML で表示失敗 | 循環参照を明示 | | ||
| | Secret Safe の短い値・引用値・複合キー・コンテナ漏れ | キーと値全体を検証してマスク | | ||
| | CSV / YAML の非表示 Source DOM に機密値が残る | 形式別に Source もマスク | | ||
| | 大きな表・未知拡張子・title 属性でマスク欠落 | 全表示経路へ適用 | | ||
| | chunk 検索で HTML 実体参照が壊れる | 生文字列で一致位置を求め、各部分をエスケープ | | ||
| | 古い chunk 検索・ページ応答で表示上書き | 世代と画面の所有状態を検証 | | ||
| | chunk 通信失敗で Loading が残る | エラーと移動ボタン状態を更新 | | ||
| | 推定行数が少ない大きな JSONL の深いリンクがずれる | 実測件数で調整 | | ||
| | CSV / TSV の引用符内改行がページ境界で壊れる | `records=1` で論理レコード単位の range / search | | ||
| | CSV 検索でヘッダーがデータ行に混ざる | ヘッダーを別メタデータとして保持 | | ||
| | Laravel CRLF ログを形式判定できない | CR を除去 | | ||
| | cron コマンド内の空白が潰れる | 本文を保持 | | ||
| | コマンドなし・実行不能日の cron を有効扱い | 構文と実行可能性を検証 | | ||
| | cron 衝突時刻のタイムゾーン不整合・検査範囲の誤表示 | JST に統一し 35 日 / 1,500 回の上限を明示 | | ||
|
|
||
| 主な回帰テスト: `tests/rendering-audit.spec.ts`、`tests/data-audit.spec.ts`。 | ||
|
|
||
| ## 画像・アルバム | ||
|
|
||
| | 問題 | 対応 | | ||
| |---|---| | ||
| | SVG の megapixels `N/A` に数値メソッドを呼び表示失敗 | 型を検証 | | ||
| | lossy WebP の寸法が 1 px 大きい | VP8 の値をそのまま使用 | | ||
| | 短い VP8L ヘッダーを読めない | 形式別の最小サイズを検証 | | ||
| | SVG の `%` 指定を px と誤認 | viewBox にフォールバック | | ||
| | 不完全 PNG の寸法・テキストを採用 | チャンク型・長さ・範囲を検証 | | ||
| | 非有限寸法で GCD が終了しない | 正で有限の値だけを採用 | | ||
| | metadata パネルの再生成で DOM が重複 | 所有要素を削除 | | ||
| | クリップボード失敗でも成功表示 | 結果を待ち、失敗を表示 | | ||
| | 古い album 応答が現在の画像・キーボード状態を上書き | AbortController と世代・DOM を検証 | | ||
| | メディア待機中にライトボックスを閉じられない | 待機前に閉じる操作を登録 | | ||
| | 遅い SVG が次の画像を上書き | 移動・終了時に中断 | | ||
| | 終了後の動画再生・ズームイベント残存 | メディアとリスナーを解放 | | ||
|
|
||
| 主な回帰テスト: `tests/image-meta-audit.test.mjs`、`tests/media-audit.spec.ts`。 | ||
|
|
||
| ## テーマ・依存関係・検証 | ||
|
|
||
| - 既存コントラスト検査の 11 失敗を修正。半透明色を背景と合成して検査し、30 テーマ × 10 項目を確認する。 | ||
| - DOMPurify、markdown-it、Mermaid、Vite と推移依存を互換範囲で更新。固定依存環境の `npm audit` は 14 件から 0 件。 | ||
| - C2PA を任意依存にし、ネイティブ導入失敗時も閲覧とバイト走査を利用可能にした。 | ||
| - npm の固定依存を `npm-shrinkwrap.json` で管理する。 | ||
| - Node の単体テストと Playwright のテスト探索を分離する。 | ||
| - GitHub Actions に Node 20.19.0 / 22 のインストール・ビルド・単体・コントラスト・E2E 検証を追加する。 | ||
|
|
||
| ## 検証上の制約と残件 | ||
|
|
||
| - ローカル環境では Chromium が起動時に SIGTRAP で終了し、接続済みブラウザーから localhost へのアクセスも拒否された。ブラウザー E2E の結果は PR の GitHub Actions 結果を参照する。テスト登録・型チェックの成功だけを E2E 成功として扱わない。 | ||
| - Windows / macOS の OS アプリ起動は実機未検証。 | ||
| - HTML 撮影では読み取り可能な Canvas を画像として保持する。CORS で読み取り不能な Canvas、closed Shadow DOM、属性に反映されないフォーム状態は完全には保持できない。CSP 等で応答を得られない場合は元 HTML の安全な描画へフォールバックする。 | ||
| - この検証環境の npm 11.9 では、最終コミットを GitHub または tarball から別プロジェクトの依存として導入すると、同梱 shrinkwrap の修正版解決が適用されず、sharp 0.34.3 / xmldom 0.9.10 が再導入された。ソース・tarball・導入後の shrinkwrap の一致と、実際の node_modules の版を確認した。audit 0 件はリポジトリの `npm ci` 環境に限定される。固定依存を使うにはチェックアウトで `npm ci` 後に `node bin/mdv.mjs /path/to/documents` を実行する。上流固定依存の解消は残件。 | ||
| - C2PA native が利用できない環境では暗号署名の検証は行えない。縮退経路は単体テストで確認した。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads'; | ||
|
|
||
| // Keep user-supplied regular expressions off the HTTP event loop. Pattern | ||
| // heuristics cannot reliably identify every exponential or polynomial case. | ||
| if (!isMainThread && workerData?.docviewRegex) { | ||
| const regex = new RegExp(workerData.pattern, 'i'); | ||
| parentPort.on('message', ({ lines, limit }) => { | ||
| const matches = []; | ||
| for (let i = 0; i < lines.length && matches.length < limit; i++) { | ||
| if (regex.test(lines[i])) matches.push(i); | ||
| } | ||
| parentPort.postMessage(matches); | ||
| }); | ||
| } | ||
|
|
||
| export function createRegexSearch(pattern, timeoutMs = 1000) { | ||
| const worker = new Worker(new URL(import.meta.url), { | ||
| workerData: { docviewRegex: true, pattern }, | ||
| }); | ||
| let pending; | ||
| function finish(error, matches) { | ||
| if (!pending) return; | ||
| const { resolve, reject, timer } = pending; | ||
| pending = undefined; | ||
| clearTimeout(timer); | ||
| if (error) reject(Object.assign(error, { regexSearch: true })); | ||
| else resolve(matches); | ||
| } | ||
| worker.on('message', (matches) => finish(null, matches)); | ||
| worker.on('error', (error) => finish(error)); | ||
| worker.on('exit', () => finish(new Error('Regex search worker stopped'))); | ||
|
|
||
| return { | ||
| findMatches(lines, limit) { | ||
| return new Promise((resolve, reject) => { | ||
| const timer = setTimeout(() => { | ||
| finish(Object.assign(new Error('Regex search took too long'), { status: 400 })); | ||
| void worker.terminate(); | ||
| }, timeoutMs); | ||
| pending = { resolve, reject, timer }; | ||
| worker.postMessage({ lines, limit }); | ||
| }); | ||
| }, | ||
| close() { return worker.terminate(); }, | ||
| }; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the wrapper receives
SIGINTorSIGTERM(for example, from Ctrl-C or a service manager), this handler suppresses Node's default signal termination and forwards the signal to the child. The child then exits withcode === null, and the existingchild.on('exit', (code) => process.exit(code ?? 0))reports success, so scripts cannot distinguish an interrupted server from a clean shutdown. Re-raise the signal in the parent or map the child's exit signal to the conventional nonzero status.Useful? React with 👍 / 👎.