Dinesh/ Blog
systems

OWASP Top 10, Secrets Mgmt & Threat Modelling

Use the OWASP Top 10:2021 as a bounded risk map, then test interpreter and access boundaries and design secrets, STRIDE and dependency controls.

21 min readChecking device speech…
Lesson preparation & details

Level: intermediate

By the end, you should be able to

  • Explain every OWASP Top 10:2021 category without misstating prevalence
  • Demonstrate safe SQL binding, tenant isolation and bounded egress-policy tests
  • Build a STRIDE threat model, secrets lifecycle and dependency-review plan

Bring with you

Sources checked: . A source check does not change the publication or update date.

Editorial review: · What review means

In this article · 16 sections

A security checklist helps recognize failure classes; it does not certify a system. This lesson uses OWASP Top 10:2021 deliberately, not as a claim that it is the latest edition in October 2026. It combines the category map with trust-boundary analysis, safe local examples, a concrete secrets lifecycle and dependency-review decisions.

Preparation and optional resources

Bring HTTP request/response semantics, S081's authentication/authorization distinction and S082's validated-transport model. This is a category-recognition and defensive-design exercise, not permission to attack an application.

The original resource block used the same exact video URL twice with incompatible titles/creators. Both learning roles are retained below so the duplicate is visible rather than silently treated as two verified recordings. Titles are historical discovery labels only; no viewing, attribution, duration, quality or availability is claimed.

What the 2021 Top Ten does and does not say

The 2021 introduction describes a standard awareness document informed by contributed data and community input. The ten entries are categories, not ten individual bugs, not a complete security standard and not a measured claim that 90% of breaches have these causes. Their order does not automatically prioritize your service's risks. Treat the fire-code analogy as a reminder to prevent known hazards, not an invented “90% of fires prevented” statistic.

A particularly important reading error concerns A01 Broken Access Control: 94% of applications were tested for some form of broken access control, while the page reports an average incidence rate of 3.81% in its contributed dataset. “Tested for” is not “vulnerable to,” and neither number establishes worldwide prevalence. The category moved up from fifth in the 2017 list; do not claim injection ranked in the top three in every edition since 2003.

All ten categories, with mechanism and control

