Description
The SecuScan frontend persists the user's API key exclusively in window.localStorage:
// api.ts lines 291-307
export function setStoredApiKey(apiKey: string): void {
window.localStorage.setItem(STORAGE_KEY, apiKey);
}
export function getStoredApiKey(): string | null {
return window.localStorage.getItem(STORAGE_KEY);
}
This API key is then sent as the sole authentication credential for every backend request (via the X-Api-Key header). The STORAGE_KEY constant is a static string ('secuscan_api_key' or similar), making it trivial to enumerate.
Why This Is a Critical Vulnerability
1. localStorage is accessible to any JavaScript executing on the same origin. This includes:
- Third-party analytics scripts
- Browser extensions with content script injection
- Any XSS payload (even a reflected XSS in a single parameter)
2. No HttpOnly or Secure flag protection. Unlike HttpOnly cookies which are inaccessible to JavaScript, localStorage items can be read with a single line:
const stolenKey = localStorage.getItem('secuscan_api_key');
This key can then be exfiltrated to an attacker-controlled server.
3. No key rotation or expiry. The API key, once stored, persists indefinitely until the user explicitly clears it. There is no mechanism to rotate the key or enforce session timeouts.
4. The attacker gains full access. With the API key, an attacker can:
- Start/stop any scan on any target
- Read all scan findings and vulnerability reports
- Access the credential vault (decrypted credentials)
- Download generated reports
- View and modify user settings
- Delete scan history
Storage Location Spread
The key is also referenced in multiple components that assume it's always in localStorage:
api.ts:getStoredApiKey() — called on every API request
- Various page components that check
getStoredApiKey() to determine auth state
- The
ApiKeySetupScreen component that initially stores the key
Impact
This is the single most critical security vulnerability in SecuScan. It turns a DOM-based XSS (even a minor one in any page) into a complete account compromise. The CVSS score would be approximately 9.1 (Critical):
- Attack Vector: Network
- Attack Complexity: Low (requires any XSS or compromised third-party script)
- Privileges Required: None
- User Interaction: None
- Scope: Changed
- Confidentiality: High
- Integrity: High
- Availability: High
Proposed Fix
Short-term (Minimal Code Change)
Replace localStorage with an in-memory variable that survives page refreshes via a session cookie with HttpOnly and Secure flags, set by the backend:
Backend change (backend/secuscan/auth.py):
@router.post("/api/v1/auth/session")
async def create_session(api_key: str = Header(...), response: Response = None):
# Validate the API key
if not validate_api_key(api_key):
raise HTTPException(status_code=401)
# Set HttpOnly session cookie
session_token = create_session_token(api_key) # Signed, short-lived
response.set_cookie(
key="secuscan_session",
value=session_token,
httponly=True,
secure=True,
samesite="strict",
max_age=3600, # 1 hour
)
return {"status": "authenticated"}
Frontend change (frontend/src/api.ts):
// Remove localStorage usage entirely
let sessionToken: string | null = null;
export async function authenticateWithApiKey(apiKey: string): Promise<void> {
// POST to backend, which returns a Set-Cookie header
await fetch('/api/v1/auth/session', {
method: 'POST',
headers: { 'X-Api-Key': apiKey },
credentials: 'include', // Important: send cookies
});
sessionToken = 'authenticated'; // Flag, not the actual secret
}
Long-term (Recommended Architecture)
- Implement a proper OAuth2 / OpenID Connect flow with short-lived access tokens and refresh tokens.
- Store the refresh token in an
HttpOnly cookie with SameSite=Strict.
- Keep the access token in memory (JS variable) and refresh it before expiry.
- Add token rotation — each refresh issues a new refresh token and invalidates the old one.
- Enforce key expiry on the backend so that even if leaked, the key is only valid for a limited time.
Additional Hardening
- Add
Content-Security-Policy headers that restrict script sources to prevent third-party script injection.
- Add a
Clear-Site-Data header on logout to clear any residual storage.
- Monitor for anomalous API key usage (requests from unexpected IPs/user-agents) and auto-rotate on detection.
Description
The SecuScan frontend persists the user's API key exclusively in
window.localStorage:This API key is then sent as the sole authentication credential for every backend request (via the
X-Api-Keyheader). TheSTORAGE_KEYconstant is a static string ('secuscan_api_key'or similar), making it trivial to enumerate.Why This Is a Critical Vulnerability
1.
localStorageis accessible to any JavaScript executing on the same origin. This includes:2. No HttpOnly or Secure flag protection. Unlike
HttpOnlycookies which are inaccessible to JavaScript,localStorageitems can be read with a single line:This key can then be exfiltrated to an attacker-controlled server.
3. No key rotation or expiry. The API key, once stored, persists indefinitely until the user explicitly clears it. There is no mechanism to rotate the key or enforce session timeouts.
4. The attacker gains full access. With the API key, an attacker can:
Storage Location Spread
The key is also referenced in multiple components that assume it's always in localStorage:
api.ts:getStoredApiKey()— called on every API requestgetStoredApiKey()to determine auth stateApiKeySetupScreencomponent that initially stores the keyImpact
This is the single most critical security vulnerability in SecuScan. It turns a DOM-based XSS (even a minor one in any page) into a complete account compromise. The CVSS score would be approximately 9.1 (Critical):
Proposed Fix
Short-term (Minimal Code Change)
Replace
localStoragewith an in-memory variable that survives page refreshes via a session cookie withHttpOnlyandSecureflags, set by the backend:Backend change (
backend/secuscan/auth.py):Frontend change (
frontend/src/api.ts):Long-term (Recommended Architecture)
HttpOnlycookie withSameSite=Strict.Additional Hardening
Content-Security-Policyheaders that restrict script sources to prevent third-party script injection.Clear-Site-Dataheader on logout to clear any residual storage.