Hysn Logo
Back to Blogs

STRIDE Threat Modeling: 6 Categories with Real Attack Examples for Modern Applications

V
Varun Kumar 14 min read 5 June 2026
STRIDE Threat Modeling

STRIDE stands for Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, and Elevation of Privilege. Most explanations of STRIDE stop at the definitions. That's the problem. A definition doesn't tell you what to look for when you're staring at a data flow diagram of a microservices app with a React frontend, three backend services, a Kafka queue, and two external payment integrations.

Certified Threat Modeling Professional

Learn STRIDE, PASTA, VAST & RTMP frameworks in one certification.

View Course
Certified Threat Modeling Professional

This post works through each STRIDE category using a concrete scenario: a web application with user authentication, a REST API, a background worker service, and a CI/CD pipeline deploying to Kubernetes. By the end you should be able to run a STRIDE session against a real system, not just recite what the letters mean.


What STRIDE Stands for and What Each Category Means in Practice

STRIDE was developed by Microsoft in the late 1990s and formalized by Loren Kohnfelder and Praerit Garg. It's not a complete threat taxonomy. It's a checklist that helps engineers systematically think about threats to a system. You use it by taking each component and each data flow in your DFD and asking whether each STRIDE category applies.

The key insight most tutorials miss: STRIDE categories map to violated security properties. Spoofing violates authentication. Tampering violates integrity. Repudiation violates non-repudiation. Information Disclosure violates confidentiality. Denial of Service violates availability. Elevation of Privilege violates authorization. When you frame it that way, the checklist becomes more intuitive.

Our reference system: a frontend React app, an API Gateway, an Auth Service, a User Service, a Payment Service, a background Worker, a PostgreSQL database per service (isolated), a Kafka cluster, and a GitHub Actions CI/CD pipeline deploying to Kubernetes. Trust boundaries exist between the browser and API Gateway, between the API Gateway and internal services, and between internal services and external payment processors.

Spoofing: Can Something Pretend to Be Something It's Not?

JWT bypass. The Auth Service issues JWTs signed with RS256. The User Service accepts JWTs and validates them. If the User Service trusts the alg header in the JWT and an attacker sends a token with alg: none, a naive implementation accepts unsigned tokens. CVE-2015-9235 covered this in the jsonwebtoken library. Fix: explicitly specify the expected algorithm in your verification call, never accept what the token claims.

JWT key confusion (RS256 to HS256). A subtler variant: if your service accepts both RS256 and HS256, an attacker can take your public RSA key (often publicly available), sign a token with it using HS256, and the server verifies it successfully because it's now treating the public key as the HMAC secret. This isn't a hypothetical. Fix: explicitly reject HS256 in your JWT verification configuration. The server's verification code should name the algorithm explicitly, not derive it from the token header.

OAuth state parameter attacks. The frontend initiates an OAuth flow to a third-party identity provider. If the application doesn't validate the state parameter on callback, an attacker can craft a login CSRF. The user authenticates with their real identity provider account but gets linked to an attacker-controlled session. Fix: generate a cryptographically random state value, store it in the session, and reject callbacks where the state doesn't match.

Subdomain takeover. Your application has a DNS CNAME record pointing api.yourapp.com to a cloud service you deprovisioned six months ago. An attacker claims that cloud resource, hosts a malicious service at the same endpoint, and now controls traffic intended for your API. Users' browsers send authentication tokens to the attacker's server. Fix: audit DNS records quarterly. Delete CNAMEs pointing to deprovisioned cloud resources immediately.

Container identity spoofing. In Kubernetes, a compromised pod can make requests to other services on the internal network. If service-to-service communication doesn't use mTLS or some other workload identity mechanism, a compromised container can spoof any internal service. SPIFFE/SPIRE solves this: each workload gets a cryptographic identity. Without it, network adjacency is your only control, and that's not enough.

