4.9 KiB
4.9 KiB
QA: security and privacy hardening
Everything below runs in task test (package test, -tags nofrontend) unless marked manual.
Cross-origin protection and cookie flags
test/cross_origin_protection_http_test.go: wrap a counting handler inmiddleware.CrossOriginProtection("https://cms.example.com").- Refused with 403 and
apperr.CrossOriginErrorMessage, handler not reached: a cross-site POST, a same-site POST fromhttps://blog.example.com, a cross-site DELETE, and a POST with onlyOrigin: https://evil.example. - Allowed: same-origin POST,
Sec-Fetch-Site: none, no headers, cross-site GET and OPTIONS, a cross-site POST withAuthorization: Bearer rcms_key, and a POST whoseOriginis the public URL whileHostis127.0.0.1:8080. - With
PUBLIC_URL::not a url, a cross-site POST is still 403.
- Refused with 403 and
test/session_revocation_http_test.godrivesapi.InitRoutes: the login cookie is HttpOnly, Secure andSameSite=Lax, and a cross-sitePOST /v1/auth/logoutis 403 and revokes nothing.
Session revocation
test/authtoken_test.go(TestAuthTokenRevocation,TestAuthTokenDecodeRejections): distinctjtiper token; explicitjtikept; a revoked token givesErrRevokedand is stored until itsexp; other tokens stay valid; store read failure givesErrInvalid; store write failure is returned byRevoke; nojtiis a no-op; noexprefuses; tokens withoutexpor with a non-stringjtiare refused byDecode.TestSessionRevocationUseCase: an already-expired session is not stored; an empty or 65-byte id issession.ErrInvalidSessionID; 64 bytes is accepted.TestRevokedSessionRepositoryon a migrated SQLite file: revoking twice is harmless,DeleteExpired(now)removes rows whoseexpires_at <= nowand reports the count.TestLogoutRevokesTheSessionthroughapi.InitRoutes: two logins, logout with the first,/mewith it is 401 and with the second is 200; logout without a cookie is 200 and stores nothing; a failing write gives 500 with the cookie cleared; a failing read makes/me401.test/verified_account_required_http_test.go: a verified user whosetoken_versionmoved on only sees online articles.
Passwords
test/password_length_limit_test.go: 72 bytes accepted, 73 bytes and 37 × "é" refused byuser.ValidatePassword; register, reset (valid code) and invite accept answer 400 withapperr.PasswordTooLongErrorMessageand change nothing; a wrong reset code still answers 403.test/hasher_test.go: an unknown username compares against a decoy hash once per attempt and hashes the decoy once.
Configuration and mail
test/runtime_settings_from_env_test.go: empty, short, 31-byte and both exampleJWT_SECRETvalues fail withconfig.ErrWeakJWTSecretand mentionopenssl rand -hex 32; 32 bytes passes.SMTP_ALLOW_INSECUREreads""/falseas false,true/TRUEas true, andmaybefails naming the variable.test/smtp_mailer_test.goagainst the fake server on127.0.0.2: no STARTTLS and no opt-out fails beforeMAIL FROMnamingSMTP_ALLOW_INSECURE; with the opt-out the message is delivered in clear text; with the opt-out and credentials, nothing is sent.
Upload metadata
test/image_metadata_stripping_test.gobuilds JPEG, PNG and WebP files carrying a GPS-like marker in EXIF, XMP, IPTC, comments, text chunks, MPF and trailing data. Assert the marker is gone, ICC is kept, pixels decode identically, the orientation-only EXIF appears only when the orientation is not 1 and sits in the documented place, WebP flags and RIFF size are correct, a plain WebP and an SVG come back unchanged, and each malformed container isErrImageUnreadable.TestUploadStoresTheStrippedImage: the stored bytes carry no marker andSize/SHA256match them.TestUploadRefusesAnImageWhoseContainerIsBroken: nothing is stored.
Retention
test/personal_data_retention_test.go: the user and invite rules at their boundaries; one run over a seeded world removes exactly one expired revocation, one abandoned account and two invites; a second run removes nothing; a session store failure touches nothing else; a user or invite deletion failure returns the partial counts; the runner logs counts only when something was removed and logs failures.
Fonts
test/security_headers_test.go: the CSP containsfont-src 'self';and no Google host.- Manual:
task build:web, open the back office with the network panel; no request leaves forfonts.googleapis.comorfonts.gstatic.com, andtask visual:check -- log-inpasses.
Manual verification
- Behind the real reverse proxy, log in, copy the
jwtcookie, log out, then replay the cookie withcurl:/v1/auth/meanswers 401. - With an SMTP relay that lacks STARTTLS on a non-loopback host, sending fails until
SMTP_ALLOW_INSECURE=trueis set.