Sample audits · Open-source projects, first eight

helmetjs/helmet

Security headers for Express.

Auditedhelmetjs/helmet at commit 876101e394f4936cdd1304a2e37b2509d52ed5c7
Date11 October 2026
How it ranAPI run on the Nacodex server, full audit, Standard review
Verdict after reviewPass with notes (rule: Fail if a High finding remains after review, otherwise Pass with notes)
0
High after review
0
Medium after review
1
Low after review
2
Info after review
0
excluded on review
How to read this page

Each finding keeps the number it has in the audit report. The rating shown first is the one after review; the first automated rating is listed with it. 1 findings were first rated Medium or High; 0 of them are Medium or High after review. Text marked "From the report" is quoted from the audit report; fixes are suggestions and were not tested. Code locations link to the file and line at the audited commit.

Low after review (1)

3. Low CSP directive values given as one-shot iterables are drained during validation
First rating: Medium · Reviewed rating: Low · Review: rated too high
From the report
Evidence
    for (const element of directiveValue) {
      if (typeof element !== "string") continue;

and later, at line 194:

    result.set(directiveName, directiveValue);
Why it matters

The public type accepts any Iterable as a directive value, and the README says "an array (or other iterable)". At setup time, parseDirectives loops over the caller's iterable to validate it, then stores that same object. Arrays and Sets survive a second pass, but a generator or any other single-use iterator does not. It is exhausted by validation. Because it is neither an Array nor a Set, the precomputed fast path is skipped, and on every request getHeaderValue (line 235) loops over the already-empty iterator. The header then carries the directive with no sources (for example a bare script-src), which blocks every script on the site. No error is raised, so the failure is silent and hard to trace. The existing test at test/content-security-policy.test.ts:166 uses an iterator that is empty from the start, so it cannot catch this.

Suggested fix, not tested

In parseDirectives, copy non-string values once (directiveValue = Array.from(rawDirectiveValue)), then validate and store the copy. That way the fast path, the per-request path and validation all see the same values. Add a test that passes a generator with at least one value, such as function* () { yield "'self'"; }(), and checks that the value appears in the header on two consecutive requests.

Review: Quotes (middlewares/content-security-policy/index.ts): Trace: a generator is iterable, not an Array, not a Set. The validation loop (186) consumes it; the same object is stored (194). stringifyDirectiveValue returns null for anything but Array/Set, so parseDirectives returns the Map, not the precomputed string. Per request, getHeaderValue loops the exhausted iterator (235), directiveValue stays "" and the else branch (about line 261) pushes the bare directive name, e.g. script-src. A bare directive means an empty source list, i.e. nothing allowed. README.md:74 says "an array (or other iterable)", so a generator is within the documented contract. The claim is correct, including that the existing test (test/content-security-policy.test.ts:166) uses an iterator that is empty from the start. Why OVERSTATED: (a) the input must be a single-pass iterator, unusual config (people pass arrays/Sets); (b) it fails closed (stricter than intended, never weaker) and shows on the first response in development; (c) no exploit, no data exposure. Even without the validation pass a generator would be empty after the first request, so the root cause is "single-pass iterables unsupported", a contract detail. Fair LOW. Library code shipped to users.

Upstream: No matching upstream report found (11 October 2026).

Info after review (2)

1. Info "Does not take options" warning duplicated five times
First rating: Low · Reviewed rating: Info · Review: rated too high
From the report
Evidence
      console.warn(
        "Origin-Agent-Cluster does not take options. Remove the property to silence this warning.",
      );
Why it matters

Five middlewares (Origin-Agent-Cluster, X-Content-Type-Options, X-Download-Options, X-Powered-By, X-XSS-Protection) each carry their own copy of the same "warn, then install the default" branch. If the wording or behaviour changes, for example to throw in a future major version or to warn only once, all five copies have to be edited together, and they can drift apart.

Suggested fix, not tested

Add one small helper, for example warnTakesNoOptions(headerName), and call it from each of the five default: branches. Leave the per-middleware switch blocks as they are.

Review: Quote (index.ts:168-170, same shape at the four others): grep -n console.warn index.ts gives exactly 168, 221, 261, 325, 346; each is the default: branch of a switch, header name differs per copy. Claim accurate. Pure maintainability; five 3-line copies, each with its own header name. Library code (helmet index.ts) but no behavioural impact. Fair INFO-LOW.

2. Info Standalone middleware packages are built from shared code but never smoke-tested
First rating: Low · Reviewed rating: Info · Review: rated too high
From the report
Evidence
  const { stdout } = await exec("npm run build");
  const middlewareToBuild = process.argv[2];
  buildAndPack(middlewareToBuild)
Why it matters

The build script can produce a separate npm package for each middleware (for example helmet-csp). It combines the shared build code, the middleware entry file and that middleware's package-overrides.json. The test suite and the CI workflow only build and install the main helmet package, never with a middleware argument. We searched package.json, .github/workflows/nodejs.yml and the tests and found no other caller. A broken override file, a CommonJS-only bundle problem or a wrong exports map in one of the standalone packages would therefore only surface after it is published. We did not find a broken package today, so this is graded as a verification gap.

Suggested fix, not tested

Add a test or a CI matrix step that runs the build for every middlewares/* directory that has a package-overrides.json. Install each tarball into a throwaway CommonJS project and assert that the expected header is set. Also check the generated package.json fields (name, version, main, types).

Review: Quote (test/project-setups.test.ts:22): const { stdout } = await exec("npm run build"); Quote (build/build-package.ts:372-373): const middlewareToBuild = process.argv[2]; then buildAndPack(middlewareToBuild). package.json script is "build": "tsx ./build/build-package.ts" (no argument). .github/workflows/nodejs.yml runs only npm ci and npm test. grep -rn "build-package\|buildAndPack" finds only build-package.ts and project-setups.test.ts. 11 middlewares have package-overrides.json (content-security-policy, cross-origin-resource-policy, referrer-policy, strict-transport-security, x-content-type-options, x-dns-prefetch-control, x-download-options, x-frame-options, x-permitted-cross-domain-policies, x-powered-by, x-xss-protection); none is built by any test or CI step. Gap confirmed. Not library runtime code: tests/CI/build only, though it affects the separately published standalone packages. The report itself says no broken package was found; LOW at most, INFO-LOW fair. Caveat: release may be verified by hand outside the repo, which cannot be seen from the repo.

Want this for your code?

Upload a ZIP or link a public GitHub repo, pick the areas and the depth, and get findings by severity with fixes.