CI/CD pipeline spoofing. An attacker who can push to a non-protected branch can trigger a GitHub Actions workflow. If that workflow has access to production secrets, the attacker has spoofed a legitimate deployment. Fix: protect your main branches, require code review, and scope workflow permissions to only the secrets that specific job needs.

Tampering: Can Data Be Modified in Transit or at Rest?

Request parameter manipulation. The User Service has an endpoint GET /api/users/{id}/profile. If the service doesn't verify that the authenticated user matches the requested id, an attacker changes the ID in the URL and reads another user's profile. This crosses from Tampering into Information Disclosure, but the root cause is parameter manipulation. Fix: always bind the resource ID to the authenticated identity server-side.

Pipeline artifact tampering. The CI/CD pipeline builds a Docker image, pushes it to a registry, and deploys it. If the deployment step doesn't verify the image digest and just pulls the latest tag, an attacker with write access to the registry can swap the image. Fix: pin deployments to digest (image@sha256:...), not tags. Use Cosign to sign and verify images as part of the pipeline.

Database record modification. The Payment Service writes transaction records to PostgreSQL. If an attacker gains access to the database connection (via SQL injection or a compromised connection string), they can modify transaction amounts. Mitigations: parameterized queries everywhere, row-level write audit logs, and application-level transaction signing if your compliance requirements demand it.

Kafka message tampering. Messages on Kafka topics are not signed by default. A producer with write access to the topic can inject arbitrary messages. The Worker consumes these without verifying the producer's identity. Fix: sign messages at the producer and verify at the consumer. This is rarely done in practice, which is a genuine gap in most microservices threat models.

Repudiation: Can Someone Deny an Action Took Place?

Repudiation is the most commonly neglected STRIDE category. Most teams focus on authentication and forget that "who did what" requires more than just knowing who authenticated.

In a microservices architecture, a single user action might touch five services. If each service logs independently with its own timestamp and no correlation ID, reconstructing what happened during a security incident takes days. I've worked incidents where we spent 40% of our time just correlating logs across services.

What adequate logging looks like: every request gets a correlation ID generated at the API Gateway and propagated through every internal service call. Every write operation logs: who, what, when, the before state, and the after state. Logs are shipped to a tamper-evident store (CloudTrail, a SIEM with immutable storage) that the application can write to but not delete from.

The Auth Service is the highest-value target for repudiation gaps. Log every authentication attempt: success, failure, the user agent, IP, and session ID. Log every token refresh and every logout. Log admin actions with full parameter detail.

Information Disclosure: Who Can Read What They Shouldn't?

Verbose error messages. The API Gateway returns a 500 error that includes a

stack trace with internal service names, database table names, and the internal IP of the failing service. This is a real finding I've seen in production. Fix: configure error handlers to return generic messages externally and log full detail internally.

Debug endpoints left open. Spring Boot Actuator's /actuator/env endpoint exposes environment variables, including secrets, if not locked down. Node.js apps with DEBUG=* in production leak internal routing. The fix is boring and important: review every framework's default endpoints as part of deployment hardening.

Insecure Direct Object References. The Payment Service endpoint GET /api/payments/{payment_id} returns full payment details including partial card numbers. If payment_id is sequential or guessable and there's no authorization check tying the payment to the requesting user, you have IDOR. Test this explicitly: authenticate as User A, grab a payment ID, authenticate as User B, request User A's payment ID.

Sensitive data in Kafka topics. A developer adds PII to a Kafka message for debugging and forgets to remove it. The topic has a 7-day retention policy. Anyone with consumer access to that topic now has a week of PII they shouldn't have. Fix: treat Kafka topics as the same sensitivity level as your databases. Review message schemas before new producer code ships.

Denial of Service: Can the Service Be Made Unavailable?

Unauthenticated resource-intensive endpoints. The API Gateway exposes a POST /api/reports/generate endpoint. Generating a report takes 30 seconds and pegs a CPU core. This endpoint requires authentication but no rate limiting. An authenticated attacker (or a compromised credential) can take down the service with 20 concurrent requests. Fix: rate limit at the API Gateway by user, add queue depth limits for async work, and add circuit breakers on the Worker.

