PrintHeader doesn't set Cache-Control, X-Frame-Options, X-Content-Type-Options, or Referrer-Policy for any response. The static file path adds Cache-Control: public (which is also wrong direction for authenticated content) but everything else goes out bare.
No X-Frame-Options / frame-ancestors → webadmin pages framable. CSRF is enforced server-side (WebModules.cpp:661-669), but the admin's browser still carries the cookie + token, so clickjacking that triggers a state-changing form submit will pass the token check.
No X-Content-Type-Options: nosniff → endpoints that serve text/plain (samplewebapi, any module that does WebSock.Write(text) with Content-Type: text/plain) are MIME-sniffed by old browsers and can yield reflected XSS.
No Cache-Control on authenticated HTML → previously rendered admin pages can be reread from browser history on a shared workstation.
Also at line 378, change Cache-Control: public for static files to Cache-Control: private, max-age=... so shared HTTP caches don't fan out skin/avatar resources across users.
Happy to PR. Easy to test with curl -i.
`PrintHeader` doesn't set `Cache-Control`, `X-Frame-Options`, `X-Content-Type-Options`, or `Referrer-Policy` for any response. The static file path adds `Cache-Control: public` (which is also wrong direction for authenticated content) but everything else goes out bare.
```cpp
// src/HTTPSock.cpp
716: bool CHTTPSock::PrintHeader(off_t uContentLength, const CString& sContentType,
unsigned int uStatusId, const CString& sStatusMsg) {
...
734: Write("HTTP/" + ... + "\r\n");
735: Write("Date: " + GetDate() + "\r\n");
736: Write("Server: " + CZNC::GetTag(false) + "\r\n");
737: if (uContentLength > 0) Write("Content-Length: ...");
741: Write("Content-Type: " + m_sContentType + "\r\n");
742: for (Set-Cookie: ...) // SameSite=Strict; HttpOnly; Secure-when-SSL
749: for (auto& it : m_msHeaders) ...
753: Write("Connection: Close\r\n");
755: Write("\r\n");
```
Concrete consequences:
- No `X-Frame-Options` / `frame-ancestors` → webadmin pages framable. CSRF is enforced server-side (`WebModules.cpp:661-669`), but the admin's browser still carries the cookie + token, so clickjacking that triggers a state-changing form submit will pass the token check.
- No `X-Content-Type-Options: nosniff` → endpoints that serve `text/plain` (`samplewebapi`, any module that does `WebSock.Write(text)` with `Content-Type: text/plain`) are MIME-sniffed by old browsers and can yield reflected XSS.
- No `Cache-Control` on authenticated HTML → previously rendered admin pages can be reread from browser history on a shared workstation.
## Fix
In `PrintHeader`, before the trailing `\r\n`:
```cpp
Write("X-Frame-Options: SAMEORIGIN\r\n");
Write("X-Content-Type-Options: nosniff\r\n");
Write("Referrer-Policy: same-origin\r\n");
if (uStatusId != 304 && !m_sContentType.StartsWith("image/") &&
!m_sContentType.StartsWith("font/") && !m_sContentType.StartsWith("text/css")) {
Write("Cache-Control: no-store, no-cache, must-revalidate, max-age=0\r\n");
Write("Pragma: no-cache\r\n");
}
```
Also at line 378, change `Cache-Control: public` for static files to `Cache-Control: private, max-age=...` so shared HTTP caches don't fan out skin/avatar resources across users.
Happy to PR. Easy to test with `curl -i`.
<!-- github-sync-id:4326795839 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
PrintHeaderdoesn't setCache-Control,X-Frame-Options,X-Content-Type-Options, orReferrer-Policyfor any response. The static file path addsCache-Control: public(which is also wrong direction for authenticated content) but everything else goes out bare.Concrete consequences:
X-Frame-Options/frame-ancestors→ webadmin pages framable. CSRF is enforced server-side (WebModules.cpp:661-669), but the admin's browser still carries the cookie + token, so clickjacking that triggers a state-changing form submit will pass the token check.X-Content-Type-Options: nosniff→ endpoints that servetext/plain(samplewebapi, any module that doesWebSock.Write(text)withContent-Type: text/plain) are MIME-sniffed by old browsers and can yield reflected XSS.Cache-Controlon authenticated HTML → previously rendered admin pages can be reread from browser history on a shared workstation.Fix
In
PrintHeader, before the trailing\r\n:Also at line 378, change
Cache-Control: publicfor static files toCache-Control: private, max-age=...so shared HTTP caches don't fan out skin/avatar resources across users.Happy to PR. Easy to test with
curl -i.