Skip to content

fix: prevent browser caching of sensitive pages over HTTPS - #1194

Open
DevipriyaS17 wants to merge 1 commit into
mainfrom
caching
Open

fix: prevent browser caching of sensitive pages over HTTPS#1194
DevipriyaS17 wants to merge 1 commit into
mainfrom
caching

Conversation

@DevipriyaS17

Copy link
Copy Markdown
Contributor

Summary:
This PR addresses by adding anti-caching response headers to sensitive Console response paths so browsers do not store local copies of authenticated/API and UI document content.

Issue Context:
The content delivered over HTTPS could be cached by browsers and later exposed on shared machines.

Security Headers Enforced:

  1. Cache-Control: no-cache, no-store, must-revalidate
  2. Pragma: no-cache
  3. Expires: -1

Behavior Notes:

  1. Sensitive API responses are now consistently marked non-cacheable.
  2. UI shell/fallback document responses are now consistently marked non-cacheable.
  3. Static assets remain cacheable by design for performance (not changed in this PR).

Manual Validation (Input and Output):

  1. Authorize request
Output:
HTTP/2 200
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
expires: -1
content-type: application/json; charset=utf-8
body: {"token":"<jwt>"}

Result: PASS
  1. Authenticated protected GET
curl --noproxy '*' -k -i -m 10 https://127.0.0.1:8181/api/v1/server/features -H Authorization: Bearer <jwt>
Output:
HTTP/2 200
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
expires: -1
content-type: application/json; charset=utf-8
body: {"ciraEnabled":false}
Result: PASS

  1. Authenticated admin POST (reported endpoint class)
    Input:
curl --noproxy '*' -k -i -m 10 https://127.0.0.1:8181/api/v1/admin/ieee8021xconfigs -H Authorization: Bearer <jwt> -H Content-Type: application/json -d {"profileName":"cacheHdrTest...","authenticationProtocol":0,"pxeTimeout":0,"wiredInterface":false,"version":""}
Output:
HTTP/2 201
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
expires: -1
content-type: application/json; charset=utf-8
body: {"profileName":"cacheHdrTest...","authenticationProtocol":0,"pxeTimeout":0,"wiredInterface":false,}
Result: PASS

  1. Scanner-like malformed path
    Input:
curl --globoff --noproxy '*' -k -i -m 10 https://127.0.0.1:8181/a%27a%5c%27b%22c%3e%3f%3e%25%7d%7d%25%25%3ec%3c[[%3f$%7b%7b%25%7d%7dcake%5c/v1/admin/domains/ouxokhIa -H Authorization: Bearer dummy
Output:
HTTP/2 200
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
expires: -1
content-type: text/html; charset=utf-8
body: <!doctype html>...
Result: PASS (fallback HTML now protected by no-cache headers)
  1. Protected API without token
curl --noproxy '*' -k -i -m 10 https://127.0.0.1:8181/api/v1/server/features
Output:
HTTP/2 401
cache-control: no-cache, no-store, must-revalidate
pragma: no-cache
expires: -1
content-type: application/json; charset=utf-8
body: {"error":"request does not contain an access token"}
Result: PASS

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds explicit anti-caching response headers to prevent browsers from storing authenticated API responses and the UI shell/fallback HTML, reducing the risk of sensitive content being retrievable from shared machines.

Changes:

  • Introduced reusable setNoCacheHeaders + noCacheHeadersMiddleware and applied them to /api/* routes.
  • Ensured UI document responses (/ and SPA fallback) are served with no-cache/no-store headers.
  • Added unit tests to validate no-cache headers for the UI fallback and the middleware.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
internal/controller/httpapi/router.go Adds no-cache constants/helpers and applies middleware to /api routing.
internal/controller/httpapi/ui.go Adds no-cache headers to UI root and SPA fallback document responses.
internal/controller/httpapi/ui_test.go Adds tests asserting no-cache headers for UI fallback and middleware behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/controller/httpapi/router.go
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.25926% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.61%. Comparing base (1900d7d) to head (95f99c4).

Files with missing lines Patch % Lines
internal/controller/httpapi/ui.go 35.29% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1194      +/-   ##
==========================================
+ Coverage   50.09%   50.61%   +0.51%     
==========================================
  Files         146      146              
  Lines       13552    13574      +22     
==========================================
+ Hits         6789     6870      +81     
+ Misses       6171     6105      -66     
- Partials      592      599       +7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (3)

internal/controller/httpapi/ui.go:88

  • Same issue in the SPA fallback: using FileFromFS("./", ...) targets a directory. Serving index.html explicitly avoids directory listing / inconsistent responses when the embedded UI isn't present.
		setNoCacheHeaders(c)
		c.FileFromFS("./", http.FS(staticFiles))
	})

internal/controller/httpapi/ui.go:48

  • Serving the UI shell via FileFromFS("./", ...) points at a directory, which can yield inconsistent behavior (e.g., directory listing or a redirect/404) when index.html is missing. Since this route is intended to serve the SPA shell, serve index.html explicitly.
	handler.GET("/", func(c *gin.Context) {
		setNoCacheHeaders(c)
		c.FileFromFS("./", http.FS(staticFiles))
	})

internal/controller/httpapi/ui_test.go:120

  • This test assumes the embedded UI root always returns 200, but in this repo the embedded ui/ directory can be empty (e.g., local builds), so the handler may legitimately return 404. The assertion should focus on the no-cache headers rather than a fixed status code.
	require.Equal(t, http.StatusOK, w.Code)
	require.Equal(t, cacheControlNoStore, w.Header().Get("Cache-Control"))
	require.Equal(t, pragmaNoCache, w.Header().Get("Pragma"))
	require.Equal(t, expiresNoCache, w.Header().Get("Expires"))

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (4)

internal/controller/httpapi/ui_test.go:127

  • This test runs in parallel but calls setupUIRoutes, which writes a fixed filename to the process temp dir (see injectConfigToMainJS using os.TempDir()/main.js). Parallel runs can race/flap by clobbering the same temp file; isolate the temp dir per test (or avoid running these in parallel).
	t.Parallel()

	engine := gin.New()
	setupUIRoutes(engine, logger.New("error"), &config.Config{})

internal/controller/httpapi/ui_test.go:110

  • This test runs in parallel but calls setupUIRoutes, which writes a fixed filename to the process temp dir (see injectConfigToMainJS using os.TempDir()/main.js). Parallel runs can race/flap by clobbering the same temp file; isolate the temp dir per test (or avoid running these in parallel).
	t.Parallel()

	engine := gin.New()
	setupUIRoutes(engine, logger.New("error"), &config.Config{})

internal/controller/httpapi/router.go:27

  • Expires header value "-1" is not a valid HTTP-date (RFC 9110/9111). Some clients/caches may ignore it; prefer a valid value like "0" (legacy) or a past HTTP-date while keeping Cache-Control: no-store as the primary control.
const (
	cacheControlNoStore = "no-cache, no-store, must-revalidate"
	pragmaNoCache       = "no-cache"
	expiresNoCache      = "-1"
)

internal/controller/httpapi/ui_test.go:93

  • This test runs in parallel but calls setupUIRoutes, which writes a fixed filename to the process temp dir (see injectConfigToMainJS using os.TempDir()/main.js). Parallel runs can race/flap by clobbering the same temp file; isolate the temp dir per test (or avoid running these in parallel).

This issue also appears in the following locations of the same file:

  • line 107
  • line 124
	t.Parallel()

	engine := gin.New()
	setupUIRoutes(engine, logger.New("error"), &config.Config{})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants