diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2f36963f..c61672bf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - name: Initialize CodeQL - uses: github/codeql-action/init@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 + uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 with: languages: ${{ matrix.language }} @@ -45,9 +45,9 @@ jobs: go-version: '1.25.5' - name: Autobuild - uses: github/codeql-action/autobuild@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 + uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4 + uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4 with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 645e02b1..089c55ee 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -152,7 +152,7 @@ jobs: - name: Upload Trivy results if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: sarif_file: 'trivy-results.sarif' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 50bd4bab..8cedc971 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -157,7 +157,7 @@ jobs: - name: Upload Trivy results if: github.event_name != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: sarif_file: 'trivy-results.sarif' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security-weekly-rebuild.yml b/.github/workflows/security-weekly-rebuild.yml index 2ee60a3b..b3fd6421 100644 --- a/.github/workflows/security-weekly-rebuild.yml +++ b/.github/workflows/security-weekly-rebuild.yml @@ -97,7 +97,7 @@ jobs: severity: 'CRITICAL,HIGH,MEDIUM' - name: Upload Trivy results to GitHub Security - uses: github/codeql-action/upload-sarif@1b168cd39490f61582a9beae412bb7057a6b2c4e # v4.31.8 + uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9 with: sarif_file: 'trivy-weekly-results.sarif' diff --git a/backend/internal/api/middleware/auth.go b/backend/internal/api/middleware/auth.go index 8e874df5..5270620e 100644 --- a/backend/internal/api/middleware/auth.go +++ b/backend/internal/api/middleware/auth.go @@ -13,14 +13,17 @@ func AuthMiddleware(authService *services.AuthService) gin.HandlerFunc { authHeader := c.GetHeader("Authorization") if authHeader == "" { - // Try cookie first for browser flows + // Try cookie first for browser flows (including WebSocket upgrades) if cookie, err := c.Cookie("auth_token"); err == nil && cookie != "" { authHeader = "Bearer " + cookie } } + // DEPRECATED: Query parameter authentication for WebSocket connections + // This fallback exists only for backward compatibility and will be removed in a future version. + // Query parameters are logged in access logs and should not be used for sensitive data. + // Use HttpOnly cookies instead, which are automatically sent by browsers and not logged. if authHeader == "" { - // Try query param (token passthrough) if token := c.Query("token"); token != "" { authHeader = "Bearer " + token } diff --git a/backend/internal/api/middleware/auth_test.go b/backend/internal/api/middleware/auth_test.go index f9724973..e46ea3e3 100644 --- a/backend/internal/api/middleware/auth_test.go +++ b/backend/internal/api/middleware/auth_test.go @@ -184,3 +184,62 @@ func TestRequireRole_MissingRoleInContext(t *testing.T) { assert.Equal(t, http.StatusUnauthorized, w.Code) } + +func TestAuthMiddleware_QueryParamFallback(t *testing.T) { + authService := setupAuthService(t) + user, err := authService.Register("test@example.com", "password", "Test User") + require.NoError(t, err) + token, err := authService.GenerateToken(user) + require.NoError(t, err) + + gin.SetMode(gin.TestMode) + r := gin.New() + r.Use(AuthMiddleware(authService)) + r.GET("/test", func(c *gin.Context) { + userID, _ := c.Get("userID") + assert.Equal(t, user.ID, userID) + c.Status(http.StatusOK) + }) + + // Test that query param auth still works (deprecated fallback) + req, err := http.NewRequest("GET", "/test?token="+token, http.NoBody) + require.NoError(t, err) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestAuthMiddleware_PrefersCookieOverQueryParam(t *testing.T) { + authService := setupAuthService(t) + + // Create two different users + cookieUser, err := authService.Register("cookie@example.com", "password", "Cookie User") + require.NoError(t, err) + cookieToken, err := authService.GenerateToken(cookieUser) + require.NoError(t, err) + + queryUser, err := authService.Register("query@example.com", "password", "Query User") + require.NoError(t, err) + queryToken, err := authService.GenerateToken(queryUser) + require.NoError(t, err) + + gin.SetMode(gin.TestMode) + r := gin.New() + r.Use(AuthMiddleware(authService)) + r.GET("/test", func(c *gin.Context) { + userID, _ := c.Get("userID") + // Should use the cookie user, not the query param user + assert.Equal(t, cookieUser.ID, userID) + c.Status(http.StatusOK) + }) + + // Both cookie and query param provided - cookie should win + req, err := http.NewRequest("GET", "/test?token="+queryToken, http.NoBody) + require.NoError(t, err) + req.AddCookie(&http.Cookie{Name: "auth_token", Value: cookieToken}) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} diff --git a/docs/plans/prev_spec_websocket_fix_dec16.md b/docs/plans/prev_spec_websocket_fix_dec16.md index f175d969..dc4d90cc 100644 --- a/docs/plans/prev_spec_websocket_fix_dec16.md +++ b/docs/plans/prev_spec_websocket_fix_dec16.md @@ -25,42 +25,50 @@ ```text Frontend Backend ──────── ─────── -localStorage.getItem('charon_auth_token') - │ - ▼ -Query param: ?token= ────────► AuthMiddleware: - 1. Check Authorization header - 2. Check auth_token cookie - 3. Check token query param ◄── MATCHES - │ - ▼ - ValidateToken(jwt) → OK - │ - ▼ - Upgrade to WebSocket +User logs in + │ + ▼ +Backend sets HttpOnly auth_token cookie ──► AuthMiddleware: + │ 1. Check Authorization header + │ 2. Check auth_token cookie ◄── SECURE METHOD + │ 3. (Deprecated) Check token query param + ▼ │ +WebSocket connection initiated ▼ +(Cookie sent automatically by browser) ValidateToken(jwt) → OK + │ │ + │ ▼ + └──────────────────────────────────► Upgrade to WebSocket ``` +**Security Note:** Authentication now uses HttpOnly cookies instead of query parameters. +This prevents JWT tokens from being logged in access logs, proxies, and other telemetry. +The browser automatically sends the cookie with WebSocket upgrade requests. + ### Logic Gap Analysis **ANSWER: NO - There is NO logic gap between Frontend and Backend.** | Question | Answer | |----------|--------| -| Frontend auth method | Query param `?token=` from `localStorage.getItem('charon_auth_token')` | -| Backend auth method | Accepts: Header → Cookie → Query param `token` ✅ | +| Frontend auth method | HttpOnly cookie (`auth_token`) sent automatically by browser ✅ SECURE | +| Backend auth method | Accepts: Header → Cookie (preferred) → Query param (deprecated) ✅ | | Filter params | Both use `source`, `level`, `ip`, `host`, `blocked_only` ✅ | | Data format | `SecurityLogEntry` struct matches frontend TypeScript type ✅ | +| Security | Tokens no longer logged in access logs or exposed to XSS ✅ | --- ## 1. VERIFICATION STATUS -### ✅ localStorage Key IS Correct +### ✅ Authentication Method Updated for Security -Both WebSocket functions in `frontend/src/api/logs.ts` correctly use `charon_auth_token`: +WebSocket authentication now uses HttpOnly cookies instead of query parameters: -- **Line 119-122** (`connectLiveLogs`): `localStorage.getItem('charon_auth_token')` -- **Line 178-181** (`connectSecurityLogs`): `localStorage.getItem('charon_auth_token')` +- **`connectLiveLogs`** (frontend/src/api/logs.ts): Uses browser's automatic cookie transmission +- **`connectSecurityLogs`** (frontend/src/api/logs.ts): Uses browser's automatic cookie transmission +- **Backend middleware**: Prioritizes cookie-based auth, query param is deprecated + +This change prevents JWT tokens from appearing in access logs, proxy logs, and other telemetry. --- @@ -186,12 +194,13 @@ The `showBlockedOnly` state in useEffect dependencies causes reconnection when t | Component | Status | Notes | |-----------|--------|-------| -| localStorage key | ✅ Fixed | Now uses `charon_auth_token` | -| Auth middleware | ✅ Working | Accepts query param `token` | +| WebSocket authentication | ✅ Secured | Now uses HttpOnly cookies instead of query parameters | +| Auth middleware | ✅ Updated | Cookie-based auth prioritized, query param deprecated | | WebSocket endpoint | ✅ Working | Protected route, upgrades correctly | | LogWatcher service | ✅ Working | Tails access.log successfully | | **Frontend memoization** | ✅ Fixed | `useMemo` in Security.tsx | | **Stable default props** | ✅ Fixed | Constants in LiveLogViewer.tsx | +| **Security improvement** | ✅ Complete | Tokens no longer exposed in logs | --- @@ -221,7 +230,9 @@ docker logs charon 2>&1 | grep -i "cerberus.*websocket" | tail -10 **Logic Gap Between Frontend/Backend:** **NO** - Both are correctly aligned -**Current Status:** ✅ All fixes applied and working +**Security Enhancement:** WebSocket authentication now uses HttpOnly cookies instead of query parameters, preventing token leakage in logs + +**Current Status:** ✅ All fixes applied and working securely --- diff --git a/docs/security/websocket-auth-security.md b/docs/security/websocket-auth-security.md new file mode 100644 index 00000000..d150626c --- /dev/null +++ b/docs/security/websocket-auth-security.md @@ -0,0 +1,131 @@ +# WebSocket Authentication Security + +## Overview + +This document explains the security improvements made to WebSocket authentication in Charon to prevent JWT tokens from being exposed in access logs. + +## Security Issue + +### Before (Insecure) + +Previously, WebSocket connections authenticated by passing the JWT token as a query parameter: + +``` +wss://example.com/api/v1/logs/live?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +**Security Risk:** +- Query parameters are logged in web server access logs (Caddy, nginx, Apache, etc.) +- Tokens appear in proxy logs +- Tokens may be stored in browser history +- Tokens can be captured in monitoring and telemetry systems +- An attacker with access to these logs can replay the token to impersonate a user + +### After (Secure) + +WebSocket connections now authenticate using HttpOnly cookies: + +``` +wss://example.com/api/v1/logs/live?source=waf&level=error +``` + +The browser automatically sends the `auth_token` cookie with the WebSocket upgrade request. + +**Security Benefits:** +- ✅ HttpOnly cookies are **not logged** by web servers +- ✅ HttpOnly cookies **cannot be accessed** by JavaScript (XSS protection) +- ✅ Cookies are **not visible** in browser history +- ✅ Cookies are **not captured** in URL-based monitoring +- ✅ Token replay attacks are mitigated (tokens still have expiration) + +## Implementation Details + +### Frontend Changes + +**Location:** `frontend/src/api/logs.ts` + +Removed: +```typescript +const token = localStorage.getItem('charon_auth_token'); +if (token) { + params.append('token', token); +} +``` + +The browser automatically sends the `auth_token` cookie when establishing WebSocket connections due to: +1. The cookie is set by the backend during login with `HttpOnly`, `Secure`, and `SameSite` flags +2. The axios client has `withCredentials: true`, enabling cookie transmission + +### Backend Changes + +**Location:** `backend/internal/api/middleware/auth.go` + +Authentication priority order: +1. **Authorization header** (Bearer token) - for API clients +2. **auth_token cookie** (HttpOnly) - **preferred for browsers and WebSockets** +3. **token query parameter** - **deprecated**, kept for backward compatibility only + +The query parameter fallback is marked as deprecated and will be removed in a future version. + +### Cookie Configuration + +**Location:** `backend/internal/api/handlers/auth_handler.go` + +The `auth_token` cookie is set with security best practices: +- **HttpOnly**: `true` - prevents JavaScript access (XSS protection) +- **Secure**: `true` (in production with HTTPS) - prevents transmission over HTTP +- **SameSite**: `Strict` (HTTPS) or `Lax` (HTTP/IP) - CSRF protection +- **Path**: `/` - available for all routes +- **MaxAge**: 24 hours - automatic expiration + +## Verification + +### Test Coverage + +**Location:** `backend/internal/api/middleware/auth_test.go` + +- `TestAuthMiddleware_Cookie` - verifies cookie authentication works +- `TestAuthMiddleware_QueryParamFallback` - verifies deprecated query param still works +- `TestAuthMiddleware_PrefersCookieOverQueryParam` - verifies cookie is prioritized over query param +- `TestAuthMiddleware_PrefersAuthorizationHeader` - verifies header takes highest priority + +### Log Verification + +To verify tokens are not logged: + +1. **Before the fix:** Check Caddy access logs for token exposure: + ```bash + docker logs charon 2>&1 | grep "token=" | grep -o "token=[^&]*" + ``` + Would show: `token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` + +2. **After the fix:** Check that WebSocket URLs are clean: + ```bash + docker logs charon 2>&1 | grep "/logs/live\|/cerberus/logs/ws" + ``` + Shows: `/api/v1/logs/live?source=waf&level=error` (no token) + +## Migration Path + +### For Users + +No action required. The change is transparent: +- Login sets the HttpOnly cookie +- WebSocket connections automatically use the cookie +- Existing sessions continue to work + +### For API Clients + +API clients using Authorization headers are unaffected. + +### Deprecation Timeline + +1. **Current:** Query parameter authentication is deprecated but still functional +2. **Future (v2.0):** Query parameter authentication will be removed entirely +3. **Recommendation:** Any custom scripts or tools should migrate to using Authorization headers or cookie-based authentication + +## Related Documentation + +- [Authentication Flow](../plans/prev_spec_websocket_fix_dec16.md#authentication-flow) +- [Security Best Practices](https://owasp.org/www-community/HttpOnly) +- [WebSocket Security](https://datatracker.ietf.org/doc/html/rfc6455#section-10) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8cf0580f..5f5c5bb0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -47,7 +47,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.25", "jsdom": "^27.3.0", - "knip": "^5.74.0", + "knip": "^5.75.1", "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", @@ -163,6 +163,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -522,6 +523,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -568,6 +570,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -3259,8 +3262,7 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -3348,6 +3350,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3358,6 +3361,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3397,6 +3401,7 @@ "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -3777,6 +3782,7 @@ "integrity": "sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.0.16", "fflate": "^0.8.2", @@ -3812,6 +3818,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4042,6 +4049,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -4244,7 +4252,8 @@ "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "peer": true }, "node_modules/data-urls": { "version": "6.0.0", @@ -4333,8 +4342,7 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -4498,6 +4506,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5390,6 +5399,7 @@ "integrity": "sha512-GtldT42B8+jefDUC4yUKAvsaOrH7PDHmZxZXNgF2xMmymjUbRYJvpAybZAKEmXDGTM0mCsz8duOa4vTm5AY2Kg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.28", "@asamuzakjp/dom-selector": "^6.7.6", @@ -5476,9 +5486,9 @@ } }, "node_modules/knip": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-5.74.0.tgz", - "integrity": "sha512-xSG+vn403ONBkQtSBf1+kcE8ulzyQHLWIDQAxvu3W7HnM0jZJqVUPlK5w6FZNUyKnp+4FInsYQW77eapDpmcNA==", + "version": "5.75.1", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.75.1.tgz", + "integrity": "sha512-raguBFxTUO5JKrv8rtC8wrOtzrDwWp/fOu1F1GhrHD1F3TD2fqI1Z74JB+PyFZubL+RxqOkhGStdPAvaaXSOWQ==", "dev": true, "funding": [ { @@ -5836,7 +5846,6 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -6250,6 +6259,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6279,7 +6289,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -6294,7 +6303,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "peer": true, "engines": { "node": ">=8" } @@ -6304,7 +6312,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "peer": true, "engines": { "node": ">=10" }, @@ -6352,6 +6359,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6361,6 +6369,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6405,8 +6414,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/react-refresh": { "version": "0.18.0", @@ -6961,6 +6969,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6998,8 +7007,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.2.2", @@ -7090,6 +7098,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7165,6 +7174,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -7402,6 +7412,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/frontend/package.json b/frontend/package.json index a9b2c90d..cc7b36c3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,7 +66,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.25", "jsdom": "^27.3.0", - "knip": "^5.74.0", + "knip": "^5.75.1", "postcss": "^8.5.6", "tailwindcss": "^4.1.18", "typescript": "^5.9.3", diff --git a/frontend/src/api/logs.ts b/frontend/src/api/logs.ts index 1f6201c5..304c5254 100644 --- a/frontend/src/api/logs.ts +++ b/frontend/src/api/logs.ts @@ -128,11 +128,8 @@ export const connectLiveLogs = ( if (filters.level) params.append('level', filters.level); if (filters.source) params.append('source', filters.source); - // Get auth token from localStorage (key: charon_auth_token) - const token = localStorage.getItem('charon_auth_token'); - if (token) { - params.append('token', token); - } + // Authentication is handled via HttpOnly cookies sent automatically by the browser + // This prevents tokens from being logged in access logs or exposed to XSS attacks const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/api/v1/logs/live?${params.toString()}`; @@ -196,11 +193,8 @@ export const connectSecurityLogs = ( if (filters.host) params.append('host', filters.host); if (filters.blocked_only) params.append('blocked_only', 'true'); - // Get auth token from localStorage (key: charon_auth_token) - const token = localStorage.getItem('charon_auth_token'); - if (token) { - params.append('token', token); - } + // Authentication is handled via HttpOnly cookies sent automatically by the browser + // This prevents tokens from being logged in access logs or exposed to XSS attacks const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/api/v1/cerberus/logs/ws?${params.toString()}`;