This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# 依存パッケージの外向き通信インベントリ
|
||||
|
||||
調査日: 2026-09-08
|
||||
基準コミット: `cf2bc52d6725da17b207202bdc6eaf7ac9f0cab7`
|
||||
|
||||
この文書は、root と UI の lockfile を全件機械集計し、展開済み依存のランタイム JavaScript/JSON をテレメトリ関連語彙で絞り込んだ初段の棚卸しである。通常利用時に依存が自動収集・送信する telemetry、analytics、crash report、usage report を主対象にする。利用者が明示した API 接続、WebFetch、通知、MCP、OAuth、LLM 接続などは対象外である。悪意ある文書や入力で通信を起こす攻撃経路も、この文書では扱わない。
|
||||
|
||||
## 結論
|
||||
|
||||
確認した依存候補から、通常利用時に自動送信される telemetry、analytics、crash report は見つからなかった。`@opentelemetry/api` と `@univerjs/telemetry` は lockfile に存在するが、前者には exporter、後者には送信実装がなく、アプリにも送信 provider の登録がない。
|
||||
|
||||
以前、通常 UI 操作で発生していた Google Fonts、jsDelivr の Monaco、unpkg の Leaflet 資産取得は、現在の変更でローカル npm 資産へ切り替えた。地図を開いた際の OpenStreetMap タイル取得は残る。これは telemetry ではないが、通常 UI 操作で第三者へ出る通信なので記録する。
|
||||
|
||||
この結論は下記の「確認済み」の経路に限る。語彙走査にヒットした全ファイルを手作業で監査したという意味ではなく、動的な全通信先監視や全依存の制御フロー監査も実施していない。
|
||||
|
||||
## Lockfile 集計
|
||||
|
||||
対象は lockfile v3 の `packages` である。空キーの root record、`link: true` のローカル workspace link、その link の実体を表す `node_modules/` 外の workspace record を各 lockfile の package 件数から除外した。`dev !== true` を production install に含まれる record、`dev === true` を development-only record とした。同一パッケージの複数版やネスト配置は別 record として数える。
|
||||
|
||||
| 対象 | 直接 production 宣言 | 直接 development 宣言 | package records | production に含む | development-only | 推移依存 records | 推移 production | 推移 development-only | 一意な名前 | 一意な名前+版 |
|
||||
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||
| root [`package-lock.json`](../../package-lock.json) | 44 | 22 | 633 | 446 | 187 | 568 | 400 | 168 | 551 | 598 |
|
||||
| UI [`ui/package-lock.json`](../../ui/package-lock.json) | 44 | 12 | 497 | 436 | 61 | 442 | 389 | 53 | 407 | 444 |
|
||||
|
||||
2 lockfile の合計は 1,130 package records で、同じ名前+版を両方の lockfile 間でも重複除外すると 984 組である。ローカル workspace link の `@aao/answer-worker-contract` とその実体 record は表から除外した。
|
||||
|
||||
集計時の SHA-256 は次の通り。
|
||||
|
||||
- `package-lock.json`: `c3a1e69f7160b1bcc29969d249ab7b9f34b7ea55b725409a7a3efb6dc0d8dad5`
|
||||
- `ui/package-lock.json`: `4c489f8f91a933a3cff6223b91a76dee570d29504c704139b47dbd4c57a49f2d`
|
||||
|
||||
直接依存の数は manifest の宣言数であり、package record の分類とは別軸である。例えば development に直接宣言されたパッケージが production 推移依存としても必要な場合、その record の `dev` は `true` にならない。
|
||||
|
||||
## 通常 runtime の外向き経路
|
||||
|
||||
| 状態 | 依存・経路 | 判定と根拠 |
|
||||
|---|---|---|
|
||||
| 確認済み・外向き通信あり | OpenStreetMap tiles | 地図詳細を表示し、場所が 1 件以上ある場合に `https://tile.openstreetmap.org/{z}/{x}/{y}.png` を取得する。タイル座標と通常のブラウザ要求メタデータが送られる。[`MapPlacesDetail.tsx`](../../ui/src/components/embed/MapPlacesDetail.tsx#L36-L55) |
|
||||
| 確認済み・ローカル化 | Leaflet 1.9.4 | JS、CSS、marker 画像を npm 依存から動的 import する。以前の unpkg script/CSS は使わない。[`MapPlacesDetail.tsx`](../../ui/src/components/embed/MapPlacesDetail.tsx#L1-L27) |
|
||||
| 確認済み・ローカル化 | Monaco Editor 0.55.1 | Monaco 本体と各 worker を npm 依存から import し、`loader.config({ monaco })` へ渡す。`@monaco-editor/loader` の既定 jsDelivr 経路は使わない。[`monaco-loader.ts`](../../ui/src/lib/monaco-loader.ts#L1-L32) |
|
||||
| 確認済み・ローカル化 | IBM Plex / JetBrains Mono | Fontsource の CSS とフォントファイルをビルド資産に含める。以前の Google Fonts CSS は使わない。[`index.css`](../../ui/src/index.css#L1-L10) |
|
||||
| 確認済み・送信なし | `prom-client` / `@opentelemetry/api` | `@opentelemetry/api` 1.9.1 は `prom-client` 15.1.3 の推移依存で、lockfile に SDK/exporter/instrumentation package はない。アプリは process metrics をローカル registry に収集し、受信した `/metrics` scrape に返す pull 型である。[`registry.ts`](../../src/metrics/registry.ts#L26-L48) [`metrics-subsystem.ts`](../../src/bridge/metrics-subsystem.ts#L31-L43) |
|
||||
| 確認済み・送信なし | `@univerjs/telemetry` 0.25.1 | 展開済み package は `ITelemetryService` identifier のみを公開する。`@univerjs/sheets-ui` は `sheetId`、`unitId`、描画時間統計を optional service へ `capture` できるが、アプリの登録プラグインに telemetry provider はない。[`UniverGrid.tsx`](../../ui/src/components/files/spreadsheet/UniverGrid.tsx#L263-L288) |
|
||||
| 確認済み・ローカル処理 | Mermaid / PDF | Mermaid は npm bundle を直接 import し、server renderer も `vendor/` または `ui/node_modules` の bundle を読む。[`FilePreviewContent.tsx`](../../ui/src/components/files/FilePreviewContent.tsx#L25-L28) [`mermaid.ts`](../../src/engine/tools/mermaid.ts#L123-L142) PDF はローカルファイルの Buffer を `PDFParse` に渡す。[`office.ts`](../../src/engine/tools/office.ts#L1673-L1679) |
|
||||
|
||||
## アプリ機能による通常の外向き通信
|
||||
|
||||
以下は自動 telemetry ではなく、設定済み機能を動かすための通信である。依存 package の存在だけでは発生せず、表の条件を満たしたときに有効になる。
|
||||
|
||||
| 機能 | 宛先と有効化条件 | 送信する主なデータ | 状態 |
|
||||
|---|---|---|---|
|
||||
| LLM 呼び出し | 設定した OpenAI 互換 endpoint。タスクがモデルを呼ぶと `POST <base>/chat/completions`。[`openai-compat.ts`](../../src/llm/openai-compat.ts#L1273-L1283) | API key、会話 messages、model、tools、生成設定 | 確認済み |
|
||||
| 設定画面の model 一覧取得 | model 設定画面で endpoint が入力されると、ブラウザから直接 `GET <endpoint>/models`。literal API key は Bearer header に入る。`env_ref` / masked key は取得を行わず、key を cleared にした場合は無認証で取得する。[`ModelSelect.tsx`](../../ui/src/components/settings/ModelSelect.tsx#L69-L96) | 設定中の endpoint、Accept header、literal の場合は API key | 確認済み |
|
||||
| LLM health / capacity probe | 設定した direct worker の `/slots`、`/health`、`/metrics`、proxy worker の `/health`。稼働中は約 5 秒ごと。direct probe は worker API key を送らず、proxy `/health` は設定された key を送る。[`backend-probes.ts`](../../src/engine/backend-probes.ts#L420-L480) [`backend-probes.ts`](../../src/engine/backend-probes.ts#L932-L944) | GET、Accept、proxy の場合は Authorization。応答から稼働状態・slot・throughput を読む | 確認済み |
|
||||
| OAuth と avatar | 管理者が Google または Gitea provider を有効にし、利用者がログインした場合。Gitea は設定 base URL の authorize/token/user/orgs、Google は Passport strategy の provider endpoint を使う。[`auth.ts`](../../src/bridge/auth.ts#L577-L672) provider が返した avatar URL は画面の `<img src>` からブラウザが取得する。[`UserAccountMenu.tsx`](../../ui/src/components/layout/UserAccountMenu.tsx#L67-L68) | OAuth client情報、authorization code/token、user profile/org照会。avatar取得時はURLと通常のbrowser metadata | 確認済み |
|
||||
| Web Push | 利用者が通知を有効にし push subscription がある場合、その subscription endpoint。[`push-service.ts`](../../src/push-service.ts#L176-L212) | 既定はevent種別、task ID、汎用文言。`includeDetails` opt-in時はtask titleとpiece/summaryも含む。VAPID署名とsubscription keysを使用 | 確認済み |
|
||||
| Space webhook | workspaceでDiscord/Slack/Teams webhookを登録・有効化し、対象eventが発生した場合、登録したHTTPS URL。[`webhook-service.ts`](../../src/webhook-service.ts#L419-L440) [`webhook-service.ts`](../../src/webhook-service.ts#L483-L527) | event、task ID、task URL。`includeDetails` 設定によりtask title、piece、space名。JSON POST | 確認済み |
|
||||
| MCP | 暗号化キーを設定し、HTTP MCP serverを登録して接続・tool list・tool callを行う場合、登録した server URL。Bearer または指定headerへtokenを付ける。[`client-factory.ts`](../../src/mcp/client-factory.ts#L34-L65) [`tool-executor.ts`](../../src/mcp/tool-executor.ts#L47-L62) | MCP初期化/capability、tool名と引数、認証token | 確認済み |
|
||||
| 起動時の不足資産補完 | `scripts/server.sh start` は毎回 `prepare.sh` を実行する。Chromium が無い場合は Playwright CDN から取得し、共有library不足時は `playwright install-deps` がOS package repositoryを使い得る。[`server.sh`](../../scripts/server.sh#L120-L127) [`prepare.sh`](../../scripts/prepare.sh#L27-L64) | Playwright/npm/OS標準のdownload request metadata。アプリのtask内容は送らない | 確認済み・条件付き |
|
||||
|
||||
`npm ci`、Docker build、`setup.sh`、`upgrade.sh` に伴う registry、Git、OS package、Playwright browser の取得は管理者が明示的に行う install/build/update 通信であり、通常 runtime の telemetry 判定には含めない。
|
||||
|
||||
## ローカル UI 資産の回帰検査
|
||||
|
||||
次の検査をリポジトリ root で再現できる。
|
||||
|
||||
```bash
|
||||
npm run security:ui-assets
|
||||
```
|
||||
|
||||
[`check-local-ui-assets.mjs`](../../scripts/security/check-local-ui-assets.mjs) は実際の `MonacoFileEditor`、`MapPlacesDetail`、`index.css` を一時 Vite アプリへ読み込み、production build を Chromium で実行する。2026-09-08 の検査は exit 0 で成功し、3 font family のローカル読込、Monaco の編集・保存と same-origin worker、Leaflet の 2 marker の表示・画像 decode を確認した。OpenStreetMap の tile request は検査内でローカル応答し、それ以外の cross-origin request と不要な WebSocket 接続は実接続前に遮断して失敗にする。画面表示後に Google Fonts を意図的に追加する negative fixture で、遅れて発生する要求も検出した。
|
||||
|
||||
フォントと地図タイルの要求件数は描画条件で変わるため、各実行の集計をコマンド末尾へ出力する。
|
||||
|
||||
この動的検査の範囲は上記 UI component と資産に限る。アプリ全画面の操作や server workload を動かして全通信を監視する検査ではない。
|
||||
|
||||
## テレメトリ語彙の候補確認
|
||||
|
||||
展開済み依存の `.js`、`.cjs`、`.mjs`、`.json` を走査し、source map は除外した。使用した語彙は `telemetr`、`analytics`、`sentry`、`datadog`、`newrelic`、`mixpanel`、`amplitude`、`posthog`、`rollbar`、`bugsnag`、`phone.?home`、`usage.?report`、`update.?check`、`license.?check`、`opentelemetry` である。
|
||||
|
||||
| 対象 | 展開済み lock records | lock と同一版 | 不一致 | 未展開 | 走査ファイル | 走査量 |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| root | 550 / 633 | 550 | 0 | 83 | 9,879 | 130,972,307 bytes |
|
||||
| UI | 469 / 497 | 469 | 0 | 28 | 15,076 | 258,538,747 bytes |
|
||||
|
||||
root と UI はこの worktree で `npm ci --ignore-scripts --no-audit --no-fund` 後の展開物を使った。存在した root 550 records と UI 469 records はすべて lockfile と同一版だった。未展開の root 83 records と UI 28 records はすべて `optional: true` で、現在の OS/CPU 以外の native package などを含む。
|
||||
|
||||
手動で送信経路まで確認した候補は次の通り。
|
||||
|
||||
- `@opentelemetry/api` とその導入元 `prom-client`: API とローカル registry だけで、exporter package と外向き送信 sink は確認されなかった。
|
||||
- `@univerjs/telemetry` と `@univerjs/sheets-ui`: identifier と optional `capture` 呼び出しはあるが、送信 provider の実装・登録は確認されなかった。
|
||||
- `monaco-editor`: telemetry という名前の内部型・service は含むが、MAESTRO が collector や転送先を登録する箇所は確認されなかった。現在は editor code と worker もローカル配信する。
|
||||
|
||||
`pdfjs-dist`、`pdf-parse`、Playwright、Mermaid、TypeScript、highlight.js、React、各種 CSS/parser package などにも語彙ヒットがあった。エラーサービス名を入力データとして扱う parser、CSS の `scrollbar` に含まれる文字列、テスト・開発コード、一般的な型名などが混在するため、これらは **keyword candidate のまま未検証** である。語彙ヒットだけを telemetry 実装の証拠にはしていない。
|
||||
|
||||
## Install script
|
||||
|
||||
lockfile の `hasInstallScript: true` を全件抽出した。これは install 時のコード実行面であり、通常 runtime の自動 telemetry とは別である。
|
||||
|
||||
| lock | package | 区分 | 展開・確認状態 | install 時の挙動 |
|
||||
|---|---|---|---|---|
|
||||
| root | `[email protected]` | 直接 production | 確認済み | `prebuild-install || node-gyp rebuild --release`。prebuild が無ければ GitHub release asset の取得を試み、失敗時はローカル build。 |
|
||||
| root | `[email protected]` | 直接 production | 確認済み | `node install.js` から optional crypto binding を `node-gyp rebuild`。script 自身に HTTP client はない。node-gyp はローカル header が無い環境では header 取得を行い得る。 |
|
||||
| root | `[email protected]` | 推移 production・optional | 確認済み | buildcheck 後に `node-gyp rebuild`。script 自身に HTTP client はない。 |
|
||||
| root | `playwright/node_modules/[email protected]` | 推移 production・optional | Linux では未展開、未検証 | lockfile flag のみ確認。macOS 向け optional package。 |
|
||||
| root | `[email protected]` | 推移 development-only | 確認済み | `node install.js`。対応 platform の optional binary が無ければ npm を起動し、それも失敗すると `https://registry.npmjs.org/@esbuild/*` の tarball を直接取得する。 |
|
||||
| root | `[email protected]` | 推移 development-only・optional | Linux では未展開、未検証 | lockfile flag のみ確認。macOS 向け optional package。 |
|
||||
| UI | `[email protected]` | 推移 production | 確認済み | `node scripts/postinstall` は親 manifest の semver 記法を検査して warning を出すだけで、通信しない。 |
|
||||
| UI | `[email protected]` | 推移 production・optional | Linux では未展開、未検証 | lockfile flag のみ確認。macOS 向け optional package。 |
|
||||
|
||||
通常の [`build-all.sh`](../../scripts/build-all.sh#L26-L31) は plain `npm ci` を使うため、該当 platform では lifecycle script が動く。Docker build は builder と runtime の両方で `npm ci --ignore-scripts` を使い、その後に検査済みの native rebuild を行う。[`Dockerfile`](../../Dockerfile#L15) [`Dockerfile`](../../Dockerfile#L126-L134)
|
||||
|
||||
## 再現方法
|
||||
|
||||
件数は次の Node.js スクリプトで再現できる。
|
||||
|
||||
```bash
|
||||
node <<'NODE'
|
||||
const fs = require('node:fs');
|
||||
for (const file of ['package-lock.json', 'ui/package-lock.json']) {
|
||||
const lock = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
const root = lock.packages[''];
|
||||
const direct = new Set([
|
||||
...Object.keys(root.dependencies ?? {}),
|
||||
...Object.keys(root.devDependencies ?? {}),
|
||||
...Object.keys(root.optionalDependencies ?? {}),
|
||||
]);
|
||||
const rows = Object.entries(lock.packages)
|
||||
.filter(([path, value]) => path.includes('node_modules/') && value.link !== true);
|
||||
const isDirect = ([path]) => {
|
||||
const name = path.replace(/^node_modules\//, '');
|
||||
return direct.has(name) && path === `node_modules/${name}`;
|
||||
};
|
||||
const count = (predicate) => rows.filter(predicate).length;
|
||||
console.log(file, {
|
||||
directProduction: Object.keys(root.dependencies ?? {}).length,
|
||||
directDevelopment: Object.keys(root.devDependencies ?? {}).length,
|
||||
records: rows.length,
|
||||
productionIncluded: count(([, v]) => v.dev !== true),
|
||||
developmentOnly: count(([, v]) => v.dev === true),
|
||||
transitive: count((row) => !isDirect(row)),
|
||||
transitiveProduction: count((row) => !isDirect(row) && row[1].dev !== true),
|
||||
transitiveDevelopment: count((row) => !isDirect(row) && row[1].dev === true),
|
||||
});
|
||||
}
|
||||
NODE
|
||||
```
|
||||
|
||||
install script 候補は次で再現できる。
|
||||
|
||||
```bash
|
||||
node -e 'for (const f of ["package-lock.json","ui/package-lock.json"]) { const l=require("./"+f); console.log(f,Object.entries(l.packages).filter(([p,v])=>p&&v.hasInstallScript).map(([p,v])=>({path:p,version:v.version,dev:!!v.dev,optional:!!v.optional}))) }'
|
||||
```
|
||||
|
||||
語彙候補は、同じ拡張子・除外条件で次のように再抽出できる。
|
||||
|
||||
```bash
|
||||
rg -l -i 'telemetr|analytics?|sentry|datadog|new\s?relic|mixpanel|amplitude|posthog|rollbar|bugsnag|phone.?home|usage.?report|update.?check|license.?check|opentelemetry' node_modules ui/node_modules --glob '*.js' --glob '*.cjs' --glob '*.mjs' --glob '*.json' --glob '!**/*.map'
|
||||
```
|
||||
|
||||
この調査で使った機械集計結果は `/tmp/dependency-egress-inventory.json`、語彙走査結果は `/tmp/dependency-egress-scan.json` に残した。これらは一時成果物であり、再現の基準は上記 lockfile hash と手順である。
|
||||
|
||||
## 限界と次段
|
||||
|
||||
- lockfile の全 records は集計したが、全依存の全関数を手作業で監査してはいない。
|
||||
- root 83 records と UI 28 records は、この OS/CPU では展開されない optional package だった。これらは lockfile の package metadata と名前だけを棚卸しし、実コードは走査していない。
|
||||
- keyword scan は難読化、動的 URL 組み立て、語彙を含まない独自収集を検出できない。逆に、多数の false positive を含む。
|
||||
- 通常 telemetry と別の、利用者設定による外部 API 通信、外部コンテンツ取得、悪意ある入力を使った egress はこの文書の結論に含めない。
|
||||
- 強い保証が必要な次段では、隔離ネットワーク上で主要 UI 操作と server workload を実行し、DNS・HTTP(S)・WebSocket を process/browser 単位で記録する。未展開 optional package は対象 OS ごとに `--ignore-scripts` で展開して静的確認し、必要な lifecycle script だけを別の隔離環境で観測する。
|
||||
Reference in New Issue
Block a user