Tests / test (pull_request) Successful in 1m8s
The login page, failed logins, redirects, and rate-limit/error pages could be stored by the browser (Symfony's default 'no-cache, private' still permits storage — it only requires revalidation). Older Safari builds may then replay a stale pre-auth response on refresh, appearing to log the user back out, or show a previous session after logging in again. - SecurityHeadersListener: send strict anti-caching headers on non-2xx responses only (no-store/no-cache/must-revalidate/proxy-revalidate, max-age=0, s-maxage=0 + Pragma, Expires, Surrogate-Control, Vary: *). 2xx grants (already-authenticated / public access) are consumed by Caddy's forward_auth check and never reach the browser, and protected services' own cache headers must stay untouched. - templates/_script.html.twig: fetch() with cache: 'no-store'; follow redirects with location.replace() to keep the login page out of history and the back-forward cache. - docs/Caddyfile: reusable (preauth_no_store) snippet imported into every forward_auth block, using header_down so the guarantee holds at the edge (verified: replaces conflicting upstream values, leaves service responses alone). - tests: unit coverage for the listener and functional coverage for the full HTTP kernel (login/failure/redirect/rate-limit not cacheable; 200 grants untouched); asserts the rendered page carries the JS changes. - readme/CHANGELOG updates.
8.4 KiB
8.4 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased] — v1.1
Added
- Public rate-limited access — Select paths can now be made publicly
accessible without TOTP authentication, with separate per-IP rate limiting.
This is useful for exposing public content (e.g., public Gitea repositories)
while protecting server resources from bot traffic.
- New
PUBLIC_PATHSenv var: comma-separated path patterns with*(single segment) and**(cross-segment) wildcard support. Optional host prefix (e.g.,code.example.com/public/**). When empty (default), the feature is fully disabled. - New
PUBLIC_BURST_COUNT/PUBLIC_BURST_TIMEenv vars for burst rate limiting (default: 100 requests per 60 seconds). - New
PUBLIC_UPPER_COUNT/PUBLIC_UPPER_TIMEenv vars for sustained rate limiting (default: 500 requests per 3600 seconds). - Authenticated users bypass the public rate limiter entirely.
- Over-limit responses include a
Retry-Afterheader. - New
PublicPathMatcherservice for path pattern matching. - New
PublicAccessListener(priority 84) in the request pipeline.
- New
Changed
- Upgraded Symfony 7.4 → 8.1 — All
symfony/*components bumped to8.1.*(resolved to 8.1.2–8.1.6). The 7.4 deprecation sweep was clean (test suite runs withfailOnDeprecation), so the major-version jump required no application code changes. Seedocs/symfony-8.1-upgrade-plan.md.
Removed
runtime/frankenphp-symfony— No longer needed:symfony/runtime8.1 handles FrankenPHP worker mode natively via its built-inFrankenPhpWorkerRunner. Theextra.runtimeoverride incomposer.jsonwas removed so the runtime auto-detects FrankenPHP. The old package'sFRANKENPHP_LOOP_MAXenv var is no longer read; an equivalent recycle limit is restored via the newMAX_REQUESTSsetting below.
Added
MAX_REQUESTSworker-thread recycle limit — TheCaddyfilenow sets FrankenPHP's nativemax_requestsfrom theMAX_REQUESTSenvironment variable: each PHP worker thread is gracefully restarted after N requests while others keep serving, containing slow memory growth across long uptime. The image default is 500 (matching the previousruntime/frankenphp-symfonydefault), baked in as a Docker build arg and overridable at runtime (MAX_REQUESTS=0disables restarts). Arbitraryfrankenphp-block configuration is still possible via the stockFRANKENPHP_CONFIGenv var.
Fixed
- Login flow responses are no longer cacheable — the login page,
failed logins, redirects, and rate-limit/error pages now send strict
anti-caching headers (
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0, s-maxage=0plusPragma,Expires,Surrogate-Control, andVary: *), the login form'sfetch()bypasses the HTTP cache, and the example Caddyfile guards everyforward_authblock with matchingheader_downrules. This prevents browsers — notably older Safari — from replaying a stale pre-auth response on refresh (previously: log in successfully, refresh, and land back on the login page). Successful (2xx) responses are deliberately excluded: they are consumed by the proxy'sforward_authcheck and never reach the browser.
[1.0.0] — v1.0 Release
Security
- Made
Remote-Userheader value configurable viaREMOTE_USERenvironment variable with four modes:session(default),static,mapped, andnone. This allows deployments to prevent user-controlled header values from reaching backend services. - Added
SecurityHeadersListenerto setX-Content-Type-Options,X-Frame-Options,Content-Security-Policy,Referrer-Policy, andStrict-Transport-Securityheaders on all responses. - Replaced
document.write()withdocument.documentElement.innerHTMLin login page JavaScript to avoid CSP violations. - Added CSS escaping (
|e('css')) to environment-configured color values in the login page template to prevent CSS injection. - Documented CSRF protection model: the nonce system provides CSRF protection for POST form logins (server-generated, single-use, 120s TTL).
- Reduced TOTP verification window from 10 periods (±5 minutes) to 1 period (±30 seconds) to reduce brute-force attack surface.
- Removed hardcoded
APP_SECRETfrombin/franken.sh(now uses environment variable or generates a random secret). - Removed backup code values from debug log output.
- Added
.envto.gitignore. - Expanded TLD list in
DomainManagerwith many missing multi-part TLDs (.com.au,.co.jp,.com.br,.co.kr,.com.tw,.co.za, etc.) to prevent open redirect vulnerabilities from incorrect domain matching. - Lowercased host before TLD lookup to fix case-sensitivity issue.
Fixed
- Fixed
$payload->jsonaccess on possibly-null$payloadinLoginListenerusing null-safe operator (?->). - Fixed
validReturn()not checkingfalsereturn fromparse_url(), which could cause aTypeErroron malformed URLs. - Added
isHit()race condition check inAcceptListenerandAllowListenerbetweenhasItem()andgetItem()calls. - Added
try/finallyinKernel::terminate()soparent::terminate()always runs even ifpersist()throws an exception. - Added input validation to
GenerateBackupCodesCommand— rejects count < 1.
Changed
- Disabled unused Symfony sessions in
framework.yaml(preauth implements its own cookie/cache-based session management). - Standardized git tag format to use
vprefix (v1.0.0instead of1.0.0). - Updated CI workflows to use
v*.*.*tag pattern and stripvprefix for Docker image tags. - Removed stale
developbranch from CI triggers. - Fixed
publish.yamlto usegit remote set-urlon re-runs instead of failing when the remote already exists. - Explicitly install
curlin the Docker final image (needed for healthcheck). - Added
declare(strict_types=1)to all interface files. - Added
#[AsCommand]attribute toGenerateBackupCodesCommand. - Fixed
BackupCodeInterfacedefault count to match implementation (10). - Used
Response::HTTP_INTERNAL_SERVER_ERRORconstant inGetTotpTraitinstead of literal500.
[0.10.0] - 2026-08-11
Added
- PHP-CS-Fixer with PSR-12 configuration and CI check.
[0.9.0] - 2026-07-15
Added
- PHPUnit test suite — 222 tests, 100% code coverage (lines, methods, classes).
[0.8.1] - 2026-05-30
Fixed
- Bug fixes and cleanup from develop branch merge.
[0.8.0] - 2026-05-29
Changed
- Renamed form fields for clarity.
- Fixed invalid login bug.
[0.7.0] - 2026-05-29
Added
- Single-use backup codes via
app:generate-backup-codesconsole command. - Cache persistence improvement — only write changed keys to file storage.
Removed
- Static password and lookup token (security risks).
Changed
- Updated to PHP 8.5, updated dependencies.
[0.6.0] - 2026-02-10
Added
- Optional (disabled by default) ability to lookup token by static password.
[0.5.0] - 2026-01-17
Added
- Optional (disabled by default) ability to use a static password as backup auth.
Changed
- Nonce-related cleanup.
[0.4.1] - 2025-12-26
Fixed
- Bug which can occur if cache files are deleted.
[0.4.0] - 2025-12-26
Changed
- Massive rewrite to listener-based architecture instead of controllers.
- Login payload sent via
X-Preauthheader instead of GET request parameters. - Enhanced cookie security.
- Removed icon system and asset system.
[0.3.0] - 2025-12-15
Changed
- Breaking: Default port and transport changed to HTTP on port 80.
- Breaking: Environment variable names have changed.
- Refactored to Symfony 7.4 with FrankenPHP.
[0.2.0] - 2025-12-03
Added
- Login rate limiting (burst + upper window).
- Error page for rate-limited clients ("too many requests").
- Example Docker Compose file.
[0.1.0] - 2025-11-14
Added
- Docker image published to Docker Hub.
- PHP-FPM based, code in
src/, templates in separate files.
[0.0.1] - 2024-06-26
Notes
- Started as a single-file script in Caddy config. Hardcoded TOTP secret, zero flexibility, but functional. Ran quietly in production for about a year before any real development began.