46 questions

Web security interview questions and answers

Junior questions

  1. 1. In the admin panel only admins see the "Delete user" button. A tester logged in as a regular user takes the DELETE /users/17 request from an admin's DevTools, puts in his own token, and the user is deleted. What is missing and where should it be checked?

    Answer

    The server checked authentication (the token is genuine, the user is known) but not authorisation, that is whether this particular user may perform this action. A hidden button in the UI protects nothing: the request can be sent directly from DevTools, curl or Postman. In OWASP API terms this is BFLA (broken function level authorization): there is no role check at the function level. The fix: permission checks on the server for every request, explicitly on every route (requireRole('admin'), a guard, a policy) under the rule "denied until explicitly allowed". The answer in this case is 403, not 401, because the user is known.

    Follow-up question: How do you make sure a new admin endpoint cannot accidentally be left without a role check?

  2. 2. In code review you see a user search where SQL is built with a template string: email from the request body is inserted into WHERE, and sort from a query parameter into ORDER BY. The author says the email is validated on the frontend. What is wrong here and how do you rewrite it?

    Answer

    This is SQL injection: user data becomes part of the query text, and a value such as ' OR '1'='1 changes its structure. Frontend validation gives nothing, because the request can be sent with curl bypassing the UI. Values are passed as parameters separately from the SQL text, for example db.query('SELECT * FROM users WHERE email = $1', [email]), and the driver never interprets them as code. Column names and sort direction cannot be passed as parameters, so sort is checked against an allowlist (a map from created to created_at) and only a value from the map is inserted. A database account with minimal privileges limits the damage further.

    Follow-up question: How do you find other places like this in a 200 thousand line codebase without reading it all by hand?

  3. 3. A customer sent support a message containing <img src=x onerror=...>. When an agent opened the ticket in a React admin panel, the script ran and created a new admin on her behalf. How did this happen if React escapes output, and what do you fix?

    Answer

    This is stored XSS: the malicious HTML is stored on the server and runs in the browser of everyone who opens the ticket, with that person's session rights. React escapes text in JSX, so the hole is almost always in a bypass: dangerouslySetInnerHTML for "formatted" text, an href with a javascript: URL, a direct innerHTML, or markdown rendered to HTML without sanitising. The fix: show the message as text, and if HTML is really needed, sanitise it with DOMPurify using a tag allowlist and check link schemes. The second line is a CSP without unsafe-inline, which will not run an inline handler. An HttpOnly cookie stops session theft but does not help here: the script makes requests on the victim's behalf anyway.

    Follow-up question: Where would you look for reflected and DOM-based XSS in the same application?

  4. 4. A user is logged in to your application, with the session in a cookie. They open a third-party site, which quietly submits a form POST https://app.example.com/account/email with the attacker's address. The email is changed. Why did the browser allow this and how do you defend?

    Answer

    This is CSRF: the browser attaches cookies to a request to your domain no matter who initiated it, and a simple form with application/x-www-form-urlencoded needs no preflight, so CORS does not help here (the attacker does not need to read the response). Defences: SameSite=Lax or Strict on the session cookie, so the browser does not send it with a cross-site POST; a CSRF token in the form or a header that the other site cannot read; checking Origin on state-changing requests. For changing an email or password, also re-confirmation: the current password or a message to the old address. An API that takes the token from the Authorization header rather than a cookie is not vulnerable to CSRF, because the browser does not add that header on its own.

    Follow-up question: Does SameSite=Lax protect against every CSRF scenario?

  5. 5. In code review you see passwords stored as sha256(salt + password) with a unique salt per user. The author says: "there is a salt, so it is safe". Do you agree? What do you propose, and how do you migrate existing users?

    Answer

    A salt only makes hashes different and defeats precomputed rainbow tables, but it does not slow brute force: SHA-256 is fast, one GPU computes billions of hashes per second, so after a database leak weak passwords are cracked within hours. Passwords need a slow function with a tunable cost: argon2id (the OWASP recommendation), scrypt or bcrypt; they generate the salt themselves and store the parameters in the hash string. Migration without a password reset: on the next login verify the old hash and rewrite it with the new one, and for users who do not log in, wrap the old hash (argon2id(sha256(...))) and mark the scheme, so no weak hashes stay in the database. Reversible encryption does not fit either: with the key you get every password.

    Follow-up question: bcrypt only uses the first 72 bytes of a password. When does this become a problem and what do you do about it?

  6. 6. A frontend developer "fixed" a CORS error by setting Access-Control-Allow-Origin: * on the API, and when that did not work with cookies, made the server echo back whatever Origin arrived together with Access-Control-Allow-Credentials: true. Is this a hole? And did the old strict CORS protect the API from an attacker using curl?

    Answer

    CORS does not protect the server: it is a browser rule that decides whether a script from another origin may read the response. Curl, Postman or the attacker's backend ignore CORS, so access to the API is protected only by authentication and authorisation. * is fine for public data without cookies, and with credentials the browser simply rejects it. But echoing any Origin together with Allow-Credentials: true is a real hole: any site a logged-in user opens can read API responses on their behalf, meaning personal data and CSRF tokens. The right way: an allowlist of specific origins with exact matching, and Vary: Origin so caches do not mix responses.

    Follow-up question: Why is a check like origin.endsWith('example.com') in such an allowlist also vulnerable?

  7. 7. An SPA keeps a JWT with a one hour lifetime in localStorage, and the "Log out" button just removes it from there. A tester copied the token before logging out, and after logout the API keeps accepting it for an hour. Is this a bug? What do you change?

    Answer

    Yes, and it is built into JWT itself: the token carries signed claims (header, payload, signature), and the server checks the signature and exp without touching any store, so deleting it on the client does not invalidate it. Options: a short access token of 5 to 15 minutes plus a refresh token that the server stores and revokes on logout; a denylist of revoked jti values until their exp, at which point validation is no longer fully stateless; or a plain server session if there is a single backend. The second problem is localStorage: any script on the page can read it, so one XSS steals the token, which is why the refresh token is better kept in an HttpOnly cookie. And the payload is not encrypted, anyone can read it, so no secrets go there.

    Follow-up question: How does an HS256 signature differ from RS256, and when do you choose which?

  8. 8. A pentest showed that the account settings page can be embedded in an <iframe> on any site, with a "Claim your prize" button laid exactly over "Delete account". What is the attack called and what do you add to the server response? Which other security headers are worth adding in the same change?

    Answer

    This is clickjacking: the user clicks on a transparent iframe of your site where they are logged in. Defence: Content-Security-Policy: frame-ancestors 'self' (or 'none'), and X-Frame-Options: DENY or SAMEORIGIN for old browsers; a cookie with SameSite=Lax also helps, because it is not sent inside a cross-site iframe. The same change usually adds Strict-Transport-Security, X-Content-Type-Options: nosniff (the browser does not guess the type and will not run an uploaded file as a script) and Referrer-Policy: strict-origin-when-cross-origin, so paths with parameters do not go to third-party sites. A CSP for scripts is introduced separately and gradually, through Content-Security-Policy-Report-Only, because it easily breaks pages.

    Follow-up question: A partner legitimately embeds your page on their own domain. How do you allow only them?

