-
Notifications
You must be signed in to change notification settings - Fork 0
Audit remediation: security hardening, power-user options, packaging & docs #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c258f6e
0aab10c
372867a
5cc9623
018e734
9e6e504
2831f98
8b0d87e
2cb5a51
cac9cbc
e108533
fb7b397
2f8a9cd
df563b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| name: Bug report | ||
| description: Something in sitepass misbehaves | ||
| body: | ||
| - type: dropdown | ||
| id: adapter | ||
| attributes: | ||
| label: Adapter | ||
| description: Which integration are you using? | ||
| options: | ||
| - cloudflare | ||
| - netlify | ||
| - next | ||
| - astro | ||
| - sveltekit | ||
| - express | ||
| - hono | ||
| - bun | ||
| - reverse proxy (sitepass proxy) | ||
| - CLI (sitepass init) | ||
| - core (createGate) | ||
| validations: | ||
| required: true | ||
| - type: input | ||
| id: versions | ||
| attributes: | ||
| label: Versions | ||
| description: sitepass version, Node/Bun version, and the framework version if relevant | ||
| placeholder: sitepass 0.1.1, Node 22.11, Next 16.2 | ||
| validations: | ||
| required: true | ||
| - type: textarea | ||
| id: repro | ||
| attributes: | ||
| label: What happened, and how to reproduce it | ||
| description: Config (redact your password/secret!), the request you made, what you expected, what you got. | ||
| validations: | ||
| required: true | ||
| - type: markdown | ||
| attributes: | ||
| value: > | ||
| **Security issues:** please do not file them here — use the private | ||
| reporting channel described in | ||
| [SECURITY.md](https://github.com/PeterM45/sitepass/blob/main/SECURITY.md). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| <!-- Thanks! Two notes before you open this: | ||
|
|
||
| 1. Read CONTRIBUTING.md — especially the guardrails (no client-side gates, | ||
| Web Crypto only in core, zero runtime dependencies). | ||
| 2. Security fixes should go through private reporting (SECURITY.md), not a PR. | ||
| --> | ||
|
|
||
| ## What | ||
|
|
||
| <!-- One or two sentences: what changes and why. --> | ||
|
|
||
| ## Checklist | ||
|
|
||
| - [ ] `bun run typecheck && bun run check && bun run test` pass | ||
| - [ ] New behavior is covered by a test | ||
| - [ ] No new runtime dependencies |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Keeps the SHA-pinned actions fresh (Dependabot updates the pin and the | ||
| # version comment together) and surfaces dev-dependency updates. The published | ||
| # package has zero runtime dependencies, so npm updates only touch the | ||
| # toolchain. | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: github-actions | ||
| directory: / | ||
| schedule: | ||
| interval: weekly | ||
| - package-ecosystem: bun | ||
| directory: / | ||
| schedule: | ||
| interval: weekly | ||
| groups: | ||
| dev-dependencies: | ||
| patterns: | ||
| - '*' |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,114 @@ All notable changes to this project are documented here. The format is based on | |||||||||
| [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project | ||||||||||
| adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||||||||
|
|
||||||||||
| ## [Unreleased] | ||||||||||
|
|
||||||||||
| Because of the two behavior changes below, this should ship as **0.2.0**, not a | ||||||||||
| 0.1.x patch — `^0.1` consumers should not pick it up automatically. | ||||||||||
|
|
||||||||||
| Heads-up for upgraders, two deliberate behavior changes: | ||||||||||
|
|
||||||||||
| - **Existing sessions are invalidated once on upgrade.** Tokens now bind a | ||||||||||
| digest of the password into the signed message, so visitors log in once more | ||||||||||
| with the unchanged password — and from then on, rotating the password | ||||||||||
| actually revokes outstanding sessions. | ||||||||||
| - **Secrets shorter than 16 characters now fail closed** (503 with the | ||||||||||
| not-configured page) instead of silently signing tokens with a | ||||||||||
| brute-forceable key. Anything written by `sitepass init` is unaffected. | ||||||||||
|
|
||||||||||
| ### Security | ||||||||||
|
|
||||||||||
| - **Core:** `publicPaths` matching now rejects literal dot-segments, backslash, | ||||||||||
| and path-parameter (`;`) segments, plus their encoded forms (`%2e`, `%2f`, | ||||||||||
| `%5c`, `%3b`, and a stray `%25` that a double-decoding origin could unwrap), | ||||||||||
| closing the remaining traversal route: the reverse proxy and Express adapters | ||||||||||
| pass the raw request target into the gate, so `/assets/../secret` matched an | ||||||||||
| `/assets` prefix verbatim while resolving elsewhere at the origin. (The basic | ||||||||||
| encoded form was fixed in 0.1.1; the edge adapters were never affected because | ||||||||||
| `URL` normalizes the pathname.) | ||||||||||
| - **Core:** a `publicPaths` entry of `/` is now an exact match on the root | ||||||||||
| path. Previously it un-gated the entire site, because trailing-slash | ||||||||||
| normalization reduced it to a prefix that matched every path. Empty entries | ||||||||||
| are ignored. | ||||||||||
| - **Core:** rotating `SITEPASS_PASSWORD` now invalidates all outstanding | ||||||||||
| sessions (previously only rotating the secret did, and nothing documented | ||||||||||
| that). | ||||||||||
| - **All adapters:** the login `POST` body is read with a 64 KiB cap and fails | ||||||||||
| closed with `413` on every adapter (the reverse proxy caps its login body at | ||||||||||
| 64 KiB too, separately from its larger forward-body limit). Previously only | ||||||||||
| Express was capped; Bun and Hono — self-hosted runtimes with no platform | ||||||||||
| limit — buffered without bound. | ||||||||||
| - **Reverse proxy:** the gate's own session cookie and the `x-sitepass-bypass` | ||||||||||
| credential are stripped before forwarding, so origin-side logs can no longer | ||||||||||
| capture a replayable credential. Other cookies forward unchanged. The | ||||||||||
| `X-Forwarded-For`/`-Proto`/`-Host` headers are set authoritatively from the | ||||||||||
| proxy-observed connection, so a client cannot spoof them to the origin. | ||||||||||
|
|
||||||||||
| ### Fixed | ||||||||||
|
|
||||||||||
| - **Core:** the login `POST` is handled before `publicPaths` matching, so an | ||||||||||
| entry covering `loginPath` can no longer make logging in impossible. | ||||||||||
| - **Cloudflare:** `export const onRequest: PagesFunction<Env> = gate()` now | ||||||||||
| typechecks (the env slice no longer demands an index signature), and | ||||||||||
| non-string bindings count as unset instead of leaking into the gate. | ||||||||||
| - **Bun:** the wrapper is generic over the handler's rest arguments, so the | ||||||||||
| `(req, server) => server.upgrade(req)` websocket pattern compiles and | ||||||||||
| `server` is actually forwarded. | ||||||||||
| - **Netlify:** importing the adapter outside the Edge runtime fails closed | ||||||||||
| (503) instead of throwing `ReferenceError: Netlify is not defined`. | ||||||||||
| - **CLI:** `--help`/`-h` print usage and exit 0 (previously "Unknown command" | ||||||||||
| and exit 1); `sitepass init --help` prints usage instead of starting an | ||||||||||
| interactive init; `.env` loading no longer silently does nothing on Node | ||||||||||
| 20.0–20.11; unknown flags are an error instead of being ignored; a missing | ||||||||||
| `SITEPASS_PASSWORD` warns at proxy startup just like a missing secret. | ||||||||||
| - **Docs:** the README no longer claims the SvelteKit adapter gates static | ||||||||||
| output (prerendered pages and `/_app` client assets bypass server hooks), | ||||||||||
| documents the Bun adapter's `gate(handler, options)` signature, and corrects | ||||||||||
| the localhost/`Secure`-cookie guidance (Chrome and Firefox allow it over | ||||||||||
| plain-HTTP localhost; Safari does not). | ||||||||||
|
|
||||||||||
| ### Added | ||||||||||
|
|
||||||||||
| - **Bypass token for CI, E2E, and uptime monitors:** set | ||||||||||
| `SITEPASS_BYPASS_TOKEN` (or the `bypassToken` option) and send the | ||||||||||
| `x-sitepass-bypass` header to pass the gate without a session. Constant-time | ||||||||||
| comparison, same as the password check. | ||||||||||
| - **Logout:** `GET <loginPath>/logout` clears the session cookie and redirects | ||||||||||
| to `/`. | ||||||||||
| - **`renderLoginPage` option** to fully replace the built-in login page | ||||||||||
| (localization, logos), plus an exported `escapeHtml` helper for safe | ||||||||||
| interpolation. | ||||||||||
| - **`onAuthFailure` option:** an observer called on every failed login | ||||||||||
| attempt, for fail2ban-style logging on platforms without access logs. It | ||||||||||
| receives only a redacted `{ method, path }` view — never the submitted | ||||||||||
| password or session cookie — so wiring it to logs can't persist credentials. | ||||||||||
| - **`cookieSecure: false` option** (and the proxy's `--insecure-cookie` flag) | ||||||||||
| for plain-HTTP LAN deployments, which previously failed as a silent login | ||||||||||
| loop. | ||||||||||
| - **`maxBodyBytes` option on every adapter** (login body cap; documented now — | ||||||||||
| previously Express-only and undocumented). | ||||||||||
| - **`sitepass/proxy` export:** `startProxy(options)` is now importable for | ||||||||||
| programmatic use; it accepts every gate option. (The files already shipped | ||||||||||
| in the tarball but were unreachable.) | ||||||||||
| - **Reverse proxy:** sends authoritative `X-Forwarded-For`/`-Proto`/`-Host` to | ||||||||||
| the origin; the CLI exposes gate options as flags (`--public-paths`, | ||||||||||
| `--login-path`, `--cookie-name`, `--session-seconds`, `--bypass-token`, | ||||||||||
| `--env-file`), with `--session-seconds` validated like `--port`. | ||||||||||
| - **CLI:** `--version`/`-v`, and `--env-file` for monorepos. | ||||||||||
| - `"sideEffects": false` for better tree-shaking, and `CHANGELOG.md` now ships | ||||||||||
| in the npm tarball. | ||||||||||
| - **Types:** every adapter exports its options type (`CloudflareGateOptions`, | ||||||||||
| …) and context interface; `publicPaths` accepts `readonly string[]`; the | ||||||||||
| declarations are precise under `exactOptionalPropertyTypes`; | ||||||||||
| `@types/express` is declared as an optional peer dependency; a | ||||||||||
| `typesVersions` map resolves subpath types under legacy node10 module | ||||||||||
| resolution (TypeScript consumers on `moduleResolution: node` previously got | ||||||||||
| no types for `sitepass/cloudflare` et al). | ||||||||||
| - **CI:** every built `dist` entry is now imported in both formats (plus a CLI | ||||||||||
| run and `publint`) before merge and before publish; the publish workflow | ||||||||||
| refuses a tag that does not match `package.json`'s version; Dependabot keeps | ||||||||||
| the SHA-pinned actions fresh; issue and PR templates. | ||||||||||
|
|
||||||||||
| ## [0.1.1] - 2026-06-05 | ||||||||||
|
|
||||||||||
| Security and hardening release. **Upgrading is recommended for all users**, in | ||||||||||
|
|
@@ -36,8 +144,9 @@ particular anyone using the Express adapter or the `sitepass init` CLI. | |||||||||
| patched releases. These are build-time only — the published package has no | ||||||||||
| runtime dependencies, so consumers were never exposed. | ||||||||||
|
|
||||||||||
| ## [0.1.0] | ||||||||||
| ## [0.1.0] - 2026-06-04 | ||||||||||
|
|
||||||||||
| Initial release. | ||||||||||
|
|
||||||||||
| [0.1.1]: https://github.com/PeterM45/sitepass/releases/tag/v0.1.1 | ||||||||||
| [0.1.0]: https://www.npmjs.com/package/sitepass/v/0.1.0 | ||||||||||
|
Comment on lines
151
to
+152
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Inconsistent version link targets. The 0.1.1 link points to a GitHub release tag, while 0.1.0 points to the npm package page. For consistency and to follow common changelog conventions, both should point to GitHub release tags (e.g., 📝 Suggested fix for consistency-[0.1.0]: https://www.npmjs.com/package/sitepass/v/0.1.0
+[0.1.0]: https://github.com/PeterM45/sitepass/releases/tag/v0.1.0📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden CI: disable persisted checkout credentials and Bun executable cache
actions/checkout): addwith: persist-credentials: falseto avoid leaving auth/SSH credentials on the runner.oven-sh/setup-bun): addwith: no-cache: trueto disable Bun executable caching.Suggested patch
📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 28-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 29-29: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default
(cache-poisoning)
🤖 Prompt for AI Agents