2021 categoryConcrete failureControl and its limit
A01 Broken Access ControlAlice can fetch Bob's private invoice with her valid sessionTenant/object/action authorization at trusted boundaries; random IDs alone do not enforce policy
A02 Cryptographic FailuresSensitive data is exposed or protected with unsuitable crypto/key handlingValidated TLS, appropriate at-rest/key controls and password KDFs; encryption does not fix excessive permission
A03 InjectionA name concatenated into SQL changes query grammar; untrusted HTML executes scriptParameters or context-correct output handling; generic quote stripping is not sufficient
A04 Insecure DesignA recovery flow has no proof-of-control or attempt budgetThreat-model and design the missing control, then implement/test it; a perfect implementation of the flawed design still fails
A05 Security MisconfigurationDebug/admin interfaces or storage are exposed unintentionallyHarden defaults, least privilege and verify deployed configuration; configuration is part of the system
A06 Vulnerable and Outdated ComponentsAn affected dependency remains deployed after a relevant advisoryInventory versions and deploy verified fixes/mitigations; a scanner needs complete inputs and current advisories
A07 Identification and Authentication FailuresSession fixation, weak credential recovery or incorrect token validationSecure authentication/session lifecycle and negative tests; login success is not authorization
A08 Software and Data Integrity FailuresUntrusted updates, build artifacts or serialized instructions are acceptedVerify provenance/integrity under a trusted build/distribution policy; a signature from a compromised signer is insufficient
A09 Security Logging and Monitoring FailuresSuspicious access is neither recorded nor escalated usefullyProtected, privacy-aware audit signals and practiced response; collecting logs alone does not detect incidents
A10 Server-Side Request ForgeryA user-controlled destination makes the server contact an unintended serviceConstrain destination and actual egress, redirects, DNS and privileges; blocking one IP string is not enough
All ten categories, with mechanism and control
Figure text equivalent — labels and relationships
  • Node “Identity and session boundary” (square).
  • Node “A07 authentication” (square).
  • Node “Object and action boundary” (square).
  • Node “A01 authorization” (square).
  • Node “Data entering interpreters” (square).
  • Node “A03 injection” (square).
  • Node “Architecture and defaults” (square).
  • Node “A04 design” (square).
  • Node “A05 configuration” (square).
  • Node “Keys and protected data” (square).
  • Node “A02 cryptography” (square).
  • Node “Dependencies and artifacts” (square).
  • Node “A06 vulnerable components” (square).
  • Node “A08 integrity” (square).
  • Node “Audit and response” (square).
  • Node “A09 monitoring” (square).
  • Node “Outbound destinations” (square).
  • Node “A10 SSRF” (square).
  • “Identity and session boundary” leads to “A07 authentication”. Line: normal; ends: arrow point.
  • “Object and action boundary” leads to “A01 authorization”. Line: normal; ends: arrow point.
  • “Data entering interpreters” leads to “A03 injection”. Line: normal; ends: arrow point.
  • “Architecture and defaults” leads to “A04 design”. Line: normal; ends: arrow point.
  • “Architecture and defaults” leads to “A05 configuration”. Line: normal; ends: arrow point.
  • “Keys and protected data” leads to “A02 cryptography”. Line: normal; ends: arrow point.
  • “Dependencies and artifacts” leads to “A06 vulnerable components”. Line: normal; ends: arrow point.
  • “Dependencies and artifacts” leads to “A08 integrity”. Line: normal; ends: arrow point.
  • “Audit and response” leads to “A09 monitoring”. Line: normal; ends: arrow point.
  • “Outbound destinations” leads to “A10 SSRF”. Line: normal; ends: arrow point.

Categories overlap. A stolen credential and excessive permissions can combine; a vulnerable library can also expose an integrity failure. Don't force a real incident into only one box to avoid examining the rest of the chain.

Treat a category identifier as the pair (edition, label). “A07” without an edition is not a stable definition across lists. For an incident review, record the failure mechanism first and only then map it to the chosen edition; translating an old label is not evidence that the underlying system changed. The three decision questions—identity, permission and interpreter use—help find boundaries, but do not replace design, key management, dependency integrity or monitoring coverage in the full ten-category map.

The interpreter boundary, not a universal sanitizer

Injection occurs when untrusted data is interpreted as instructions. SQL parameters bind values, not arbitrary table/column names; dynamic identifiers require a separate allowlist or safe composition API. Drivers need not literally send data and query over separate network channels—SQLite is in-process—yet parameter binding still preserves the distinction.

For HTML, use text nodes or a framework's context-aware escaping. Rich HTML requires an appropriate maintained sanitizer and a defined allowed subset. Attribute, URL, CSS and JavaScript contexts have different rules; auto-escaping is not a license to inject into arbitrary script contexts. Command argument arrays avoid shell grammar but do not prevent the called program from treating a user value as a dangerous option; validate allowed operations and use end-of-options handling where supported. Validate paths against intended roots, bound resource use and avoid unsafe deserialization of untrusted bytes. A single string-stripping middleware cannot implement all those contracts.

Authorization is harder to infer generically because business policy determines who should access a row. But it is not “invisible to all automation”: policy-as-code, tenant-scoped repositories, database controls, static checks for known patterns and negative tests can detect violations of expressed invariants. A WAF cannot infer a missing business ownership rule from a legitimate-looking request, although identity-aware controls can enforce specified policies. Secure frameworks reduce common unsafe patterns, not permanently eliminate every vulnerability class.

Safe SQL, IDOR, SSRF and throttling laboratory