Middle questions

  1. 1. Before a release the client asks you to "check the API for OWASP Top 10 compliance". You have two days and a REST API with 20 endpoints. Where do you start and what do you actually check?

    Answer

    The OWASP Top 10 is a ranking of risk categories for awareness and prioritisation, not a standard you can "comply with"; for verification there is OWASP ASVS, and for APIs a separate API Security Top 10. In two days I take what breaks most often: access control (A01, first place), meaning every endpoint called with another user's token and another role's token; injection wherever there is raw SQL or command building; authentication (login limits, token lifetime and validation). In parallel the automated part: a dependency scanner (npm audit, Dependabot), a secret scan of the repository, a basic DAST run such as ZAP baseline, configuration (CORS, debug mode, stack traces in responses). The client gets a list of findings with risk and fix status, not the phrase "we comply with the Top 10".

    Follow-up question: Which category is first in the Top 10, and why do automated scanners catch it poorly?

  2. 2. The site opens in the browser with a padlock, but a partner's Node.js service and curl fail with unable to verify the first certificate. A colleague suggests setting NODE_TLS_REJECT_UNAUTHORIZED=0. What is actually wrong and how do you check it?

    Answer

    Most often the server sends only the domain certificate without the certificate authority's intermediate certificate. Browsers hide this: they cache intermediates and can fetch a missing one from the AIA link, while Node.js, curl, Java and mobile clients build the chain only from what the server sent and never reach a trusted root. To check: openssl s_client -connect example.com:443 -servername example.com -showcerts or SSL Labs will show a single certificate in the chain. The fix: point the server config at fullchain.pem instead of cert.pem. NODE_TLS_REJECT_UNAUTHORIZED=0 turns off certificate verification for the whole process and opens the way to MITM, so it is not a fix.

    Follow-up question: What else does a client check in a certificate besides the chain to a trusted root?

  3. 3. Which items of OWASP API Security Top 10 2023 do you see most often in a backend API, and how does this list differ from the classic Top 10?

    Answer

    The most frequent: API1 BOLA, where an object is looked up by an id from the URL without an ownership check; API3 BOPLA, extra fields in the response or mass assignment in the request body; API5 BFLA, an admin function without a role check, often on another method or API version; API4 and API6, no limits and no protection of business flows from bots. The API list is shifted toward authorization and endpoint inventory rather than XSS or rendering, because an API has no browser UI.

    Follow-up question: How do you find forgotten old versions and staging endpoints (API9) in time?

  4. 4. A pentest found that when updating a profile a regular user became admin, and the response shows a password hash. What is this vulnerability and how do you fix it?

    Answer

    This is BOPLA: mass assignment on input and excessive fields on output. The controller copies the request body into an entity without a whitelist, and the client adds role: "admin", and on the response the whole entity is returned. Fix: separate input and output DTOs listing only allowed fields, validation that rejects unknown properties, and explicit response construction instead of serializing the model. Fields that change privileges are allowed only on a dedicated admin endpoint.

    Follow-up question: Why is a role check on the endpoint not enough if the role can be changed through the profile?

  5. 5. A user changed the id in the URL /orders/123 to /orders/124 and saw someone else's order. What is this and how do you fix it systematically?

    Answer

    This is IDOR, in OWASP API terms BOLA: the server checks that the user is logged in, but not that the object belongs to them. Fix: check the owner or permission on every object access, or better build it into the database query (WHERE id = ? AND owner_id = ?) or a common access layer, so you do not rely on a developer's memory. Unpredictable UUIDs instead of sequential ids make enumeration harder but do not replace permission checks. For regressions, write tests that try other users' ids.

    Follow-up question: How do you check a large API for BOLA automatically?

  6. 6. A colleague says an ORM protects against SQL injection. When is that not true?

    Answer

    An ORM protects only when values go in as parameters. If in a query builder or raw query a value is interpolated with a template string (where(name = '${name}')), that is an ordinary injection. The same goes for column names and the direction in ORDER BY that come from the client: they cannot be passed as parameters, so they are checked against a list of allowed values. In Prisma, $queryRawUnsafe is dangerous, while the tagged $queryRaw parameterizes interpolations automatically. Reviewing every raw query is mandatory.

    Follow-up question: How do you check code for SQL injection automatically?

  7. 7. A login on Express and Mongoose: an attacker gets in as admin without knowing the password. What request body could he have sent?

    Answer

    This is NoSQL injection through operators. If the code passes the body straight into the query User.findOne({ email, password }), the attacker sends JSON { "email": "[email protected]", "password": { "$ne": null } }, and the condition becomes true for any password. Defense: validate the type of input fields (a string, not an object) with a schema such as zod or class-validator, do not compare passwords through a database query but fetch the user by email and verify the hash in code, and if needed sanitize keys containing $.

    Follow-up question: Why is it important for a JSON API to reject unknown and nested fields?

  8. 8. A backend serves comments for an SPA. After a stored XSS incident, the frontend says it is the backend's problem and the backend says it is the frontend's. Who is right?

    Answer

    Responsibility is shared. The main defense is output escaping at the place of rendering, that is on the frontend: React escapes text by default, while dangerouslySetInnerHTML or innerHTML create a hole. The backend is responsible for validation, sanitizing the allowed HTML with a proven library if HTML is needed, the correct Content-Type and security headers, CSP and HttpOnly cookies, so session theft is harder. One layer is not enough, you need defense in depth.

    Follow-up question: Why should you not write an HTML sanitizer yourself with regexes?

  9. 10. Design a Content-Security-Policy for a React admin panel. Why is unsafe-inline for scripts dangerous?

    Answer

    Start with default-src 'self' and open up only what is needed: script-src for your own domain, connect-src for the API, img-src, and frame-ancestors 'none' against clickjacking. unsafe-inline allows any inline script to run, which is exactly what an XSS injects, so CSP loses its point. Instead you use a nonce or hashes that the server generates for each response. First enable Content-Security-Policy-Report-Only, collect reports, then move to enforcement.

    Follow-up question: How does CSP get along with third-party analytics scripts?

  10. 11. A user provides a webhook URL, the service sends a request to it and shows the response. What vulnerability is this and how do you close it?

    Answer

    This is SSRF: the server can be made to reach internal addresses such as http://localhost, private networks or the cloud metadata address 169.254.169.254, and hand back the result. Defense: allow only https and an allow-list of hosts where possible; resolve DNS yourself and reject private, loopback and link-local addresses; do not follow redirects after the check; limit time and response size; keep such requests in an isolated network segment with no access to internal services. Do not show the response to the user raw.

    Follow-up question: Why is a blocklist by host string easy to bypass?

  11. 12. An endpoint accepts a zip, extracts the files and serves them by a link from the same domain. What are the risks?

    Answer

    Zip slip: names like ../../etc/x write files outside the target directory, so the path after normalization must stay within the folder. Zip bomb: a small archive expands into gigabytes, so you limit total size, file count and depth. Serving uploaded files from the main domain leads to stored XSS (HTML or SVG), so they are served from a separate domain or with Content-Disposition: attachment and X-Content-Type-Options: nosniff. The file type is checked by content, not by extension, and uploaded files are not executed.

    Follow-up question: Why can an uploaded SVG be an XSS vector?

  12. 13. A service generates image previews by calling ImageMagick through exec with a user-provided file name. What injections are there, and is escaping quotes enough?

    Answer

    Command injection: exec starts a shell, so a name like a.png; rm -rf / runs an extra command. There is also argument injection: a name starting with - is interpreted as an option of the utility. Quotes do not solve the problem. The right way: use execFile/spawn without a shell and with an argument array, put -- before file names, generate your own file names on disk instead of user-supplied ones, and better still call a library instead of a process. Run with minimal privileges in a restricted environment.

    Follow-up question: How is execFile safer than exec in Node?

  13. 14. How do you choose argon2id or bcrypt parameters for login, and what is a pepper?

    Answer

    Parameters are tuned so that one check takes about 100 to 500 ms on your hardware: argon2id with parameters from OWASP recommendations, or bcrypt with a cost of at least 10 to 12. They are raised over time and the password is rehashed on the next successful login. A pepper is a secret shared by the whole system, added to the password or hash and stored separately from the database, for example in a secrets manager. It helps when only the database leaked. A heavy hash also needs DoS protection: limits on login rate and concurrency.

    Follow-up question: Why does bcrypt truncate long passwords and how is that worked around?

  14. 15. By what paths do secrets leak from a service, and is it enough to delete .env from the repo in the next commit?

    Answer

    It is not enough: the file stays in git history, so the key is treated as compromised and rotated. Typical leak paths: git history, Docker image layers, environment variables in logs and in docker inspect, error messages with a connection string, CI logs, artifacts and client bundles that got variables without a prefix check. Defense: secret scanners in pre-commit and CI, a secrets manager, least-privilege keys with a short lifetime, masking in logs, separate keys per environment.

    Follow-up question: How do you check that a secret did not end up in a client JS bundle?

  15. 16. TLS is terminated at Nginx, and the app listens on localhost behind it. What should you pay attention to in the configuration?

    Answer

    Allow only TLS 1.2 and 1.3 with modern ciphers, enable HSTS, an HTTP to HTTPS redirect and automatic certificate renewal (Let's Encrypt). The app must know the original request was secure: Nginx passes X-Forwarded-Proto and X-Forwarded-For, and the app trusts these headers only from its own proxy, otherwise a client can forge them. Otherwise Secure cookies and absolute links will misbehave. The hop between Nginx and the app on the same host is protected by isolation, but across a separate network you need encryption.

    Follow-up question: Why is trusting X-Forwarded-For dangerous without proxy configuration?

  16. 17. You enabled Strict-Transport-Security: max-age=63072000; includeSubDomains; preload and submitted the domain to the preload list. The next day the internal legacy.example.com, which runs only over HTTP, opens for nobody. Why, how do you roll back, and how should it have been rolled out?

    Answer

    HSTS tells the browser to use only HTTPS for the domain for max-age and does not let the user click through a certificate error, and includeSubDomains extends this to every subdomain, so the HTTP-only legacy stopped opening. Rollback is slow: you can send max-age=0, but browsers that do not visit the main domain afterwards keep the old rule for up to two years, and the preload list is built into browsers, so removal stretches over months of releases. The right way: first move every subdomain to HTTPS, start with a small max-age (minutes, then a week, a month) without includeSubDomains, and only then add it and preload. In return HSTS closes the SSL stripping window on the first HTTP request, and with preload it protects even the first visit.

    Follow-up question: Which public Wi-Fi attack does HSTS prevent, and why is a redirect from HTTP to HTTPS not enough for that?

  17. 18. In a multi-tenant SaaS on one database, customer A saw customer B's invoices. Where does the mistake usually hide and how do you make isolation reliable?

    Answer

    Typical holes: a query without a tenant_id condition, lookup by id without a tenant check, background jobs or cache without a tenant key, aggregations and exports. Relying on a developer to remember WHERE tenant_id is unreliable. Isolation is built into the access layer (a mandatory filter in the repository or ORM scope), the tenant comes from the authenticated context rather than a request parameter, and at the database level you enable Row Level Security as the last line. Add tests with two tenants that check cross-tenant access.

    Follow-up question: Why must the cache key also contain the tenant identifier?

  18. 19. In code review the authentication middleware calls jwt.decode(token) and takes userId from the payload, and another route calls jwt.verify(token, key) with no options at all. What is dangerous here and what else must the backend check besides the signature?

    Answer

    jwt.decode only parses base64url and does not check the signature, so anyone can forge a payload with someone else's userId. verify without options is weak too: the algorithm must be pinned with a list (algorithms: ['RS256']), otherwise old libraries accepted alg: none, and swapping RS256 for HS256 turns the public key into an HMAC secret. Besides the signature you check exp and nbf, plus iss and aud so you do not accept a token issued for another service, and you do not trust a kid or jku pointing to an arbitrary URL with keys. Tokens meant for something else, such as an id token, are not accepted as an access token; RFC 8725 collects all of this.

    Follow-up question: How do you rotate the signing key without logging every user out?

  19. 20. Partners integrate with your API through keys. How do you design the key format, storage, verification and rotation?

    Answer

    The key is generated by a cryptographic generator, long enough, with a prefix that identifies the type and lets scanners find leaks. It is shown once, and the database stores only a hash (a fast SHA-256 is enough because the key has high entropy) and a short public prefix for lookup. For each key you set scopes and limits, an expiry and an owner. Rotation: two keys can be active at once so the client switches without downtime. Usage is logged and suspicious keys are revoked immediately.

    Follow-up question: Why do API keys not need a slow argon2 like passwords do?

  20. 21. A reporting service pulls data from your API hourly without a user. Which OAuth2 flow do you use?

    Answer

    Client credentials: the service as a client authenticates to the authorization server with its client_id and secret (or better a certificate or signed assertion) and receives a short-lived access token with the needed scopes. There is no user here, the token represents the service itself. The token is cached until it expires rather than requested on every call. The client secret is stored as a secret, rotated, and granted minimal scopes.

    Follow-up question: How does client credentials differ from authorization code?

  21. 22. The authorization code flow has state, nonce and code_verifier. What does each one protect against?

    Answer

    state binds the response to the request that this very user initiated and protects the redirect endpoint from CSRF. nonce is embedded in the id token and checked by the client, which protects against replay of the id token. code_verifier in PKCE proves that the code exchange is done by the same client that started the flow: an intercepted code without the verifier is useless. PKCE is now recommended for all clients, not only public ones.

    Follow-up question: Why is the implicit flow no longer recommended?

  22. 23. A user pressed 'Log out' in an app that signs in through Keycloak. What should happen on the client, in the backend and in the provider?

    Answer

    On the client you delete the local session and tokens from memory or cookies. In the provider you end the SSO session through the end-session endpoint (RP-initiated logout), otherwise the next login will pass without a password. Refresh tokens are revoked through the revocation endpoint. A JWT access token stays valid until exp, so its lifetime is kept short, and for critical operations revocation is checked through introspection or a denylist. You can also use back-channel logout so the provider notifies all applications.

    Follow-up question: What is back-channel logout?

  23. 24. An API accepts tokens from a shared realm, and a pentest got into the API with an id token. Why is this a problem and how do you fix it?

    Answer

    An id token is meant for the client to learn who logged in, not for calling an API. Its aud is the client, not your API, so an API that checks only the signature and iss accepts a token issued for any application in the realm. Fix: the API checks aud (or azp and scope) against its own identifier, accepts only access tokens of the right type, and tells them apart by claims such as typ. Use a separate audience per resource server.

    Follow-up question: How does an access token differ from an id token in content and purpose?

  24. 25. The security team found tokens, passwords and password reset links in the logs. How do you fix this systematically?

    Answer

    Log selectively, not 'the whole request and response'. Set up masking at the logger level by a list of sensitive keys (password, authorization, token, cookie) and never log the bodies of authentication requests. Reset links and tokens in URLs end up in proxy logs, so make them short-lived and single-use. Add tests that check no secrets appear in logs, and restrict access to the logs themselves and their retention. Whatever already got into logs is considered compromised and rotated.

    Follow-up question: Why are secrets in query parameters worse than secrets in headers?

  25. 26. Password reset emails a six-digit code valid for 15 minutes. The limit on the code check endpoint is 5 requests per minute per IP. A pentester resets the password of any account within an hour. How, and what do you fix?

    Answer

    There are only a million codes, and a per-IP limit is bypassed by rotating addresses through proxies or a botnet, so brute force from a few thousand IPs fits within the code's lifetime; on top of that the code can usually be reissued to start over. The limit belongs on the code and the account: after 5 failed attempts the code is voided, reissues per account are capped, and delays and CAPTCHA kick in on suspicion. Better still, instead of a short code send a link with a random single-use token of at least 128 bits and a short lifetime, storing only its hash in the database. The answer "if such an account exists, we sent an email" does not reveal which emails are registered, and permanent account lockout is avoided, because an attacker would use it to lock out other users.

    Follow-up question: The same endpoint sends an SMS instead of an email. Which other attack appears and how do you limit it?

  26. 27. After a phishing campaign several employees with SMS and TOTP 2FA turned on still lost their accounts: they entered the password and the code on a fake login page. Why did MFA not help and what would have?

    Answer

    Modern phishing works as a proxy between the victim and the real site (adversary in the middle, tools like Evilginx): it relays the password and the one-time code in real time and takes the session cookie, so SMS, TOTP and push are all bypassed. Passkeys and FIDO2 keys on WebAuthn resist this: the private key stays on the device and the signature is bound to the domain (RP ID), so a fake domain gets no valid response. Push without number matching is also open to prompt fatigue, and SMS to SIM swap. Account recovery and enrolment of a new factor need closing separately, because attackers often go there, and sessions are shortened or bound to the device.

    Follow-up question: How would you roll out passkeys to existing users without breaking login for those who do not have one yet?

Senior questions

  1. 1. A colleague proposes removing CSRF tokens because browsers now default to SameSite=Lax. When is he right and when not?

    Answer

    Lax blocks the cookie on cross-site POSTs and subrequests, so the main class of attacks is closed. But there are limits: the cookie is still sent on top-level GET navigation, so state-changing GET endpoints remain vulnerable. Requests within the same site but across different origins, such as from a sibling subdomain, are not filtered by SameSite. Old browsers and third-party clients behave differently, and Chrome gives a two minute window for new cookies without an explicit attribute. So tokens or an Origin check remain a useful second layer.

    Follow-up question: How does a site differ from an origin?

  2. 2. Code resolves a domain and checks the IP is not private, then makes the request. Is that enough against SSRF?

    Answer

    No, because of DNS rebinding and TOCTOU. The attacker controls DNS: the first answer gives a public IP and passes the check, and at connection time the client resolves again and gets 127.0.0.1. Fix: resolve once and connect to exactly that verified IP, passing the original Host, or check the address at the socket level, in a custom lookup or through an outbound proxy. Also check every redirect anew and cover IPv6 variants and decimal or hexadecimal forms of addresses.

    Follow-up question: Why is an outbound proxy with an allow-list more reliable than a check in application code?

  3. 3. After another service's database leak, your login received 2 million attempts from 40 thousand IPs, 1 to 3 attempts per account. What do you do?

    Answer

    This is credential stuffing: login and password pairs from someone else's leak. Per-IP limits are useless because the IPs are spread out. What helps: MFA or passkeys, checking passwords against lists of exposed ones (for example via the k-anonymity API of Have I Been Pwned), limits per account and by anomalies (new devices, bot behavior), CAPTCHA or proof-of-work when suspicious, and notifying users about a login from a new device. Do not reveal whether an account exists: identical responses and timing on failure.

    Follow-up question: How does k-anonymity work in checking exposed passwords?

  4. 4. Compare RBAC, ABAC and ReBAC on the example of a document system: a user sees their own documents, their team's documents and shared ones.

    Answer

    RBAC grants rights by roles (editor, admin): simple and clear, but roles multiply and rules about specific objects are hard to express. ABAC decides by attributes of the user, resource and context (department, document status, time): flexible, but rules are hard to audit. ReBAC derives access from a graph of relationships (owner, team member, a document in a folder that was shared), as in Google Drive or Zanzibar-like systems, and describes shared access naturally. In practice they are combined: roles for broad rights, relationships for objects, attributes for exceptions.

    Follow-up question: How does ReBAC answer the question 'who has access to this document'?

  5. 5. You enabled Row Level Security to isolate tenants. It works in tests, but in production under load clients occasionally see foreign data. What do you suspect?

    Answer

    Most likely the tenant is passed through a session setting (SET app.tenant_id), and the connection pool returns a connection carrying the previous request's tenant. Solution: SET LOCAL inside a transaction so the value resets when it ends, set at the start of each one. Other pitfalls: the table owner and superuser bypass RLS unless FORCE ROW LEVEL SECURITY is set, and the application must not connect as the owner role. PgBouncer in transaction pooling mode needs exactly a per-transaction setting.

    Follow-up question: How do you debug an RLS policy when it silently hides rows?

  6. 6. Where should an SPA keep tokens? A colleague stores access and refresh tokens in localStorage because HttpOnly cookies are 'inconvenient'.

    Answer

    Anything in localStorage can be read by any script on the page, so one XSS exposes both tokens, including the long-lived refresh token. A more reliable pattern is BFF: a small backend on your domain performs the OAuth exchange, keeps the tokens on the server, and gives the browser only an HttpOnly, Secure, SameSite cookie session. Then tokens are inaccessible to JS, but you need CSRF protection. If a token must still be in the browser, keep it in memory, make it short-lived and rotate the refresh token.

    Follow-up question: What are the drawbacks of a BFF compared with tokens in the browser?

  7. 7. After introducing refresh token rotation with reuse detection, users with two tabs are massively logged out. Why?

    Answer

    With rotation each refresh token is single-use, and reuse of an old one is treated as theft and the whole token family is revoked. Two tabs refresh at the same time with the same refresh token, the second loses the race, and the system sees 'reuse'. Solution: coordination on the client (one tab refreshes, the others wait via a lock or BroadcastChannel), and on the server a small grace window in which the previous token is still accepted, which gives idempotency on network retries. Also check that the new pair definitely reached the client.

    Follow-up question: What do you do with the token family on a real reuse?

  8. 8. The billing service trusts the X-User-Id header set by the gateway. Through SSRF in another service an attacker called billing directly. What went wrong?

    Answer

    The internal network was considered trusted, so billing accepted any X-User-Id without checking the source. One SSRF made it possible to bypass the gateway and substitute someone else's identity. Fix: services verify a cryptographically proven identity, not a header. That means a signed token (the user's JWT passed along, or a service token), mTLS between services or network policies that admit only known services to billing. This is the zero trust idea: the network is not trust.

    Follow-up question: How do you pass a user's identity between services while keeping verification?

  9. 9. Internal services talk over HTTP inside the cluster and pass one shared API key. Security demands mTLS. What does it give you compared to the key, what does it cost to operate, and how would you roll it out?

    Answer

    mTLS is TLS in which not only the server but also the client presents a certificate, so every service has a cryptographic identity and traffic is encrypted even inside a "trusted" network. Unlike a shared key, which leaks from any one service and opens all of them, a certificate is bound to a specific service, lives hours or days, and its private key is never sent in a request, so it does not end up in logs. The cost: an internal CA, issuing and automatically rotating short-lived certificates, expiry monitoring, harder debugging. That is why mTLS is usually handed to a service mesh (Istio, Linkerd) or SPIFFE/SPIRE, which make rotation transparent, and enabled gradually: permissive mode first, then strict. mTLS answers "who is calling", while what that service may do is still decided by authorisation.

    Follow-up question: How do you avoid an outage caused by an expired internal certificate?

  10. 10. npm audit shows 47 vulnerabilities, the team has ignored them for a long time, and the news is full of attacks through packages. What do you do?

    Answer

    Do not drown in noise: prioritize by reachability and exploitability, because many vulnerabilities are in dev dependencies or in code that is never called. Pin versions through a lock file and reproducible installs (npm ci), automate updates (Dependabot or Renovate) and scanning in CI. Against supply chain attacks it helps to vet new packages and maintainers, avoid arbitrary postinstall scripts from unknown packages, keep dependencies minimal, and use an SBOM and artifact signatures. Publishing and CI tokens are protected, and secrets are kept out of the build environment.

    Follow-up question: How does typosquatting differ from dependency confusion?

  11. 11. On Friday evening you learn that the JWT signing secret has been in a contractor's public GitHub repo for three days. What do you do?

    Answer

    Assume compromise: tokens can be forged. First containment: rotate the signing key, which invalidates all issued tokens (users log in again), with a transition period through kid if needed. Then investigation: check logs for suspicious tokens, unusual permissions and actions that started in this period. Remove the secret from the repo but treat it as already copied. Notify those responsible and, if needed, users or a regulator. After that a postmortem: how the secret got out and how to prevent a repeat.

    Follow-up question: How do you rotate a JWT signing key without mass logout?

Train these questions

Study the cards, answer by text or voice and get an AI grade. Needs a signed in account.

Answer out loud and get graded

Here you get the questions with model answers. In the ShortLesson app you answer yourself, by text or voice, and AI grades the answer against the rubric and asks a follow-up, like a real interviewer.

The app is coming to the stores soon.

Other topics