Kafka consumer group attacks. A malicious actor who can join a consumer group can cause partitions to rebalance continuously by rapidly joining and leaving the group, starving legitimate consumers. This requires access to the Kafka broker, but it's worth modeling. Fix: restrict consumer group membership using Kafka ACLs.

Unthrottled login attempts. The Auth Service's POST /auth/login endpoint has no rate limiting. An attacker can attempt password spraying at machine speed. Fix: rate limit per IP and per username, implement exponential backoff, lock accounts after N failures with a lockout policy that doesn't create its own DoS vector.

Elevation of Privilege: Can Someone Gain Permissions They Shouldn't Have?

SSRF to metadata service. The User Service accepts a URL parameter for a user profile image. It fetches the image server-side to generate a thumbnail. An attacker provides http://169.254.169.254/latest/meta-data/iam/security-credentials/ and gets the EC2 instance role credentials. This is a well-documented attack pattern. Fix: validate and allowlist URL schemes and hosts before making outbound requests. IMDSv2 adds a layer of protection on AWS but doesn't eliminate the risk if your service is making arbitrary HTTP requests.

Mass assignment. A PATCH /api/users/me endpoint accepts a JSON body and binds it directly to a user model. The user model has an is_admin field. The developer assumed users would only send profile fields, but an attacker sends {"name": "Alice", "is_admin": true}. If the model update doesn't have an explicit allowlist of updatable fields, the attacker is now an admin. Fix: define explicit field allowlists on every endpoint that updates a model. Never bind request bodies directly to persistence models without filtering.

Container breakout. A container running as root with access to the Docker socket can escalate to host root. The Worker service doesn't need root, doesn't need the Docker socket, and doesn't need CAP_SYS_ADMIN. It runs with them anyway because nobody changed the defaults. Fix: run containers as non-root, drop all capabilities, use securityContext.allowPrivilegeEscalation: false in your Kubernetes pod spec.

Overprivileged service accounts. The Payment Service's Kubernetes service account has cluster-admin because a developer needed to debug a deployment issue six months ago and never revoked it. That service account is now attached to every pod running that service. If any pod in that deployment is compromised, the attacker has cluster admin. Fix: audit service account permissions in every namespace. Apply least privilege. Use namespaced roles, not cluster roles, wherever possible.

Complete STRIDE Analysis: Service A Calls Service B, Which Writes to a DB

This is the pattern in almost every microservices system: one service calls another, and that second service writes to a database. Here's a full STRIDE table for this scenario.

The setup: Service A (a User Profile service) calls Service B (a Notifications service) over internal HTTP to trigger an email notification. Service B writes the notification record to a PostgreSQL database before sending. There's no mTLS. Service A authenticates to Service B using a shared API key stored as a Kubernetes secret.

Threat

Category

Component

Current Control

Response

Any pod on the internal network can call Service B without authentication if the API key is guessed or leaked

Spoofing

Service B

API key in K8s secret

Mitigate: implement mTLS with SPIFFE/SPIRE or restrict Service B network policy to only allow traffic from Service A's pod label

An attacker who compromises Service A can call Service B with arbitrary payloads, including crafted notification content that reaches the email template

Tampering

Service B

Input validation (none documented)

Mitigate: validate all fields in Service B before DB write; sanitize notification content before templating

Service B processes notifications asynchronously; there's no audit trail correlating which Service A request triggered which notification

Repudiation

Notification DB

DB write log exists

Mitigate: add correlation ID generated at Service A, propagated to Service B, stored in notification record

Service B's error responses include the DB query that failed, which reveals table names and schema

Information Disclosure

Service B

None

Mitigate: catch DB errors, return generic 500, log details internally only

Service A retries on 5xx from Service B; a slow DB can cause cascading retries that exhaust Service B's connection pool

Denial of Service

Service B

None

Mitigate: circuit breaker on Service A's HTTP client to Service B; connection pool limit on Service B's DB client

