What Is Threat Modeling in Cybersecurity?
HuntRule Team · · 9 min read

On this page
Threat modeling is four questions. What are we working on, what can go wrong, what are we going to do about it, and did we do a good job.
Adam Shostack published that frame in Threat Modeling: Designing for Security (2014). The Threat Modeling Manifesto, a 2020 document Shostack co-authored, adds one word to the fourth question: did we do a good enough job. Shostack kept the terse version. Either phrasing works. The frame is not a methodology, it is the shape a methodology has to fill.
Nearly everything written about threat modeling assumes you are shipping an application and the deliverable is a set of code changes. This post is for the other side of the house. You are not fixing the code. You are deciding which detection to write next, and the deliverable is a gap list.
STRIDE, and what it is for
Loren Kohnfelder and Praerit Garg wrote STRIDE at Microsoft in 1999, and it later shipped inside the Microsoft Security Development Lifecycle. Six categories, each named after the security property it violates.
Spoofing violates authentication.
Tampering violates integrity.
Repudiation violates non-repudiation.
Information disclosure violates confidentiality.
Denial of service violates availability.
Elevation of privilege violates authorization.
STRIDE is a prompt list. Point it at one element in a data flow diagram and it asks six questions you would otherwise forget to ask. It does not rank anything, it does not know what your logging looks like, and it will not tell you where to spend the next sprint.
Two other methodologies come up often enough to name. PASTA (Process for Attack Simulation and Threat Analysis), from Tony UcedaVélez and Marco Morana, runs seven stages from business objectives down to risk and impact. Attack trees, from Bruce Schneier in 1999, decompose one attacker goal into sub-goals with AND/OR logic. Both are heavier than STRIDE, and neither produces a detection backlog on its own.
Modeling an environment instead of an application
The questions stay the same. What you enumerate changes.
An appsec threat model enumerates elements in a data flow diagram: processes, data stores, flows, trust boundaries. A detection threat model walks the same boundaries and asks a different question at each one. If an attacker crossed here, what record would exist, and in which log.
Three asset classes carry most of the weight in an environment model.
Identity. Every principal that can act, human or machine, and every path by which one principal becomes another.
Telemetry. The logs are themselves an asset. If they can be disabled, shortened or were never collected, everything downstream is theatre.
Administrative paths. The routes that legitimately hold privilege, because those are the routes an attacker prefers over anything novel.
The output is a ranked list of attacker paths with a yes or no beside each one. Can we see this today, and with which telemetry. That list is the point. If you are new to turning that list into rules, detection engineering covers the pipeline it feeds.
What are we working on
The system below is a composite built for this post, not a customer environment. It is a shape you will recognise.
A private GitHub repository, acme-corp/payments-api, holds a GitHub Actions workflow that builds a container and deploys it to ECS in a production AWS account. There are no static AWS access keys anywhere. The workflow authenticates with GitHub's OIDC provider and assumes an IAM role named prod-deploy. The trust policy on that role is the entire authorization decision.
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::012345678901:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:acme-corp/payments-api:*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}The wildcard in that sub condition is the first finding. repo:acme-corp/payments-api:* matches every ref in the repository, not just main. Datadog Security Labs published research showing IAM roles in the wild where the sub condition was missing altogether, which lets a GitHub Actions workflow in any repository assume the role and retrieve credentials.
Four trust boundaries fall out of that description.
Write access to the repository, into the workflow file.
The GitHub OIDC provider, into the
subclaim AWS trusts.The runner process, which holds the credentials in memory for the job's lifetime.
The IAM policy attached to
prod-deploy, into every AWS API it can call.
What can go wrong
CloudTrail writes an AssumeRoleWithWebIdentity event on every successful assumption. The record shape below is from the Datadog Security Labs writeup, with their placeholders left as published.
{
"userIdentity": {
"type": "WebIdentityUser",
"userName": "repo:SOURCE-ORG/SOURCE-REPO:ref:refs/heads/BRANCH",
"identityProvider": "arn:aws:iam::012345678901:oidc-provider/token.actions.githubusercontent.com"
},
"eventSource": "sts.amazonaws.com",
"eventName": "AssumeRoleWithWebIdentity",
"requestParameters": {
"roleArn": "arn:aws:iam::012345678901:role/github-actions-role",
"roleSessionName": "MySessionName",
"durationSeconds": 3600
},
"responseElements": {
"subjectFromWebIdentityToken": "repo:SOURCE-ORG/SOURCE-REPO:ref:refs/heads/BRANCH",
"provider": "arn:aws:iam::012345678901:oidc-provider/token.actions.githubusercontent.com",
"audience": "sts.amazonaws.com"
}
}responseElements.subjectFromWebIdentityToken carries the repository and the ref that got the credentials. Almost nobody alerts on that field. It is the highest-value string in the whole pipeline.