The following exact program uses only an in-memory SQLite database and deterministic data. It has no Flask listener, network requests, credentials or cloud metadata access. One deliberately vulnerable SQL query is used solely against two synthetic local rows to demonstrate the parser boundary. Authorization receives a trusted principal as an explicit fixture; accepting X-User-Id from an untrusted request would invalidate the demonstration.

import sqlite3
from urllib.parse import urlsplit
from ipaddress import ip_address
 
DB = sqlite3.connect(":memory:")
DB.execute("CREATE TABLE users(id INTEGER PRIMARY KEY, tenant TEXT, name TEXT)")
DB.executemany("INSERT INTO users VALUES (?, ?, ?)", [(1, "A", "alice"), (2, "B", "bob")])
 
def bad_lookup(name):
    # Deliberately unsafe ONLY in this disposable in-memory fixture.
    return DB.execute("SELECT id FROM users WHERE name = '" + name + "'").fetchall()
 
def good_lookup(name):
    return DB.execute("SELECT id FROM users WHERE name = ?", (name,)).fetchall()
 
def account(principal, uid):
    # principal is established by the trusted authentication boundary, not a header.
    if principal is None:
        return None
    return DB.execute("SELECT id, name FROM users WHERE id = ? AND tenant = ?",
                      (uid, principal["tenant"])).fetchone()
 
def allowed_destination(url, resolved_addresses):
    # Pure policy predicate, NOT a fetcher or complete DNS-rebinding defense.
    if not isinstance(url, str) or any(ord(c) < 33 or ord(c) == 127 for c in url):
        return False
    try:
        p = urlsplit(url)
        if p.scheme != "https" or p.hostname != "images.example.test":
            return False
        if p.username is not None or p.password is not None or p.port not in (None, 443):
            return False
        if p.fragment or not resolved_addresses:
            return False
        return all(ip_address(a).is_global for a in resolved_addresses)
    except ValueError:
        return False
 
class Limiter:
    def __init__(self, limit=5, window=60):
        if limit <= 0 or window <= 0:
            raise ValueError("positive limits required")
        self.limit, self.window, self.times = limit, window, {}
 
    def allow(self, key, now):
        history = [t for t in self.times.get(key, []) if now-self.window < t <= now]
        self.times[key] = history
        if len(history) >= self.limit:
            return False
        history.append(now)
        return True
 
payload = "' OR 1=1 --"
assert bad_lookup(payload) == [(1,), (2,)]
assert good_lookup(payload) == []
assert good_lookup("alice") == [(1,)]
assert account({"tenant": "A"}, 1) == (1, "alice")
assert account({"tenant": "A"}, 2) is None
assert account(None, 1) is None
assert allowed_destination("https://images.example.test/x", ["8.8.8.8"])
assert not allowed_destination("https://images.example.test/x", ["127.0.0.1"])
assert not allowed_destination("https://images.example.test/x", ["8.8.8.8", "::1"])
assert not allowed_destination("https://images.example.test@evil.example/x", ["8.8.8.8"])
assert not allowed_destination("https://images.example.test:444/x", ["8.8.8.8"])
limiter = Limiter()
assert [limiter.allow("synthetic-source", 0) for _ in range(10)] == [True]*5 + [False]*5
assert limiter.allow("synthetic-source", 60)
assert DB.execute("SELECT count(*) FROM users").fetchone()[0] == 2
print("SQL data/code separation, tenant scope, egress predicate and rate window: PASS")

The public IP above is just a numeric fixture, never contacted. The SSRF predicate assumes its resolved addresses belong to the exact approved connection. A real implementation must bind the allowed hostname, resolved address, TLS identity and connection route; otherwise a second DNS resolution can change the destination. Disable redirects unless each hop undergoes the same validation, and enforce actual egress with a controlled proxy/network policy. Consider IPv6, alternative address forms, link-local/private/reserved ranges, DNS rebinding, proxy environment variables, ports, schemes, credentials in URLs, response-size limits and timeouts. A names-only allowlist is not enough if its names can resolve into forbidden networks. OWASP's SSRF sheet explains these distinct checks.

