Web Security interview questions
Web Security questions a Cybersecurity can be asked, from first job to senior. Open one to see the strongest answer and why it works.
Why must credentials stay out of query strings?
The strongest answer: URLs land in logs, history, Referer, and support tickets — a password in a GET is a leak, not a shortcut
Why it works: Exactly. HTTPS protects the path on the wire; it does not unsay access logs and browser history.
- No secrets in URLs.
- Logs and Referer copy query strings.
- HTTPS is not enough.
- Use a body or a header.
When do you return 401 versus 403 on an API?
The strongest answer: 401 when we do not have a valid identity; 403 when we know who you are and you still may not do this — do not hide both as 404 unless you have a reason to
Why it works: Exactly. Clients branch: sign in versus give up. 404-as-policy is a choice you can name, not a default muddle.
- 401 = authenticate.
- 403 = you may not.
- 404-hiding is a named policy.
- Do not wrap either in 200.
What is the core of injection, in this interview?
The strongest answer: Untrusted input reaching an interpreter — SQL, shell, HTML, LDAP — without a boundary that keeps it data
Why it works: Exactly. Data versus instruction. SQL is the famous case, not the only interpreter.
- Untrusted input + interpreter.
- Keep it data.
- Not only SQL.
- A scanner is not the definition.
A query is built with string concatenation from a form field. What do you do?
The strongest answer: Use a parameterised query / prepared statement — I do not “escape and hope” and I do not trust a client-side check
Why it works: Exactly. Parameters keep data as data. Frontend checks and blacklists are theatre.
- Parameterise the query.
- Do not escape-and-hope.
- Do not blacklist words.
- Client checks are not a boundary.
A profile bio renders as HTML. What is the risk?
The strongest answer: Stored XSS — a script in the bio runs as the victim — encode or sanitise on output, do not “trust our users”
Why it works: Exactly. Output encoding or a strict sanitiser. Trust is not a control.
- Treat bios as untrusted.
- Encode or sanitise on output.
- HTTPS does not stop XSS.
- Do not trust “our users.”
When do you worry about CSRF?
The strongest answer: When a cookie or other ambient credential will be sent on a cross-site POST — use a token or SameSite, and do not run state change on GET
Why it works: Exactly. Ambient auth + a browser that will send it. JSON is not a CSRF charm if cookies still ride along.
- Ambient credentials + cross-site POST.
- Token or SameSite.
- No state change on GET.
- JSON is not a free pass.
A new endpoint has no authorization check “because it is internal.” What do you say?
The strongest answer: Internal is a network story, not an authz story — default deny, then allow the roles that need it
Why it works: Exactly. Reachability is not permission. Frontends are not boundaries.
- Default deny.
- Internal ≠ authorized.
- Path names are not authz.
- UI hiding is not a control.
Users can upload a profile picture. What do you check before that ships?
The strongest answer: Validate the type by content rather than by the file name, cap the size, store it outside the web root or in object storage under a generated name, and serve it from a path that never executes code
Why it works: Exactly — the real risks are executing what was uploaded and serving it back from a trusted path.
- Uploads are untrusted input.
- Check content, not the name.
- Store outside the web root.
- Serve from a non-executing path.
Logout only hides the UI and leaves the session in the store. What is wrong?
The strongest answer: The server must revoke the session — a stolen cookie still works if you only hid a button
Why it works: Exactly. Logout is a server event. UI hide is theatre.
- Revoke on the server.
- UI hide is not logout.
- Know where the token lives.
- Do not make logout cosmetic.
How do you use the OWASP Top 10 in a design review?
The strongest answer: As a risk checklist for this feature — broken access, injection, crypto, design — not as trivia to recite the numbers
Why it works: Exactly. It is a conversation starter about this change. Reciting numbers is trivia.
- Map Top 10 to this feature.
- Broken access first, often.
- Do not recite numbers.
- Modern ≠ immune.
GET /invoices/184 works for any logged-in user. What is the bug?
The strongest answer: IDOR / broken object authz — the server must check this caller may see 184, not only that they have a cookie
Why it works: Exactly. Identity is not authorization on this row. Hiding the URL is not a check.
- Check object-level authz.
- A session is not enough.
- Do not hide the id as a fix.
- Rate limit is a different control.
Users can pass a URL the server will fetch for a preview. What do you lock down?
The strongest answer: Scheme, host allow-list, and no link-local / metadata IPs — SSRF is how a preview becomes a cloud-credential theft
Why it works: Exactly. The server is the attacker’s proxy unless you bind where it may go.
- Allow-list hosts.
- Block link-local and metadata.
- HTTPS ≠ safe fetch.
- Follow redirects with the same rules.
The SPA keeps an access token in localStorage. What do you say?
The strongest answer: Any XSS becomes account takeover — if we keep it there, we owe a ruthless XSS budget; a short-lived cookie with the right flags is often the smaller story
Why it works: Exactly. Not-sent-automatically helps CSRF and hurts XSS. Name the trade.
- Name the XSS trade.
- Prefer short-lived, tight cookies when you can.
- JWT ≠ unreadable.
- Minify is not a defence.
A PATCH endpoint binds the whole JSON body onto the user record and saves it.
What is the bug, and what fix survives the next field being added?
The strongest answer: Anything in the body becomes a field, so a caller can set their own role. The fix is an explicit allow-list of updatable fields, which leaves a newly added column closed by default
Why it works: Right — allow-list over deny-list is exactly the property that makes the next field safe without anyone remembering.
- Bind an allow-list, not the body.
- New fields default to closed.
- Authentication is not authorisation.
- Never trust the client’s shape.
A teammate “secures” an API by tightening CORS. What is still missing?
The strongest answer: Authorization — CORS is a browser courtesy; curl does not care, and a stolen token still works
Why it works: Exactly. CORS is not authn or authz. Servers still check the caller.
- CORS ≠ authorization.
- Non-browsers ignore CORS.
- Still check the token and the object.
- Do not use CORS as a firewall.
Where do you put rate limits first?
The strongest answer: On login, reset, and any expensive or token-issuing path — I do not start by rate-limiting the marketing homepage
Why it works: Exactly. Limit the paths that print money or passwords. Equal GET limits are how you miss the spray.
- Login, reset, token mint first.
- Protect expensive work.
- Do not treat all GETs the same.
- Give testers a logged bypass.
Which headers do you actually want on a web app, and why?
The strongest answer: CSP you can enforce, frame ancestors, referrer policy, and HSTS when you mean it — I do not paste a 20-header blog list I cannot debug
Why it works: Exactly. Headers you can operate. A copy-paste CSP that you set to report-only forever is furniture.
- Operate CSP and HSTS.
- Frame and referrer policies.
- Do not paste a novel.
- Do not advertise the stack.
A banking session never expires “for convenience.” What do you change?
The strongest answer: Idle and absolute timeouts that match the risk, plus re-auth on money moves — convenience is a phishing window
Why it works: Exactly. Time is a control. UI-only expiry is theatre. Remember-me on money is a red flag.
- Idle + absolute timeouts.
- Step-up on money.
- Do not expire only the clock.
- No remember-me on transfers.
A partner wants to iframe your checkout. What do you check?
The strongest answer: Whether you mean to allow that ancestor — CSP frame-ancestors or X-Frame-Options — clickjacking is a user pressing Pay under a fake button
Why it works: Exactly. Framing is a product decision. * is how a stranger overlays Pay.
- Set frame-ancestors on purpose.
- Do not allow * on money.
- Clickjacking is a real user.
- CSS is not a defence.
How do you design a password-reset token?
The strongest answer: Random, single-use, short-lived, stored hashed, sent to the existing email — I do not put the new password in the link and I do not reveal whether the account exists if that is your policy
Why it works: Exactly. The token is a credential. Predictable or eternal tokens are the bug.
- Random, hashed, short, once.
- Do not put the password in the URL.
- Mind user-enumeration policy.
- No tweet-resets.
CSV export of “my invoices” returns every invoice in the tenant. What was missing?
The strongest answer: The same object-level authz as the list API — exports are APIs with a friendlier filename, not a backdoor
Why it works: Exactly. If the list is scoped, the export is scoped. Watermarks are not authz.
- Scope the export like the API.
- Exports are just APIs.
- Watermarks are not authz.
- Do not rely on slowness.
You keep finding IDOR in new list endpoints. What is the systemic fix?
The strongest answer: A default query scope in the data layer — this caller’s tenant / user — so a new list cannot forget the where clause
Why it works: Exactly. Make the safe path the easy path. UUIDs slow guessing; they do not authorize.
- Default-scope in the data layer.
- New lists inherit the where.
- UUIDs ≠ authz.
- Do not rely on careful.
A SaaS query missed the tenant id once. How do you make that rare?
The strongest answer: Force tenant context in the connection or the ORM — a missing where cannot compile or cannot run — plus a test that two tenants never see each other’s rows
Why it works: Exactly. Make the safe query the only query. Comments, luck, and lawyers are not isolation.
- Force tenant on the session.
- Test cross-tenant reads fail.
- UUIDs ≠ isolation.
- Do not rely on comments.
You are the security owner for a multi-tenant SaaS. What is the one bug class you fund first?
The strongest answer: Cross-tenant read and write — isolation tests, default scopes, and a bounty signal — pretty XSS on the marketing site can wait
Why it works: Exactly. Tenant isolation is the business. Marketing CSP is real and not first.
- Fund cross-tenant tests.
- Default scopes in the data layer.
- Listen to bounty isolation finds.
- Do not lead with marketing CSP.