From c1d6816a311bc48520cf93a08a446bb86a39dff3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 21:57:57 +0000 Subject: [PATCH 1/3] chore: harden template baseline against Scorecard/zizmor findings Pin the HTMLHint install to a checksummed tarball instead of resolving it through the npm registry at run time, add a SECURITY.md with a private-reporting path, and add a CODEOWNERS fallback so branch protection can require code owner review. This template is the golden baseline new sites are cloned from, so fixing it here propagates to everything built on top of it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H1WirNLJU8fNqht6aFxTnx --- .github/CODEOWNERS | 2 ++ .github/workflows/quality.yml | 13 ++++++++++++- SECURITY.md | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .github/CODEOWNERS create mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..1d8b726 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Fallback owner for everything not matched below. +* @Stensel8 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 9f09e79..c932dfc 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -65,11 +65,22 @@ jobs: with: node-version: 'lts/*' + # Fetched as a tarball and checksummed, rather than `npm install -g + # htmlhint@version`: that resolves through the registry at run time with + # no verification against what actually comes back, which is what + # Scorecard's Pinned-Dependencies check and zizmor's adhoc-packages audit + # both flag it for. - name: Install HTMLHint env: # renovate: datasource=npm depName=htmlhint HTMLHINT_VERSION: "1.9.2" - run: npm install -g "htmlhint@${HTMLHINT_VERSION}" + HTMLHINT_SHA256: "c32775c6abf4ed12bbb87c7310ff76802270989f4880a25196d196b7c7fe7c17" + run: | + curl -sSL --fail-with-body -o htmlhint.tgz \ + --retry 5 --retry-delay 3 --retry-all-errors \ + "https://registry.npmjs.org/htmlhint/-/htmlhint-${HTMLHINT_VERSION}.tgz" + echo "${HTMLHINT_SHA256} htmlhint.tgz" | sha256sum -c - + npm install -g ./htmlhint.tgz # HTMLHint: structural errors, missing attributes, accessibility. Runs on # the built output, not on the templates: a Hugo layout is not a diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..fca4eff --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Supported versions + +This is a template repository, not a versioned library — there is no +release history to track. The `main` branch is the only supported state; +sites created from this template inherit whatever `main` looked like at +the time they were generated and are responsible for their own updates +afterwards. + +## Reporting a vulnerability + +If you find a security issue in this template (for example: an XSS vector +in a layout, a workflow that leaks secrets, or a supply-chain issue in a +pinned dependency), please report it privately rather than opening a +public issue. + +Use GitHub's [private vulnerability reporting](https://github.com/THectic-NL/hugo-template/security/advisories/new) +for this repository. You should get an initial response within a few +business days. + +Please do not report vulnerabilities in Hugo itself, or in third-party +Actions/npm packages this template depends on — report those upstream. From 21bff2d5906690436c2636df0816e24ad1fea510 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Aug 2026 22:06:58 +0000 Subject: [PATCH 2/3] fix: install HTMLHint via a real lockfile, not npm install -g zizmor's adhoc-packages audit still flagged the previous fix: it pattern-matches on `npm install ` regardless of whether the argument is a checksummed local tarball, and only treats `npm ci` against a lockfile as lockfile-aware. Move HTMLHint into its own package.json/package-lock.json under .github/htmlhint/ and install it with `npm ci`, which also satisfies Scorecard's Pinned-Dependencies check. Enable Renovate's npm manager (scoped to this one lockfile) so version bumps keep working the same way they did through the old custom regex. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01H1WirNLJU8fNqht6aFxTnx --- .github/htmlhint/package-lock.json | 314 +++++++++++++++++++++++++++++ .github/htmlhint/package.json | 8 + .github/workflows/quality.yml | 25 +-- renovate.json | 4 +- 4 files changed, 334 insertions(+), 17 deletions(-) create mode 100644 .github/htmlhint/package-lock.json create mode 100644 .github/htmlhint/package.json diff --git a/.github/htmlhint/package-lock.json b/.github/htmlhint/package-lock.json new file mode 100644 index 0000000..1fded9f --- /dev/null +++ b/.github/htmlhint/package-lock.json @@ -0,0 +1,314 @@ +{ + "name": "hugo-template-htmlhint", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hugo-template-htmlhint", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "htmlhint": "1.9.2" + }, + "description": "Pinned HTMLHint install for .github/workflows/quality.yml. Not part of the site." + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/htmlhint": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/htmlhint/-/htmlhint-1.9.2.tgz", + "integrity": "sha512-PweWSPA1Pb+AVFIOSpIGu5KhLdmtk/uf/0CpjvrDf6XUWmdTyqUljlylwSxQ0AWLvPGcBxK2n8uISsI4lCOkBQ==", + "license": "MIT", + "dependencies": { + "async": "3.2.6", + "chalk": "4.1.2", + "commander": "11.1.0", + "glob": "^13.0.6", + "is-glob": "^4.0.3", + "node-sarif-builder": "3.2.0", + "strip-json-comments": "3.1.1", + "xml": "1.0.1" + }, + "bin": { + "htmlhint": "bin/htmlhint" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "Open Collective", + "url": "https://opencollective.com/htmlhint" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/node-sarif-builder": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.2.0.tgz", + "integrity": "sha512-kVIOdynrF2CRodHZeP/97Rh1syTUHBNiw17hUCIVhlhEsWlfJm19MuO56s4MdKbr22xWx6mzMnNAgXzVlIYM9Q==", + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + } + } +} diff --git a/.github/htmlhint/package.json b/.github/htmlhint/package.json new file mode 100644 index 0000000..c5162c3 --- /dev/null +++ b/.github/htmlhint/package.json @@ -0,0 +1,8 @@ +{ + "name": "hugo-template-htmlhint", + "private": true, + "description": "Pinned HTMLHint install for .github/workflows/quality.yml. Not part of the site.", + "dependencies": { + "htmlhint": "1.9.2" + } +} diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c932dfc..290a8f3 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -65,28 +65,21 @@ jobs: with: node-version: 'lts/*' - # Fetched as a tarball and checksummed, rather than `npm install -g - # htmlhint@version`: that resolves through the registry at run time with - # no verification against what actually comes back, which is what - # Scorecard's Pinned-Dependencies check and zizmor's adhoc-packages audit - # both flag it for. + # `npm ci` against a checked-in package-lock.json (.github/htmlhint/), + # rather than `npm install -g htmlhint@version`: the latter resolves + # through the registry at run time with nothing to verify what comes + # back against, which is what Scorecard's Pinned-Dependencies check and + # zizmor's adhoc-packages audit both flag it for. `npm ci` installs + # exactly what the lockfile's integrity hashes say, and bumping the + # version is a normal Renovate PR against that lockfile. - name: Install HTMLHint - env: - # renovate: datasource=npm depName=htmlhint - HTMLHINT_VERSION: "1.9.2" - HTMLHINT_SHA256: "c32775c6abf4ed12bbb87c7310ff76802270989f4880a25196d196b7c7fe7c17" - run: | - curl -sSL --fail-with-body -o htmlhint.tgz \ - --retry 5 --retry-delay 3 --retry-all-errors \ - "https://registry.npmjs.org/htmlhint/-/htmlhint-${HTMLHINT_VERSION}.tgz" - echo "${HTMLHINT_SHA256} htmlhint.tgz" | sha256sum -c - - npm install -g ./htmlhint.tgz + run: npm ci --prefix .github/htmlhint # HTMLHint: structural errors, missing attributes, accessibility. Runs on # the built output, not on the templates: a Hugo layout is not a # standalone HTML document, so linting one only produces noise. - name: Check HTML - run: htmlhint "src/public-lint/**/*.html" + run: .github/htmlhint/node_modules/.bin/htmlhint "src/public-lint/**/*.html" # Installed by hand rather than through lycheeverse/lychee-action, which # fetches its binary with a bare `curl -sfLO`: no retry, and no check on diff --git a/renovate.json b/renovate.json index 29d4e39..c44a301 100644 --- a/renovate.json +++ b/renovate.json @@ -25,6 +25,7 @@ "minimumReleaseAge": "7 days", "enabledManagers": [ "github-actions", + "npm", "custom.regex" ], "customManagers": [ @@ -56,7 +57,8 @@ { "description": "Hugo, actionlint, HTMLHint and lychee. Not automerged: some are pinned alongside a checksum, and update-checksums.yml has to push the recalculated hash onto the branch first.", "matchManagers": [ - "custom.regex" + "custom.regex", + "npm" ], "groupName": "Build tooling versions", "addLabels": [ From a5a9f04885f19f34cbdf874659826079b7f5d5fd Mon Sep 17 00:00:00 2001 From: "Sten T." <102481635+Stensel8@users.noreply.github.com> Date: Sun, 23 Aug 2026 00:11:08 +0200 Subject: [PATCH 3/3] Delete .github/CODEOWNERS --- .github/CODEOWNERS | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 1d8b726..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,2 +0,0 @@ -# Fallback owner for everything not matched below. -* @Stensel8