diff --git a/.github/scripts/check_sri.py b/.github/scripts/check_sri.py
new file mode 100644
index 0000000..20cf705
--- /dev/null
+++ b/.github/scripts/check_sri.py
@@ -0,0 +1,45 @@
+"""Check the CDN scripts in index.html.
+
+- A script WITH an `integrity` attribute must also have `crossorigin`, and its hash
+ must match the file the CDN serves today. A wrong hash makes the browser refuse to
+ run the script, so FinLens would break with no visible error. That fails the check.
+- A script WITHOUT `integrity` is reported as a warning for now. Content Security
+ Policy + SRI for every CDN script is PR #22; once it's on master, change
+ MISSING_IS_ERROR to True so no new unpinned script can slip in.
+"""
+import base64, hashlib, pathlib, re, sys, urllib.request
+
+MISSING_IS_ERROR = False
+
+root = pathlib.Path(__file__).resolve().parents[2]
+html = (root / "index.html").read_text(encoding="utf-8")
+errors = 0
+
+for tag in re.finditer(r"", html)
+with tempfile.TemporaryDirectory() as tmp:
+ for n, body in enumerate(inline, 1):
+ f = pathlib.Path(tmp) / f"inline-{n}.js"
+ f.write_text(body, encoding="utf-8")
+ check(f, f"index.html (inline script {n})")
+
+sys.exit(1 if failed else 0)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..fd71b76
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,36 @@
+name: CI
+
+# Runs on every pull request (any base branch, so stacked PRs are checked too)
+# and on every push to master. Nothing here deploys or changes the repo.
+on:
+ pull_request:
+ push:
+ branches: [master]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ syntax:
+ name: JavaScript syntax
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ - run: python3 .github/scripts/check_syntax.py
+
+ sri:
+ name: CDN scripts (SRI)
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - run: python3 .github/scripts/check_sri.py
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a41488d..1d2c039 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
---
+## [2.0.0] — 2026-11-04
+
+### Technical — CI
+- **GitHub Actions checks on every pull request** (`.github/workflows/ci.yml`). Two jobs run on every PR, whatever branch it targets, and on every push to `master`. Nothing in the app changes.
+ - **JavaScript syntax** (`.github/scripts/check_syntax.py`) — runs `node --check` on every file in `js/` and on the inline `