Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/sysinternals.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Check Sysinternals mirror

on:
workflow_dispatch:
schedule:
- cron: '17 6 * * 1'

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Clone this repo
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: .node-version
cache: npm

- name: Install dependencies
run: npm ci

- name: Check Sysinternals files
run: npm run sysinternals:check
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ npm run audit:known
npm run sysinternals:check
npm run build
npm run smoke
npm run validate
```

The build renders the Astro site, copies non-Markdown files from `content/`
Expand All @@ -38,6 +39,7 @@ referenced by a `resources` or `attachments` shortcode unless they are an
intentional mirror/bulk asset listed in `scripts/content-policy.json`.
`npm run check:links` validates internal Markdown links, anchors, images, and
downloadable assets. External links are inventoried without network calls.
`npm run validate` runs the full local validation gate.

Use `npm run sysinternals:check` to compare the published Sysinternals files
with `https://live.sysinternals.com/`. Use `npm run sysinternals:sync` to
Expand All @@ -46,12 +48,9 @@ directories, marker files, and files over the 25MB Cloudflare Pages limit.

## Security Notes

`npm audit` currently reports upstream `esbuild` advisories through Astro/Vite.
Use `npm run audit:known` in CI and local checks: it allows only the documented
Astro/Vite/esbuild advisory chain and fails on any new vulnerability. Do not run
`npm audit fix --force` for that advisory, because npm currently proposes an
invalid Astro downgrade path. Keep Astro/Vite updated through Renovate and review
the advisory again when a compatible upstream fix is available.
`npm run audit:known` expects a clean `npm audit` result and fails on any
reported vulnerability. Keep Astro/Vite updated through Renovate and review
dependency advisories before adding any exception.

## Contributing

Expand Down
142 changes: 22 additions & 120 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@
"audit:known": "node scripts/check-audit.mjs",
"sysinternals:check": "node scripts/sync-sysinternals.mjs --check",
"sysinternals:sync": "node scripts/sync-sysinternals.mjs --write",
"smoke": "node scripts/smoke-build.mjs"
"smoke": "node scripts/smoke-build.mjs",
"validate": "npm run check && npm run check:content && npm run check:links && npm run build && npm run smoke && npm run audit:known"
},
"dependencies": {
"@astrojs/check": "^0.9.9",
"@astrojs/sitemap": "^3.7.2",
"astro": "^6.3.7",
"gray-matter": "^4.0.3",
"astro": "^6.4.7",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
"pagefind": "^1.1.1",
"typescript": "^6.0.0"
"typescript": "^6.0.0",
"yaml": "^2.9.0"
},
"overrides": {
"esbuild": "0.28.1",
"js-yaml": "4.2.0",
"vite": "7.3.5",
"volar-service-yaml": "0.0.71"
}
}
44 changes: 7 additions & 37 deletions scripts/check-audit.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { spawnSync } from "node:child_process";

const expectedSources = new Set([1120679, 1120680]);
const expectedNames = new Set(["astro", "esbuild", "vite"]);

const result = spawnSync("npm", ["audit", "--json"], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"]
Expand Down Expand Up @@ -35,43 +32,16 @@ if (vulnerabilities.length === 0) {
process.exit(0);
}

const unexpected = vulnerabilities.filter((vulnerability) => !isExpected(vulnerability));
if (unexpected.length) {
console.error("npm audit found unexpected vulnerabilities:");
for (const vulnerability of unexpected) {
console.error(`- ${vulnerability.name} (${vulnerability.severity})`);
}
process.exit(1);
}

const advisories = [...collectSources(vulnerabilities)].sort((a, b) => a - b).join(", ");
console.warn(`npm audit: only known upstream Astro/Vite/esbuild advisories found (${advisories})`);

function isExpected(vulnerability) {
if (!expectedNames.has(vulnerability.name)) return false;

const sources = collectSources([vulnerability]);
if (sources.size > 0) {
return [...sources].every((source) => expectedSources.has(source));
}

return asArray(vulnerability.via).every((via) => {
if (typeof via === "string") return expectedNames.has(via);
return expectedSources.has(via.source);
});
}

function collectSources(vulnerabilities) {
const sources = new Set();
for (const vulnerability of vulnerabilities) {
for (const via of asArray(vulnerability.via)) {
if (typeof via === "object" && Number.isInteger(via.source)) {
sources.add(via.source);
}
console.error("npm audit found vulnerabilities:");
for (const vulnerability of vulnerabilities) {
console.error(`- ${vulnerability.name} (${vulnerability.severity})`);
for (const via of asArray(vulnerability.via)) {
if (typeof via === "object" && via.title) {
console.error(` - ${via.title}`);
}
}
return sources;
}
process.exit(1);

function asArray(value) {
return Array.isArray(value) ? value : [];
Expand Down
4 changes: 2 additions & 2 deletions scripts/check-content.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readdir, readFile, stat } from "node:fs/promises";
import path from "node:path";
import matter from "gray-matter";
import { parseFrontmatter } from "../src/lib/frontmatter.mjs";

const root = process.cwd();
const contentDir = path.join(root, "content");
Expand Down Expand Up @@ -91,8 +91,8 @@ async function walk(dir) {
async function parsePages() {
for (const file of markdownFiles) {
const raw = await readFile(file, "utf8");
const parsed = matter(raw);
const relativeFile = slash(path.relative(contentDir, file));
const parsed = parseFrontmatter(raw, relativeFile);
const slug = slugFromFile(relativeFile);

pages.push({
Expand Down
Loading