有料 Chrome 拡張機能はどれも 30 KB で作り直せる
有料 Chrome 拡張機能のほとんどは課金処理と分析コードです。AI エージェントを使えば、どれも一晩で再実装できます。30 KB、サブスクなし。
ほとんどの有料 Chrome 拡張機能は、ファイルサイズの 1 ~ 3 % しかコードでありません。残りはすべて課金、ログイン、ビリング、分析、そしてキュレーション済みストアからインストールできるという事実です。
つまり、どれも個人利用なら一晩で AI エージェントと一緒に作り直せます。2 ~ 3 メガバイトではなく 30 KB。サブスクなし、ログインなし。本来の機能は DOM ウォーキングとフォーマット変換の 5 ~ 10 KB で、課金まわりを取り除けばすべて崩れ落ちます。
私はこれを、年 99 USD の拡張機能で試しました。ずっと使っていたものです。面白い部分 (フォーマット、テンプレート、快適な設定) は Google ログインとサーバー側のライセンスチェックの裏に隠されていました。欲しかった本物のコードは、洗練された React の殻に包まれた数百行の JavaScript でした。Claude と過ごした一晩で、同じ機能を持つ 30 KB のクローンができ、バックエンドはありません。
このパターンは一般化できます。落とし穴も同じです。
一番大変だったのはフォルダーをコピーすること
Chrome 拡張機能は %LOCALAPPDATA%\Google\Chrome\User Data\<Profile>\Extensions\<id>\<version>\ にあります。chrome://extensions/ から正しい <id> を見つけ、フォルダーへ移動し、コピーします。それが計画で、まさにそこで一晩が始まる前に終わりかけました。
git-bash からの cp は Access denied を返します。PowerShell の Copy-Item も同じです。robocopy /B も同じです。私のユーザーが管理者でも、ACL が FullControl を付与していても、Chrome は排他共有ロックでファイルを掴んでいて、OS は同時読み取りを拒否します。
特権のパラドックスが二つ目の罠です。whoami /priv はこう表示します。
SeBackupPrivilege Disabled
SeRestorePrivilege Disabled
特権はトークンに載っていますが、無効化されています。実際に使うには AdjustTokenPrivileges を呼びます。10 行の P/Invoke、問題ありません。ただし AdjustTokenPrivileges は true を返した直後に GetLastError() が 1300 / ERROR_NOT_ALL_ASSIGNED を返します。特権は本当には無いのです。表示上だけ並んでいます。最近のサンドボックスプロセスはトークンをこの方法でフィルタリングします。
正解は Volume Shadow Copy です。VSS はボリュームの読み取り専用なポイントインタイムスナップショットを作成する Windows サービスです。ライブハンドルや共有ロックを無視する、並行のファイルシステムビューです。WMI から直接アクセスできます。
$cls = [wmiclass]"root\cimv2:Win32_ShadowCopy"
$r = $cls.Create("C:\", "ClientAccessible")
$shadow = Get-CimInstance Win32_ShadowCopy | Where-Object { $_.ID -eq $r.ShadowID }
$shadow.DeviceObject
# \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy9このパス経由で、Chrome が開いているのと同じファイルが、競合のないスナップショットバイトとして見えます。最後の罠: Copy-Item は \\?\GLOBALROOT\... という構文を理解しません。何も言わずに空のフォルダーを作成し、成功を報告します。[System.IO.Directory]::EnumerateFileSystemEntries と [System.IO.File]::Copy まで降りる必要があります。これらは Win32 を直接呼び出します。10 行のコードで、フルエラーコンテキストを与えれば AI エージェントに完全に委譲できます。
スナップショットを取った後は削除します。さもないと VSS シャドウコピーがディスクに溜まっていきます。
これが本来 cp -r で済むはずだった部分です。一晩の前半を食い潰しました。「Chrome 拡張機能を作り直す」系のチュートリアルのほとんどはこの部分を飛ばしてすぐ JavaScript に行き、バイト列はすでに手元にある前提で進むので、書く価値があります。
中身と、価値が無い場所
最近の Chrome 拡張機能のディスク内容はほとんど UI の重さです。manifest が 2 KB。content スクリプトが 10 KB。ポップアップが 1 メガバイト。バックグラウンドのサービスワーカーが 0.5 メガバイトで、Google ログイントークンを保持するために Firebase SDK を取り込んでいます。
実際のアルゴリズム (DOM ウォーキング、フォーマット変換) は 5 ~ 10 KB です。残りはすべて課金、16 言語のロケール、分析、そして「buy me a coffee」ボタンです。
これを理解するのに難読化された JavaScript を 1 行も読む必要はありません。サイズ比そのものが診断結果です。そしてバンドル全体で最も情報量の多いファイルは _locales/en/messages.json で、すべての Pro 機能が人間の言葉で名付けられています。翻訳者がそれを必要とするからです。
"upgradeButton": { "message": "Upgrade now" },
"upgradeTip": { "message": "After upgrading you can use all professional features such as customizing the file name, dialog formatting with H1, and exported file header settings." }30 秒で、何が無料で、何が有料で、マーケティングのランディングページが何を誇張していて、どの UI フラグメントがゲートされているかが分かります。UI 文字列に難読化はありません。人間に向けてレンダリングされるので、できないのです。
ゲートはブール値
典型的なフリーミアム Chrome 拡張機能の課金ゲートは、サーバー応答からセットされる React state 変数です。署名付きトークンも暗号もありません。
const [isPremium, setIsPremium] = useState(false)
useEffect(() => {
if (signedInUser) {
fetch(`${API_BASE}/api/check_user`, {
method: "POST",
body: JSON.stringify({ user_email: signedInUser.email })
})
.then(r => r.json())
.then(({ data }) => setIsPremium(!!data?.status))
}
}, [signedInUser])
{isPremium && <ProSettings />}サーバーが true か false を返します。クライアントはそれを信じます。「課金 → アンロック」のリンクは評判で繋がっていて、暗号で繋がっているわけではありません。本気のユーザーなら DevTools で 1 分以内にレスポンスをパッチできますが、それは関係ありません。本当のターゲット層が DevTools を開かないからです。
念のため: これはエクスプロイトではなく、この記事は課金ゲートを回避する話でもありません。要点は、ゲートは回避する必要すらないということです。同じ機能を書き直すなら、そもそもゲートを足さないだけです。
アーキテクチャはビリングが形作る
オリジナルのアーキテクチャを見ると、IPC で結ばれた 3 つのレイヤーが見えます。ポップアップは React を 1 メガバイト動かします。バックグラウンドのサービスワーカーは Firebase の認証状態を保持し、ライセンスバックエンドと通信し、アクティブなタブに対して chrome.scripting.executeScript を実行します。3 つのバンドル、3 つのランタイム、2 回のメッセージポートホップ。
この形に機能的な理由はありません。ビリングの理由があります。認証トークンを保持する場所が必要です (ポップアップが閉じても永続するバックグラウンド)。UI が消えた後も生き残るコンテキストでサブスクステータスをチェックする必要があります (またバックグラウンド)。重い処理をポップアップから出す必要があります。ポップアップは課金ゲートをレンダリングしながらも軽快に保ちたいからです。
認証、ライセンスチェック、ゲートを取り除けば、アーキテクチャは 2 ファイルに崩れます。
popup.html (vanilla JS, ~5 KB)
↕ chrome.tabs.sendMessage
content.js (matched on the target domain, ~10 KB)
↕ DOM API
target page
サービスワーカーなし。長期間生きるポートなし。トークンなし。ポップアップが 1 つメッセージを送り、コンテンツスクリプトが仕事をし、結果が戻ってきます。合計 30 KB。同じ機能。
実験で一番面白いのはこの部分です。オリジナルは悪くエンジニアリングされていたわけではありません。実際の要件 (「これで人々に課金しなければならない」を含む) に対して正しくエンジニアリングされていました。出荷されたソフトウェアで「複雑さ」と呼ばれるものの多くは、ツールではなくプロダクトであることのコストです。
AI がやったこと、私がやったこと
Claude は manifest を書き、正しい permissions と host_permissions を選び、ポップアップ UI のスキャフォールディングを作り、Markdown を出力するノードビジターとして DOM ウォーキングコードを書き、jsdom でテストフィクスチャを生成し、自分の最初の出力をレビューして過剰設計を削りました。タイピングの約 80 % です。
私が手を入れる必要があった場所:
AI は難読化されたバンドルから物を見つけるのが得意ですが、単語の境界に錨を下ろし、十分な周辺コンテキストを読むときだけです。素朴な grep "isPro" は React の内部の isPropagationStopped や isProtoOf のような偽陽性を何十も返します。AI は楽しそうに「Pro フラグの言及を 6 件見つけました」と報告しますが、実際には 0 件しか見つかっていません。毎回 \bisPro\b を最低 60 文字のコンテキスト付きで頼む必要があります。
ライブサイトに対するセレクターは、トレーニングデータから信用できません。AI は一部のサイトが 1 年前にどう見えたかを覚えています。今では間違っています。DevTools を開き、現在の DOM が出力するものを確認し、それをエージェントに渡します。20 分の手作業です。
そして AI はやり過ぎが好きです。明示的な「v1 スコープ、YAGNI」指示がないと、こっそりバッチ処理、複数選択、Undo、テンプレート、設定ページを足してきます。同じ理由ではないにせよ、結局オリジナルと同じ 1 メガバイトのバンドルになります。「だめ、その 1 機能だけ」という規律はあなたから来る必要があります。
法的な枠組みもあなたが扱います。個人利用や相互運用のためのリバースエンジニアリングはほぼどこでも許容されています。米国の DMCA §1201(f)、EU のソフトウェア指令第 6 条。やってはいけないこと: オリジナルのバイナリを再配布する、同じ名前とアイコンのクローンを出荷する、難読化されたコードをそのまま自分のバージョンへコピーする。「インスピレーションを受けた、似ている、個人的な趣味プロジェクト、Web Store には出さない」が標準のフレームで、しかもそれは事実です。
利便性が商品
「文字列を --- マーカーで挟んでブール値を保存する React コンポーネント」のために誰も年 99 USD は払いません。人々は、unpacked 拡張機能をインストールしなくて済むため、data-message-author-role の意味を知らなくて済むため、Volume Shadow Copy をデバッグしなくて済むために払います。利便性が商品で、コードは価値連鎖の最小部分です。
これは両刃の剣です。空いている一晩、AI エージェント、スタックの大まかな理解があれば、これらのツールのどれでも個人利用のために、合法的に、サブスクが 1 か月に節約してくれる時間より少ない時間で置き換えられます。そしてあなたがこれらのツールのどれかを売っているなら、技術的な課金ゲートは演劇です。誠実な人を引き留めるだけです。守れる資産はブランド、UX、流通、サポート契約であって、JavaScript ではありません。それらの周りにビジネスを組み立てれば大丈夫です。「機能 X をサブスクの裏に」で組み立てれば、Claude と一晩で溶ける何かを売っています。
どちらの側面も悪い知らせではありません。2026 年のブラウザー拡張機能の立ち位置です。