Skip to content

Commit 839b709

Browse files
committed
fix(csp): drop unsafe-inline from style-src
1 parent 22adcc5 commit 839b709

13 files changed

Lines changed: 152 additions & 11 deletions

File tree

assets/css/chroma.css

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/* Background */ .bg { background-color: #ffffff; }
2+
/* PreWrapper */ .chroma { background-color: #ffffff; }
3+
/* Other */ .chroma .x { }
4+
/* Error */ .chroma .err { color: #a61717; background-color: #e3d2d2 }
5+
/* CodeLine */ .chroma .cl { }
6+
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
7+
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
8+
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
9+
/* LineHighlight */ .chroma .hl { background-color: #e5e5e5 }
10+
/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
11+
/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
12+
/* Line */ .chroma .line { display: flex; }
13+
/* Keyword */ .chroma .k { color: #000000; font-weight: bold }
14+
/* KeywordConstant */ .chroma .kc { color: #000000; font-weight: bold }
15+
/* KeywordDeclaration */ .chroma .kd { color: #000000; font-weight: bold }
16+
/* KeywordNamespace */ .chroma .kn { color: #000000; font-weight: bold }
17+
/* KeywordPseudo */ .chroma .kp { color: #000000; font-weight: bold }
18+
/* KeywordReserved */ .chroma .kr { color: #000000; font-weight: bold }
19+
/* KeywordType */ .chroma .kt { color: #445588; font-weight: bold }
20+
/* Name */ .chroma .n { }
21+
/* NameAttribute */ .chroma .na { color: #008080 }
22+
/* NameBuiltin */ .chroma .nb { color: #0086b3 }
23+
/* NameBuiltinPseudo */ .chroma .bp { color: #999999 }
24+
/* NameClass */ .chroma .nc { color: #445588; font-weight: bold }
25+
/* NameConstant */ .chroma .no { color: #008080 }
26+
/* NameDecorator */ .chroma .nd { color: #3c5d5d; font-weight: bold }
27+
/* NameEntity */ .chroma .ni { color: #800080 }
28+
/* NameException */ .chroma .ne { color: #990000; font-weight: bold }
29+
/* NameFunction */ .chroma .nf { color: #990000; font-weight: bold }
30+
/* NameFunctionMagic */ .chroma .fm { }
31+
/* NameLabel */ .chroma .nl { color: #990000; font-weight: bold }
32+
/* NameNamespace */ .chroma .nn { color: #555555 }
33+
/* NameOther */ .chroma .nx { }
34+
/* NameProperty */ .chroma .py { }
35+
/* NameTag */ .chroma .nt { color: #000080 }
36+
/* NameVariable */ .chroma .nv { color: #008080 }
37+
/* NameVariableClass */ .chroma .vc { color: #008080 }
38+
/* NameVariableGlobal */ .chroma .vg { color: #008080 }
39+
/* NameVariableInstance */ .chroma .vi { color: #008080 }
40+
/* NameVariableMagic */ .chroma .vm { }
41+
/* Literal */ .chroma .l { }
42+
/* LiteralDate */ .chroma .ld { }
43+
/* LiteralString */ .chroma .s { color: #dd1144 }
44+
/* LiteralStringAffix */ .chroma .sa { color: #dd1144 }
45+
/* LiteralStringBacktick */ .chroma .sb { color: #dd1144 }
46+
/* LiteralStringChar */ .chroma .sc { color: #dd1144 }
47+
/* LiteralStringDelimiter */ .chroma .dl { color: #dd1144 }
48+
/* LiteralStringDoc */ .chroma .sd { color: #dd1144 }
49+
/* LiteralStringDouble */ .chroma .s2 { color: #dd1144 }
50+
/* LiteralStringEscape */ .chroma .se { color: #dd1144 }
51+
/* LiteralStringHeredoc */ .chroma .sh { color: #dd1144 }
52+
/* LiteralStringInterpol */ .chroma .si { color: #dd1144 }
53+
/* LiteralStringOther */ .chroma .sx { color: #dd1144 }
54+
/* LiteralStringRegex */ .chroma .sr { color: #009926 }
55+
/* LiteralStringSingle */ .chroma .s1 { color: #dd1144 }
56+
/* LiteralStringSymbol */ .chroma .ss { color: #990073 }
57+
/* LiteralNumber */ .chroma .m { color: #009999 }
58+
/* LiteralNumberBin */ .chroma .mb { color: #009999 }
59+
/* LiteralNumberFloat */ .chroma .mf { color: #009999 }
60+
/* LiteralNumberHex */ .chroma .mh { color: #009999 }
61+
/* LiteralNumberInteger */ .chroma .mi { color: #009999 }
62+
/* LiteralNumberIntegerLong */ .chroma .il { color: #009999 }
63+
/* LiteralNumberOct */ .chroma .mo { color: #009999 }
64+
/* Operator */ .chroma .o { color: #000000; font-weight: bold }
65+
/* OperatorWord */ .chroma .ow { color: #000000; font-weight: bold }
66+
/* Punctuation */ .chroma .p { }
67+
/* Comment */ .chroma .c { color: #999988; font-style: italic }
68+
/* CommentHashbang */ .chroma .ch { color: #999988; font-style: italic }
69+
/* CommentMultiline */ .chroma .cm { color: #999988; font-style: italic }
70+
/* CommentSingle */ .chroma .c1 { color: #999988; font-style: italic }
71+
/* CommentSpecial */ .chroma .cs { color: #999999; font-weight: bold; font-style: italic }
72+
/* CommentPreproc */ .chroma .cp { color: #999999; font-weight: bold; font-style: italic }
73+
/* CommentPreprocFile */ .chroma .cpf { color: #999999; font-weight: bold; font-style: italic }
74+
/* Generic */ .chroma .g { }
75+
/* GenericDeleted */ .chroma .gd { color: #000000; background-color: #ffdddd }
76+
/* GenericEmph */ .chroma .ge { color: #000000; font-style: italic }
77+
/* GenericError */ .chroma .gr { color: #aa0000 }
78+
/* GenericHeading */ .chroma .gh { color: #999999 }
79+
/* GenericInserted */ .chroma .gi { color: #000000; background-color: #ddffdd }
80+
/* GenericOutput */ .chroma .go { color: #888888 }
81+
/* GenericPrompt */ .chroma .gp { color: #555555 }
82+
/* GenericStrong */ .chroma .gs { font-weight: bold }
83+
/* GenericSubheading */ .chroma .gu { color: #aaaaaa }
84+
/* GenericTraceback */ .chroma .gt { color: #aa0000 }
85+
/* GenericUnderline */ .chroma .gl { text-decoration: underline }
86+
/* TextWhitespace */ .chroma .w { color: #bbbbbb }

assets/css/main.css

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,32 @@
3636
@tailwind components;
3737
@tailwind utilities;
3838

39+
/* Chroma syntax highlighting — class-based output so CSP can drop
40+
* 'unsafe-inline' from style-src. Generated from `hugo gen chromastyles
41+
* --style=github`. To change theme, regenerate with a different style name. */
42+
@import "chroma.css";
43+
3944
@layer base {
4045
html { scroll-behavior: smooth; }
4146
body { -webkit-font-smoothing: antialiased; }
4247
}
4348

4449
@layer components {
50+
/* ── Utility classes replacing inline style="..." attributes ──
51+
* Added so we can drop 'unsafe-inline' from the style-src CSP directive. */
52+
.hero-grid-bg {
53+
background-image:
54+
linear-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1px),
55+
linear-gradient(90deg, rgba(255, 255, 255, 0.08) 1px, transparent 1px);
56+
background-size: 32px 32px;
57+
}
58+
59+
.reading-progress {
60+
/* Initial width; JS updates element.style.width on scroll (JS-set styles
61+
* bypass style-src CSP, unlike HTML style="..." attributes). */
62+
width: 0;
63+
}
64+
4565
/* ── Navigation ── */
4666
.nav-link {
4767
@apply text-sm font-medium text-gray-600 hover:text-gray-900

assets/css/showcase-colors.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{/* Generated per-slug showcase colors — avoids inline style="background: ..."
2+
so CSP can drop 'unsafe-inline' from style-src.
3+
4+
Matches the [data-slug="..."] attribute rendered by
5+
layouts/showcase/{list,single}.html. */}}
6+
7+
{{ range where .Site.RegularPages "Section" "showcase" }}
8+
{{- /* Use File.ContentBaseName so "bhdicaire-com.en.md" and
9+
"bhdicaire-com.fr.md" share a slug and a color rule */ -}}
10+
{{ $slug := .File.ContentBaseName }}
11+
{{ $color := .Params.color | default "#0369a1" }}
12+
.showcase-logo[data-slug="{{ $slug }}"] { background: {{ $color }}; }
13+
{{ end -}}

assets/js/app.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,25 @@
220220
showMsg((t.search_no_results || 'No results for "%s"').replace('%s', q));
221221
return;
222222
}
223+
224+
// Pagefind returns every result whose index chunks contain ANY partial match,
225+
// ranked by relevance. For foreign words or typos, this produces noisy fuzzy
226+
// hits with very low scores. Filter to results the user would actually care
227+
// about. Empirically, Pagefind scores above ~0.5 are meaningful matches;
228+
// below that is mostly incidental term-overlap noise.
229+
var MIN_SCORE = 0.5;
230+
var filtered = results.filter(function (r) {
231+
return typeof r.score !== 'number' || r.score >= MIN_SCORE;
232+
});
233+
234+
if (!filtered.length) {
235+
showMsg((t.search_no_results || 'No results for "%s"').replace('%s', q));
236+
return;
237+
}
238+
223239
if (searchPlaceholder) searchPlaceholder.classList.add('hidden');
224240

225-
Promise.all(results.slice(0, 8).map(function (r) { return r.data(); }))
241+
Promise.all(filtered.slice(0, 8).map(function (r) { return r.data(); }))
226242
.then(function (items) {
227243
items.forEach(function (item) {
228244
var a = document.createElement('a');

content/security.en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Every response from vanityURLs.link includes the following headers, defined in `
5757
```
5858
default-src 'self';
5959
script-src 'self' 'wasm-unsafe-eval' https://cdn.jsdelivr.net;
60-
style-src 'self' 'unsafe-inline';
60+
style-src 'self';
6161
font-src 'self';
6262
img-src 'self' data:;
6363
connect-src 'self';

content/security.fr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Chaque réponse de vanityURLs.link inclut les en-têtes suivants, définis dans
3636
```
3737
default-src 'self';
3838
script-src 'self' 'wasm-unsafe-eval' https://cdn.jsdelivr.net;
39-
style-src 'self' 'unsafe-inline';
39+
style-src 'self';
4040
font-src 'self';
4141
img-src 'self' data:;
4242
connect-src 'self';

hugo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ markup:
133133
style: "github"
134134
lineNos: false
135135
tabWidth: 2
136-
noClasses: true
136+
noClasses: false
137137
goldmark:
138138
renderer:
139139
unsafe: true

layouts/blog/single.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919

2020
<!-- Reading progress bar -->
2121
<div id="reading-progress"
22-
class="fixed top-0 left-0 h-0.5 bg-brand-500 dark:bg-brand-400 z-50 transition-none"
23-
style="width:0%"></div>
22+
class="reading-progress fixed top-0 left-0 h-0.5 bg-brand-500 dark:bg-brand-400 z-50 transition-none"></div>
2423

2524
<article class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
2625

layouts/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66

77
<!-- ═══════ HERO ═══════ -->
88
<section class="relative overflow-hidden bg-gradient-to-br from-gray-900 via-brand-900 to-gray-900 text-white">
9-
<div class="absolute inset-0 opacity-10"
10-
style="background-image: linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px); background-size: 32px 32px;"></div>
9+
<div class="absolute inset-0 opacity-10 hero-grid-bg"></div>
1110
<div class="relative max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-28">
1211
<div class="flex flex-col items-center text-center">
1312

layouts/partials/head.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@
6565
{{ $css := resources.Get "css/main.css" | css.PostCSS $opts | resources.Minify | resources.Fingerprint }}
6666
<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">
6767

68+
{{/* Per-showcase-item color rules — generated from frontmatter so we don't
69+
need inline style="background: ..." (which style-src CSP would block).
70+
Only emit the stylesheet on pages where showcase cards actually render. */}}
71+
{{ if or (eq .Section "showcase") .IsHome }}
72+
{{ $showcase := resources.Get "css/showcase-colors.css" | resources.ExecuteAsTemplate "css/showcase-colors.css" . | resources.Minify | resources.Fingerprint }}
73+
<link rel="stylesheet" href="{{ $showcase.RelPermalink }}" integrity="{{ $showcase.Data.Integrity }}">
74+
{{ end }}
75+
6876
{{/* ── Fonts ──────────────────────────────────────────────────
6977
Self-hosted from /fonts/ (see @font-face declarations in assets/css/main.css).
7078
Preload the upright weights that body text needs immediately; italic + mono can load lazily.

0 commit comments

Comments
 (0)