al_folio_distill 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +31 -0
- data/assets/js/distillpub/provenance.json +4 -3
- data/assets/js/distillpub/transforms.v2.js +26 -4
- data/lib/al_folio_distill/version.rb +1 -1
- data/lib/al_folio_distill.rb +119 -3
- data/scripts/distill/sync_distill.sh +29 -1
- data/templates/distill/render.liquid +2 -3
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43e18fcf0822a90ad412005985178d449d7267f6924b38b3181aba3edf176cff
|
|
4
|
+
data.tar.gz: 1d2a83960addd942b34e39891854356b906d9380ff748252fab0a15cf6eb90fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e75ede64f8075366893d771808ba5840c957ffb75d92f328a166f2361caf8ad63af727c41e94f4e7398443bd62438451e9ca1f82be2d40d1c70394c137c927cd
|
|
7
|
+
data.tar.gz: cbfd8b4eabf21748c1a5267566f675bbc49c5bde027cb2867614dcfc7b317456a85a941fdcbd0288cda91608968ce565d2bd5148f0ced73652e97851765bb7e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3 - 2026-07-27
|
|
4
|
+
|
|
5
|
+
- Security: removed the unpinned remote Distill runtime loader. The vendored `assets/js/distillpub/transforms.v2.js` shipped upstream's `Polyfills` transform, which strips the page's `template.v2.js` tag and re-injects it from a hard-coded `https://distill.pub/template.v2.js` with no subresource integrity — discarding the vendored, hash-pinned copy this gem exists to ship and handing arbitrary JS execution on every Distill page to whoever controls that origin. The transform now re-injects `window.alFolioDistill.templateLoader`, falling back to the same-origin `src` of the tag it just removed, and never falls back to a remote origin. Recorded under `local_patches` and flipped `remote_loader_patched` to `true` (port it to the upstream distill-template so a future re-sync preserves it).
|
|
6
|
+
- `templates/distill/render.liquid` now emits the runtime via the new `{% al_folio_distill_runtime_scripts %}` tag, which serves `template.v2.js`/`transforms.v2.js` from the vendored copy with `integrity` pinned to the SHA-256 digests committed in `provenance.json`.
|
|
7
|
+
- Remote loading is now explicit opt-in via `al_folio.distill.allow_remote_loader` (default `false`), with `al_folio.distill.remote_loader_url` and `al_folio.distill.remote_loader_integrity`. When an SRI hash is configured the injected tag carries `integrity` + `crossorigin="anonymous"`; the build warns when opting in without one.
|
|
8
|
+
- Added a build-time check that the vendored runtime on disk matches the digests pinned in `provenance.json`, so drift is reported at build time instead of failing subresource integrity in visitors' browsers.
|
|
9
|
+
- `scripts/distill/sync_distill.sh` now derives `remote_loader_patched` from the synced bytes instead of hard-coding `false`, preserves `local_patches`, and exits non-zero if a re-sync reintroduces the remote loader.
|
|
10
|
+
|
|
3
11
|
## 1.0.2 - 2026-06-01
|
|
4
12
|
|
|
5
13
|
- De-jQueried the vendored `assets/js/distillpub/overrides.js`. Its top-level `$(window).on("load", ...)` threw `ReferenceError: $ is not defined` on every distill page (jQuery was removed in al-folio v1), so the footnote/citation dark-theme overrides never applied and the page logged a console error. Switched to `window.addEventListener("load", ...)`; the handler body was already vanilla. Updated the pinned SHA-256 in `provenance.json` and the runtime contract test, and recorded the change under `local_patches` (port it to the upstream distill-template so a future re-sync preserves it).
|
data/README.md
CHANGED
|
@@ -42,6 +42,37 @@ Refresh assets:
|
|
|
42
42
|
./scripts/distill/sync_distill.sh <commit-sha>
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
The sync script records the real digests in `provenance.json` and fails if the
|
|
46
|
+
synced ref reintroduces the hard-coded remote Distill loader (see below).
|
|
47
|
+
|
|
48
|
+
## Runtime loading policy
|
|
49
|
+
|
|
50
|
+
Distill pages load the runtime from the vendored copy under
|
|
51
|
+
`/assets/js/distillpub/`, with `integrity` pinned to the SHA-256 digests
|
|
52
|
+
committed in `provenance.json`. Upstream's `Polyfills` transform removes the
|
|
53
|
+
page's template tag and re-adds it; the vendored copy is patched so it re-adds
|
|
54
|
+
the vendored URL rather than a hard-coded `distill.pub` one.
|
|
55
|
+
|
|
56
|
+
Loading the runtime from a third-party origin is opt-in:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
al_folio:
|
|
60
|
+
distill:
|
|
61
|
+
engine: distillpub-template
|
|
62
|
+
source: al-org-dev/distill-template#al-folio
|
|
63
|
+
# Default false. When false, the runtime is only ever loaded from the
|
|
64
|
+
# vendored, integrity-pinned copy.
|
|
65
|
+
allow_remote_loader: false
|
|
66
|
+
# Only used when allow_remote_loader is true.
|
|
67
|
+
remote_loader_url: https://distill.pub/template.v2.js
|
|
68
|
+
# Optional SRI for remote_loader_url. When set, the injected tag carries
|
|
69
|
+
# integrity + crossorigin="anonymous". The build warns when it is missing.
|
|
70
|
+
remote_loader_integrity: sha256-...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Keep `allow_remote_loader: false` unless you control the remote origin: whoever
|
|
74
|
+
serves `remote_loader_url` executes arbitrary JavaScript on every Distill page.
|
|
75
|
+
|
|
45
76
|
## Contributing
|
|
46
77
|
|
|
47
78
|
Distill runtime/template behavior belongs here. Starter-only docs/demo changes belong in `al-folio`.
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
"toolchain": {
|
|
8
8
|
"sync_mode": "copy-dist-artifacts"
|
|
9
9
|
},
|
|
10
|
-
"remote_loader_patched":
|
|
10
|
+
"remote_loader_patched": true,
|
|
11
11
|
"local_patches": [
|
|
12
|
-
"overrides.js: de-jQuery the load handler ($(window).on('load', ...) -> window.addEventListener('load', ...)); jQuery is not loaded in al-folio v1. Port this upstream so the next sync preserves it."
|
|
12
|
+
"overrides.js: de-jQuery the load handler ($(window).on('load', ...) -> window.addEventListener('load', ...)); jQuery is not loaded in al-folio v1. Port this upstream so the next sync preserves it.",
|
|
13
|
+
"transforms.v2.js: the Polyfills transform re-injected the runtime from a hard-coded remote distill.pub template URL with no SRI, discarding the vendored copy. It now re-injects window.alFolioDistill.templateLoader (vendored + integrity-pinned by default), falling back to the same-origin src of the tag it removed. Port this upstream so the next sync preserves it."
|
|
13
14
|
],
|
|
14
15
|
"assets": {
|
|
15
16
|
"template.v2.js": "4790831ced02f7c4f2009b2cdf6978ceda8351f0060d3b59dd9b3aab132e271a",
|
|
16
|
-
"transforms.v2.js": "
|
|
17
|
+
"transforms.v2.js": "5d85590f5652b910ab2411019749c83ef5a5a3fbb9b739adc92b4557b6bf3d39",
|
|
17
18
|
"overrides.js": "41ce4a3744a9083e8913df1256432d2233ba16ff9b40c4878bdb3f35d73c0e8e"
|
|
18
19
|
}
|
|
19
20
|
}
|
|
@@ -14060,11 +14060,31 @@
|
|
|
14060
14060
|
//
|
|
14061
14061
|
// `;
|
|
14062
14062
|
|
|
14063
|
-
|
|
14063
|
+
// al-folio local patch: upstream re-injects the Distill runtime from a
|
|
14064
|
+
// hard-coded remote distill.pub template URL with no subresource integrity,
|
|
14065
|
+
// which silently discards the vendored, hash-pinned copy this gem ships.
|
|
14066
|
+
// Re-inject the loader al-folio declared instead
|
|
14067
|
+
// (`window.alFolioDistill.templateLoader`, emitted by
|
|
14068
|
+
// templates/distill/render.liquid), falling back to the same-origin src of the
|
|
14069
|
+
// tag we just removed. Never fall back to a remote origin.
|
|
14070
|
+
const addBackIn = (templateSrc) => `
|
|
14064
14071
|
window.addEventListener('WebComponentsReady', function() {
|
|
14065
14072
|
console.warn('WebComponentsReady');
|
|
14066
|
-
|
|
14067
|
-
|
|
14073
|
+
var loader = (window.alFolioDistill && window.alFolioDistill.templateLoader) || {};
|
|
14074
|
+
var fallbackSrc = ${JSON.stringify(templateSrc)};
|
|
14075
|
+
var loaderTag = document.createElement('script');
|
|
14076
|
+
if (loader.url) {
|
|
14077
|
+
loaderTag.src = loader.url;
|
|
14078
|
+
if (loader.integrity) {
|
|
14079
|
+
loaderTag.integrity = loader.integrity;
|
|
14080
|
+
loaderTag.crossOrigin = 'anonymous';
|
|
14081
|
+
}
|
|
14082
|
+
} else if (fallbackSrc) {
|
|
14083
|
+
loaderTag.src = fallbackSrc;
|
|
14084
|
+
} else {
|
|
14085
|
+
console.error('al_folio_distill: no Distill template URL configured; refusing to load the runtime from a remote origin.');
|
|
14086
|
+
return;
|
|
14087
|
+
}
|
|
14068
14088
|
document.head.insertBefore(loaderTag, document.head.firstChild);
|
|
14069
14089
|
});
|
|
14070
14090
|
`;
|
|
@@ -14072,7 +14092,9 @@ window.addEventListener('WebComponentsReady', function() {
|
|
|
14072
14092
|
function render(dom) {
|
|
14073
14093
|
// pull out template script tag
|
|
14074
14094
|
const templateTag = dom.querySelector('script[src*="template.v2.js"]');
|
|
14095
|
+
let templateSrc = null;
|
|
14075
14096
|
if (templateTag) {
|
|
14097
|
+
templateSrc = templateTag.getAttribute("src");
|
|
14076
14098
|
templateTag.parentNode.removeChild(templateTag);
|
|
14077
14099
|
} else {
|
|
14078
14100
|
console.debug("FYI: Did not find template tag when trying to remove it. You may not have added it. Be aware that our polyfills will add it.");
|
|
@@ -14085,7 +14107,7 @@ window.addEventListener('WebComponentsReady', function() {
|
|
|
14085
14107
|
|
|
14086
14108
|
// add loader event listener to add tempalrte back in
|
|
14087
14109
|
const addTag = dom.createElement("script");
|
|
14088
|
-
addTag.innerHTML = addBackIn;
|
|
14110
|
+
addTag.innerHTML = addBackIn(templateSrc);
|
|
14089
14111
|
dom.head.insertBefore(addTag, dom.head.firstChild);
|
|
14090
14112
|
|
|
14091
14113
|
// create polyfill script tag
|
data/lib/al_folio_distill.rb
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "jekyll"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "json"
|
|
4
6
|
require_relative "al_folio_distill/version"
|
|
5
7
|
|
|
6
8
|
module AlFolioDistill
|
|
7
9
|
PLUGIN_ROOT = File.expand_path("..", __dir__)
|
|
8
10
|
TEMPLATES_ROOT = File.join(PLUGIN_ROOT, "templates")
|
|
9
11
|
ASSETS_ROOT = File.join(PLUGIN_ROOT, "assets")
|
|
12
|
+
RUNTIME_ROOT = File.join(ASSETS_ROOT, "js", "distillpub")
|
|
13
|
+
RUNTIME_URL_PREFIX = "/assets/js/distillpub"
|
|
14
|
+
PROVENANCE_PATH = File.join(RUNTIME_ROOT, "provenance.json")
|
|
10
15
|
DISTILL_REMOTE_LOADER_PATTERN = %r{https://distill\.pub/template\.v2\.js}
|
|
16
|
+
DEFAULT_REMOTE_LOADER_URL = "https://distill.pub/template.v2.js"
|
|
17
|
+
RUNTIME_SCRIPTS = ["template.v2.js", "transforms.v2.js"].freeze
|
|
18
|
+
SHA256_HEX_PATTERN = /\A[0-9a-f]{64}\z/.freeze
|
|
11
19
|
|
|
12
20
|
class PluginStaticFile < Jekyll::StaticFile; end
|
|
13
21
|
|
|
@@ -21,6 +29,51 @@ module AlFolioDistill
|
|
|
21
29
|
site.config.dig("al_folio", "distill", "allow_remote_loader") == true
|
|
22
30
|
end
|
|
23
31
|
|
|
32
|
+
def remote_loader_url(site)
|
|
33
|
+
configured = site.config.dig("al_folio", "distill", "remote_loader_url")
|
|
34
|
+
configured.is_a?(String) && !configured.empty? ? configured : DEFAULT_REMOTE_LOADER_URL
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remote_loader_integrity(site)
|
|
38
|
+
configured = site.config.dig("al_folio", "distill", "remote_loader_integrity")
|
|
39
|
+
configured if configured.is_a?(String) && !configured.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Committed, known-good digests for the vendored runtime. Refreshed by
|
|
43
|
+
# `scripts/distill/sync_distill.sh`; asserted by the runtime contract test.
|
|
44
|
+
def provenance
|
|
45
|
+
@provenance ||= File.file?(PROVENANCE_PATH) ? JSON.parse(File.read(PROVENANCE_PATH)) : {}
|
|
46
|
+
rescue JSON::ParserError => e
|
|
47
|
+
Jekyll.logger.warn("al_folio_distill:", "could not parse provenance.json (#{e.message}); runtime integrity pinning is disabled")
|
|
48
|
+
@provenance = {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def pinned_digest(asset)
|
|
52
|
+
digest = provenance.dig("assets", asset)
|
|
53
|
+
digest if digest.is_a?(String) && digest.match?(SHA256_HEX_PATTERN)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Subresource Integrity wants base64, provenance.json pins hex. Same bytes.
|
|
57
|
+
def integrity_for(asset)
|
|
58
|
+
digest = pinned_digest(asset)
|
|
59
|
+
digest && "sha256-#{[[digest].pack("H*")].pack("m0")}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def runtime_url(site, asset)
|
|
63
|
+
"#{site.config["baseurl"].to_s.chomp("/")}#{RUNTIME_URL_PREFIX}/#{asset}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Describes where the Distill runtime may be re-injected from after the
|
|
67
|
+
# polyfill transform strips the original tag. Local + pinned unless a site
|
|
68
|
+
# explicitly opts into a remote origin.
|
|
69
|
+
def template_loader(site)
|
|
70
|
+
if remote_loader_allowed?(site)
|
|
71
|
+
{ "url" => remote_loader_url(site), "integrity" => remote_loader_integrity(site), "remote" => true }.compact
|
|
72
|
+
else
|
|
73
|
+
{ "url" => runtime_url(site, "template.v2.js"), "integrity" => integrity_for("template.v2.js"), "remote" => false }.compact
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
24
77
|
class AssetsGenerator < Jekyll::Generator
|
|
25
78
|
safe true
|
|
26
79
|
priority :low
|
|
@@ -48,9 +101,48 @@ module AlFolioDistill
|
|
|
48
101
|
)
|
|
49
102
|
end
|
|
50
103
|
end
|
|
104
|
+
|
|
105
|
+
# Emits the vendored Distill runtime with integrity pinned to the committed
|
|
106
|
+
# provenance digests, plus the loader descriptor the patched polyfill
|
|
107
|
+
# transform reads when it re-injects the template tag.
|
|
108
|
+
class RuntimeScriptsTag < Liquid::Tag
|
|
109
|
+
def render(context)
|
|
110
|
+
site = context.registers[:site]
|
|
111
|
+
return "" unless site && AlFolioDistill.enabled?(site)
|
|
112
|
+
|
|
113
|
+
lines = [
|
|
114
|
+
"<script>",
|
|
115
|
+
" window.alFolioDistill = window.alFolioDistill || {};",
|
|
116
|
+
" window.alFolioDistill.templateLoader = #{json_literal(AlFolioDistill.template_loader(site))};",
|
|
117
|
+
"</script>",
|
|
118
|
+
]
|
|
119
|
+
AlFolioDistill::RUNTIME_SCRIPTS.each do |asset|
|
|
120
|
+
lines << script_tag(AlFolioDistill.runtime_url(site, asset), AlFolioDistill.integrity_for(asset))
|
|
121
|
+
end
|
|
122
|
+
lines.join("\n")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def script_tag(url, integrity)
|
|
128
|
+
attributes = %(src="#{escape(url)}")
|
|
129
|
+
attributes += %( integrity="#{escape(integrity)}") if integrity
|
|
130
|
+
"<script #{attributes}></script>"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Inline JSON must never be able to close the surrounding <script> element.
|
|
134
|
+
def json_literal(value)
|
|
135
|
+
JSON.generate(value).gsub("</", '<\/')
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def escape(value)
|
|
139
|
+
value.to_s.gsub("&", "&").gsub('"', """).gsub("<", "<").gsub(">", ">")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
51
142
|
end
|
|
52
143
|
|
|
53
144
|
Liquid::Template.register_tag("al_folio_distill_render", AlFolioDistill::RenderTag)
|
|
145
|
+
Liquid::Template.register_tag("al_folio_distill_runtime_scripts", AlFolioDistill::RuntimeScriptsTag)
|
|
54
146
|
|
|
55
147
|
Jekyll::Hooks.register :site, :after_init do |site|
|
|
56
148
|
next unless site.respond_to?(:includes_load_paths)
|
|
@@ -61,12 +153,36 @@ end
|
|
|
61
153
|
|
|
62
154
|
Jekyll::Hooks.register :site, :post_read do |site|
|
|
63
155
|
next unless AlFolioDistill.enabled?(site)
|
|
64
|
-
next if AlFolioDistill.remote_loader_allowed?(site)
|
|
65
156
|
|
|
66
|
-
|
|
157
|
+
# The served bytes must match the digests we pin in the emitted `integrity`
|
|
158
|
+
# attributes, otherwise every Distill page fails SRI in the browser.
|
|
159
|
+
AlFolioDistill::RUNTIME_SCRIPTS.each do |asset|
|
|
160
|
+
pinned = AlFolioDistill.pinned_digest(asset)
|
|
161
|
+
asset_path = File.join(AlFolioDistill::RUNTIME_ROOT, asset)
|
|
162
|
+
next unless pinned && File.file?(asset_path)
|
|
163
|
+
next if Digest::SHA256.file(asset_path).hexdigest == pinned
|
|
164
|
+
|
|
165
|
+
Jekyll.logger.warn(
|
|
166
|
+
"al_folio_distill:",
|
|
167
|
+
"vendored #{asset} does not match the digest pinned in provenance.json; Distill pages will fail subresource integrity"
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if AlFolioDistill.remote_loader_allowed?(site)
|
|
172
|
+
unless AlFolioDistill.remote_loader_integrity(site)
|
|
173
|
+
Jekyll.logger.warn(
|
|
174
|
+
"al_folio_distill:",
|
|
175
|
+
"`al_folio.distill.allow_remote_loader` is true without `al_folio.distill.remote_loader_integrity`; " \
|
|
176
|
+
"the Distill runtime will be loaded from #{AlFolioDistill.remote_loader_url(site)} without subresource integrity"
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
next
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
transforms_path = File.join(AlFolioDistill::RUNTIME_ROOT, "transforms.v2.js")
|
|
67
183
|
next unless File.file?(transforms_path)
|
|
68
184
|
|
|
69
|
-
content = File.read(transforms_path)
|
|
185
|
+
content = File.read(transforms_path, encoding: "UTF-8")
|
|
70
186
|
if content.match?(AlFolioDistill::DISTILL_REMOTE_LOADER_PATTERN)
|
|
71
187
|
Jekyll.logger.warn("al_folio_distill:", "remote Distill template loader detected while `al_folio.distill.allow_remote_loader` is false")
|
|
72
188
|
end
|
|
@@ -49,6 +49,16 @@ TEMPLATE_SHA256="$(shasum -a 256 "${OUT_DIR}/template.v2.js" | awk '{print $1}')
|
|
|
49
49
|
TRANSFORMS_SHA256="$(shasum -a 256 "${OUT_DIR}/transforms.v2.js" | awk '{print $1}')"
|
|
50
50
|
SYNCED_AT_UTC="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
51
51
|
|
|
52
|
+
# The upstream Polyfills transform re-injects the Distill runtime from a
|
|
53
|
+
# hard-coded remote origin with no subresource integrity, which discards the
|
|
54
|
+
# vendored copy we just pinned. Report the real state instead of assuming it.
|
|
55
|
+
REMOTE_LOADER_URL="https://distill.pub/template.v2.js"
|
|
56
|
+
if grep -qF "${REMOTE_LOADER_URL}" "${OUT_DIR}/transforms.v2.js"; then
|
|
57
|
+
REMOTE_LOADER_PATCHED="false"
|
|
58
|
+
else
|
|
59
|
+
REMOTE_LOADER_PATCHED="true"
|
|
60
|
+
fi
|
|
61
|
+
|
|
52
62
|
cat > "${OUT_DIR}/provenance.json" <<JSON
|
|
53
63
|
{
|
|
54
64
|
"upstream_repo": "${UPSTREAM_REPO}",
|
|
@@ -59,7 +69,11 @@ cat > "${OUT_DIR}/provenance.json" <<JSON
|
|
|
59
69
|
"toolchain": {
|
|
60
70
|
"sync_mode": "copy-dist-artifacts"
|
|
61
71
|
},
|
|
62
|
-
"remote_loader_patched":
|
|
72
|
+
"remote_loader_patched": ${REMOTE_LOADER_PATCHED},
|
|
73
|
+
"local_patches": [
|
|
74
|
+
"overrides.js: de-jQuery the load handler (\$(window).on('load', ...) -> window.addEventListener('load', ...)); jQuery is not loaded in al-folio v1. Port this upstream so the next sync preserves it.",
|
|
75
|
+
"transforms.v2.js: the Polyfills transform re-injected the runtime from a hard-coded remote distill.pub template URL with no SRI, discarding the vendored copy. It now re-injects window.alFolioDistill.templateLoader (vendored + integrity-pinned by default), falling back to the same-origin src of the tag it removed. Port this upstream so the next sync preserves it."
|
|
76
|
+
],
|
|
63
77
|
"assets": {
|
|
64
78
|
"template.v2.js": "${TEMPLATE_SHA256}",
|
|
65
79
|
"transforms.v2.js": "${TRANSFORMS_SHA256}",
|
|
@@ -70,3 +84,17 @@ JSON
|
|
|
70
84
|
|
|
71
85
|
echo "Synced Distill runtime from ${UPSTREAM_REPO}@${SOURCE_COMMIT}"
|
|
72
86
|
echo "Updated assets in ${OUT_DIR}"
|
|
87
|
+
|
|
88
|
+
if [ "${REMOTE_LOADER_PATCHED}" = "false" ]; then
|
|
89
|
+
cat >&2 <<MSG
|
|
90
|
+
|
|
91
|
+
ERROR: ${OUT_DIR}/transforms.v2.js still hard-codes ${REMOTE_LOADER_URL}.
|
|
92
|
+
|
|
93
|
+
The synced ref reintroduced the unpinned remote Distill loader, so Distill pages
|
|
94
|
+
would fetch the runtime from a third-party origin with no subresource integrity
|
|
95
|
+
instead of the vendored copy. Land the loader patch listed under "local_patches"
|
|
96
|
+
in ${UPSTREAM_REPO} (${UPSTREAM_BRANCH}) and re-sync, or re-apply it by hand and
|
|
97
|
+
refresh the digests in provenance.json.
|
|
98
|
+
MSG
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
{% include head.liquid %}
|
|
5
5
|
<link rel="stylesheet" href="{{ '/assets/css/al-folio-distill.css' | relative_url | bust_file_cache }}">
|
|
6
6
|
|
|
7
|
-
<!-- Distill js -->
|
|
8
|
-
|
|
9
|
-
<script src="{{ '/assets/js/distillpub/transforms.v2.js' | relative_url }}"></script>
|
|
7
|
+
<!-- Distill js (vendored + integrity-pinned; see assets/js/distillpub/provenance.json) -->
|
|
8
|
+
{% al_folio_distill_runtime_scripts %}
|
|
10
9
|
{% if page._styles %}
|
|
11
10
|
<!-- Page/Post style -->
|
|
12
11
|
<style type="text/css">
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: al_folio_distill
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- al-folio maintainers
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-07-29 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: jekyll
|
|
@@ -127,6 +128,7 @@ metadata:
|
|
|
127
128
|
allowed_push_host: https://rubygems.org
|
|
128
129
|
homepage_uri: https://github.com/al-org-dev/al-folio-distill
|
|
129
130
|
source_code_uri: https://github.com/al-org-dev/al-folio-distill
|
|
131
|
+
post_install_message:
|
|
130
132
|
rdoc_options: []
|
|
131
133
|
require_paths:
|
|
132
134
|
- lib
|
|
@@ -141,7 +143,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
143
|
- !ruby/object:Gem::Version
|
|
142
144
|
version: '0'
|
|
143
145
|
requirements: []
|
|
144
|
-
rubygems_version:
|
|
146
|
+
rubygems_version: 3.5.22
|
|
147
|
+
signing_key:
|
|
145
148
|
specification_version: 4
|
|
146
149
|
summary: Distill rendering plugin for al-folio v1.x
|
|
147
150
|
test_files: []
|