Two ideas sit at the end of most security learning paths because they tie everything else together. Zero trust says: do not grant trust based on network location; verify every request. Threat modeling says: before building something, think systematically about how it could be attacked. Both sound like enterprise programmes with consultants and diagrams. In practice, both fit into a team's normal work.

This post explains zero trust in terms of decisions you make in code and infrastructure, then walks through a STRIDE threat model of a feature we built recently: customers uploading documents to their account.

Zero trust in five decisions

The traditional model trusted anything inside the corporate network or VPN. Zero trust removes that assumption. In practice it comes down to a handful of decisions:

  1. Authenticate every request, including service-to-service calls inside the network. Internal APIs require tokens or mutual TLS, not just "it came from the private subnet".
  2. Authorise every request against the resource and the identity, at the time of the request. A valid session is not permission to access everything.
  3. Use least privilege for users, services and machines, and prefer short-lived credentials.
  4. Consider device and context: a login from a managed laptop and a login from an unknown device can get different access to sensitive tools.
  5. Assume breach: segment systems, log access, and design so that one compromised component cannot reach everything.

None of these needs a new product. They need consistent choices: an API gateway that requires tokens on internal routes, network policies that deny by default, identity-aware access to admin tools instead of a flat VPN.

Threat modeling: four questions

A useful framing for threat modeling is four questions:

  1. What are we working on?
  2. What can go wrong?
  3. What are we going to do about it?
  4. Did we do a good enough job?

STRIDE helps with the second question by giving categories of threats to check for each part of a system:

Letter Threat Property violated Example
S Spoofing Authentication Using someone else's session token
T Tampering Integrity Modifying a file or request in transit or storage
R Repudiation Non-repudiation Denying an action because nothing logged it
I Information disclosure Confidentiality Reading other customers' documents
D Denial of service Availability Exhausting storage or processing
E Elevation of privilege Authorization A customer performing admin actions

Worked example: document uploads

1. What are we working on?

Customers upload documents (contracts, ID scans) to their account. Staff review them in an admin tool. We drew the data flow on a whiteboard:

[Customer browser] --HTTPS--> [API] --presigned URL--> [Object storage: uploads bucket]
                                 |                            |
                                 |                     (event) v
                                 |                      [Scanner worker] --> [Object storage: clean bucket]
                                 v
                           [Database: document records]
                                 ^
[Staff browser] --SSO--> [Admin tool]

Trust boundaries: between the browser and the API, between the API and storage, between the scanner and storage, between staff and the admin tool.

2. What can go wrong?

We walked each element and each arrow with STRIDE. A selection of what we found:

Element STRIDE Threat
Browser to API S Upload request with a stolen session
Presigned upload URL T URL reused to overwrite another file, or upload a much larger file
Uploads bucket I Bucket readable publicly or by other customers' URLs
Uploaded file T / E Malicious file (script, macro document, polyglot) opened by staff
API document listing I Customer requests another customer's document id
Admin tool R Staff views or deletes a document without a record
Scanner worker D Huge or decompression-bomb files exhaust the worker
Admin tool E Support role can delete documents, which only compliance should

3. What are we going to do about it?

Each threat became a mitigation and a ticket:

Threat Mitigation
Stolen session Re-authentication for uploading ID documents; session binding to device
Presigned URL abuse Short expiry (5 minutes), fixed object key per upload, content-length limit in the signed policy
Bucket exposure Block public access, bucket policy allowing only the API and scanner roles
Malicious files Content-type check by magic bytes, malware scan, files served to staff only after scanning, with Content-Disposition: attachment
Cross-customer access Every document query scoped to the authenticated customer; authorization tests
Missing audit Audit log for view, download and delete in the admin tool
Scanner DoS Size limit before scan, timeouts, separate queue with concurrency limits
Over-privileged support role Delete restricted to compliance role; support can only view

Some threats were accepted, with a reason: for example, we accepted that a customer could upload many small files within rate limits, because storage cost was low and monitoring would show abuse.

4. Did we do a good enough job?

We checked that every ticket was linked to the threat model, that security acceptance tests existed for the authorization and presigned URL rules, and that the penetration test scope for the next quarter included the upload flow. The threat model document, one page plus the diagram, lives next to the code and is updated when the feature changes.

How zero trust showed up in the model

Several mitigations were zero trust decisions:

  • The scanner worker authenticates to storage with its own short-lived role; it cannot read the database.
  • The admin tool is reached through identity-aware access with SSO and device checks, not a VPN.
  • The API does not trust the customer_id in the request; it takes the identity from the token and scopes every query to it.
  • Network location grants nothing: the admin tool's backend requires a valid staff token even on internal routes.

When to threat model

Not every feature needs a session. Ours run for new external-facing features, changes to authentication or authorization, new integrations that receive our data, and anything that moves data across tenants or accounts. The trigger questions on the ticket template decide. A feature that adds a field to an existing internal screen does not need one; a feature that adds file uploads does.

Running a threat model session

  • Who: the developers building the feature, a product person, a security champion. Five people at most.
  • How long: 60 to 90 minutes for a feature of this size.
  • Materials: a whiteboard or a shared diagram, the STRIDE table, the ticket.
  • Output: diagram, threats with mitigations or acceptance, tickets.

Teams that do one a month get noticeably faster. After a few sessions, people start raising STRIDE-style questions in normal design discussions without being asked.

Key takeaways

  • Zero trust means verifying identity and authorization on every request, regardless of network location.
  • It is implemented through everyday choices: tokens on internal APIs, least privilege, segmentation, logging.
  • Threat modeling answers four questions; STRIDE structures "what can go wrong?".
  • Draw the data flow, walk every element and boundary with STRIDE, and turn threats into tickets.
  • Keep threat models short, stored with the code, and updated as features change.

References