On AWS EC2, the familiar 169.254.169.254 is a metadata endpoint; whether it returns role credentials depends on instance configuration and attached roles. IMDSv2 uses a session token acquired through PUT and supplied in subsequent requests. Requiring it mitigates several SSRF paths but is not a universal defense against attackers who can perform the necessary methods/headers. Combine least privilege, metadata settings and egress restrictions. This lesson performs no metadata requests.

The limiter demonstrates five accepted attempts then five denials in a fixed synthetic instant. A web adapter could map unsuccessful allowed logins to 401 and throttled ones to 429. It does not prove credential stuffing is prevented: per-account/source/global limits, distributed atomic state, bounded key retention, trusted client-address derivation and account-lockout abuse remain. Test the time boundary and different keys without a request loop against a server.

Secrets are a lifecycle, not a file-extension judgment

A .env file is a configuration mechanism. Committing real secrets, exposing files, broad permissions, logging process environments or keeping stale copies are the risks; “not in .env” is not sufficient. CI injection removes a secret from source code but moves trust to CI identities, workflows and logs. An established secret manager can centralize access policy, audit and rotation, but capabilities vary and must be enabled. Static KV secrets do not become dynamic database credentials simply because Vault stores them.

A complete lifecycle includes creation, least-privilege delivery, use, rotation, revocation, expiry, audit and emergency response. Bootstrap the workload with a supported identity method; do not hardcode a manager's root token to fetch another secret. Use short-lived workload credentials where possible. Restrict services to exactly the secret versions/paths they need, and protect the manager's availability and audit logs. Applications may cache values to tolerate outages, but then rotation and stale-credential behavior need a defined policy.

Concrete Vault KV v2 reference

The KV v2 API uses a mount/path distinction and a nested response. This reference reads a synthetic conceptual path secret/data/payments/config; an actual mount name is deployment-specific. No Vault service, credential or network call was created or used here.

Workload authenticates via its configured workload-identity auth method.
Manager issues a narrowly scoped, short-lived credential.
GET /v1/secret/data/payments/config
  Authorized by the manager's supported credential transport, over verified TLS.
Read response.data.data; response.data.metadata.version identifies the KV version.
Validate required field and type; never log the response or credential.
# Reference policy for a KV v2 mount named "secret".
# Read-only access to exactly one data path; no root/list/write/delete permission.
path "secret/data/payments/config" {
  capabilities = ["read"]
}
# Exact local decoder test: no manager/network/real secret.
def decode_kv2(response):
    data = response["data"]["data"]
    version = response["data"]["metadata"]["version"]
    if not isinstance(data.get("fixture_value"), str) or type(version) is not int:
        raise ValueError("unexpected secret schema")
    return data["fixture_value"], version
 
assert decode_kv2({"data": {"data": {"fixture_value": "PUBLIC_TEST_VALUE"},
                                "metadata": {"version": 3}}}) == ("PUBLIC_TEST_VALUE", 3)
print("KV-v2 nested response decoding: PASS")

For a real application, retrieve through the service's supported SDK/agent, validate TLS and identity, redact logs, fail closed on malformed/missing data, and refresh via a deliberate cache policy. Rotation of a payment key requires coordination with the issuer: create new key, make consumers accept/use it, verify safe overlap, revoke old key, then check errors. Updating a CI environment variable does not rewrite a running process's environment; a reload/restart or runtime fetch mechanism may be needed. Database dynamic credentials have leases and renewal/revocation semantics distinct from static KV versioning. AWS Secrets Manager and other providers have their own rotation integrations; don't promise every manager automatically rotates every API key.

