While working on the SSDLC compliance for the Landscape UI, specifically the threat model, I’ve been thinking about assets, data flows, and how we can better protect them.
I wanted to share a few security “controls” that are relatively straightforward to implement (mostly on the backend) but can significantly boost the security posture of any service. Let’s discuss how we might apply these to your stack.
1. Ditch Local Storage for HttpOnly & Secure Cookies 
This is a big one for how we handle authentication tokens (JWTs).
-
What it is: Storing the session JWT in a browser cookie with the
HttpOnly
andSecure
flags set by the server, instead of keeping it inlocalStorage
. -
The Threat: Cross-Site Scripting (XSS). If an attacker finds an XSS vulnerability, they can inject JavaScript to steal the JWT directly from
localStorage
. Once they have the token, they can impersonate the user completely. -
How it Helps & How it Works:
-
The
HttpOnly
flag makes the cookie inaccessible to JavaScript, which means even if an XSS flaw exists, the attacker’s script can’t read the token. This is a huge win. -
The
Secure
flag ensures that the cookie is only sent over HTTPS, preventing it from being intercepted over unencrypted connections. -
For us on the frontend, the implementation changes slightly. Instead of manually attaching a
Bearer
token to every API call, the browser handles sending the cookie automatically. To check for an active session on page load, you’d rely on a dedicated endpoint like/api/me
.-
If the
/api/me
endpoint returns user data, you know they’re logged in. -
If it returns a
401 Unauthorized
or403 Forbidden
, the cookie is missing or invalid, and you redirect to the login page.
-
2. Control Information Leaks with Referrer-Policy 
This is a simple header that prevents you from accidentally leaking sensitive information.
-
What it is: An HTTP response header that governs what information is sent in the
Referer
header when a user navigates from your site to another. -
The Threat: Leaking sensitive data in URLs. Imagine a URL like
https://landscape.canonical.com/reset-password?token=<some_secret_token>
. If a user clicks a link on that page that goes to an external site, the full URL, including the secret token, could be sent to that external site in theReferer
header. -
How it Helps & How it Works:
-
By setting this header, you can control the referrer data. A great, safe default is
Referrer-Policy: strict-origin-when-cross-origin
-
This policy tells the browser to:
-
Send the full URL as a referrer for requests on the same origin (e.g., navigating from one page to another).
-
Send only the origin (e.g.,
https://landscape.canonical.com
) for cross-origin requests (e.g., clicking a link to an external documentation site). The path and query string are stripped.
-
-
This is a one-line change in the server configuration that prevents a whole class of data leaks.
3. Lock Down Scripts with a Content Security Policy (CSP) 
CSP is our primary defense against XSS. It’s a powerful tool, but requires careful configuration.
-
What it is: An HTTP response header that gives us fine-grained control over which resources (scripts, styles, images, etc.) the browser is allowed to load for our application.
-
The Threat: Malicious script execution from XSS attacks. If an attacker can inject a
<script>
tag into your app, a strong CSP can prevent the browser from ever running it. It also helps as a detective control by logging violation reports to a specified endpoint. -
How it Helps & How it Works:
-
You define a whitelist of trusted sources. For example, our initial draft looked like this:
content-security-policy:
script-src
'self'
'unsafe-inline'
'unsafe-eval'
assets.ubuntu.com
www.googletagmanager.com
www.google-analytics.com
script.crazyegg.com
www.google.com
www.google.ca
https://*.maze.co/;
style-src
'self'
'unsafe-inline'
assets.ubuntu.com
https://*.maze.co/;
img-src
'self'
assets.ubuntu.com
data:
www.googletagmanager.com
www.google-analytics.com
script.crazyegg.com
www.google.com
www.google.ca
https://*.maze.co/;
font-src
'self'
https://*.maze.co/;
connect-src
'self'
https://*.maze.co/
-
The Challenge: The
'unsafe-inline'
and'unsafe-eval'
values significantly weaken the policy. A key goal should be to eliminate them. -
We hit a common problem when we needed to add the Monaco editor, which required an external script. Instead of falling back to ‘unsafe-inline’, we can use nonces or hashes.
-
Nonce (Number used once): The server generates a random string, includes it in the CSP header (
script-src 'nonce-r4nd0m...'
), and adds it as an attribute to the<script>
tag (<script nonce="r4nd0m...">
). This allows that specific script to run. -
Hash: The server includes a hash of the allowed script’s content in the CSP header (
script-src 'sha256-h4sh0fscr1pt...'
). This is more secure but can be trickier to manage with dynamic scripts.
-
4. Defend Against Brute Force with Rate Limiting 
This control protects your authentication endpoints from being hammered by automated attacks.
-
What it is: A server-side mechanism to limit how many times a user or IP address can repeat an action within a specific timeframe (e.g., no more than 5 failed login attempts per minute from one IP).
-
The Threat:
-
Brute-Force & Credential Stuffing: Attackers use bots to guess passwords or try lists of stolen credentials on your login page.
-
Denial-of-Service (DoS): An attacker could overwhelm an API endpoint (like “send password reset email”) by spamming it with requests, degrading service for legitimate users.
-
How it Helps & How it Works:
-
The backend implements a counter for actions like failed logins from a specific IP. After a certain threshold is reached, you can either temporarily block that IP or lock the associated account.
-
This makes automated attacks incredibly slow, noisy, and ineffective, deterring most attackers.
-
On the frontend, you just need to be prepared to handle a
429 Too Many Requests
HTTP status code from the API and display a helpful message to the user (e.g., “You have made too many login attempts. Please try again in 5 minutes.”).
I believe adopting these would be a great step forward for your security. Happy to discuss any of these points further!