Skip to content

Commit c628f73

Browse files
authored
0.3.1 (#121)
* feat: graphql server now supports unique threat name * docs: updated docs to support updated gql server * chore: updated tm3 example import * test: updated gql server test for gh actions * test: bumped the timeout up to 30s
1 parent 1867e01 commit c628f73

13 files changed

Lines changed: 278 additions & 120 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
## 0.3.1
2+
3+
### Dec 26, 2025
4+
5+
CHANGES:
6+
7+
* Adjusted GraphQL to support unique `threat` names
8+
19
## 0.3.0
210

311
### Dec 24, 2025

cmd/threatcl/generate_interactive.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,14 @@ func (c *GenerateInteractiveCommand) Run(args []string) int {
535535
break
536536
} else {
537537
var tQs = []*survey.Question{
538+
{
539+
Name: "tname",
540+
Prompt: &survey.Input{
541+
Message: "[Threat] Name:",
542+
Help: "A unique name for this threat. For example, 'User Impersonation via Stolen Credentials'",
543+
},
544+
Validate: survey.Required,
545+
},
538546
{
539547
Name: "tdescription",
540548
Prompt: &survey.Input{
@@ -560,6 +568,7 @@ func (c *GenerateInteractiveCommand) Run(args []string) int {
560568
}
561569

562570
tAnswers := struct {
571+
Tname string
563572
Tdescription string
564573
Timpacttypes []string
565574
Tcontrol string
@@ -568,7 +577,8 @@ func (c *GenerateInteractiveCommand) Run(args []string) int {
568577
err = survey.Ask(tQs, &tAnswers)
569578

570579
t := spec.Threat{
571-
Description: tAnswers.Tdescription,
580+
Name: tAnswers.Tname,
581+
Description: tAnswers.Tdescription,
572582
ImpactType: tAnswers.Timpacttypes,
573583
Control: tAnswers.Tcontrol,
574584
}

cmd/threatcl/server_test.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,22 @@ func TestServerIntegration(t *testing.T) {
149149
done <- code
150150
}()
151151

152-
// Give server time to start
153-
time.Sleep(2 * time.Second)
152+
// Wait for server to be ready with retry logic
153+
var resp *http.Response
154+
var err error
155+
maxRetries := 300 // 30 retries * 100ms = 30 seconds max
156+
client := &http.Client{Timeout: 1 * time.Second}
157+
158+
for range maxRetries {
159+
resp, err = client.Get(fmt.Sprintf("http://127.0.0.1:%d/health", port))
160+
if err == nil {
161+
break
162+
}
163+
time.Sleep(100 * time.Millisecond)
164+
}
154165

155-
// Test health endpoint
156-
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/health", port))
157166
if err != nil {
158-
t.Fatalf("Failed to connect to server: %s", err)
167+
t.Fatalf("Failed to connect to server after %d retries: %s", maxRetries, err)
159168
}
160169
defer resp.Body.Close()
161170

@@ -169,10 +178,9 @@ func TestServerIntegration(t *testing.T) {
169178
}
170179

171180
// Test GraphQL endpoint with a simple query
172-
client := &http.Client{Timeout: 5 * time.Second}
173181
graphqlReq := strings.NewReader(`{"query": "{ stats { totalThreatModels } }"}`)
174182
resp, err = client.Post(
175-
fmt.Sprintf("http://localhost:%d/graphql", port),
183+
fmt.Sprintf("http://127.0.0.1:%d/graphql", port),
176184
"application/json",
177185
graphqlReq,
178186
)
@@ -541,4 +549,3 @@ func TestServerHelpNoLongerMentionsNotImplemented(t *testing.T) {
541549
t.Error("Help text should still mention -watch flag")
542550
}
543551
}
544-

docs/graphql-api.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ threats(filter: ThreatFilter): [Threat!]!
159159

160160
**Arguments:**
161161
- `filter` (optional): Filter criteria
162+
- `name`: String - Filter by threat name (case-insensitive substring match)
162163
- `impacts`: [String!] - Filter by impact types (e.g., ["Confidentiality", "Integrity"])
163164
- `stride`: [String!] - Filter by STRIDE categories (e.g., ["Spoofing", "Tampering"])
164165
- `hasImplementedControls`: Boolean - Filter threats with/without implemented controls
@@ -167,6 +168,7 @@ threats(filter: ThreatFilter): [Threat!]!
167168
```graphql
168169
query {
169170
threats(filter: { stride: ["Spoofing", "Elevation Of Privilege"] }) {
171+
name
170172
description
171173
stride
172174
impacts
@@ -178,6 +180,20 @@ query {
178180
}
179181
```
180182

183+
**Example (filtering by name):**
184+
```graphql
185+
query {
186+
threats(filter: { name: "theft" }) {
187+
name
188+
description
189+
impacts
190+
threatModel {
191+
name
192+
}
193+
}
194+
}
195+
```
196+
181197
#### `informationAssets`
182198

183199
Retrieve all information assets, optionally filtered by classification.
@@ -275,6 +291,7 @@ Represents a security threat.
275291
**Fields:**
276292
```graphql
277293
type Threat {
294+
name: String!
278295
description: String!
279296
impacts: [String!]!
280297
stride: [String!]!

examples/graphql-queries.md

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ Find all threats categorized as "Spoofing":
122122
```graphql
123123
query SpoofingThreats {
124124
threats(filter: { stride: ["Spoofing"] }) {
125+
name
125126
description
126127
stride
127128
impacts
@@ -139,6 +140,25 @@ Find threats that impact confidentiality:
139140
```graphql
140141
query ConfidentialityThreats {
141142
threats(filter: { impacts: ["Confidentiality"] }) {
143+
name
144+
description
145+
impacts
146+
stride
147+
threatModel {
148+
name
149+
}
150+
}
151+
}
152+
```
153+
154+
### 9. Threats by Name
155+
156+
Search for threats by name (supports partial, case-insensitive matching):
157+
158+
```graphql
159+
query ThreatsByName {
160+
threats(filter: { name: "theft" }) {
161+
name
142162
description
143163
impacts
144164
stride
@@ -149,7 +169,7 @@ query ConfidentialityThreats {
149169
}
150170
```
151171

152-
### 9. Confidential Information Assets
172+
### 10. Confidential Information Assets
153173

154174
Find all assets classified as confidential:
155175

@@ -168,7 +188,7 @@ query ConfidentialAssets {
168188

169189
## Detailed Queries
170190

171-
### 10. Threat Model with All Details
191+
### 11. Threat Model with All Details
172192

173193
Get complete information about a threat model:
174194

@@ -194,6 +214,7 @@ query CompleteThreatModel {
194214
}
195215

196216
threats {
217+
name
197218
description
198219
impacts
199220
stride
@@ -228,7 +249,7 @@ query CompleteThreatModel {
228249
}
229250
```
230251

231-
### 11. All Threats with Controls
252+
### 12. All Threats with Controls
232253

233254
Get all threats and their associated controls:
234255

@@ -237,6 +258,7 @@ query AllThreatsWithControls {
237258
threatModels {
238259
name
239260
threats {
261+
name
240262
description
241263
impacts
242264
stride
@@ -251,7 +273,7 @@ query AllThreatsWithControls {
251273
}
252274
```
253275

254-
### 12. Threat Models with Data Flow Diagrams
276+
### 13. Threat Models with Data Flow Diagrams
255277

256278
Find threat models that have DFDs:
257279

@@ -290,7 +312,7 @@ query ModelsWithDFDs {
290312

291313
## Analysis Queries
292314

293-
### 13. Security Control Coverage
315+
### 14. Security Control Coverage
294316

295317
Analyze which threat models have the most controls:
296318

@@ -300,6 +322,7 @@ query ControlCoverage {
300322
name
301323
author
302324
threats {
325+
name
303326
description
304327
controls {
305328
name
@@ -310,7 +333,7 @@ query ControlCoverage {
310333
}
311334
```
312335

313-
### 14. Implementation Status
336+
### 15. Implementation Status
314337

315338
Find threats with unimplemented controls:
316339

@@ -319,6 +342,7 @@ query UnimplementedControls {
319342
threatModels {
320343
name
321344
threats {
345+
name
322346
description
323347
controls {
324348
name
@@ -332,7 +356,7 @@ query UnimplementedControls {
332356

333357
Note: You'll need to filter client-side for `implemented: false`.
334358

335-
### 15. Risk Reduction Analysis
359+
### 16. Risk Reduction Analysis
336360

337361
Analyze risk reduction across all controls:
338362

@@ -341,6 +365,7 @@ query RiskReductionAnalysis {
341365
threatModels {
342366
name
343367
threats {
368+
name
344369
description
345370
controls {
346371
name
@@ -358,7 +383,7 @@ query RiskReductionAnalysis {
358383
}
359384
```
360385

361-
### 16. Third-Party Dependency Audit
386+
### 17. Third-Party Dependency Audit
362387

363388
Find all third-party dependencies and their criticality:
364389

@@ -380,7 +405,7 @@ query ThirdPartyAudit {
380405

381406
## Multiple Queries in One Request
382407

383-
### 17. Dashboard Data
408+
### 18. Dashboard Data
384409

385410
Get all data needed for a dashboard in one query:
386411

@@ -413,6 +438,7 @@ query Dashboard {
413438
}
414439

415440
allThreats: threats {
441+
name
416442
description
417443
stride
418444
threatModel {
@@ -422,7 +448,7 @@ query Dashboard {
422448
}
423449
```
424450

425-
### 18. Security Posture Report
451+
### 19. Security Posture Report
426452

427453
Generate a comprehensive security posture report:
428454

@@ -439,6 +465,7 @@ query SecurityPostureReport {
439465
internetFacing: threatModels(filter: { internetFacing: true }) {
440466
name
441467
threats {
468+
name
442469
description
443470
impacts
444471
controls {
@@ -459,7 +486,7 @@ query SecurityPostureReport {
459486

460487
## Advanced Queries
461488

462-
### 19. Using Query Variables
489+
### 20. Using Query Variables
463490

464491
Define reusable queries with variables:
465492

@@ -470,6 +497,7 @@ query GetModelByName($modelName: String!) {
470497
author
471498
description
472499
threats {
500+
name
473501
description
474502
impacts
475503
}
@@ -484,12 +512,13 @@ Variables:
484512
}
485513
```
486514

487-
### 20. Fragments for Reusable Fields
515+
### 21. Fragments for Reusable Fields
488516

489517
Use fragments to avoid repeating field selections:
490518

491519
```graphql
492520
fragment ThreatDetails on Threat {
521+
name
493522
description
494523
impacts
495524
stride
@@ -522,7 +551,7 @@ query ModelsWithThreatDetails {
522551

523552
While the API provides server-side filtering, sometimes you need client-side filtering. Here are some examples using JavaScript:
524553

525-
### 21. Find Threats Without Implemented Controls
554+
### 22. Find Threats Without Implemented Controls
526555

527556
```javascript
528557
const query = `
@@ -551,7 +580,7 @@ const threatsWithoutControls = data.threatModels.flatMap(tm =>
551580
);
552581
```
553582

554-
### 22. Calculate Control Implementation Rate
583+
### 23. Calculate Control Implementation Rate
555584

556585
```javascript
557586
const query = `

examples/tm3.hcl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
spec_version = "0.2.3"
22

33
threatmodel "Modelly model" {
4-
imports = ["https://raw.githubusercontent.com/xntrik/hcltm/main/examples/aws-security-checklist.hcl", "https://raw.githubusercontent.com/xntrik/hcltm/main/examples/owasp-proactive-controls.hcl"]
4+
imports = ["https://raw.githubusercontent.com/threatcl/threatcl/main/examples/aws-security-checklist.hcl", "https://raw.githubusercontent.com/threatcl/threatcl/main/examples/owasp-proactive-controls.hcl"]
55
author = "@xntrik"
66

77
diagram_link = "https://link.to.somewhere/diagram"

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
328328
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
329329
github.com/threatcl/go-otm v0.0.2 h1:zN/Wwiy6QVw0SRm+YiZA7lDf2yI0jVyxPmNW1k9kgpw=
330330
github.com/threatcl/go-otm v0.0.2/go.mod h1:U1CNSpdQQ8S0lqr5LNn3tFZxsLgBKOz27+5k+IzFho0=
331-
github.com/threatcl/spec v0.2.1 h1:hWaM/40s3X/9e1QiJX+CfCNKP5TZI1RnvYlXNTFlclg=
332-
github.com/threatcl/spec v0.2.1/go.mod h1:Qe8mAmBX1tC/BRb0ydtZXBkpiTki9Fj0v1GzL9kSDzk=
333-
github.com/threatcl/spec v0.2.2 h1:CwOHdHbHeIu6sOpoJN+9YenQIcSOyjLzAy4U2daDgAU=
334-
github.com/threatcl/spec v0.2.2/go.mod h1:Qe8mAmBX1tC/BRb0ydtZXBkpiTki9Fj0v1GzL9kSDzk=
335331
github.com/threatcl/spec v0.2.3 h1:mSPhsZDd9FJtDKLuDKD7qHle4H4vqyKyHP9cKj26F2g=
336332
github.com/threatcl/spec v0.2.3/go.mod h1:Qe8mAmBX1tC/BRb0ydtZXBkpiTki9Fj0v1GzL9kSDzk=
337333
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=

0 commit comments

Comments
 (0)