If a real secret reaches Git, revoke/rotate it first, identify affected scopes and logs/artifacts, then clean history through an authorized process. Rewriting history alone cannot retract existing clones. Secret scanning has provider/pattern/coverage limits and is neither guaranteed to alert within minutes nor proof that an unflagged value is safe. Never use a realistic-looking live-key string as a public example when PUBLIC_TEST_VALUE suffices.

STRIDE across a concrete data-flow diagram

Microsoft's STRIDE definitions name Spoofing, Tampering, Repudiation, Information disclosure, Denial of service and Elevation of privilege. Apply them to assets, processes, stores and flows with explicit attacker capabilities. A 30-minute session is an initial exercise, not a complete threat model for any service.

STRIDE across a concrete data-flow diagram
Figure text equivalent — labels and relationships
  • Node “Untrusted browser” (square).
  • Node “API boundary” (square).
  • Node “Tenant records” (cylinder).
  • Node “Secret manager” (square).
  • Node “Image provider” (square).
  • Node “Protected audit pipeline” (square).
  • “Untrusted browser” leads to “API boundary”: HTTPS and session. Line: normal; ends: arrow point.
  • “API boundary” leads to “Tenant records”: scoped query. Line: normal; ends: arrow point.
  • “API boundary” leads to “Secret manager”: workload identity. Line: normal; ends: arrow point.
  • “API boundary” leads to “Image provider”: approved destination through egress policy. Line: normal; ends: arrow point.
  • “API boundary” leads to “Protected audit pipeline”: sanitized events. Line: normal; ends: arrow point.

Mark the browser/API boundary, service/DB permissions, manager identity boundary, outbound network policy and audit readers. A private network or Kubernetes namespace alone is not a complete trust model. For each threat record entry point, asset, precondition, failure mechanism, impact, control, owner, verification and residual risk. Likelihood × impact can be an ordinal ranking aid, not a precise measured probability if its inputs are qualitative. Accepted risks need an owner, rationale and review/expiry—not “low means ignore forever.”

Worked password-reset threat model

STRIDEFailure mechanismControl and verification
SpoofingAttacker completes reset without controlling intended recovery channelUnpredictable, short-lived, single-use token bound to user/purpose; wrong-user/expired/reused-token tests
TamperingToken or target-account fields are modifiedValidated TLS; server-side token binding or correctly authenticated token; ignore client role/user substitutions
RepudiationReset action cannot be investigatedProtected audit of request/completion and relevant context, with access/retention policy; never store raw reset token
Information disclosureDifferent response/timing or logs reveal account/tokenGeneric outward result, comparable work, redacted links and referrer protections
Denial of serviceRepeated resets flood inbox or exhaust hash/email resourcesAccount/source/global budgets, bounded queues and anti-lockout design; test resource caps
Elevation of privilegeReset changes roles or takes over a more privileged accountOnly update the intended credential under proof; reauthenticate sensitive changes and revoke sessions per policy

A signed link is one option, not mandatory: a CSPRNG token whose hash is stored server-side can be safe with binding/expiry/single-use checks. Token signature alone does not prevent replay. OWASP's forgot-password sheet supports this lifecycle. Security testing here uses only synthetic pure fixtures, not requests against real accounts.

Dependencies, SBOMs and release gates

An SBOM records component identities/versions/relationships, often in CycloneDX or SPDX. Its usefulness depends on completeness, deployed-artifact association and update cadence. A lockfile is valuable but may omit bundled binaries, OS packages or dynamically loaded components. Scan the artifact and environment actually shipped, not just the repository's direct dependencies.

GitHub's dependency-review action documentation describes review of dependency changes in pull requests. The following is an unexecuted configuration example, not a change to this repository's release gates. Confirm feature availability for the chosen repository/account before adoption; pin actions to verified immutable commits under the organization's supply-chain policy.

name: dependency-review-reference
on: [pull_request]
permissions:
  contents: read
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with:
          fail-on-severity: high

