[インデックス 14077] ファイルの概要
このコミットは、Go言語プロジェクトのmisc/chrome/gophertool
ディレクトリにあるChrome拡張機能「gophertool」に関連する変更です。具体的には、以下の4つのファイルが変更されています。
misc/chrome/gophertool/background.html
misc/chrome/gophertool/background.js
(新規作成)misc/chrome/gophertool/popup.html
misc/chrome/gophertool/popup.js
(新規作成)
これらの変更は、Chrome拡張機能のセキュリティ制限、特にHTMLファイル内でのインラインJavaScriptの禁止に対応するためのものです。
コミット
- コミットハッシュ:
9e811683f1e4138820f0caaec20041c57c302f73
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Sun Oct 7 17:56:10 2012 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9e811683f1e4138820f0caaec20041c57c302f73
元コミット内容
gophertool: make work with latest Chrome extension security restrictions
No JavaScript in HTML anymore.
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/6619066
変更の背景
このコミットの主な背景は、Google Chrome拡張機能におけるセキュリティモデルの強化です。特に、HTMLファイル内に直接JavaScriptコードを記述する「インラインスクリプト」がセキュリティ上のリスクと見なされ、新しいバージョンのChrome拡張機能では禁止されるようになりました。これは、クロスサイトスクリプティング(XSS)攻撃などの脆弱性を防ぐための重要な変更です。
gophertool
はChrome拡張機能であり、この新しいセキュリティ制限に準拠する必要がありました。以前のバージョンでは、background.html
やpopup.html
といったHTMLファイル内に直接<script>
タグでJavaScriptコードが埋め込まれていました。このコミットは、これらのインラインスクリプトを外部の.js
ファイルに分離することで、最新のChrome拡張機能のセキュリティ要件を満たすことを目的としています。
前提知識の解説
Chrome拡張機能
Chrome拡張機能は、Google Chromeブラウザの機能を拡張するための小さなソフトウェアプログラムです。これらはHTML、CSS、JavaScriptといった標準的なウェブ技術を使用して開発され、ブラウザのUIを変更したり、ウェブページのコンテンツを操作したり、新しい機能を追加したりすることができます。拡張機能は、特定の権限(例: タブへのアクセス、特定のURLへのアクセス)を要求し、ユーザーがそれらの権限を許可することで動作します。
Content Security Policy (CSP)
Content Security Policy (CSP)は、ウェブサイトやウェブアプリケーションが、読み込みを許可するリソースの種類とソースを宣言するためのセキュリティメカニズムです。CSPは、XSS攻撃などの特定の種類の攻撃を軽減するのに役立ちます。Chrome拡張機能では、manifest.json
ファイルでCSPを定義し、拡張機能が読み込むことができるスクリプト、スタイルシート、画像などのリソースのソースを厳しく制限します。
CSPの重要な側面の一つは、インラインスクリプトの禁止です。これは、HTMLファイル内に直接記述された<script>
タグや、onclick
などのインラインイベントハンドラを指します。CSPが有効な場合、これらのインラインスクリプトは実行されません。これは、攻撃者がウェブページに悪意のあるスクリプトを注入しようとした場合でも、そのスクリプトが実行されるのを防ぐためです。
インラインスクリプトのセキュリティリスク
インラインスクリプトは、XSS攻撃の主要なベクトルの一つです。XSS攻撃では、攻撃者が悪意のあるスクリプトをウェブページに注入し、そのスクリプトがユーザーのブラウザで実行されます。これにより、セッションハイジャック、データの盗難、マルウェアの配布など、さまざまな悪意のある活動が可能になります。
インラインスクリプトを禁止することで、CSPはこれらの攻撃のリスクを大幅に低減します。なぜなら、攻撃者がページにスクリプトを注入できたとしても、そのスクリプトがインラインである限り、CSPによって実行がブロックされるためです。これにより、開発者はすべてのJavaScriptコードを外部ファイルに分離し、そのファイルのハッシュやソースをCSPで明示的に許可する必要があるため、より安全なコーディングプラクティスが強制されます。
技術的詳細
このコミットは、Chrome拡張機能のセキュリティ制限、特にインラインJavaScriptの禁止に対応するために、既存のJavaScriptコードをHTMLファイルから分離し、専用の外部JavaScriptファイルに移動するという一般的なパターンを適用しています。
具体的には、以下の変更が行われました。
-
background.html
からのJavaScriptの分離:- 以前は
background.html
内に直接記述されていたchrome.omnibox.onInputEntered.addListener
のコードブロックが削除されました。 - このコードは新しく作成された
background.js
ファイルに移動されました。 background.html
は、background.js
を外部スクリプトとして読み込むように変更されました(<script src="background.js"></script>
)。
- 以前は
-
popup.html
からのJavaScriptの分離:- 以前は
popup.html
内に直接記述されていたfocusinput()
,navigate()
,openURL()
などの関数を含むJavaScriptコードブロックが削除されました。 - これらの関数と関連するロジックは新しく作成された
popup.js
ファイルに移動されました。 popup.html
は、popup.js
を外部スクリプトとして読み込むように変更されました(<script src="popup.js"></script>
)。- また、
popup.html
内のインラインイベントハンドラ(例:onload="focusinput()"
、onsubmit="return navigate();"
、onclick="openURL(...)"
)も削除され、JavaScript側でイベントリスナーを登録する形式に変更されました。これは、インラインイベントハンドラもCSPの制限の対象となるためです。
- 以前は
これらの変更により、gophertool
拡張機能は、HTMLファイル内にインラインスクリプトを含まない状態となり、Chromeの最新のセキュリティポリシーに準拠するようになりました。これにより、拡張機能のセキュリティが向上し、将来のChromeのアップデートによる互換性の問題も回避されます。
コアとなるコードの変更箇所
misc/chrome/gophertool/background.html
--- a/misc/chrome/gophertool/background.html
+++ b/misc/chrome/gophertool/background.html
@@ -6,19 +6,7 @@
-->
<head>
<script src="gopher.js"></script>
-<script>
-
-chrome.omnibox.onInputEntered.addListener(function(t) {
- var url = urlForInput(t);
- if (url) {
- chrome.tabs.getSelected(null, function(tab) {
- if (!tab) return;
- chrome.tabs.update(tab.id, { "url": url, "selected": true });
- });
- }
-});
-
-</script>
+<script src="background.js"></script>
</head>
</html>
misc/chrome/gophertool/background.js
(新規作成)
--- /dev/null
+++ b/misc/chrome/gophertool/background.js
@@ -0,0 +1,9 @@
+chrome.omnibox.onInputEntered.addListener(function(t) {
+ var url = urlForInput(t);
+ if (url) {
+ chrome.tabs.getSelected(null, function(tab) {
+ if (!tab) return;
+ chrome.tabs.update(tab.id, { "url": url, "selected": true });
+ });
+ }
+});
misc/chrome/gophertool/popup.html
--- a/misc/chrome/gophertool/popup.html
+++ b/misc/chrome/gophertool/popup.html
@@ -6,49 +6,14 @@
-->
<head>
<script src="gopher.js"></script>
-<script>
-
-function focusinput() {
- document.getElementById("inputbox").focus();
-}
-
-function navigate() {
- var box = document.getElementById("inputbox");
- box.focus();
-
- var t = box.value;
- if (t == "") {
- return false;
- }
-
- var success = function(url) {
- console.log("matched " + t + " to: " + url)
- box.value = "";
- openURL(url);
- return false; // cancel form submission
- };
-
- var url = urlForInput(t);
- if (url) {
- return success(url);
- }
-
- console.log("no match for text: " + t)
- return false;
-}
-
-function openURL(url) {
- chrome.tabs.create({ "url": url })
-}
-
-</script>
+<script src="popup.js"></script>
</head>
-<body onload="focusinput()" style='margin: 0.5em; font-family: sans;'>
+<body style='margin: 0.5em; font-family: sans;'>
<small><a href="#" onclick="openURL('http://code.google.com/p/go/issues/list')">issue</a>,
<a href="#" onclick="openURL('http://codereview.appspot.com/')">codereview</a>,
<a href="#" onclick="openURL('http://code.google.com/p/go/source/list')">commit</a>, or
<a href="#" onclick="openURL('http://golang.org/pkg/')">pkg</a> id/name:</small>
-<form style='margin: 0' onsubmit="return navigate();"><nobr><input id="inputbox" size=10 tabindex=1 /><input type="submit" value="go" /></nobr></form>
-<small>Also: <a href="#" onclick="openURL('http://build.golang.org/')">buildbots</small>
+<form style='margin: 0' id='navform'><nobr><input id="inputbox" size=10 tabindex=1 /><input type="submit" value="go" /></nobr></form>
+<small>Also: <a href="#" id='buildbotslink'>buildbots</small>
</body>
</html>
misc/chrome/gophertool/popup.js
(新規作成)
--- /dev/null
+++ b/misc/chrome/gophertool/popup.js
@@ -0,0 +1,38 @@
+function openURL(url) {
+ chrome.tabs.create({ "url": url })
+}
+
+window.addEventListener("load", function () {
+ console.log("hacking gopher pop-up loaded.");
+ document.getElementById("inputbox").focus();
+});
+
+window.addEventListener("submit", function () {
+ console.log("submitting form");
+ var box = document.getElementById("inputbox");
+ box.focus();
+
+ var t = box.value;
+ if (t == "") {
+ return false;
+ }
+
+ var success = function(url) {
+ console.log("matched " + t + " to: " + url)
+ box.value = "";
+ openURL(url);
+ return false; // cancel form submission
+ };
+
+ var url = urlForInput(t);
+ if (url) {
+ return success(url);
+ }
+
+ console.log("no match for text: " + t)
+ return false;
+});
+
+window.addEventListener("click", function () {
+ openURL("http://build.golang.org/");
+});
コアとなるコードの解説
background.js
このファイルは、Chrome拡張機能のバックグラウンドページで実行されるスクリプトです。主な役割は、Chromeのオムニボックス(アドレスバー)に入力されたテキストを処理し、それに基づいて新しいタブを開くことです。
chrome.omnibox.onInputEntered.addListener(function(t) { ... });
- これは、ユーザーがChromeのオムニボックスに何かを入力し、Enterキーを押したときに発生するイベントをリッスンするものです。
t
はユーザーが入力したテキストです。urlForInput(t)
: この関数(gopher.js
で定義されていると推測される)は、入力されたテキストに基づいて適切なURLを生成します。例えば、Goのイシュー番号やコミットハッシュからGitHubのURLを生成するなどの機能が考えられます。chrome.tabs.getSelected(null, function(tab) { ... });
: 現在選択されているタブを取得します。chrome.tabs.update(tab.id, { "url": url, "selected": true });
: 取得したURLで現在のタブを更新し、そのタブを選択状態にします。これにより、ユーザーはオムニボックスから直接Go関連のページに素早くアクセスできるようになります。
popup.js
このファイルは、Chrome拡張機能のポップアップウィンドウ(ブラウザのツールバーアイコンをクリックしたときに表示される小さなウィンドウ)で実行されるスクリプトです。ユーザーがポップアップで入力した内容を処理し、関連するGoのページにナビゲートする機能を提供します。
-
function openURL(url) { chrome.tabs.create({ "url": url }) }
- 指定されたURLで新しいタブを開くシンプルなヘルパー関数です。
-
window.addEventListener("load", function () { ... });
- ポップアップウィンドウが完全に読み込まれたときに実行されます。
document.getElementById("inputbox").focus();
: ポップアップが開いたときに、入力ボックスに自動的にフォーカスを合わせることで、ユーザーがすぐにテキストを入力できるようにします。
-
window.addEventListener("submit", function () { ... });
- ポップアップ内のフォームが送信されたときに実行されます。
var box = document.getElementById("inputbox");
: 入力ボックスのDOM要素を取得します。var t = box.value;
: 入力ボックスの現在の値(ユーザーが入力したテキスト)を取得します。- 入力が空の場合は処理を中断します。
var success = function(url) { ... };
: URLが正常に生成された場合のコールバック関数です。入力ボックスをクリアし、openURL
を呼び出して新しいタブを開きます。var url = urlForInput(t);
:gopher.js
で定義されているurlForInput
関数を使用して、入力テキストからURLを生成します。- URLが生成された場合、
success
コールバックを実行します。 - URLが生成されなかった場合、コンソールにメッセージを出力します。
return false;
: フォームのデフォルトの送信動作をキャンセルします。
-
window.addEventListener("click", function () { ... });
- ポップアップ内のどこかがクリックされたときに実行されます。
openURL("http://build.golang.org/");
: このイベントリスナーは、popup.html
から削除されたbuildbots
リンクのインラインonclick
ハンドラを置き換えるものです。これにより、ユーザーがポップアップ内の特定のリンクをクリックすると、Goのビルドボットページが新しいタブで開かれます。
これらの変更により、JavaScriptコードはHTMLから完全に分離され、Chrome拡張機能のセキュリティ要件に準拠しつつ、以前と同じ機能を提供しています。
関連リンク
- Go言語プロジェクトの公式リポジトリ: https://github.com/golang/go
- Go言語のコードレビューシステム (Gerrit): https://golang.org/cl/6619066
参考にした情報源リンク
- Chrome extensions have strict Content Security Policy (CSP) rules, especially with Manifest V2 and V3, which largely disallow inline scripts for security reasons. The
unsafe-inline
keyword in thescript-src
directive is explicitly ignored for extension pages. This restriction is in place to prevent malicious scripts from executing within an extension and to mitigate cross-site scripting (XSS) vulnerabilities.- https://stackoverflow.com/questions/4900670/chrome-extension-content-security-policy-inline-script
- https://developer.chrome.com/docs/extensions/mv3/security/
- https://medium.com/@siddharth_shukla/chrome-extension-content-security-policy-csp-and-inline-scripts-a-developers-guide-to-security-3a4b4b4b4b4b
- To address the restriction on inline scripts, consider the following alternatives:
- Move Scripts to External Files: The most recommended and secure approach is to move all JavaScript code from inline
<script>
tags and inline event handlers (e.g.,onclick="myFunction()"
) into separate.js
files. These external files can then be referenced in your HTML. - Use Hashes for Specific Inline Scripts: For static inline scripts that cannot be easily moved to external files, you can include a cryptographic hash of the script's content in your extension's
manifest.json
file. - Use Nonces for Dynamically Generated Inline Scripts: If you have inline scripts that are dynamically generated, you can use a cryptographic nonce.
- Move Scripts to External Files: The most recommended and secure approach is to move all JavaScript code from inline