Security fundamentals
OWASP Top 10:2025 awareness, the injection/XSS/CSRF trio and how they differ, hashing vs encryption, password salting, and least privilege.
Security at the senior level isn’t memorizing exploits — it’s holding an accurate threat model and knowing the few mistakes that cause most real breaches. This chapter anchors on the OWASP Top 10:2025, then drills the attacks you’ll be asked to distinguish (injection, XSS, CSRF) and the cryptography fundamentals everyone gets wrong (hashing vs encryption, salting). The through-line: almost every entry on the list is a trust-boundary failure — data crossed from “untrusted” to “trusted” without anyone checking it.
OWASP Top 10:2025 — the ordering
The OWASP Top 10:2025 was announced in November 2025 and finalized in early 2026, built on 175,000+ CVE records across ~2.8 million applications plus a survey of security experts. Three shifts matter most: A01 Broken Access Control stays #1 (it has held the top spot since 2003) and now absorbs SSRF, whose root cause is an access-control failure; A03 Software Supply Chain Failures is the headline new entry, surging into the top three; and injection has dropped to A05. The exact list:
| Rank | Category | What changed in 2025 |
|---|---|---|
| A01 | Broken Access Control | #1 since 2003; now absorbs SSRF (its root cause is access control) |
| A02 | Security Misconfiguration | rose from #5 in 2021 |
| A03 | Software Supply Chain Failures | new framing — expanded from 2021's "Vulnerable & Outdated Components"; surged into the top 3 |
| A04 | Cryptographic Failures | — |
| A05 | Injection | dropped from #3 (2021) to #5 |
| A06 | Insecure Design | — |
| A07 | Authentication Failures | — |
| A08 | Software or Data Integrity Failures | — |
| A09 | Security Logging & Alerting Failures | — |
| A10 | Mishandling of Exceptional Conditions | the only entirely new standalone category |
The two facts an interviewer is most likely to probe: SSRF is no longer its own entry because it’s fundamentally an access-control failure (so it folded into A01), and supply-chain risk jumped into the top three after a wave of compromised packages and build pipelines. The other genuinely new 2025 category is A10 Mishandling of Exceptional Conditions — errors and edge cases handled in ways that leak information or fail open (e.g. a crash that defaults to “access granted”).
Injection, XSS, and CSRF — how they differ
These three get conflated constantly. The crisp distinction is who gets attacked and what trust boundary is crossed:
| Attack | Who is the victim | Mechanism | Primary defense |
|---|---|---|---|
| Injection (SQLi) | the server / database | input is parsed as SQL/commands | parameterized queries (bind parameters) |
| XSS | another user's browser | attacker markup runs as JS in the victim's page | output-encode + Content-Security-Policy |
| CSRF | the user's session | victim's browser is tricked into sending an authed request | anti-CSRF token + SameSite cookies |
The vulnerability is building a query by concatenating user input, so the input can change the query’s structure:
// VULNERABLE: input is glued into the SQL text
const q = "SELECT * FROM users WHERE email = '" + email + "'";
// If email = "' OR '1'='1", the WHERE is always true → dumps every user.
// If email = "'; DROP TABLE users; --", you've lost the table.The fix is a parameterized (prepared) query: the SQL structure is fixed up front, and the input is sent separately as data that the driver binds — it can never be parsed as SQL:
// SAFE: the ? is a bound parameter, never concatenated into the query text
const rows = await db.query(
"SELECT * FROM users WHERE email = ?",
[email]
);The principle generalizes: never build an interpreted string (SQL, shell, HTML) by gluing in untrusted input. Use parameter binding for SQL, and context-aware output encoding for HTML.
For XSS, the equivalent bug is echoing user input into a page without encoding it. If a comment
body containing <script>steal()</script> is written straight into the HTML, the browser runs it
as code for every viewer. The defense is to output-encode untrusted data for its context (so
< becomes < and the markup renders as inert text) and to layer a Content-Security-Policy
that refuses inline scripts.
Hashing vs encryption, and why salting matters
| Property | Hashing | Encryption |
|---|---|---|
| Direction | one-way — cannot be reversed | reversible with the key |
| Key | no key (a salt isn't a key) | requires a secret key |
| Use it for | passwords, integrity/checksums | data in transit (TLS), secrets at rest |
| Right tool | bcrypt / argon2 (slow, salted) | AES-GCM, TLS |
| "Get the original back?" | No — you only compare hashes | Yes — decrypt with the key |
A plain hash isn’t enough for passwords for two reasons. First, fast hashes like MD5 or SHA-256 let an attacker try billions of guesses per second against a stolen hash. Second, without a salt, identical passwords produce identical hashes, so one cracked hash cracks every user who shares it, and precomputed rainbow tables work instantly. The answer is a slow, salted password hash — bcrypt or argon2 — which bakes in a per-user salt and a deliberately expensive, tunable work factor.
import bcrypt from "bcrypt";
// REGISTER: bcrypt generates a random per-user salt and bakes it into the hash.
// The cost factor (12) sets how slow each hash is — tune it up as hardware speeds up.
const hash = await bcrypt.hash(plaintextPassword, 12);
// → "$2b$12$Q9s...salt...hash" (algo · cost · salt · digest, all in one string)
// LOGIN: never decrypt — re-hash the attempt with the stored salt and compare.
const ok = await bcrypt.compare(attempt, hash); // true / falseThree things to notice. The salt is random and per-user, embedded in the output string, so two users with the same password get different hashes. The cost factor makes each hash deliberately slow, so brute-forcing a stolen hash is infeasible. And login never decrypts — it re-hashes the attempt and compares, because a hash is one-way by design.
Two cross-cutting principles tie it together. Least privilege: every user, service, and token gets the minimum access needed and nothing more, so a compromise has a small blast radius. Secrets management: credentials live outside the codebase, are rotated, and are scoped — never in source control, never in a plaintext config committed to git.
Securing the supply chain (A03)
You ship far more code you didn’t write than code you did — dependencies, their transitive dependencies, base images, build tools. A03 Software Supply Chain Failures jumped into the top three because that’s where attackers now aim: a malicious or typosquatted package, a compromised maintainer account, or a poisoned build step lets them into thousands of apps at once. Three layers of defense, plus provenance:
- Dependency scanning (SCA) — scan your dependency tree for known CVEs and pin versions with a
lockfile (
npm audit, Dependabot, Snyk, Trivy). The highest-leverage one, because most breaches here are known vulnerabilities in outdated packages nobody updated. - SAST (static analysis) — analyze your source for vulnerable patterns before it runs (Semgrep, CodeQL, SonarQube), wired into CI so a risky pattern blocks the PR.
- DAST (dynamic analysis) — probe the running app from the outside for exploitable behavior (OWASP ZAP), catching what static analysis can’t see.
- SBOM + SLSA (provenance) — a Software Bill of Materials lists exactly what’s in a build; SLSA is a framework of build-integrity levels that attest an artifact came from your trusted pipeline, unmodified — so a tampered build is detectable.
| Tool | Analyzes | Runs | Catches |
|---|---|---|---|
| SCA (dep scan) | your dependencies | CI / on update | known CVEs in packages you pull in |
| SAST | your source code | CI, pre-merge | vulnerable patterns (injection sinks, hardcoded secrets) |
| DAST | the running app | staging / CI | exploitable behavior from outside (auth, XSS, headers) |
The cheap, high-impact habits: commit a lockfile, turn on automated dependency updates, give CI tokens least privilege (a leaked CI secret is itself a supply-chain foothold), and be wary of packages that run install scripts.
01 Learning objectives
0 / 5 done02 Curated reading
03 Knowledge check
- 01easy
The OWASP Top 10:2025 #1 category is:
- 02easy
The highest-leverage defense against OWASP A03 (supply-chain) failures is…
- 03medium
The correct primary defense against SQL injection is:
- 04medium
Passwords should be stored using:
- 05medium
SAST vs DAST:
- 06hard
Hashing vs encryption:
04 Interview questions
browse all ↗What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.
-
What sits at #1 of the OWASP Top 10:2025, and name a couple of categories that are new or changed this edition.
A01: Broken Access Control is #1 — and in the 2025 edition it now absorbs Server-Side Request Forgery (SSRF). It means users acting outside their intended permissions: missing authorization checks, IDOR, privilege escalation.
What is new/notable in 2025:
- A03: Software Supply Chain Failures is new and surged into the top 3 — broadened from the old 'Vulnerable and Outdated Components' to the whole dependency/build ecosystem.
- A10: Mishandling of Exceptional Conditions is brand new — improper error handling, failing open, logic errors on abnormal input.
- The full order: A01 Broken Access Control, A02 Security Misconfiguration, A03 Software Supply Chain Failures, A04 Cryptographic Failures, A05 Injection, A06 Insecure Design, A07 Authentication Failures, A08 Software/Data Integrity Failures, A09 Logging & Alerting Failures, A10 Mishandling of Exceptional Conditions.Follow-ups they push on- Why did SSRF get folded into Broken Access Control?
- What does 'failing open' mean under A10, and why is it dangerous?
Red flag Quoting the 2021 list as current (e.g. putting Injection at #3 or naming 'Vulnerable and Outdated Components') — in 2025 Injection is A05 and supply-chain is its own A03.
source: OWASP Top 10:2025 ↗ -
How do XSS and CSRF differ, and how do you defend against each?
XSS injects attacker-controlled JavaScript that runs in the victim's browser in *your* site's origin. Defenses: context-aware output encoding, a strict Content-Security-Policy, sanitize any HTML you must render, and
HttpOnlycookies so stolen script cannot read the session token.CSRF tricks an already-authenticated browser into firing an unwanted state-changing request (the browser auto-attaches the cookie). Defenses: anti-CSRF tokens,
SameSitecookies, and verifying theOrigin/Refererheader.The crisp distinction: XSS abuses the site's trust in user input; CSRF abuses the site's trust in the user's authenticated session.
Follow-ups they push on- Why does SameSite=Lax mitigate most CSRF?
- Why don't CSRF tokens help against XSS?
Red flag Claiming CSRF tokens stop XSS — if you have XSS, the attacker's script can just read the CSRF token and forge a valid request.
source: OWASP — Cross Site Request Forgery (CSRF) ↗ -
What is SQL injection, and what is the *one* correct defense?
SQL injection is when untrusted input is concatenated into a query so the attacker can change its structure — e.g.
' OR '1'='1to bypass a login, or'; DROP TABLE users;--to destroy data.The primary defense is parameterized queries / prepared statements: the SQL text and the data travel on separate channels, so input is always treated as a value, never as code. ORMs do this for you when used correctly.
Defense in depth adds least-privilege DB accounts and allow-list input validation — but escaping by hand is error-prone and not the real fix. The principle (separate code from data) generalizes to *all* injection: OS command, LDAP, NoSQL, etc.
Follow-ups they push on- Why is manual escaping or a blocklist of bad characters not a reliable defense?
- How does an ORM still let you write injectable queries?
Red flag Saying 'sanitize/escape the input' as the primary fix — parameterization is the answer; ad-hoc escaping misses cases.
source: OWASP — SQL Injection Prevention Cheat Sheet ↗ -
Hashing vs encryption — what's the difference, and which do you use for passwords?
Encryption is reversible: with the key you can recover the plaintext. Use it for data you must read back — data in transit (TLS), secrets at rest.
Hashing is one-way: you cannot invert it; you can only re-hash a candidate and compare. Use it when you only ever need to *verify*, never recover — exactly the password case.
So passwords are hashed, not encrypted — if you can decrypt them, so can an attacker who steals your key. And not just any hash: use a slow, memory-hard password hash with a per-password salt.
Follow-ups they push on- Where does encoding (Base64) fit — is it a security control?
- What's the difference between hashing and an HMAC/keyed hash?
Red flag Saying you 'encrypt passwords' — that is the wrong primitive; passwords should be hashed with a dedicated password hash so they are non-recoverable.
source: OWASP — Password Storage Cheat Sheet ↗ -
Why is SHA-256 a bad choice for storing passwords, and what's the salt for?
General-purpose hashes like SHA-256 are *designed to be fast* — which is exactly wrong for passwords. An attacker with the hash file can compute billions of guesses per second on a GPU.
Use a slow, memory-hard password hash: OWASP recommends Argon2id (then scrypt; bcrypt only for legacy). Their tunable work factor keeps verification fast for you but brute force expensive for attackers.
The salt is a unique random value per password, stored alongside the hash. It ensures two users with the same password get different hashes and defeats precomputed rainbow tables — the attacker must crack each password individually. (A site-wide secret pepper can be layered on top.)
Follow-ups they push on- Why does a salt have to be unique per password rather than one site-wide value?
- What is a pepper and how does it differ from a salt?
Red flag Using a fast hash (MD5/SHA-1/SHA-256) for passwords, or reusing one salt for everyone — both leave you open to rainbow-table and GPU attacks.
source: OWASP — Password Storage Cheat Sheet ↗ -
What is the principle of least privilege, and how does it apply to secrets management?
Least privilege: every user, service, and credential gets only the permissions it needs to do its job — no more, no less. It shrinks the blast radius when something is compromised.
Applied to secrets:
- Don't hardcode secrets in source or commit them to git; store them in a secrets manager (Vault, AWS/GCP Secrets Manager) or injected env vars.
- Scope each secret narrowly — a service's DB credential can touch only its own schema, not everything.
- Rotate secrets, and prefer short-lived/dynamic credentials over long-lived static keys.
- Audit access so a leaked key is detectable and revocable.This maps to OWASP A02 (Security Misconfiguration) and A08 (Integrity Failures).
Follow-ups they push on- Why is a leaked secret in git history not fixed by just deleting the file in a new commit?
- How do short-lived/dynamic credentials reduce risk versus static keys?
Red flag Granting broad, permanent admin credentials 'to keep things simple' — it maximizes blast radius and violates least privilege.
source: OWASP — Secrets Management Cheat Sheet ↗ -
What is Software Supply Chain risk (OWASP A03:2025), and how do you reduce it?
Your app is mostly code you didn't write — third-party packages, their transitive deps, base images, and the build/CI pipeline itself. A03:2025 covers compromises anywhere in that chain: a malicious or vulnerable dependency, a typosquatted package, a poisoned build step, or a tampered artifact.
Mitigations:
- Pin and lock dependencies (lockfiles, hashes) so builds are reproducible.
- Scan deps for known CVEs (SCA tools) and patch promptly.
- Generate an SBOM so you know what you ship.
- Verify provenance / sign artifacts (e.g. Sigstore) and protect CI credentials.
- Minimize and pin base images.This was broadened in 2025 from the older 'Vulnerable and Outdated Components' to the whole ecosystem.
Follow-ups they push on- What is an SBOM and why did regulators start requiring it?
- How would a typosquatted npm package actually compromise you?
Red flag Treating supply-chain security as just 'keep dependencies updated' — it also covers the build pipeline, artifact provenance, and transitive deps.
source: OWASP Top 10:2025 — Introduction (A03 Software Supply Chain Failures) ↗ -
A login endpoint returns 'user not found' for unknown emails and 'wrong password' for known ones. What's wrong?
It is a user-enumeration vulnerability. The two distinct messages let an attacker probe which emails are registered, building a target list for credential stuffing, phishing, or password spraying.
Fix: return a single generic message — 'invalid email or password' — for both cases, and keep the response *timing* uniform (still run a dummy password hash when the user doesn't exist) so the attacker can't distinguish via latency either. The same care applies to signup ('email already in use') and password-reset flows.
This ties to OWASP A07 (Authentication Failures).
Follow-ups they push on- How could an attacker still enumerate users via response timing, and how do you prevent that?
- How does this interact with the password-reset 'we sent an email if it exists' pattern?
Red flag Fixing only the message text but leaving a timing side-channel (fast 'not found' vs slow bcrypt compare) that still leaks which accounts exist.
source: OWASP — Authentication Cheat Sheet (account enumeration) ↗ -
What is the root cause shared by all injection attacks, and why is parameterization the fix?
Every injection flaw — SQL, OS command, LDAP, NoSQL, XPath, even XSS — has the same root cause: untrusted data is interpreted as code because data and instructions travel on the same channel. The interpreter can't tell which bytes you meant as a value and which as syntax, so attacker input rewrites the command's structure.
Parameterization fixes this by *separating the channels*: the query/command template (the code) is sent and compiled independently of the parameters (the data), so user input is bound as a literal value and can never change the parsed structure.
SELECT * FROM users WHERE id = ?with a bound parameter treats'; DROP TABLEas a harmless string.This is why the generalized defense is 'keep code and data separate' — prepared statements for SQL, argument arrays (not shell strings) for OS commands, and context-aware encoding for output. Escaping/blocklisting is a fragile fallback, not the primary control.
What a strong answer coversRoot cause of all injection: untrusted data is parsed as code because they share one channel.
Parameterization sends template and data separately, so input binds as a literal and can't alter structure.
Generalizes beyond SQL: arg arrays for OS commands, parameterized APIs for LDAP/NoSQL, encoding for output (XSS).
Escaping/blocklists are fallbacks, not the fix — they miss encodings and edge cases.
Quick self-checkWhy do parameterized queries prevent SQL injection?
-
Correct — separating the code channel from the data channel is exactly what stops input from being parsed as SQL.
-
Parameterization doesn't strip anything; it binds input as data. Character-stripping is the fragile escaping approach.
-
Encryption is unrelated; injection is about how the query is parsed, not whether it's encrypted in transit.
-
Least privilege is useful defense-in-depth but doesn't prevent the injection itself.
Follow-ups they push on- Why is OS command injection still possible even with a 'parameterized' shell call if you pass a single string?
- How does XSS fit the same 'data interpreted as code' model?
Red flag Thinking injection is a SQL-specific problem solved by a SQL-specific trick — it's a universal code/data-confusion flaw, and the universal fix is separating the two, not escaping characters.
source: OWASP — Injection Prevention Cheat Sheet ↗ -
What is an IDOR, and why does Broken Access Control sit at #1 of the OWASP Top 10:2025?
An IDOR (Insecure Direct Object Reference) is the canonical Broken Access Control bug: an endpoint exposes a reference to an object —
/api/orders/1043— and the server returns it based on the URL alone, without checking that *this* user is allowed to see *that* object. Change1043to1044and you read someone else's order.It's #1 in OWASP Top 10:2025 (as it was in 2021) because authorization is per-request, per-object logic that's easy to forget on some path, hard for scanners to find, and devastating when wrong — it's the most commonly found weakness. The 2025 edition also folded SSRF into this category.
The fix is to enforce authorization server-side on every request, checking ownership/role against the authenticated identity — never trusting a client-supplied ID, never relying on the object reference being unguessable, and denying by default. Using unpredictable IDs (UUIDs) is hardening, not a substitute for the check.
What a strong answer coversIDOR: the server returns an object from a client-supplied reference without verifying the user is authorized for it.
#1 because authz is per-object, easy to miss, hard to scan for, and catastrophic — the most prevalent weakness.
Enforce authorization server-side on every request, deny by default, check ownership against the session identity.
Unguessable IDs (UUIDs) are hardening — not a replacement for the access-control check; SSRF now lives in this category (2025).
Follow-ups they push on- Why is switching from sequential IDs to UUIDs not a real fix for IDOR?
- Why was SSRF moved under Broken Access Control in 2025?
Red flag Relying on 'unguessable' object IDs or hiding the endpoint instead of performing a real per-request authorization check — security by obscurity, not access control.
source: OWASP Top 10:2025 — A01 Broken Access Control ↗ -
What is defense in depth, and why isn't input validation alone enough to stop XSS?
Defense in depth is layering independent controls so that no single failure is fatal — if one layer is bypassed, another still stands. No control is perfect, so you don't bet everything on one.
For XSS, input validation alone is insufficient because the danger depends on output context, not the input. A string that's harmless in an HTML body can break out inside a
<script>block, an HTML attribute, a URL, or a CSS context — and validation at the input boundary can't know where the value will eventually be rendered. Worse, data arrives from many sources (DB, other services) that never passed your input filter.So you layer: context-aware output encoding at the point of rendering (the primary defense), a strict Content-Security-Policy as a backstop that limits what injected script can do,
HttpOnlycookies so stolen script can't read the session token, and input validation as one more (not the only) layer.What a strong answer coversDefense in depth = independent layers; a single bypass shouldn't compromise the system.
XSS safety depends on output context (HTML body vs attribute vs JS vs URL), which input validation can't anticipate.
Primary defense is context-aware output encoding at render time; CSP is the backstop.
Data also enters from sources that never hit your input filter (DB, other services), so input validation alone is incomplete.
Follow-ups they push on- Why does the same string need different encoding in an HTML attribute vs a JavaScript context?
- What does a Content-Security-Policy actually restrict?
Red flag Treating input validation as the complete XSS fix — encoding must happen at output based on context, and CSP/HttpOnly provide the additional layers that catch what slips through.
source: OWASP — Cross Site Scripting Prevention Cheat Sheet ↗ -
What's the difference between authentication and authorization, and why must both be enforced server-side?
Authentication (authn) is *who are you?* — verifying identity (password, token, passkey). Authorization (authz) is *what are you allowed to do?* — checking that the verified identity has permission for this action/resource. Authn always comes first; authz decides what that authenticated identity may access.
Both must be enforced server-side because the client is fully under the attacker's control: hiding a button, disabling a form field, or checking a role in JavaScript stops only honest users. An attacker just crafts the HTTP request directly (curl, Burp), bypassing every front-end check. The browser is a convenience layer, never a trust boundary.
So the server must, on every request, verify the credential *and* re-check that this identity is permitted — front-end checks are UX, not security.
What a strong answer coversAuthn = who you are (verify identity); authz = what you may do (verify permission). Authn precedes authz.
The client is attacker-controlled — any check in JS/HTML can be bypassed by crafting the raw request.
Enforce both on the server, every request; front-end checks are UX, not a trust boundary.
Skipping the server-side authz re-check is exactly the Broken Access Control (#1) failure.
Quick self-checkAn admin-only button is hidden in the UI for non-admins, but the /admin/delete endpoint has no server-side role check. What's true?
-
Correct — hiding UI is not authorization; the server must enforce the role check on the request itself.
-
Hiding the button stops only honest users; an attacker crafts the HTTP request directly.
-
Authentication proves identity but doesn't check permission — an authenticated non-admin still gets through.
-
This is broken access control (missing authorization), not script injection.
Follow-ups they push on- How can an attacker bypass a front-end-only role check?
- Where do authentication failures (A07) differ from access-control failures (A01)?
Red flag Enforcing access control only in the UI (hidden buttons, disabled fields) — the server must re-verify, since the client can forge any request directly.
source: OWASP — Authorization Cheat Sheet ↗ -
What is Security Misconfiguration (OWASP A02:2025), and give concrete examples.
Security Misconfiguration is risk introduced by how systems are set up rather than by code flaws — and it climbed to A02 in the 2025 Top 10, reflecting how common it is across the increasingly complex, configurable stacks we run.
Concrete examples: default or unchanged credentials; verbose error pages or stack traces leaking internals in production; unnecessary features/ports/services left enabled; an S3 bucket or admin console open to the public; missing security headers (HSTS, CSP); directory listing on; debug mode on in prod; overly permissive CORS.
The defense is a repeatable, hardened baseline: minimal install (remove what you don't use), secure defaults, infrastructure-as-code so every environment is configured identically and reviewably, automated configuration scanning, and segregated environments. It overlaps tightly with least privilege and supply-chain hygiene.
What a strong answer coversRisk from setup, not code — defaults, exposed services, leaked errors, missing headers.
Rose to A02 in 2025 because modern stacks have huge configurable surface area.
Examples: default creds, public buckets, debug mode in prod, verbose stack traces, permissive CORS.
Fix with a hardened, minimal, repeatable baseline (IaC + config scanning + identical environments).
Follow-ups they push on- Why does Infrastructure-as-Code reduce misconfiguration risk?
- Why are verbose production error messages a security problem, not just a UX one?
Red flag Treating misconfiguration as a one-time setup task — config drifts across environments and over time; without IaC and scanning, prod quietly diverges into an insecure state.
source: OWASP Top 10:2025 — A02 Security Misconfiguration ↗ -
Why store a session token in an HttpOnly, Secure, SameSite cookie rather than localStorage?
localStorageis fully readable by any JavaScript on the page — so a single XSS flaw lets an attacker's script exfiltrate the token instantly. A cookie markedHttpOnlyis invisible to JavaScript: even with XSS, the script can't read the token to steal it.The other flags close the remaining gaps:
Securesends the cookie only over HTTPS (no plaintext interception), andSameSite(Lax/Strict) stops the browser from auto-attaching it on cross-site requests, which mitigates CSRF — the attack that cookie-based auth otherwise invites.The tradeoff: HttpOnly cookies are auto-sent by the browser, so you take on CSRF risk and must defend it (SameSite + anti-CSRF tokens). localStorage avoids CSRF but trades it for far worse XSS token theft. The consensus is HttpOnly cookies with CSRF defenses, because XSS token exfiltration is the more dangerous failure.
What a strong answer coverslocalStorage is readable by any JS — one XSS = instant token theft.
HttpOnly hides the cookie from JavaScript, so XSS can't read/exfiltrate it.
Secure = HTTPS-only; SameSite blocks cross-site auto-send, mitigating CSRF.
Cookies trade XSS-theft risk for CSRF risk — so pair them with SameSite + anti-CSRF tokens.
Follow-ups they push on- If HttpOnly cookies are auto-sent, what new attack do you now have to defend, and how?
- Can an attacker with XSS still abuse an HttpOnly session cookie even without reading it?
Red flag Storing JWTs/session tokens in localStorage 'for convenience' — it's directly readable by any injected script, turning any XSS into full account takeover.
source: OWASP — Session Management Cheat Sheet ↗ -
What is encoding (Base64), and why is it not a security control?
Encoding transforms data into another representation for safe transport or storage — Base64, URL-encoding, hex. It's a fully reversible, keyless, public algorithm: anyone can decode it with no secret. Its purpose is *compatibility* (e.g. putting binary in a text/JSON field), not secrecy.
That's the trap: Base64 *looks* scrambled, so people mistake it for protection. But
dXNlcjpwYXNzdecodes touser:passin one trivial step — it provides zero confidentiality.The three are distinct: encoding = reversible, no key, for compatibility; encryption = reversible *with a key*, for confidentiality; hashing = one-way, no key, for integrity/verification. Anytime someone says 'we Base64 the password before sending,' that's a misunderstanding — over HTTP it's plaintext; you need TLS (encryption) for confidentiality.
What a strong answer coversEncoding is reversible and keyless — its job is transport/compatibility, not secrecy.
Base64 'looks' encrypted but decodes in one public step → zero confidentiality.
Distinguish the trio: encoding (no key, compat) vs encryption (key, confidentiality) vs hashing (one-way, integrity).
Base64-ing a credential adds no protection; only TLS/encryption provides confidentiality on the wire.
Quick self-checkWhich statement about Base64 encoding is correct?
-
Correct — anyone can decode Base64 without a secret; it's not a security control.
-
Base64 uses no key and is trivially reversible — that's encryption, which Base64 is not.
-
That describes hashing; Base64 is fully reversible.
-
Base64-stored passwords are effectively plaintext; passwords must be hashed with a slow algorithm.
Follow-ups they push on- Where is Base64 a legitimate, correct choice?
- Why is 'we Base64-encode the API key in the header' not securing anything?
Red flag Mistaking Base64 (or any encoding) for encryption — it's a reversible public transform with no key and provides no confidentiality whatsoever.
source: OWASP — Cryptographic Storage Cheat Sheet ↗