AppSec in practice
Review a system, build a test plan, and carry a finding through to verified remediation.
- Translate a trust-boundary concern into a falsifiable test.
- Carry a finding from triage through candidate and release evidence.
- Distinguish observed effects, proposed controls and unsupported assurance claims.
- Read HTTP behavior and a simple data-flow diagram.
- Use the supplied systems and test identities; no access to a live application is necessary.
Estimated time: about 8.5–11 hours across 18 steps, including 10 browser labs.
By the end: A scoped review dossier containing a threat case, permission test plan, business invariants, outbound fetch policy, dependency decision and release-aware remediation record.
0 of 18 steps complete
Trace the supplied tenant operation through every service, worker and store. Select one attacker-controlled value that crosses a trust boundary.
Keep: An abuse case with preconditions, asset, expected forbidden effect and test oracle.
Complete when: The case names a specific enforcing component and separates established architecture from assumptions.
Open guide : Review tenant boundaries before implementationTranslate the guide’s object relationships into allowed and forbidden requests, including a legitimate control.
Keep: A role/tenant/ownership test matrix linked to the abuse case pattern.
Complete when: Each result follows from current authoritative relationships; hidden UI controls are not accepted as enforcement.
Open guide : Enforce authorization for every object actionRepair the supplied lab and retain the results for legitimate, same-tenant and cross-tenant cases.
Keep: A reproducible regression note identifying the tested code.
Complete when: The repair preserves intended access and fails forbidden cases for the correct reason; candidate evidence is not relabeled as production evidence.
Open lab : IDOR lab: Repair an invoice authorization bugApply the guide's server-side invariants to a checkout: list each price, quantity, coupon and workflow rule that a request must not set.
Keep: An invariant list naming, for each rule, the request field it distrusts and the server data it uses instead.
Complete when: Every amount an order charges traces to server data or a written rule, never to a value the client sent.
Open guide : Prevent business-logic flaws with server-side invariantsRun the business logic lab: price each line from the catalog, reject impossible quantities and repeated lines, and apply at most one coupon under its own limits.
Keep: A repaired pricing function with the lab's passing cases.
Complete when: Client prices, negative or excessive quantities and stacked or reused coupons change nothing, and ordinary orders total as before.
Open lab : Business logic lab: Price an order without trusting the clientUse the guide's table of special-purpose address ranges to write the destination policy for one outbound fetch, redirects included.
Keep: A destination policy naming the allowed schemes, ports and address ranges, and how each redirect is checked.
Complete when: The policy judges every resolved address rather than the host name, and treats each redirect as a new request.
Open guide : Constrain server-side fetches to their intended destinationsRun the JavaScript SSRF lab: classify every address each hop resolves to, re-check each redirect target and stop after five redirects.
Keep: A repaired fetch guard with the lab's passing cases.
Complete when: Metadata, loopback, private and documentation addresses are blocked directly, through encodings and through redirects, while public partner hosts are still fetched.
Open lab : SSRF lab: Guard an outbound fetch against SSRFRun the Python SSRF lab: parse with urlsplit, resolve the host, and allow only addresses that the ipaddress module reports as global.
Keep: A repaired validator with the lab's passing cases.
Complete when: Encoded loopback, IPv4-mapped loopback, carrier-grade NAT and a name with one private answer are blocked, and a public name or literal is allowed.
Open lab : SSRF validation lab: Validate a fetch URL with ipaddressEvaluate the supplied advisory against installed identity, deployed version and vulnerable behavior. Keep this fixture separate from the tenant example.
Keep: A scoped advisory/exposure decision and a proposed closure condition.
Complete when: Explain why a package match is an investigation input and why candidate patch evidence alone cannot close deployed exposure.
Open guide : Respond to an exploited or malicious dependencyApply the guide’s policy to the competing build records and compare the approved digest with the proposed deployment selector.
Keep: A release decision listing source, builder, artifact and policy predicates.
Complete when: Reject every near miss with a specific reason and explain how a mutable tag can break the verification-to-deployment link.
Open guide : Protect the path from pull request to releaseUse the webhook trace to distinguish rejected authenticity, duplicate delivery and duplicate payment.
Keep: A cause-and-effect timeline with a durable closure oracle.
Complete when: The report identifies the business ledger observation needed to prove one effect, rather than relying on an HTTP response.
Open guide : Verify webhook deliveries before actingRepair the webhook lab, then produce a short report with initial cause, change, observed tests, scope and residual limits.
Keep: A complete practice finding report backed by the browser lab observations.
Complete when: Another engineer can explain why the fix works and what deployment evidence would still be required for real closure.
Open lab : Webhook signature and replay lab: Repair a webhook handler that pays twiceRun the command injection lab: replace the shell command line with execFile and an argument list, put "--" before the path, refuse refs that start with a dash and allow only the log, blame and diff views.
Keep: A repaired git runner with the lab's passing cases.
Complete when: Separators, pipes, substitutions, newlines and redirections in any value start nothing else, option-like refs and other subcommands are refused, and file names with spaces, quotes, non-ASCII characters or a leading dash still work.
Open lab : OS command injection lab: Run git without a shellRun the memory-safety model lab: before echoing a heartbeat, check the header, the claimed payload length and the 16 bytes of padding against the record's real length, and discard anything else.
Keep: A repaired heartbeat handler with the lab's passing cases, and a note on where a parser in your own code compares a length field with the input it received.
Complete when: An oversized claim no longer returns the neighbouring buffer or reads past the heap, malformed messages are discarded, and ordinary heartbeats are echoed exactly.
Open lab : Buffer over-read lab: Stop a heartbeat over-readRun the SSTI lab: render the user's display name through a fixed Jinja template with autoescaping on, rather than building the template from the name.
Keep: A repaired render function with the lab's passing cases.
Complete when: A name containing {{ }}, {% %} or HTML is shown as text and cannot read the render context.
Open lab : SSTI lab: Render a welcome banner without template injectionRun the XXE lab: configure the lxml parser with resolve_entities=False, no_network=True and load_dtd=False so a crafted invoice cannot read a local file.
Keep: A repaired XML parser with the lab's passing cases.
Complete when: An external entity pointing at a file or URL is left unresolved, while built-in entities and ordinary invoices still parse.
Open lab : XXE lab: Parse an uploaded invoice without resolving external entitiesRun the deserialization lab: parse the token as JSON and validate it against a fixed schema, with no fallback that unpickles untrusted bytes.
Keep: A repaired token loader with the lab's passing cases.
Complete when: A base64 pickle, even one carrying a valid HMAC, is rejected rather than run, and a JSON token that breaks the schema is refused.
Open lab : Pickle deserialization lab: Load a session token without pickleRank the nine probe findings in the guide’s exercise, then run the probe script against a local server you set up with a .git folder and a source map.
Keep: A ranked findings table with the exposure, the fix and the ASVS 5.0 V13.4 requirement for each.
Complete when: Name each framework’s production switch for debug output and show the probe reports nothing on a hardened server.
Open guide : Ship without debug modes, verbose errors or exposed filesDecide whether a bulk export fix is ready
Use the complete synthetic fixture below in a notebook or text file. No live target, paid service or secret is needed. Make your own decision before opening the worked assessment. Then take the implementation option below, a local project in which you repair running code until its tests pass, and keep its test output with your written answers.
A team fixed its ordinary invoice endpoint, but is asking you to approve an export release. Build a finding, regression plan and release decision from the supplied evidence.
Supplied material
Release candidate: source r42, artifact sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.
Policy: authenticated users may export only invoices they own in their active tenant. A finance-admin role may export all invoices in that tenant only.
Alice = north ordinary user; Bob = north finance-admin; Eve = south ordinary user.
Rows: n1 belongs Alice/north; n2 belongs another north user; s1 belongs Eve/south.
GET /invoices/n1 performs a server-side ownership check.
POST /exports takes tenantId and columns from JSON. Worker query filters by tenantId from that JSON. Cache key is 'export:'+requested format. Worker runs as a privileged service.
Observed authorized test: Alice requests north/pdf and gets n1,n2; Eve then requests south/pdf and receives the cached north document.
CI record T19 passed on r41/artifact bbbb…; r42 adds the export cache. The r42 build also contains library parsekit 2.4.0. The supplied synthetic advisory affects parsekit below 2.4.1 when XML external entity parsing is enabled. This export consumes JSON and produces PDF; there is no complete call-path inventory yet.
Webhook event export-paid-9 is retried after the export worker times out.Work through the case
Trace API→queue→worker→cache→download and identify where authority must be carried and checked.
Write a reproducible two-tenant finding and a minimum positive/negative test matrix including cache order reversal.
Assess the dependency record without claiming the JSON path proves the vulnerable XML code is unreachable everywhere.
Specify a release gate and durable handling of the retried event; decide whether the supplied evidence justifies shipping.
Decide the fixed cases
These parts of the case have one right answer, taken from the worked assessment. Check them here; the rubric below assesses your written reasoning. The capstone meets the bar when every decision is right and the self-check reaches it.
Which invoices may each person export under the policy?
Decide the release questions.
Evidence to retain
- Finding with impact, exact synthetic requests, affected candidate and causal chain.
- Actor/role/object/cache test plan plus worker authorization checks.
- Dependency applicability record separating known, unknown and next evidence.
- Release decision naming blocking evidence and responsible roles.
Assess your work
Score each criterion with the anchor whose evidence your answer shows: 0, 1 or 2 points. Every anchor describes something a reviewer can point to in your work. Aim for at least 7 of 8, with no unresolved critical failure.
Score your answer against the anchors
Choose the anchor that matches your answer for each criterion.
Recorded checks stay in this browser only, so you can compare attempts. This is a self-assessment, not a certification.
Compare with the worked assessment
Block the candidate on tenant disclosure and same-tenant ownership bypass. The worker needs trusted actor/tenant/action context and authorization at the actual read/download boundary; a broad service credential does not inherit the user’s permissions. Cache separation must reflect the authorized result audience and invalidation policy, or private exports should bypass shared cache. Bob may export n1,n2 for north, Alice n1 only, and Eve s1 only. T19 is for another artifact and cannot close r42. The dependency remains under investigation until deployed paths/configuration are accounted for; do not label it not affected merely because the reviewed endpoint uses JSON. Retried events must not create duplicate billing effects.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Implementation option: Build and verify a signed software release
Build the release, then repair the consuming gate in exercise.mjs until all 20 tests pass, so evidence about one artifact can no longer approve another: the T19-on-r41 mistake in this case.
Keep: Failing and passing test output, and the test that rejects evidence bound to a different artifact digest, mapped to your release decision.
Node.js 22+ · no dependencies · 60–90 min · 20 tests
Other connected projects
- Secure a real HTTP API — Node.js 22+ · npm
Build an evidence-driven security program
Set ownership and requirements, produce release-specific evidence, manage exceptions and scope external obligations.
Your progress, your browser
Progress is stored on this device. Export it to carry it to another browser, or clear it whenever you want. Importing a file adds its completed items to the progress already here; items this site no longer offers are skipped and counted, and nothing else is removed.