The Kubernetes service account for Service B has cluster-admin from a debugging session six months ago

Elevation of Privilege

Service B pod

None (over-privileged)

Mitigate: replace with a namespaced role that permits only get/list/watch on required secrets; audit all service accounts quarterly

SQL injection via notification content field if the write to DB is not parameterized

Tampering

Notification DB

Unknown

Mitigate: verify all DB queries in Service B use parameterized statements; add to code review checklist

Service B sends notifications using a shared SMTP credential; if that credential leaks, attackers can send email as your domain

Spoofing (outbound)

Notification Service

SMTP credential in K8s secret

Mitigate: use DKIM signing, SPF records, and rotate SMTP credentials quarterly; consider per-service sending identities

That's eight findings from one service-to-service data flow. Not all of them are equal. The over-privileged service account and the correlation ID gap are the first two tickets. The SMTP credential is the third. The rest get scheduled.

Running a STRIDE Session: The Practical Format

Invite four people: the engineer who owns the system, a security engineer, a product person who can make risk decisions, and an ops person who knows the deployment. Timebox to 60 minutes. First 15 minutes: draw the DFD together. Next 35 minutes: run STRIDE against each trust boundary crossing. Last 10 minutes: prioritize findings and assign tickets.

The output should be a table: threat description, STRIDE category, affected component, current control (if any), response (mitigate/accept), and ticket link.


Practical Agenda Template for a 60-Minute STRIDE Session

This is the schedule that keeps sessions focused and produces actionable output. Adjust times for smaller or simpler systems.

0:00 - 0:10: Draw the DFD (together, not pre-prepared)

The engineer who built the system draws it on the whiteboard with the group. Pre-prepared diagrams often exclude the messy reality: the admin backdoor, the debug endpoint, the manual import script. Drawing together surfaces those. Label every arrow with protocol and data type.

0:10 - 0:15: Identify trust boundaries

Draw dotted lines around components that share a security context. Mark every crossing. These are the high-value targets for STRIDE analysis.

0:15 - 0:45: STRIDE analysis at trust boundary crossings

Work left to right across your DFD. For each trust boundary crossing, run all six STRIDE questions. Write findings as one-sentence problem statements on a sticky note or in a shared doc. Don't debate solutions yet. Keep moving. You have 30 minutes for this phase.

0:45 - 0:55: Triage findings

Group findings by severity: critical (fix before merge), high (next sprint), medium (backlog), accepted (document and move on). A finding is critical if exploitation is trivially easy and the impact is account takeover, data exposure, or service disruption.

0:55 - 1:00: Assign tickets

Every "fix before merge" and "next sprint" finding gets a ticket created in the room. Write the ticket number on the sticky note. The threat model is attached to the epic. The session closes with a linked backlog, not a list of vague concerns.

If you want structured training on running these sessions, the Certified DevSecOps Professional program walks through hands-on threat modeling labs against real application architectures.


FAQ

No. Not every category is relevant to every component. A static file CDN doesn't have an Elevation of Privilege concern the same way a service account does. Use STRIDE as a prompt, not a mandatory checklist that produces findings even when there's nothing there.

Use a simple impact-likelihood matrix. High impact, high likelihood findings go to the next sprint. High impact, low likelihood get documented and accepted or scheduled. Low impact findings can be batched. Don't over-engineer the prioritization; the goal is action, not a perfect risk score.

Developers should lead. They know the system. Security engineers should facilitate by keeping the session focused on STRIDE and pushing for specificity when findings are too vague. The session fails when security takes over and developers disengage.

For new features touching auth, data storage, or external integrations: every sprint. For existing systems: quarterly review of the DFD plus STRIDE analysis of anything that changed. Running it annually as a compliance checkbox produces threat models that are outdated within two months.

V

Varun Kumar

Security Research Writer

Varun is a Security Research Writer specializing in DevSecOps, AI Security, and cloud-native security. He takes complex security topics and makes them straightforward. His articles provide security professionals with practical, research-backed insights they can actually use.

Related articles

All blogs →