The major-version tags are readable reference versions, not immutable security pins. Dependency review of a diff complements periodic full scans, container scans (for example Trivy), updates (for example Dependabot) and deployed inventory. A tool name is not a patch-management program. Triage each finding against exact package/version, vulnerable feature/reachability, exposure, advisory/fix, transitive path and compensating controls. Preserve evidence; time-limit and approve any exception. A clean scan does not prove safe design, and a high-severity finding is not automatically exploitable in every configuration. Do not weaken an existing gate because a tutorial shows a different threshold.

Historical incidents as evidence, not sensational counts

The FTC's July 2019 Equifax settlement announcement describes failures to secure data, including an unpatched critical vulnerability, and a settlement of at least USD 575 million potentially up to USD 700 million. It documents a prebreach instruction to patch affected systems within 48 hours and a failure to verify that instruction was carried out. That is not evidence of the original lesson's fully implemented postincident “48-hour SLA” program, precise USD 1.4 billion breakdown, or every executive/remediation anecdote. The mechanism is inventory plus accountable patch deployment and verification, not merely notifying a team that a patch exists.

Apache's Log4j security page specifies affected versions, prerequisites and fixes for CVE-2021-44228 and subsequent distinct advisories. Do not reduce all of them to “upgrade to 2.17.1 forever,” or assert billions of servers were exploitable. Untrusted logged content interacting with dangerous lookup behavior illustrates why data paths and transitive dependencies matter. Inventory affected deployments, apply the currently applicable supported fix, test configuration and verify the shipped artifact; temporary WAF signatures do not replace remediation.

For the Capital One-style scenario, a server-side fetch, reachable metadata and overbroad cloud role can form an exfiltration chain. This lesson teaches those controls from OWASP/AWS documentation rather than claiming a specific company's post-incident IMDSv2 rollout or exact S3 permissions without primary evidence. Similarly, Target/SolarWinds/Morris-worm history is not necessary to assert made-up prevalence; the retained teaching is third-party identity, build/distribution integrity and unsafe parser exposure, all represented in the threat model.

Where to spend a limited security budget

A WAF can be a useful temporary filter or virtual patch for known request patterns, with bypass/false-positive limits. Structural fixes—safe query APIs, scoped data access, default-deny routing, least privilege—reduce recurring implementation risk. Detection and response help contain failures that prevention misses and require sustained staffing/tuning. Choose a portfolio from your assets and threats; these are complementary controls, not a universal first/second/third spending order. A rapid code fix can be more useful today than an elaborate WAF rollout, and a detection system can trigger preventive containment.

A corrected fifteen-point review checklist

  1. Bind SQL values; separately constrain dynamic identifiers and raw ORM escape hatches.
  2. Authorize principal, tenant, object and action on all access paths, including caches/exports.
  3. Use modern password KDFs with salts and calibrated parameters; plain salted SHA-256 is still wrong.
  4. Define session/token lifetime, revocation, freshness and reauthentication; no universal 24-hour rule.
  5. Set CORS intentionally, but remember it controls browser response access, not general authentication or CSRF.
  6. Bound attempts and expensive work across accounts/sources/global capacity without easy victim lockout.
  7. Keep secrets out of source/logs/artifacts; manage identity, rotation, revocation and leak response.
  8. Validate TLS and service identity; deploy HSTS only with a tested domain/subdomain policy.
  9. Validate input types, sizes and business constraints on trusted servers.
  10. Use context-appropriate output handling; sanitize intended rich HTML with maintained rules.
  11. Inventory/scan dependencies and shipped artifacts; enforce existing gates and reviewed exceptions.
  12. Return safe errors without exposing internal queries, tokens or stack details.
  13. Keep necessary audit evidence while minimizing/redacting personal and secret data.
  14. Bound uploads, inspect actual content, isolate storage/serving and scan where appropriate; MIME headers alone are untrusted.
  15. Update threat model and negative tests when trust boundaries, privileges or architecture change.