Can we see it today
This is the deliverable. Every row is an attacker path through one of the four boundaries, the telemetry that would carry it, and an honest answer.
Attacker path | ATT&CK | Telemetry | Detected today |
|---|---|---|---|
Push to a non- | T1195.002 | GitHub audit | No. Nothing compares the subject claim to an allowlist |
Trust policy edited to widen or drop the | T1078.004 | CloudTrail | No. IAM policy edits go to a compliance dashboard, not a queue |
Register a self-hosted runner to catch jobs and read the OIDC token | T1550.001 | GitHub audit | Yes. SigmaHQ ships a rule at low severity |
Add or overwrite an Actions secret to exfiltrate it on the next run | T1552.001 | GitHub audit | Yes. SigmaHQ ships a rule |
Delete or override branch protection so unreviewed code reaches the deploy branch | T1685 | GitHub audit | No |
Use | T1098.001, T1136.003 | CloudTrail | Partial. The SigmaHQ IAM backdoor rule fires but is not scoped to CI roles |
Stop the trail before the noisy part of the intrusion | T1685.002 | CloudTrail | Yes. Standard coverage |
Run a payload on the runner during the job | T1059.004 | None on GitHub-hosted runners | No, and not fixable. Ephemeral VMs you do not own and cannot instrument |
Two of those rows are structural gaps rather than missing rules. GitHub Git events (git.push, git.clone, git.fetch) are retained for seven days on GitHub Enterprise Cloud, far shorter than the rest of the audit log, and are not available in the audit log web interface at all, so a weekly query will miss them. And there is no host telemetry on GitHub-hosted runners at all. Moving to self-hosted runners buys EDR coverage and costs a new persistence surface, a trade the model should state rather than resolve.
The rule that came out of it
Row one is the cheapest fix and had no coverage. The subject claim is already in CloudTrail, so the rule is an allowlist comparison.
title: AWS Deploy Role Assumed From Unexpected GitHub Actions Subject
id: 7b3e9c41-2a6d-4f18-9c05-8de4b1f7a2c3
status: experimental
description: |
Detects sts:AssumeRoleWithWebIdentity against a GitHub Actions deploy role where the
OIDC subject claim is not one of the repository and ref pairs the role is meant to
serve. Fires on trust policy drift, on an over-broad StringLike condition, and on a
workflow running from an unexpected branch, tag or environment.
references:
- https://securitylabs.datadoghq.com/articles/exploring-github-to-aws-keyless-authentication-flaws/
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
author: HuntRule
date: 2026-07-31
tags:
- attack.initial-access
- attack.persistence
- attack.t1078.004
logsource:
product: aws
service: cloudtrail
detection:
selection:
eventSource: 'sts.amazonaws.com'
eventName: 'AssumeRoleWithWebIdentity'
requestParameters.roleArn|endswith: ':role/prod-deploy'
filter_main_expected_subject:
responseElements.subjectFromWebIdentityToken:
- 'repo:acme-corp/payments-api:ref:refs/heads/main'
- 'repo:acme-corp/payments-api:environment:production'
condition: selection and not filter_main_expected_subject
falsepositives:
- A release branch or deployment environment added to the trust policy without updating this allowlist
- Tag-triggered deploys, which produce repo:acme-corp/payments-api:ref:refs/tags/<tag>
- Repository renamed or transferred, which changes the subject string
level: highWhat it catches: any credential issuance to the deploy role from a ref, environment or repository that is not on the list. That covers the wildcard sub case, a trust policy someone widened during an outage, and a workflow triggered from a fork if the trigger allows it.
What it misses: everything after the credentials are issued. If an attacker pushes to main through a legitimate merge, the subject matches and this rule is silent. It also cannot see a stolen id_token replayed against a second role in another account, because that produces a different roleArn and needs its own selection. And it says nothing about what prod-deploy did once it held credentials, which is row six's problem.
The allowlist is the maintenance cost. Every new deploy environment is a false positive until someone edits the rule, which is the same maintenance the trust policy already needs. Broader cloud identity rules live under /rules?domain=cloud.
The failure mode nobody admits
Most threat models produce a diagram, a spreadsheet of findings, and no follow-through. The diagram goes stale the week the architecture changes and nobody opens it again. The exercise above is worth the afternoon only if the gap column turns into tickets with owners.
Two cheap habits help. Store the gap table in the same repository as the detection content, so a pull request that changes the pipeline lands next to the rules that watch it. And re-run the model on a trigger rather than a calendar: a new trust policy, a new runner group, a new production account. Annual review dates get skipped. Change triggers do not.
Summary
Threat modeling asks what can go wrong before it does, and for a detection team the answer belongs in a gap list rather than a diagram. Shostack's four questions give the shape, STRIDE gives the prompt list, and the environment version swaps application components for identity, telemetry and administrative paths. One pipeline produced eight attacker paths, four uncovered, two of them structural gaps no rule will close. One of the four became a Sigma rule that reads a field CloudTrail was already writing.
Collect these before you model a pipeline like this one.
AWS CloudTrail management events (all regions), specifically:
sts.amazonaws.com AssumeRoleWithWebIdentity, AssumeRole
iam.amazonaws.com UpdateAssumeRolePolicy, CreateAccessKey, CreateUser, AttachRolePolicy
cloudtrail.amazonaws.com StopLogging, DeleteTrail, UpdateTrail
GitHub Enterprise Cloud audit log streaming, specifically:
workflows.created_workflow_run
protected_branch.destroy, protected_branch.policy_override
repo.register_self_hosted_runner, org.runner_group_updated
repo.create_actions_secret, org.create_actions_secret, environment.create_actions_secret
git.push (7 day retention, REST API and streaming only)
Fields the rules depend on:
responseElements.subjectFromWebIdentityToken
userIdentity.userName
userIdentity.sessionContext.sessionIssuer.userName
requestParameters.roleArnRelated articles

What Are Hunting Rules? Queries Too Noisy to Alert On
SigmaHQ's rules-threat-hunting/ folder holds 140 rules. The rules/ folder beside it holds 3,137. Both describe attacker behaviour in the same YAML format, against the same log sources, with the same…
2024-11-07 · 8 min read

What Is Threat Hunting? A Practical Guide for Modern SOCs
C:\Users\jhale\AppData\Local\Temp\Rar$EXa0.372\GUP.exe is the shape a side-loading host leaves behind. One host, one execution, a real vendor name in the file metadata, sitting inside a WinRAR…
2024-10-27 · 10 min read

What Is a Hypothesis-Driven Hunt?
Remote WMI process creation lands on the target as a child of C:\Windows\System32\wbem\WmiPrvSE.exe. That is one sentence of fact, and it is the entire reason the hunt below can exist. Without a fact…
2024-10-02 · 9 min read