Threat Modeling and Attack Surface Analysis Questions
Systematically identifying how a system can be attacked and where its exposure lies. Covers structured methodologies (STRIDE, PASTA, DREAD, OCTAVE, attack trees), enumerating and reducing attack surface, mapping trust boundaries and data flows via DFDs, profiling likely threat actors, and prioritizing identified threats by likelihood and impact during design. Includes applying this methodology to specific architectural substrates (cloud-native and serverless, microservices, ML/AI systems, IoT, CI/CD pipelines, cryptographic subsystems) and operationalizing it as a recurring program (SDLC integration, governance, tooling, KPIs). The proactive 'think like an attacker before you build' discipline: distinct from live penetration testing (the adversarial validation of a built system), from runtime detection/monitoring (recognizing an attack already in progress), and from implementing the resulting security controls (a separate design-and-build discipline).
Explain what a Security SLO could look like and give an example. For the example (e.g., 'time-to-patch critical CVEs'), define the SLO target, the error budget policy (what happens when the SLO burns), and how SREs would enforce or measure compliance operationally.
Sample Answer
Direct answer
A Security service-level objective (SLO) is a measurable, time-boxed commitment about security posture, built with the same three pieces as a reliability SLO: a specific metric (what you measure), a numeric target (the goal), and a rolling measurement window. A concrete example is time-to-patch for critical vulnerabilities: 95% of in-scope systems with a Critical-severity vulnerability are patched or otherwise mitigated within 72 hours of public disclosure, measured over a rolling 30-day window. The remaining 5% is the error budget, and what happens when it burns down is the part that makes the commitment real rather than aspirational: staged consequences, from a mandatory triage at partial burn to a change freeze at full burn, enforced through the same operational tooling and escalation paths a team already uses for reliability SLOs.
Structured elaboration
What a Security SLO looks like. Like any SLO, it needs three parts. A service-level indicator (SLI): the specific, objectively measurable thing being tracked, for example "percentage of Critical-severity vulnerabilities patched or mitigated within 72 hours of disclosure." A target: the numeric goal the SLI must meet, for example 95%. A measurement window: the period over which the target is evaluated, for example a rolling 30 days rather than a single point in time, so one bad week does not by itself constitute a breach. What separates a Security SLO from a general security policy statement is that it is continuously measured against real operational data on a dashboard, not a document that says "we should patch quickly" without a number attached to it.
Worked example: time-to-patch critical Common Vulnerabilities and Exposures (CVEs).
- SLI: percentage of in-scope, internet-facing systems carrying a Critical-band vulnerability (a Common Vulnerability Scoring System, or CVSS, score of 9.0 to 10.0) that are patched or have an approved compensating control applied within 72 hours of public disclosure.
- SLO target: 95%, measured over a rolling 30-day window.
- Why "patched or mitigated" rather than "patched": counting only full patches penalizes a team for a legitimate, faster compensating control (a temporary network-level block, a web application firewall rule) that also reduces risk, and creates pressure to rush a risky patch instead of doing it safely. The SLI should credit any control that closes the exposure, not just one specific remediation path.
Error budget policy: what happens when the SLO burns. The error budget is the gap between the target and 100%, here 5% of in-scope critical-vulnerability instances per rolling month that are allowed to miss the 72-hour window before the SLO itself is considered breached. The policy stages consequences by how much of that budget has been consumed, rather than treating every late system as identical:
- Under 50% of the budget consumed: normal operation, tracked but no escalation.
- At 50% of the budget consumed: trigger escalation. A named on-call security owner is assigned, a mandatory triage review happens within a defined short window (for example four hours), and non-critical, unrelated release work that would consume the same engineering capacity is temporarily deprioritized so remediation gets the resources it needs.
- At 100% of the budget consumed (the SLO itself breached): a stronger response, for example a freeze on non-essential production changes until the team is back under budget, plus a blameless postmortem examining why the target was missed (a process gap, a tooling gap, an understaffed on-call rotation) so the fix addresses the cause, not just the current backlog.
How operations teams enforce and measure this in practice. The metric is not manually tallied; it is produced by joining two data sources a mature operations function already runs: a vulnerability feed that timestamps public disclosure for each Common Vulnerabilities and Exposures identifier, and the organization's own asset inventory and patch or scan status for each in-scope system. Joining the two produces, per vulnerability instance, a live "time since disclosure versus remediation status" number, which rolls up into the SLI on the same dashboard cadence the team already uses for its reliability SLOs. The escalation and freeze mechanics reuse the same operational machinery a team applies to a reliability error budget (paging, a defined freeze scope, a postmortem template), which matters beyond convenience: it means security is competing for engineering attention through a mechanism the organization already trusts and understands, rather than through a separate, bespoke security process that has to earn credibility on its own.
Worked example
Take an organization with 400 systems that carried at least one Critical-severity vulnerability in a given rolling 30-day window. At the 95% target, the error budget allows up to 400×0.05=20 of those systems to miss the 72-hour window before the SLO itself is breached, and the 50%-of-budget escalation trigger fires once 20×0.50=10 systems have missed it. Suppose 12 systems actually missed the window that month. As a share of all 400 systems that is only 12/400=3%, which sounds small in isolation, but as a share of the 5% error budget it is 12/20=60% burned, above the 50% trigger and enough to activate escalation (mandatory triage, a named on-call owner, non-critical release work deprioritized), while still short of the 100% mark that would trigger a full freeze. This is the point of staging the response by budget burn rather than by raw count: 12 late systems reads as a minor number on its own, but expressed against the budget it is already past the point where the policy calls for an active response, not a passive note in a monthly report.
Trade-offs and pitfalls
The most common design mistake is setting the target aspirationally instead of against a realistic current baseline: a team currently patching critical vulnerabilities in an average of five days that commits to a 99% within-24-hours target will either miss it constantly, which trains everyone to ignore the SLO as noise, or will hit it only by taking on damaging shortcuts. Start from where the team actually is and tighten the target on a defined cadence as the process improves, rather than picking a number that sounds impressive in a slide. A second pitfall is measuring only patch completion and ignoring compensating controls, which, as above, creates pressure toward risky rushed patches instead of a safer staged response. A third is treating the freeze as a free lever: pausing non-essential releases has a real cost to the business, and a team that reaches for a freeze thoughtlessly will face the same stakeholder pushback a security team gets when it tries to reprioritize work without first making the business case with data, so the freeze scope and trigger thresholds should be negotiated with the stakeholders who own that competing work before the policy is enforced for the first time, not discovered by them when it first fires. Finally, an SLO that exists as a document but is not wired into a live, continuously updated dashboard is not actually an SLO: the entire value of the error-budget framing is that burn is visible before it becomes a crisis, and a policy nobody is watching provides none of that early warning.
Design an enterprise threat modeling program for a global company with 10,000 employees and 500 applications. Define governance (roles and responsibilities), end-to-end process workflows, tooling (including automation and integration points), KPIs to measure program health, onboarding for new teams, and how to scale peer reviews while keeping models current.
Sample Answer
Direct answer
At 10,000 employees and 500 applications you cannot run threat modeling as a bespoke, architect-led exercise per application; the program has to be built around three decisions: a small central security architecture team that owns methodology and standards rather than doing all the work, a federated network of trained "security champions" embedded in product teams who actually run most sessions, and a risk-tiering scheme that routes scarce expert review time to the riskiest 10-15% of applications while automating or templating the rest. Governance says who is accountable at each tier, the workflow says when a model is created and re-opened, tooling closes the gap between the diagram and the real system, and the KPIs have to prove risk went down, not just that paperwork was produced.
Structured elaboration
Governance: roles and responsibilities
- Central Security Architecture / Threat Modeling Center of Excellence (a small standing team, "CoE" below): 3-6 people for an organization this size. Owns the chosen methodology (typically STRIDE, an acronym for Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, and Elevation of privilege, used to systematically walk each element of a diagram for threat types), the written standard, the champion training curriculum, the tooling roadmap, and personally reviews only the highest-risk tier of applications.
- Security champions: one to two engineers per product team, trained and certified by the CoE. They facilitate the actual modeling session for their team's applications and are the first escalation point before anything reaches the CoE.
- Application/product owner: accountable for a model existing, staying current, and its findings being tracked to closure or a documented, approved risk acceptance. This is a business accountability, not a security one, which is deliberate: it keeps the finding on the product roadmap instead of parked in a security backlog nobody funds.
- Architecture or risk review board: signs off the highest-risk tier's models and any request to formally accept a risk above a defined severity or duration threshold.
- Risk tiering, which everything else keys off: Tier 1 (internet-facing, regulated or highly sensitive data, revenue-critical) gets a CoE-facilitated model, refreshed on a fixed maximum cadence. Tier 2 (internal, moderate sensitivity) is champion-facilitated with the CoE auditing a sample. Tier 3 (low-risk internal tooling) uses a self-service template with no mandatory review queue at all. Without this tiering step, "review all 500 applications" and "review only the ones that matter" collapse into the same unstaffable ask.
End-to-end process workflow
- Intake and tiering: a new project or a major architecture change registers through the same intake process architecture review already uses, and gets scored into a tier using a rubric (internet exposure, data sensitivity class, revenue impact, regulatory scope).
- Model creation: the champion (or the CoE for Tier 1) builds a data-flow diagram, an intentionally simple diagram of how data moves between processes, data stores, and external entities, marks the trust boundaries (points where data crosses between zones of differing trust, such as internet to load balancer, or service to database), and applies STRIDE to each element that touches a boundary.
- Risk scoring and prioritization: each identified threat is scored on likelihood and impact and ranked, and each gets a disposition: mitigate now, schedule for later, or formally accept the residual risk.
- Review and sign-off: Tier 1 goes to the architecture or risk review board; Tier 2 and 3 self-certify, with the CoE sampling a slice for quality.
- Tracking to closure: findings become real backlog tickets with owners and due dates in the same issue tracker engineering already uses, not a separate governance-only tool nobody opens.
- Re-validation trigger: not purely calendar-based. A model is reopened on whichever comes first: its tier's maximum age (for example, 6 months for Tier 1, 12 for Tier 2), a defined material-change trigger (a new trust boundary, a new data classification, a new external integration), or a relevant incident or near-miss that suggests the model missed something.
Tooling, automation, and integration points
- A shared modeling tool that stores the data-flow diagram as a versioned artifact (not a slide deck), so models are diffable over time and don't silently rot.
- Infrastructure-as-code and architecture-as-code repositories are watched for changes that imply a new trust boundary, a new public endpoint, or a new external integration; a match auto-flags the affected application's model as "possibly stale" and opens a ticket for the owning team, rather than relying on anyone remembering to check.
- Issue-tracker integration so every finding is a real, assigned ticket with an SLA tied to its severity, and a CI pipeline gate on Tier 1 applications that blocks release if a material-change flag is open and unresolved.
- A lightweight internal dashboard aggregating model status, age, and open-finding counts across all 500 applications; this is also the primary data source for the KPIs below.
KPIs to measure program health
- Coverage: the percentage of Tier 1 and Tier 2 applications with a model younger than that tier's maximum age.
- Freshness: the median age of active models, plus the count of models flagged stale by the material-change trigger but not yet re-reviewed (a leading indicator of drift the coverage number alone hides).
- Time-to-mitigate: median days from a critical or high finding being logged to it being closed or formally risk-accepted.
- Champion capacity: trained and active champions versus the number the coverage target actually requires, so the program can see a capacity shortfall before it shows up as missed coverage.
- A leading and a lagging metric paired together: models started per quarter (leading) against critical findings discovered in production or during a red-team exercise that a prior threat model should have caught (lagging). A program can look healthy on the leading metric while still missing real threats; pairing them is how you catch that.
Onboarding for new teams
- A hands-on workshop, roughly half a day, where a new champion walks a real system (not a toy example) through the methodology alongside a CoE member.
- A day-one template pack with pre-built diagram shapes for the organization's common patterns (web app with a database, event-driven microservice, batch data pipeline), so a new team is not starting from a blank canvas.
- A grace period: a new team's first model is CoE-reviewed regardless of its assigned tier, purely to calibrate quality, before the team is trusted to self-certify going forward.
Scaling peer review while keeping models current
- Review depth scales with tier, not with headcount: Tier 1 gets full CoE peer review, Tier 2 gets structured self-review against a checklist plus a random 10% CoE audit sample (weighted toward anything the automation already flagged), and Tier 3 has no review queue at all.
- A rotating peer pool among trained champions cross-reviews each other's Tier 2 models, a few per person per quarter. This scales review capacity linearly with the champion population instead of bottlenecking on the CoE, and it improves quality by exposing each champion to other teams' patterns.
- Automation absorbs the highest-volume, lowest-judgment work (noticing that something changed) so human reviewers spend their time on judgment calls (is this threat actually mitigated) instead of on remembering to check 500 applications on a calendar.
Worked example
Take the Tier 1 slice of the portfolio. A defensible planning assumption (not a measured figure) is that a CoE reviewer can sustain roughly 15-20 full Tier 1 reviews per quarter at 2-3 hours of reviewer time each. With 3 CoE reviewers, that is 45-60 reviews per quarter, or 180-240 per year. If Tier 1 is 10-15% of 500 applications, that is 50-75 applications, and refreshing each on a 6-month cycle requires 100-150 review-events per year (two per application per year). That falls inside the 180-240 the CoE can sustain, with headroom for new-team onboarding reviews and audit sampling of Tier 2. The number that makes the design choice legible: reviewing all 500 applications at the same depth, on the same 6-month cycle, would require 1,000 review-events per year, which at 2-3 hours each is 2,000-3,000 reviewer-hours; dividing that same 1,000 review-events by the 60-80 reviews a single reviewer can sustain per year (the same throughput assumption used above, not a separate raw-hours figure) gives roughly 13-17 full-time reviewers, not 3. The tiering decision is what turns an unstaffable program into a staffable one; it is the load-bearing choice in this design, not a detail.
Trade-offs and pitfalls
- Over-centralizing kills scale. Routing every one of 500 applications through a 3-6 person CoE turns the program into the release bottleneck, and teams will quietly route around it under deadline pressure. The champion network exists specifically to avoid this.
- Under-tiering recreates the same bottleneck with extra steps. If every team calls its application Tier 1 "to be safe," the CoE is back to reviewing everything; the rubric only works if it is enforced and periodically audited for tier-inflation drift.
- A stale model is worse than no model, because it creates false confidence that the system was analyzed. This is why re-validation is triggered by material change, not only by a calendar date: calendar-only cadence misses the actual risk driver, which is an unreviewed change, not the passage of time.
- Measuring the program by "models produced" alone rewards checkbox compliance. The KPI set above deliberately pairs an activity metric with an outcome metric (time-to-mitigate, and production findings that should have been caught) specifically to keep the incentive on real risk reduction rather than on paperwork volume.
- A senior answer names the tiering rubric and the staffing math explicitly, the way the worked example above does, rather than asserting "we'll scale with champions" without showing why the arithmetic actually closes.
Write clear pseudocode for a prioritization algorithm that accepts a list of vulnerabilities where each entry includes: cvss (0-10), exploitability_index (0-1), asset_value (1-10), and threat_intel_score (0-1). The algorithm should compute a weighted risk score, normalize results to 0-100, and assign remediation windows (Immediate, 1 week, 1 month, 3 months) based on configurable thresholds and confidence. Describe how uncertainty or low confidence should affect remediation assignment.
Sample Answer
Direct answer
Score each vulnerability as a weighted blend of CVSS, exploitability, asset value, and threat intelligence, normalized to 0-100, then map that score to a remediation window through fixed thresholds. The part that actually separates a strong answer from a mechanical one is what happens when one or more of those four signals is missing: a naive implementation either crashes or silently treats a missing signal as zero, which quietly under-scores real risk. The fix is to renormalize the weights over only the signals that are present, and to track a confidence value (the fraction of total weight backed by real data) that can tighten a remediation window but must never loosen one.
Structured elaboration
Weighting scheme. Each of the four signals gets a base weight that reflects how directly it measures exploitable risk versus business context:
| signal | base weight | why |
|---|---|---|
| CVSS (0-10) | 0.35 | standardized severity, most portable across vulnerability types |
| exploitability_index (0-1) | 0.25 | how close the vulnerability is to a working exploit right now |
| asset_value (1-10) | 0.20 | business-impact multiplier, not a vulnerability property |
| threat_intel_score (0-1) | 0.20 | is anyone actually targeting this right now |
Weights sum to 1.0 when all four signals are present. When a signal is missing, drop it from both the numerator and the weight-sum denominator and recompute the score over whatever remains, rather than treating the missing value as 0 (which would silently push every incomplete record toward looking safe) or as the population mean (which hides the fact that you don't know).
Confidence. Define confidence as the fraction of the total weight mass that is backed by real data: confidence = sum(weights of present signals). Four-of-four present gives confidence 1.0; two-of-four (say CVSS and asset_value only) gives 0.55.
Remediation windows. Fixed thresholds on the normalized 0-100 score: >=85 Immediate, >=60 1 week, >=35 1 month, else 3 months.
How uncertainty changes the assignment. The score itself already renormalizes over available signals, so a record with fewer signals is not automatically pushed to a safer bucket. The remaining policy question is what LOW CONFIDENCE (I use <0.6, roughly "fewer than half the weight mass is backed by data") should do beyond that. The rule that holds up under scrutiny: confidence can only ever TIGHTEN a window (pull it toward Immediate), never loosen one. Loosening on missing data would mean the algorithm rewards ignorance with a longer grace period, which is the wrong incentive and the wrong risk posture. I tighten by exactly one tier when the record's raw score already sits within 10 points of the next tighter tier's threshold, i.e., the record is a plausible candidate for that tier if the missing signals had turned out unfavorably. This is deliberately not a blanket "low confidence always means Immediate" rule: that would flood the Immediate queue with genuinely low-risk-but-sparsely-scored records (imagine a CVSS-4.0 finding with three missing fields) and destroy the queue's credibility as a real urgency signal.
Worked example (executed)
from dataclasses import dataclass
from typing import Optional
@dataclass
class Vuln:
vuln_id: str
cvss: float
exploitability_index: Optional[float]
asset_value: Optional[float]
threat_intel_score: Optional[float]
BASE_WEIGHTS = {"cvss": 0.35, "exploitability_index": 0.25, "asset_value": 0.20, "threat_intel_score": 0.20}
THRESHOLDS = [(85, "Immediate"), (60, "1 week"), (35, "1 month"), (0, "3 months")]
def normalize(v):
return {
"cvss": v.cvss / 10.0,
"exploitability_index": v.exploitability_index,
"asset_value": None if v.asset_value is None else (v.asset_value - 1) / 9.0,
"threat_intel_score": v.threat_intel_score,
}
def score_vuln(v):
fields = normalize(v)
present = {k: val for k, val in fields.items() if val is not None}
missing = [k for k, val in fields.items() if val is None]
present_weight_mass = sum(BASE_WEIGHTS[k] for k in present)
confidence = present_weight_mass
if present_weight_mass == 0:
return {"vuln_id": v.vuln_id, "score": 0.0, "confidence": 0.0, "window": "3 months"}
weighted_sum = sum(BASE_WEIGHTS[k] * present[k] for k in present)
score_0_100 = round((weighted_sum / present_weight_mass) * 100, 1)
window = next(w for t, w in THRESHOLDS if score_0_100 >= t)
if confidence < 0.6:
idx = next(i for i, (t, w) in enumerate(THRESHOLDS) if score_0_100 >= t)
if idx > 0 and score_0_100 >= THRESHOLDS[idx - 1][0] - 10:
window = THRESHOLDS[idx - 1][1]
return {"vuln_id": v.vuln_id, "score": score_0_100, "confidence": round(confidence, 2),
"missing_signals": missing, "window": window}
vulns = [
Vuln("V-1", 9.8, 0.9, 9, 0.8),
Vuln("V-2", 9.8, 0.9, 9, None),
Vuln("V-3", 8.5, 0.8, 8, None),
Vuln("V-6", 8.0, None, 7, None),
Vuln("V-4", 4.0, 0.2, 3, 0.1),
Vuln("V-5", 6.5, None, None, None),
]
for v in vulns:
print(score_vuln(v))
Actual output from running this:
{'vuln_id': 'V-1', 'score': 90.6, 'confidence': 1.0, 'missing_signals': [], 'window': 'Immediate'}
{'vuln_id': 'V-2', 'score': 93.2, 'confidence': 0.8, 'missing_signals': ['threat_intel_score'], 'window': 'Immediate'}
{'vuln_id': 'V-3', 'score': 81.6, 'confidence': 0.8, 'missing_signals': ['threat_intel_score'], 'window': '1 week'}
{'vuln_id': 'V-6', 'score': 75.2, 'confidence': 0.55, 'missing_signals': ['exploitability_index', 'threat_intel_score'], 'window': 'Immediate'}
{'vuln_id': 'V-4', 'score': 25.4, 'confidence': 1.0, 'missing_signals': [], 'window': '3 months'}
{'vuln_id': 'V-5', 'score': 65.0, 'confidence': 0.35, 'missing_signals': ['exploitability_index', 'asset_value', 'threat_intel_score'], 'window': '1 week'}
V-5 is the record that directly exercises the bug this code guards against: CVSS is the only signal present (confidence 0.35, well below the 0.6 floor), and it runs cleanly to a score of 65.0 because normalize() explicitly checks None before computing asset_value's range conversion; the earlier, buggy draft of this function computed (v.asset_value - 1) / 9.0 unconditionally and raised a TypeError on exactly this record, which is why it is in the test set. V-6 is the case that demonstrates the tightening rule: its raw renormalized score (75.2) falls in the '1 week' tier (60-85), but with only 2 of 4 signals present (confidence 0.55, missing exploitability and threat intel) and a score within 10 points of the Immediate threshold, it tightens to Immediate. V-3 is the record that actually isolates the confidence floor: one signal missing (confidence 0.8) and a raw score of 81.6 that IS inside the 10-point borderline band below the Immediate threshold of 85, so the single reason it stays at '1 week' is that 0.8 clears the 0.6 floor. Raise the floor above 0.8 and V-3 tightens to Immediate; that sensitivity is what makes it a real test of the rule. V-2 is deliberately NOT that test, and it is worth being explicit about why: at 93.2 it is already in the tightest tier, so the idx > 0 guard blocks any adjustment no matter what its confidence is. Citing V-2 as evidence that the confidence floor works would be citing a record whose output is identical whether the floor is enforced, inverted, or deleted. Every rule in a scoring policy needs at least one case whose result would change if the rule were removed, otherwise the test set is decorative.
Trade-offs and pitfalls
A fixed confidence floor (0.6) and a fixed borderline window (10 points) are policy knobs, not laws of nature; they should be tuned against your own false-Immediate rate, not copied verbatim. Renormalizing weights over present signals implicitly assumes the missing signals are 'unbiased noise' rather than systematically missing for a reason (a common trap: threat_intel_score is often missing precisely for the vulnerabilities that are NEW and not yet catalogued by intel feeds, which biases the missing set toward exactly the underscored, high-uncertainty vulnerabilities you most need confidence penalties for, which is worth stating explicitly to a reviewer). The single most common implementation bug, which this code deliberately guards against, is computing asset_value normalization unconditionally even when the value is None, which raises a TypeError rather than falling into the missing-signal path; that bug shipped in an earlier draft of this exact code and was caught by adding a test case (V-5, CVSS only) that exercises it.
Explain the role of asset classification in threat modeling. Provide an example classification scheme (e.g., public/internal/confidential/secret) and describe how classification affects threat identification and mitigation prioritization specifically for an HR data store containing PII and payroll data.
Sample Answer
Direct answer
Asset classification tells you where to spend your limited threat-modeling and mitigation effort before you even start listing threats: an asset's classification level sets the bar for how seriously you treat threats against it, because the same threat (say, unauthorized read access) is a minor annoyance against public marketing content and a severe incident against payroll records. A typical scheme is public, internal, confidential, and secret, ordered by increasing sensitivity and increasing consequence if the confidentiality, integrity, or availability of that asset is violated. For an Human Resources (HR) data store holding personally identifiable information (PII, data that can identify a specific individual) and payroll data, that store lands at confidential or secret, which changes both which threats get analyzed first and how aggressively their mitigations get funded.
Structured elaboration
An example classification scheme
- Public: intended for unrestricted release (marketing pages, published job listings). A confidentiality breach here has little to no consequence, since the information was already meant to be public.
- Internal: meant for employees only, but not independently damaging if it leaked (internal wiki pages, org charts). A breach is embarrassing or gives a competitor minor insight, but rarely triggers a regulatory or financial event.
- Confidential: business-sensitive or personal data whose exposure causes real harm (customer PII, financial forecasts, most HR records). A breach here typically has legal, regulatory, or reputational consequences.
- Secret: the highest tier, data whose exposure causes severe, possibly irreversible harm (authentication credentials, encryption keys, payroll and banking details, health information). A breach here can trigger regulatory penalties, direct financial fraud, or harm to specific individuals.
Why classification matters for threat modeling, and how it changes prioritization
Classification is not paperwork sitting alongside the threat model, it is an input to it, in two concrete ways:
- It changes threat identification scope. A data-flow diagram (a diagram of how data moves between processes, data stores, and external entities) for a public-content system needs a lighter pass, spoofing and denial-of-service matter more than information disclosure, since there is little to disclose. The same diagram shape for a confidential-or-higher data store needs the full STRIDE pass (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege), with information disclosure and tampering given the most scrutiny, because those are the threat categories that directly violate what makes the asset sensitive in the first place.
- It changes mitigation prioritization. When two findings compete for the same sprint's engineering time, the one touching the higher-classified asset wins, all else equal, because the expected harm from a realized threat scales with classification. A medium-likelihood finding against a secret-tier asset is typically prioritized over a high-likelihood finding against a public-tier asset, since the consequence term in a likelihood-times-impact assessment is what classification is directly encoding.
Applied to the HR data store
An HR data store containing PII and payroll data is not internal, and arguably not merely confidential either, because it combines two distinct sensitivity drivers: PII triggers privacy-regulation obligations (many jurisdictions have specific legal requirements for how PII must be protected and what happens if it is exposed), and payroll data is financially sensitive in its own right (bank account numbers, compensation figures) with direct fraud potential if disclosed or tampered with. A reasonable classification is confidential for general HR records and secret for the payroll and banking-detail subset specifically, since that subset has the highest direct-fraud potential if tampered with or disclosed. That split matters in practice: it means the threat model treats the payroll subsystem's trust boundaries (who can read it, who can write to it, what logs its access) with the tightest scrutiny, while general HR records (job titles, org structure) get real but comparatively lighter scrutiny.
Worked example
Take two findings from a threat model of an HR system: Finding A is a medium-likelihood information-disclosure risk on the general employee-directory service (classified internal to confidential, mostly names and job titles), and Finding B is a medium-likelihood information-disclosure risk on the payroll service (classified secret). Both findings have the same likelihood rating and the same threat category. Classification is what breaks the tie: Finding B is prioritized first, because the consequence of the same threat materializing is categorically higher, direct financial and privacy harm to individuals versus reputational embarrassment. Without classification as an explicit input, both findings would look identical on a bare likelihood scale, and prioritization would have no principled basis for choosing between them.
Trade-offs and pitfalls
- The common wrong turn is classifying at the system level instead of the asset level. Calling the entire HR system "confidential" and stopping there misses that the payroll subset inside it deserves stricter treatment than the org-chart subset; classification should be granular enough to actually drive different mitigation decisions within one system, not just a single label on the whole thing.
- Classifying everything as the highest tier "to be safe" defeats the purpose: it removes classification's ability to prioritize at all, since prioritization only works if it can distinguish between assets.
- Classifying once and never revisiting it misses that a data store's sensitivity can change, for example if a previously internal-only HR system starts also storing bank details for direct-deposit payroll, which should trigger a reclassification and a fresh look at that asset's threats, not a threat model that quietly continues operating on a stale classification.
Design a prioritized 90-day attack surface reduction plan for a mid-size company's AWS environment covering network exposure, IAM policies, unused services, container registries, and third-party integrations. Provide milestones, measurable goals, and quick wins that reduce exposure while remaining operationally feasible.
Sample Answer
Direct answer
Sequence the 90 days by cost and reversibility, not by theoretical risk alone: week one is discovery and the cheapest, lowest-risk wins (closing overly broad network ingress and dead services nobody will miss), the middle weeks tackle IAM (Identity and Access Management) and registry hardening that needs testing before enforcement, and the final weeks lock in prevention (guardrails that stop the surface from creeping back) rather than just one-time cleanup. Track progress with a single weighted attack-surface score computed from counts across the five categories (network exposure, IAM, unused services, container registries, third-party integrations), so "reduced exposure" is a number that goes down, not a subjective claim.
Structured elaboration
Discovery first (days 1-10, prerequisite to everything else)
Before touching anything, inventory each category with existing tooling: security-group and route-table exports for network exposure, IAM Access Analyzer and policy-simulator output for over-permissive policies, a CloudTrail-based usage query (which resources have zero API calls or traffic in the last 90 days) for unused services, registry access logs for container registries, and the org's vendor/API-key inventory for third-party integrations. This inventory is also what makes the plan's "quick wins" genuinely quick: closing something before confirming nobody depends on it is how a hardening plan turns into an outage.
Days 1-30: quick wins, chosen for low blast-radius
- Network exposure: close security-group rules allowing
0.0.0.0/0ingress on any port that is not deliberately public (HTTP/HTTPS on a load balancer), replacing them with scoped CIDR ranges or a bastion/VPN path. This is the highest-leverage quick win because a broad ingress rule with zero legitimate traffic in the discovery data is almost always safe to close immediately. - Unused services: decommission the zero-traffic resources found in discovery (idle EC2 instances, orphaned load balancers, stopped-but-not-terminated instances still holding public IPs). Low risk because "zero traffic for 90 days" is itself the safety check.
- Container registries: require authentication for all image pulls; a registry that allows anonymous or unauthenticated pull is a quick, low-disruption fix since legitimate consumers already have credentials.
- Third-party integrations: revoke API keys and OAuth grants for integrations discovery shows are no longer in active use.
Days 31-60: changes that need validation before enforcement
- IAM policies: this is the category quick wins should NOT touch directly, because a wildcard
Action: "*"policy attached to something actively in use will break it the moment it is tightened. The 30-day process is: generate least-privilege policy replacements from IAM Access Analyzer's actual-usage data, deploy them in a non-blocking audit/simulate mode, review what the tightened policy would have denied over a full business cycle (to catch monthly or quarterly jobs discovery's shorter window might miss), then switch to enforcing. - Container registries, phase two: add image scanning at push time and quarantine images with critical known vulnerabilities, which needs a grace period and a remediation path for teams with existing non-compliant images rather than an immediate hard block.
- Third-party integrations, phase two: for integrations still in active use, move from standing, broadly-scoped credentials to short-lived, narrowly-scoped ones where the vendor's API supports it, coordinated with each integration's owning team since this can require code changes on their side.
Days 61-90: guardrails that prevent the surface from creeping back
A cleanup with no prevention step regresses within a quarter as new resources get provisioned the old way. Close the loop with: a policy-as-code check (for example, an AWS Config rule or a pre-deploy IaC scan) that blocks new 0.0.0.0/0 ingress rules and new wildcard IAM policies from being created at all, an automated 90-day idle-resource report that runs on a recurring schedule instead of a one-time discovery pass, and a lightweight approval gate for any new third-party integration request so standing credentials are scoped correctly from day one instead of retrofitted later.
Applying the same reduction methodology to a heavily-exposed API gateway
The same design-time versus runtime split applies at a finer grain to a gateway exposing several hundred endpoints, which is a common concentrated instance of "network exposure" above: design-time reduction means not exposing more than the client actually needs (deprecating unused endpoint versions, consolidating near-duplicate routes, requiring every route to declare an explicit authentication/authorization requirement rather than defaulting to open, and removing debug/introspection endpoints from production builds); runtime reduction means constraining what a request can do once it reaches an endpoint that must stay exposed (per-route rate limiting, a web application firewall rule set scoped to that route's expected input shape, and request-size/schema validation at the edge before the request reaches application code). Both are attack-surface reduction; the design-time work shrinks the count of things that could go wrong, and the runtime work bounds the damage from the ones that remain exposed by necessity.
Worked example
A weighted attack-surface score makes "reduced exposure" measurable instead of a narrative claim. Assign each finding category a severity weight (illustrative, 1-5 scale, reflecting relative blast radius if that class of finding is abused) and count instances before and after the 90-day plan:
| Category | Weight | Count, day 0 | Count, day 90 |
|---|---|---|---|
Security-group rules allowing 0.0.0.0/0 ingress | 3 | 40 | 8 |
IAM policies with wildcard (*:*) actions/resources | 5 | 15 | 3 |
| Unused/orphaned running services | 2 | 25 | 5 |
| Container registries allowing unauthenticated pull | 4 | 3 | 0 |
| Third-party integrations holding standing prod credentials | 3 | 10 | 4 |
score=∑categoryweight×count
Working the sum out term by term:
scoreday 0=40(3)+15(5)+25(2)+3(4)+10(3)=120+75+50+12+30=287
scoreday 90=8(3)+3(5)+5(2)+0(4)+4(3)=24+15+10+0+12=61
reduction=287287−61=78.7%
The same computation as a script, which is the form that matters because the point of scoring this way is rerunning it on a cadence against real counts:
# Weighted attack-surface score. Weights are 1-5 by relative blast radius;
# counts are illustrative planning assumptions, not measurements.
FINDINGS = [
# (category, weight, day_0, day_90)
("Security groups allowing 0.0.0.0/0 ingress", 3, 40, 8),
("IAM policies with wildcard actions/resources", 5, 15, 3),
("Unused/orphaned running services", 2, 25, 5),
("Container registries allowing unauthenticated pull", 4, 3, 0),
("Third-party integrations with standing prod creds", 3, 10, 4),
]
def score(findings, day):
idx = 2 if day == 0 else 3
return sum(w * f[idx] for f in findings for w in [f[1]])
d0 = score(FINDINGS, 0)
d90 = score(FINDINGS, 90)
print(f"{'category':52s} {'w':>2s} {'d0':>4s} {'d90':>4s} {'w*d0':>5s} {'w*d90':>6s}")
for name, w, a, b in FINDINGS:
print(f"{name:52s} {w:2d} {a:4d} {b:4d} {w*a:5d} {w*b:6d}")
print(f"\nscore day 0 = {d0}")
print(f"score day 90 = {d90}")
print(f"reduction = ({d0} - {d90}) / {d0} = {(d0 - d90) / d0:.3f} ({(d0 - d90) / d0 * 100:.1f}%)")
print("\nper-category reduction (why the aggregate alone is not enough):")
for name, w, a, b in FINDINGS:
print(f" {name:52s} {(a - b) / a * 100:5.1f}%")
Output:
category w d0 d90 w*d0 w*d90
Security groups allowing 0.0.0.0/0 ingress 3 40 8 120 24
IAM policies with wildcard actions/resources 5 15 3 75 15
Unused/orphaned running services 2 25 5 50 10
Container registries allowing unauthenticated pull 4 3 0 12 0
Third-party integrations with standing prod creds 3 10 4 30 12
score day 0 = 287
score day 90 = 61
reduction = (287 - 61) / 287 = 0.787 (78.7%)
per-category reduction (why the aggregate alone is not enough):
Security groups allowing 0.0.0.0/0 ingress 80.0%
IAM policies with wildcard actions/resources 80.0%
Unused/orphaned running services 80.0%
Container registries allowing unauthenticated pull 100.0%
Third-party integrations with standing prod creds 60.0%
Every count in this table is an illustrative planning assumption, not a measured figure from any real environment; what the example demonstrates is the METHOD (a weighted, category-broken-down score that can be recomputed on any real cadence) rather than a specific number to expect. The same formula run monthly turns "we reduced exposure" into a number a security leader can put in front of a board, and the per-category breakdown (rather than one aggregate figure) is what tells engineers which of the five categories still needs work.
Trade-offs and pitfalls
- Sequencing quick wins before validated changes is deliberate, not laziness. Closing broad network ingress and dead services first builds momentum and trust with stakeholders before asking teams to accept the riskier, validation-heavy IAM and credential-rotation work; doing IAM tightening first, before the team has any track record on this plan, is a common way to cause an outage and lose the mandate for the rest of the 90 days.
- "Zero traffic in the discovery window" is not proof of safety, only of no traffic in that window; a resource used quarterly or only during an annual event will look identical to a truly dead one. Extend the observation window for anything about to be deleted (not just closed off) rather than trusting the initial discovery pass alone.
- A single aggregate score can hide a regression in one category behind improvement in another. Report the per-category breakdown alongside the aggregate, since a security leader who only sees "score down 79%" cannot tell that container registries hit zero while third-party integrations, the laggard in the per-category output above, only dropped by 60%, information that changes what gets funded next quarter.
- Guardrails without an exception process get bypassed. A hard policy-as-code block on wildcard IAM with no documented, time-boxed exception path pushes teams toward workarounds (requesting a broader role "temporarily" through a different channel) that undo the whole effort; pair every guardrail with a fast, auditable exception process.
Unlock Full Question Bank
Get access to hundreds of Threat Modeling and Attack Surface Analysis interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.