Pin CDN dependencies and add SRI hashes
Alpine.js 3.15.8, Alpine Persist 3.15.0, OverType 2.1.1.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/AGENTS.md b/AGENTS.md
index b8be0a4..00f8f13 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -98,5 +98,5 @@ Output goes to `~/.cache/ketchup/snapshots/` (or `$XDG_CACHE_HOME`). Templates f
- **Migrations:** Sequel migrations in `db/migrate/`, numbered sequentially (`001_`, `002_`, …). Migrations auto-run on boot.
- **User identification:** Current user from `HTTP_TAILSCALE_USER_LOGIN` / `HTTP_TAILSCALE_USER_NAME` request headers.
- **Testing:** Minitest with `Rack::Test`. Fake Tailscale headers via helper.
-- **Client-side:** Alpine.js for reactivity, Alpine Persist for state persistence, OverType for markdown editing. No build step — all loaded via CDN.
+- **Client-side:** Alpine.js for reactivity, Alpine Persist for state persistence, OverType for markdown editing. No build step — all loaded via CDN with pinned versions and SRI hashes in `views/layout.rb`. To update a dependency: fetch the new versioned URL, generate a hash with `curl -sL <url> | openssl dgst -sha384 -binary | openssl base64 -A`, and update both the `src` and `integrity` attributes.
- **Ownership scoping:** User has `many_through_many :tasks` through `:series`. Routes use `@user.tasks_dataset` and `@user.series_dataset` to scope lookups.
diff --git a/lib/ketchup/views/layout.rb b/lib/ketchup/views/layout.rb
index 331a386..9265682 100644
--- a/lib/ketchup/views/layout.rb
+++ b/lib/ketchup/views/layout.rb
@@ -19,10 +19,16 @@ module Views
link(rel: "stylesheet", href: "/css/reset.css")
link(rel: "stylesheet", href: "/css/utopia.css")
link(rel: "stylesheet", href: "/css/app.css")
- script(src: "https://unpkg.com/overtype/dist/overtype.min.js")
+ script(src: "https://unpkg.com/overtype@2.1.1/dist/overtype.min.js",
+ integrity: "sha384-zp8RL0j4VLfaFKgqehca9l8rfcE4Jh0Nt1CFoVyUBn+qa4velUokXJXsW2h0J5xT",
+ crossorigin: "anonymous")
script(src: "/js/app.js", defer: true)
- script(src: "https://cdn.jsdelivr.net/npm/@alpinejs/persist@3/dist/cdn.min.js", defer: true)
- script(src: "https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js", defer: true)
+ script(src: "https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.15.0/dist/cdn.min.js",
+ integrity: "sha384-6WOLkykwLb3YWzXZ6lAq+GI0p3V+enUm9jY6yIXGpIriiAUOSF5dgNJLoSSNam4j",
+ crossorigin: "anonymous", defer: true)
+ script(src: "https://cdn.jsdelivr.net/npm/alpinejs@3.15.8/dist/cdn.min.js",
+ integrity: "sha384-LXWjKwDZz29o7TduNe+r/UxaolHh5FsSvy2W7bDHSZ8jJeGgDeuNnsDNHoxpSgDi",
+ crossorigin: "anonymous", defer: true)
end
body do
header(class: "site-header") do