// 00 — Verify first
主張ではなく、検証から始める
この文書の結論は、読む前に自分で確かめられます。手順はごく単純です。本サイトを開き、開発者ツール(F12、または Ctrl/Cmd+Shift+I)の Network パネルを開いたまま、本文を貼り付けて変換してみてください。
変換しても、ネットワークリクエストは 一本も発生しません。XHR も Fetch も WebSocket も、ビーコンも、画像の GET によるピングも記録されません。もう一歩踏み込むなら、Console で performance.getEntriesByType('resource') を実行すると、ページが読み込んだリソースの URL が全部並びます。そこに自オリジン(rubicon.pages.dev)以外の宛先は出てきません。
この先は、なぜそうなるのかをコードのレベルまで分解した説明です。先に検証結果(通信ゼロ)があって、あとはその仕組みを追っていくだけ。この順序には意味があります——結論は実際に観測できる事実であって、こちらの言い分を信じてもらう必要がないからです。
// 01 — Threat model
何を「漏れない」と言っているのか
プライバシーの話は、何を守りたいのか(保護対象)と、何から守るのか(脅威)をはっきりさせないと意味を持ちません。守りたいものは明確で——あなたが入力欄に貼り付けた本文そのものです。未公開の原稿、筆名や作風、伏せ字や傍点の付け方といった執筆上の癖まで含めた、テキスト全体を指します。
これに対して、想定する脅威は次の四つです。
- 運営者が本文を見る・ためる — 運営(Takeyabu Studio)が、あなたの本文を受け取って記録したり分析したりすること。
- 第三者による解析やトラッキング — 解析タグや広告 SDK の類が、本文や入力の動きを外に送ること。
- 通信経路での盗み見 — 本文が平文のままネットワークに出て、途中で読まれること。
- 保存されたものが後から漏れる — 本文がブラウザの中に残り、あとで別の誰かに読まれること。
この四つすべてに対する答えは、最終的に一つの事実に行き着きます——本文がプロセスのメモリから外へ出ていく経路が、実装のどこにもない。送信が起きなければ、受信も蓄積も盗み見も解析も成り立ちません。保存もしていません。以下では、この「経路がない」を四つの層(アプリのコード/ビルド成果物/ブラウザのオリジン境界/配信時の CSP)に分けて、一つずつ確かめていきます。
この文書が扱わない範囲も書いておきます。これはあくまで本文の機密性についての話で、サービスが落ちない保証(可用性)や、あなたの端末そのものがすでにマルウェアに感染している場合の防御までは対象にしていません。後者は、どんな Web アプリの設計をもってしても防げない領域です。
// 02 — Data flow
本文の全経路を追跡する
入力された文字列がどこを通るのかを、関数の呼び出し単位で端から端まで追ってみます。途中でネットワークの境界もストレージの境界も跨がないことを、経路そのもので示します。
<textarea> の .value を読み取って、JavaScript の文字列としてメモリ上に持つ。DOM プロパティを読むだけ。ここまでネットワークは関与しません。textContent で結果カードに表示する。必要なら、あなたの操作で navigator.clipboard.writeText() を呼んでローカルのクリップボードへコピー。表示は DOM、コピーは OS への操作。どちらもネットワークではありません。
この経路のどこにも、fetch / XMLHttpRequest / WebSocket / sendBeacon / new Image().src といった送信のための命令は出てきません。中核(パーサー・レンダラー・トークナイザー・AST の構築)は、引数の文字列を受け取って値を返すだけの関数の集まりで、document への参照すら持っていません。本文は プロセスのメモリから画面までで完結します。これが設計上の前提(不変条件)です。
// 03 — Absence of egress primitives
送信の命令はソースにない
ソースツリー全体(src/ 配下の JavaScript すべて)を対象に、ネットワーク送信を起こしうる API を片っ端から検索しました。探したのは fetch( / XMLHttpRequest / .open( / .send( / WebSocket / EventSource / navigator.sendBeacon / new Image(、それに axios のような HTTP ライブラリの参照です。
実際に実行されるコードとしては、一件もヒットしませんでした。引っかかったのは二つだけ。一つは navigator.clipboard.writeText()(これはネットワークではなく OS のクリップボードへの書き込みです)、もう一つは「外部送信は一切行わない」という趣旨のコメント文字列です。前者はローカルの操作、後者は実行されないただのテキストです。
送信の命令がそもそも書かれていない、というのは「送信しないように気をつけて実装した」よりずっと強いことです。送信するための語彙が、コードベースに一度も登場しない。気をつけたかどうかの話ではなく、有無の話だからです。
// 04 — The one fetch in the bundle
配信バンドル唯一の通信命令を解剖する
誠実さのために、いちばん鋭いツッコミに先回りしておきます。ビルドした後の配信物(ミニファイ済みの dist/assets/*.js)を調べると、fetch( が ちょうど一か所だけ見つかります。ソースには無いものが成果物にある以上、これは説明しておかないといけません。
該当箇所は、ミニファイされてこんな形になっています。
function o(s){ if(s.ep) return; s.ep=!0;
const r=e(s); fetch(s.href,r); }
これは Vite が自動で吐く モジュールプリロード・ポリフィル(__vitePreload という仕組み)です。役目は、<link rel="modulepreload"> に並んだコード分割チャンクを、対応していないブラウザのために先読みしておくこと。fetch の引数 s.href は、その link 要素の href、つまり このサイト自身の、同じオリジンの JS チャンク(/assets/*.js)を指します。ここにユーザーの入力が入り込む余地はありません。取りに行くのはいつでも自分自身のプログラムの一部で、GET でスクリプトを読むだけです。
しかも今回のビルドでは、出力された dist/index.html に rel="modulepreload" のリンクが 一つもありません(単一バンドル構成なので、Vite が preload リンクを出していない)。先読みする対象がないということは、この関数 o() を呼ぶ入口もない——つまり 一度も実行されない死コードです。仮にこの先コード分割が入って呼ばれるようになったとしても、送り先は自オリジンの .js に限られますし、あとで触れる CSP の connect-src 'self' が、それより外への逸脱を止めます。
まとめると、成果物にある唯一の fetch は、ビルドツールが付けたもので、宛先は自オリジンに限られ、今回のビルドではそもそも実行されません。本文を運ぶ経路ではないということです。ここを隠さず解剖しておくことが、残りの説明の信頼性につながると考えています。
// 05 — Content-Security-Policy, directive by directive
CSP を一条ずつ読む
配信時、本サイトは次の Content-Security-Policy をレスポンスヘッダとして付けます(public/_headers に定義していて、ビルド成果物の dist/_headers にも同じ内容で出力されます)。
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://www14.a8.net;
connect-src 'self';
font-src 'self' data:;
frame-ancestors 'none';
base-uri 'self';
form-action 'self'
各ディレクティブが何をしていて、本文の機密性にどう効くのかを、一つずつ見ていきます。
default-src 'self'- 個別に指定していないリソース取得の既定値を、自オリジンに絞ります。以下の各ディレクティブは、この既定をリソースの種類ごとに上書きしたり追認したりするものです。フォールバックが
'self'である時点で、明示的に許可しない限り外部は塞がっています。 script-src 'self'- スクリプトの取得元を自オリジンに限ります。
'unsafe-inline'も'unsafe-eval'も与えていないので、インラインの<script>・eval・on*属性によるコード実行は拒否されます。これで外部の解析タグや広告 SDK を注入できなくなり、同時に XSS の実行段も一つ塞げます。第三者の JS が走らない以上、第三者が本文を読み出して送る、という役回り自体が成立しません。 connect-src 'self'- 本文の機密性にとって、ここがいちばん効く条項です。
fetch/XMLHttpRequest/WebSocket/EventSource/navigator.sendBeaconの接続先を自オリジンに限ります。仮にコードが(あるいは将来の依存が)外部へ送ろうとしても、ブラウザが接続そのものを拒否します。コードに送信が無いこと(03・04)に加えて、ブラウザの側でも外部の宛先が遮断される。これが二重化の片方です。 img-src 'self' data: https://www14.a8.net- 画像の取得元を、自オリジン・
data:URI・A8.net の指定ホストに限ります。A8.net を許可しているのは、将来のアフィリエイトバナー(<img>での表示)を見越した先回りの設定ですが、許しているのは 画像を読み込むことだけです。script-srcは'self'のままなので広告由来の JS は走らず、connect-src 'self'のままなので広告ホストへ能動的に接続することもありません。画像読み込みの GET に本文を載せる、といった実装も存在しません。 font-src 'self' data:- フォントの取得元を自オリジンと
data:に限ります。Google Fonts のような外部フォントは読み込みません。外部フォントは「どのページを見たか」を第三者に伝えてしまうことがありますが、その経路自体を断っています。 frame-ancestors 'none'- 本サイトを
<iframe>などで他サイトに埋め込むことを禁じます。クリックジャッキングや、埋め込み元からの入力の盗み取りを防ぎます。後述のX-Frame-Options: DENYと意味が重なりますが、CSP 側のほうが新しく、より細かく制御できます。 base-uri 'self'<base>要素で基準 URL を書き換えることを、自オリジンに限ります。注入された<base>で相対 URL の解決先を外部にすり替え、スクリプトやフォームの送り先を乗っ取る、という攻撃を封じます。form-action 'self'- フォームの送信先を自オリジンに限ります。もっとも本サイトはそもそも
<form>を持たず(変換は<button type="button">と JS のハンドラーで処理しています)、入力した本文がフォーム送信で外へ飛ぶ経路は二重に存在しません。
あわせて、次のレスポンスヘッダも付けています。
Strict-Transport-Security(HSTS。max-age1年・includeSubDomains・preload) — これ以降の接続を HTTPS に強制し、平文へのダウングレードを防ぎます。万一どこかで本文が通信に乗ることがあっても経路は暗号化されます(実際には送信そのものがありません)。X-Frame-Options: DENY— 古いブラウザ向けのフレーム埋め込み拒否。frame-ancestorsの後方互換です。X-Content-Type-Options: nosniff— MIME スニッフィングを禁じ、リソースの種類を取り違えて実行してしまうのを防ぎます。Referrer-Policy: strict-origin-when-cross-origin— 外部へ移動するときに送るリファラを切り詰め、どこを見ていたかが漏れるのを抑えます。Permissions-Policy: camera=(), microphone=(), geolocation=()— カメラ・マイク・位置情報の API をまるごと無効化します。このツールに要らない権限を残さず、攻撃の入口を減らします。
一点だけ補足を。_headers は Cloudflare Pages がレスポンスに付けてくれる仕組みなので、本番で実際に適用されるかどうかは配信環境側の責任で、コードだけでは断言できません。コードの側で確実に言えるのは、ポリシーが正しく定義されていて、ビルド成果物にも同じ内容で出力されている、というところまでです。実際に効いているかは、配信後に先ほどの方法(レスポンスヘッダの確認)で照らし合わせられます。
// 06 — Same-origin policy × CSP
二重防壁:オリジン境界とCSPは別の層で効く
送信が起きないことは、性質の違う二つの仕組みで支えられています。よく混同されるので、分けて説明します。
一つめは、ブラウザの同一オリジンポリシー(SOP)と CORS です。これは「どの応答をスクリプトが読めるか」を決める標準の仕組みで、クロスオリジンの読み取りを原則として禁じます。ただし SOP が縛るのは主に 読み取りのほうで、送信(とくに no-cors の単純リクエストやビーコン)は完全には縛りません。だから SOP だけでは「外に出さない」の証明には足りません。
二つめが、CSP の connect-src です。これは「どこへ接続してよいか」を出口の側で縛ります。'self' を指定しているので、応答が読めるかどうか以前に、自オリジン以外への接続要求そのものがブラウザに拒否されます。SOP が読み取りの壁なら、connect-src は送信の壁です。
この二つは独立していて、片方をすり抜けてももう片方が残ります。さらにその手前で、そもそもアプリのコードに送信の命令がありません(03)。「命令がない」→「接続が拒否される」→「読み取りも禁じられる」の三段で、外への送信は重ねて塞がれています。どこか一点が破られたら終わり、という作りにはなっていません。
// 07 — textContent over innerHTML
なぜ innerHTML を使わないかが効く
変換結果を表示するときは textContent だけを使い、innerHTML は使いません。これは見た目の好みではなく、注入を防ぐための判断です。
入力本文には、ユーザーが書いた任意の文字列が入ります。<script> や <img onerror=...> のような断片が混じることもあります。これを innerHTML に流し込むと、ブラウザは文字列を HTML として解釈してしまい、注入されたタグやイベントハンドラーが動いてしまうことがあります。これが DOM-based XSS の典型的な入口です。XSS が成立すれば、攻撃用のスクリプトがページの中で動き、本文を読み出して外へ送る、という役回りができてしまう——機密性の前提が崩れます。
textContent は、文字列を必ずプレーンテキストとして差し込みます。< や > はタグではなく、ただの文字として表示されるだけで、タグもハンドラーも動きません。入力に何が入っていようと、テキストとして描かれるだけです。
この選び方は、script-src 'self'(インライン実行の禁止)と補い合います。仮に注入の経路があっても CSP が実行を止めますし、そもそも textContent が注入の経路を作りません。入口を塞ぐ実装と実行を止めるポリシーの二段で、XSS で本文が漏れる筋道を断っています。
// 08 — No persistence
永続化しない:履歴は残らない
送信を断っても、ローカルにため込んでいれば、あとで漏れる余地が残ります。そこで本ツールは、ブラウザのどんな保存領域にも本文を書き込みません。localStorage / sessionStorage / IndexedDB / document.cookie / Cache API / window.name への書き込みを片っ端から検索した結果、ソースでも配信バンドルでも ゼロ件でした。
その結果、変換した本文はセッションのメモリ上にしか存在せず、タブを閉じれば消えます。変換履歴という形で端末に残ることもありません。共有のパソコンや貸し出し端末でも、あとから使った人が前の本文を掘り出す経路がない、ということです。Cookie も発行しないので、トラッキング用の識別子も生まれません。
// 09 — Zero runtime dependencies
ランタイム依存ゼロ:サプライチェーン面を畳む
送信がなくても、第三者のライブラリが紛れ込めば、そのコードが何をするかはまた別の問題になります。最近の脅威でいえば、正規のパッケージが乗っ取られたり、悪意のあるアップデートが配られたりする サプライチェーン攻撃です。本ツールは、この面を構造から小さくしています。
package.json の依存は vite と vitest の devDependencies だけです。これらはビルドとテストの段階でしか使われず、配信される本番バンドルには第三者のランタイムコードが入りません。出荷される JS は、自分たちの src/ 由来のコードだけでできています。つまり本番でユーザーのブラウザ上を動くコードに、外部由来の実行コードが存在しないということです。
これは「依存を信頼する」のではなく「本番に依存を持ち込まない」という構えです。信頼しなければならない第三者がゼロなら、その誰かが侵害されるリスクもゼロになります。点検すべき対象は、自分たちのコードの範囲に収まります。
// 10 — Honest disclosure
正直に開示する、本文とは無関係な外部要素
「本文を送らない」ことと、「ウェブサイトとして外部要素がまったくない」ことは別です。後者を装うと、文書全体がかえって信用できなくなります。本文の機密性とは層が違う外部要素を、隠さずに挙げておきます。どれも入力本文にはアクセスできません。
ホスティングのアクセスログ
本サイトは Cloudflare Pages で静的に配信されます。ウェブサイトである以上、配信事業者がリクエストごとに IP・User-Agent・日時・取得したパスなどを インフラ層のログとして記録することはあり得ます。これはどんなウェブサイトにも共通する仕組みで、アプリのコードには現れません(コードからは、あるともないとも言えない層です)。
ただし本文はリクエストに乗りません(送信していないので)。だからこのログに本文が含まれることはありません。記録されうるのは「接続があった」という事実だけで、「何を変換したか」ではありません。
アフィリエイト広告(A8.net)
運営費のために、将来 A8.net のバナーを載せることがあります。載せたとしても、CSP は広告に 画像の表示(img-src)だけを許し、script-src 'self' が広告由来の JS を、connect-src 'self' が広告ホストへの接続を止めます。広告がページ内のコードを動かしたり、入力欄に触れたりする経路は、ポリシー上ありません。
今のところ広告タグは置いておらず、表示枠(プレースホルダ)だけがあります。Cookie などが使われるとしても、それは広告画像の配信に伴うもので、本文とは関係ありません。
この層をあえて開示するのは、前半の話を強くするためです。隠す必要のある部分がないからこそ、本文については「出ない」と言い切れます。
// Summary
結論
- 送信の経路がない(コードの層):ソースに送信のための命令がゼロ件。本文は
textarea → 純粋な関数 → textContentで完結します。 - 成果物が透明(ビルドの層):バンドルにある唯一の
fetchは Vite のプリロードポリフィルで、宛先は自オリジン限定、しかも今回のビルドでは実行されない死コードです。 - 出口を塞ぐ(ブラウザの層):
connect-src 'self'が外部接続を拒否します。同一オリジンポリシーと合わせて二重に塞がっています。 - 注入に強い:
textContentだけを使い、script-src 'self'と合わせて DOM-based XSS で漏れる筋道を断っています。 - 保存しない:どの保存領域にも本文を書き込みません。履歴も識別子も残しません。
- 攻撃の入口が少ない:ランタイム依存ゼロ。本番に第三者の実行コードがありません。
- 正直に開示:ホスティングのログや広告は外部要素として存在し得ますが、本文にはアクセスできません。
// 注釈 — コードに詳しくない方へ
ここまでを、ひと言で
技術的な話が続きました。コードに馴染みのない方のために、要点だけ、ふだんの言葉で言い換えておきます。
- あなたが入力した小説は、あなたのパソコンやスマホの中だけで変換されます。インターネットのどこか(運営のサーバーなど)に送られることはありません。
- 送られない理由は、「送らないと約束しているから」ではありません。そもそも送るための仕組みが、プログラムに作られていないからです。約束よりも確実です。
- 変換した文章は、その場限りです。ページを閉じれば消えます。履歴として残らないので、共用のパソコンでも安心して使えます。
- 会員登録もメールアドレスも要りません。あなたが誰なのかを記録する仕組み(Cookie など)も使っていません。
- もし不安なら、自分で確かめる方法があります。本ツールのページでキーボードの F12 を押すと出てくる画面の「Network(ネットワーク)」を見ながら変換してみてください。通信が一度も起きないこと自体が、文章がどこにも送られていない証拠になります。
ひとことで言えば——あなたの原稿は、この画面の外に出ません。上の技術的な説明は、その事実を隅々まで裏づけているだけです。
// Contact
このポリシーや実装についての問い合わせは、運営(Takeyabu Studio)まで。るびこんは MIT ライセンスのオープンソースで、ここに書いたことはすべて誰でも確かめられます。
最終更新:2026年6月