Skip to the content

Capsid menu

update-owasp.mjs

72 lines and 5 functions in DrDustinEdwards/site-runtime, at commit 89e9d58 on 2026-10-24. This file on GitHub.

Functions

The functions of update-owasp.mjs
FunctionLinesComplexityCoveredCRAPMutation
here25 to 251No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
read28 to 322No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:41>41 to 412No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:44>44 to 441No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet
<anonymous:48>48 to 481No data: no coverage report in this mapNo data: no coverage report in this mapNo data: mutation testing has not run on this repo yet

Imports

It imports no other module of this repo. No module of this repo imports it.

Source

#!/usr/bin/env node
/**
 * Refreshes the package's copy of the OWASP Secure Headers Project data from the project itself, so no default is
 * typed from memory:
 *
 *   node update-owasp.mjs
 *
 * It reads, from the project's repository (https://github.com/OWASP/www-project-secure-headers, the source of
 * https://owasp.org/www-project-secure-headers/):
 *   - ci/headers_add.json     the headers OSHP recommends a response carry, with its `last_update_utc`
 *   - ci/headers_remove.json  the headers OSHP recommends a response not carry
 *   - subprojects/validator/tests_suite.yml  OSHP's own header test suite (a venom suite), kept verbatim
 *
 * and writes owasp-headers.mjs and oshp-tests-suite.yml beside this file, and the "Source" block of README.md with
 * the page, the files and the date they were read. Exits non-zero when a file cannot be read or has an unexpected
 * shape, so a failed refresh never leaves a half-written set.
 */

import { readFile, writeFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";

const RAW = "https://raw.githubusercontent.com/OWASP/www-project-secure-headers/master";
const PAGE = "https://owasp.org/www-project-secure-headers/";
const REPO = "https://github.com/OWASP/www-project-secure-headers";
const here = (/** @type {string} */ name) => fileURLToPath(new URL(name, import.meta.url));

/** @param {string} path */
async function read(path) {
  const res = await fetch(`${RAW}/${path}`, { headers: { "user-agent": "security-headers update" } });
  if (!res.ok) throw new Error(`${RAW}/${path} answered ${res.status}`);
  return res.text();
}

const [addText, removeText, suite] = await Promise.all([
  read("ci/headers_add.json"),
  read("ci/headers_remove.json"),
  read("subprojects/validator/tests_suite.yml"),
]);
const add = JSON.parse(addText);
const remove = JSON.parse(removeText);
if (!Array.isArray(add.headers) || add.headers.length === 0 || add.headers.some((/** @type {any} */ h) => typeof h?.name !== "string" || typeof h?.value !== "string")) {
  throw new Error("headers_add.json is not a list of { name, value }");
}
if (!Array.isArray(remove.headers) || remove.headers.some((/** @type {any} */ h) => typeof h !== "string")) throw new Error("headers_remove.json is not a list of names");
if (!suite.includes("testcases:")) throw new Error("tests_suite.yml has no testcases");

const fetchedAt = new Date().toISOString().slice(0, 10);
const headers = Object.fromEntries(add.headers.map((/** @type {{ name: string, value: string }} */ h) => [h.name, h.value]));

const module = `// GENERATED by update-owasp.mjs from the OWASP Secure Headers Project. Do not edit: run the script.
// Page: ${PAGE}
// Data: ${REPO}/tree/master/ci (headers_add.json, headers_remove.json), read ${fetchedAt}.

/** Where these values were read, and when. */
export const OWASP_SOURCE = Object.freeze({
  page: ${JSON.stringify(PAGE)},
  data: ${JSON.stringify(`${REPO}/tree/master/ci`)},
  readOn: ${JSON.stringify(fetchedAt)},
  lastUpdateUtc: ${JSON.stringify(add.last_update_utc ?? null)},
});

/** The headers OSHP recommends a response carry, as OSHP writes them. */
export const OWASP_RECOMMENDED = Object.freeze(${JSON.stringify(headers, null, 2)});

/** The headers OSHP recommends a response not carry (they name the software that served it). */
export const OWASP_REMOVE = Object.freeze(${JSON.stringify(remove.headers, null, 2)});
`;
await writeFile(here("owasp-headers.mjs"), module, "utf8");
await writeFile(here("oshp-tests-suite.yml"), suite, "utf8");

const block = [
  "<!-- owasp:begin -->",
  `The defaults are read from the OWASP Secure Headers Project, not typed here: page ${PAGE}, data ${REPO}/tree/master/ci`,
  `(\`headers_add.json\`, \`headers_remove.json\`, last updated by OWASP ${add.last_update_utc ?? "(not stated)"} UTC), and its header test suite`,
  `\`subprojects/validator/tests_suite.yml\`, all read on **${fetchedAt}**. Refresh with \`node update-owasp.mjs\`.`,
  "<!-- owasp:end -->",
].join("\n");
const readmePath = here("README.md");
const readme = await readFile(readmePath, "utf8");
if (!readme.includes("<!-- owasp:begin -->")) throw new Error("README.md has no owasp:begin marker");
await writeFile(readmePath, readme.replace(/<!-- owasp:begin -->[\s\S]*?<!-- owasp:end -->/, block), "utf8");
console.log(`read ${Object.keys(headers).length} recommended headers, ${remove.headers.length} to remove, and the suite, on ${fetchedAt}`);