Recall and worked answers

  1. A01 and the 94% statistic? Broken Access Control; 94% were tested for it, not found vulnerable. Read the 3.81% average-incidence metric in its contributed-dataset context.
  2. AuthN/AuthZ categories? A07 establishes identity/session assurance; A01 governs allowed resource actions. Both can fail in the same flow.
  3. SQL fix? execute("SELECT ... WHERE id = ?", (uid,)) for SQLite values; use the actual driver's placeholder syntax and separate identifier controls.
  4. EC2 metadata defense? Require IMDSv2 where applicable, least-privilege roles and actual egress restrictions; do not probe the endpoint to demonstrate the idea.
  5. Equifax category? A06 is a useful lens for the unpatched component; inventory, deployment verification and other control failures also matter.
  6. Three secret leak paths? Source/history, runtime logs/dumps, and third-party telemetry/artifacts. This is a set of examples, not a measured prevalence ranking.
  7. STRIDE? Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege, applied to specific assets/flows/boundaries.
  8. bcrypt versus SHA-256? Configurable costly password verification versus a fast general hash; Argon2id is the captured OWASP new-system preference. Runtime cost is hardware-dependent.
  9. SBOM purpose? Locate exact affected components in deployed artifacts; completeness/provenance and advisory matching still need verification, not a promised 30-minute response.
  10. Reset-password controls? Use the six-row model above with proof binding, single use/expiry, audit/privacy, attempt budgets and no privilege changes. A signed token without those properties is incomplete.

The final exercise is to choose one real design you are authorized to review, draw its trust boundaries, write six concrete threats and bind each control to an observable negative test—without creating vulnerable public services or scanning systems you do not own.

Prerequisites and study route

Bring HTTP, authentication/authorization, TLS and basic database use. This review connects IAM/least privilege, supply-chain integrity, audit/incident response and secure system design. The 2021 category labels are an explicitly versioned study aid.

Connect this risk map back to TLS/PKI for authenticated transport and to HTTP for HSTS, CSP and framing controls: each header needs its own semantics and deployment policy, not a promise to solve all injection. Observability supplies protected audit and response; IAM/least privilege constrains identities and resources; supply-chain work covers SBOMs, signatures and reproducible builds; payment-system design exercises the resulting threat model. Old S108/S110/S123 labels are historical topic cues, not verified current destinations or compliance approval.

Optional original incident-reading paths: Equifax secondary account, Capital One secondary account, and Log4Shell secondary overview. These preserve discovery context, not acceptance of the original counts, attributed remediation or exploit instructions. Governing factual passages remain the FTC, Apache, AWS and OWASP references above.

Next: S084 — The ML mental model: features, labels and splits. This is a curriculum transition, not another authentication lesson or a renewed technical review of the destination. Hub: The 6-Month Learning Plan.

Study pacing: the original pre-read block suggested 28 minutes, separately from its 90-minute session cue; individual resource durations were not verified. The session proposed intuition (5), visual walkthrough (15), hands-on (25), production cases (15) and recall/stretch (10). These are planning cues, not measured completion times; the five named blocks total 70 minutes, leaving 20 minutes of the stated session budget unallocated. Use the safe local fixtures or source-only references as marked, then explain the failure cases without notes. This retains the original 130-session-series context without promising the current catalog is an unchanged evergreen set.

Sources and verification boundary

Primary source passages were read and captured on 2026-10-08, with raw/canonical hashes, exact quote offsets and claim-specific entailment retained in the batch evidence. Historical pins describe their own versions, not current deployment recommendations.

Exact in-memory SQLite, egress-policy, rate-limit and KV-response fixtures were executed. No metadata request, external vulnerability probe, secret manager, CI workflow or cloud write was run.

Pause / Recall / Apply

Can you explain it without the page?

Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.

Stored in this browser only. No account, no sync. Clearing browser data removes your record.