Skip to content

fix(security): the publisher code scanning alerts - #42

Merged
DarrellVS merged 1 commit into
devfrom
25-codeql-publisher
Sep 22, 2026
Merged

DarrellVS merged 1 commit into
devfrom
25-codeql-publisher

Conversation

@DarrellVS

Copy link
Copy Markdown
Owner

Closes #25. Code scanning alerts 3, 10, 11, 12, 13, 14.

Missing rate limiting (alerts 10, 11, 12)

publisher/src/middlewares/rateLimits.ts, on express-rate-limit:

  • Embed page: 300 a minute per visitor.
  • API: only failures count, 60 a minute, checked before the token so a wrong guess counts. A batch publish over a LAN is several good requests a second; a ceiling on those would refuse the owner.
  • /media not limited (a seeking video is a burst of Range requests).

Key = CF-Connecting-IP, then first X-Forwarded-For hop, then socket. Behind a reverse proxy req.ip is the proxy for everyone, and keying on it would be one bucket for the whole internet. Forging a header buys a fresh bucket (no worse than no limit) and never gets anybody in.

Polynomial regex (alert 3)

/^Bearer\s+(.+)$/ replaced with a hand parse; test: under 100 ms on 200,000 spaces.

Tainted format string (alerts 13, 14)

Paths carrying the uploaded filename moved out of the first console.warn argument.

Gates

  • tests/unit/publisher: 12 new cases.
  • node scripts/publisher-views-check.mjs: 20 checks, exit 0, including 61st wrong guess = 429, another visitor still 200, 80 good requests all 200.
  • node scripts/discord-webhook-check.mjs: exit 0.
  • npm run check: 689 tests.

Before you redeploy

New dependency in publisher/package.json, so the image needs a rebuild. If your proxy is not Cloudflare and does not set X-Forwarded-For, every visitor shares one bucket; nginx and Nginx Proxy Manager set it by default.

🤖 Generated with Claude Code

Closes #25.

**Rate limits, keyed on the visitor rather than the proxy.** The embed
page and the API both read the disk on every request, and nothing capped
either. `middlewares/rateLimits.ts` on `express-rate-limit`:

- The public embed page: 300 a minute per visitor.
- The API: **only failures count**, 60 a minute, and the limit runs before
  the token check so a wrong guess is one. A batch publish of short clips
  over a LAN is several good requests a second, and a ceiling on those
  would eventually refuse the owner.
- `/media` is not limited: a video that seeks is a burst of Range
  requests, and the edge answers most of them.

The key is `CF-Connecting-IP`, then the first `X-Forwarded-For` hop, then
the socket. This runs behind a reverse proxy, so `req.ip` is the proxy for
every request and a limit keyed on it would be one bucket for the whole
internet. Forging either header buys a fresh bucket, which is no worse
than no limit, and never gets anybody in.

**The bearer header is read by hand.** `/^Bearer\s+(.+)$/` let `\s+` and
`.+` fight over the same spaces, quadratic on the one header anybody on
the internet can send. A test holds the new parse under 100 ms on 200,000
spaces.

**Two log lines no longer use the filename as a format string.** A `%s`
in an uploaded name would have been read as a directive.

Gates: `tests/unit/publisher`, 12 new cases. `publisher-views-check.mjs`
gained three checks (the 61st wrong guess is a 429, another visitor still
gets in, 80 good requests in a row are all answered), 20 in all, exit 0.
`discord-webhook-check.mjs` exit 0. `npm run check`: 689 tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DarrellVS DarrellVS added this to the 3.5.0 milestone Sep 22, 2026
@DarrellVS DarrellVS added publisher The Express half, in Docker security Code scanning and hardening labels Sep 22, 2026
@DarrellVS DarrellVS self-assigned this Sep 22, 2026
@DarrellVS
DarrellVS merged commit 6945b66 into dev Sep 22, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

publisher The Express half, in Docker security Code scanning and hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant