Threatcl GraphQL API
The threatcl GraphQL API provides a powerful, flexible way to query and analyze your threat models. The API is served by the threatcl server command and exposes all threat model data through a type-safe GraphQL interface.
Getting Started
Starting the Server
# Basic usagethreatcl server -dir ./examples
# With custom portthreatcl server -dir ./examples -port 3000
# With file watching for auto-reloadthreatcl server -dir ./examples -watchAccessing the API
- GraphQL Playground:
http://localhost:8080/ - GraphQL Endpoint:
http://localhost:8080/graphql - Health Check:
http://localhost:8080/health
Schema Overview
The GraphQL schema is organized around the following main types:
ThreatModel- Complete threat model with all associated dataThreat- Individual threats within a threat modelControl- Security controls (mitigations) for threatsInformationAsset- Data assets being protectedDataFlowDiagram- Data flow diagrams (DFDs) for visualizing system architectureStatistics- Aggregated statistics across all threat models
Query Reference
Root Queries
threatModels
Retrieve all threat models, optionally filtered by criteria.
Signature:
threatModels(filter: ThreatModelFilter): [ThreatModel!]!Arguments:
filter(optional): Filter criteriaauthor: String - Filter by authorinternetFacing: Boolean - Filter by internet-facing attributenewInitiative: Boolean - Filter by new initiative attributeinitiativeSize: String - Filter by initiative size (e.g., “Small”, “Medium”, “Large”)
Example:
query { threatModels(filter: { internetFacing: true }) { name author attributes { internetFacing initiativeSize } }}threatModel
Retrieve a single threat model by name.
Signature:
threatModel(name: String!): ThreatModelArguments:
name(required): The exact name of the threat model
Returns: ThreatModel or null if not found
Example:
query { threatModel(name: "Tower of London") { name author description threats { description impacts } }}threats
Search for threats across all threat models, optionally filtered.
Signature:
threats(filter: ThreatFilter): [Threat!]!Arguments:
filter(optional): Filter criteriaimpacts: [String!] - Filter by impact types (e.g., [“Confidentiality”, “Integrity”])stride: [String!] - Filter by STRIDE categories (e.g., [“Spoofing”, “Tampering”])hasImplementedControls: Boolean - Filter threats with/without implemented controls
Example:
query { threats(filter: { stride: ["Spoofing", "Elevation Of Privilege"] }) { description stride impacts controls { name implemented } }}informationAssets
Retrieve all information assets, optionally filtered by classification.
Signature:
informationAssets(classification: String): [InformationAsset!]!Arguments:
classification(optional): Filter by information classification (e.g., “Confidential”, “Restricted”, “Public”)
Example:
query { informationAssets(classification: "Confidential") { name description informationClassification threatModel { name } }}stats
Get aggregated statistics across all threat models.
Signature:
stats: Statistics!Example:
query { stats { totalThreatModels totalThreats totalInformationAssets totalControls implementedControls averageRiskReduction }}Type Reference
ThreatModel
Represents a complete threat model.
Fields:
type ThreatModel { name: String! author: String! description: String link: String diagramLink: String createdAt: Int updatedAt: Int attributes: Attributes additionalAttributes: [AdditionalAttribute!]! informationAssets: [InformationAsset!]! threats: [Threat!]! useCases: [UseCase!]! exclusions: [Exclusion!]! thirdPartyDependencies: [ThirdPartyDependency!]! dataFlowDiagrams: [DataFlowDiagram!]! sourceFile: String!}Attributes
Standard threat model attributes.
Fields:
type Attributes { newInitiative: Boolean internetFacing: Boolean initiativeSize: String}Threat
Represents a security threat.
Fields:
type Threat { description: String! impacts: [String!]! stride: [String!]! controls: [Control!]! informationAssetRefs: [String!]! threatModel: ThreatModel!}Bidirectional Reference:
threatModelfield provides reverse navigation to the parent threat model
Control
Security control or mitigation for a threat.
Fields:
type Control { name: String! description: String! implemented: Boolean! implementationNotes: String riskReduction: Int attributes: [ControlAttribute!]!}InformationAsset
Data or information being protected.
Fields:
type InformationAsset { name: String! description: String informationClassification: String! source: String threatModel: ThreatModel!}Bidirectional Reference:
threatModelfield provides reverse navigation to the parent threat model
DataFlowDiagram
Data flow diagram representing system architecture.
Fields:
type DataFlowDiagram { name: String! processes: [Process!]! dataStores: [DataStore!]! externalElements: [ExternalElement!]! flows: [Flow!]! trustZones: [TrustZone!]!}Statistics
Aggregated statistics across all threat models.
Fields:
type Statistics { totalThreatModels: Int! totalThreats: Int! totalInformationAssets: Int! totalControls: Int! implementedControls: Int! averageRiskReduction: Float}Calculations:
averageRiskReductionis only calculated when controls have risk reduction values > 0- Returns
nullif no controls have risk reduction values
Advanced Query Examples
Complete Threat Model with All Nested Data
query { threatModel(name: "My Application") { name author description createdAt updatedAt sourceFile
attributes { newInitiative internetFacing initiativeSize }
informationAssets { name informationClassification description }
threats { description impacts stride controls { name description implemented implementationNotes riskReduction } }
useCases { description }
exclusions { description }
thirdPartyDependencies { name description saas openSource uptimeDependency }
dataFlowDiagrams { name processes { name trustZone } dataStores { name trustZone informationAsset } flows { name from to } trustZones { name } } }}Filtering by Multiple Criteria
query { # Internet-facing large initiatives largeFacingInternet: threatModels(filter: { internetFacing: true initiativeSize: "Large" }) { name author }
# Threats with specific STRIDE categories authThreats: threats(filter: { stride: ["Spoofing", "Elevation Of Privilege"] }) { description threatModel { name } }
# Confidential assets confidentialAssets: informationAssets(classification: "Confidential") { name threatModel { name } }}Threat Models with Unimplemented Controls
query { threatModels { name threats { description controls { name implemented riskReduction } } }}Then filter client-side for implemented: false.
Statistics Dashboard
query DashboardStats { stats { totalThreatModels totalThreats totalInformationAssets totalControls implementedControls averageRiskReduction }
allModels: threatModels { name threats { description controls { implemented } } }}Integration Examples
cURL
# Query statisticscurl -X POST http://localhost:8080/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ stats { totalThreatModels totalThreats } }"}'
# Query specific threat modelcurl -X POST http://localhost:8080/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ threatModel(name: \"Tower of London\") { name author } }"}'JavaScript/Node.js
const fetch = require('node-fetch');
async function queryThreatModels() { const query = ` query { threatModels { name author threats { description } } } `;
const response = await fetch('http://localhost:8080/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) });
const data = await response.json(); console.log(JSON.stringify(data, null, 2));}
queryThreatModels();Python
import requests
def query_stats(): query = """ query { stats { totalThreatModels totalThreats implementedControls } } """
response = requests.post( 'http://localhost:8080/graphql', json={'query': query} )
print(response.json())
query_stats()Go
package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type GraphQLRequest struct { Query string `json:"query"`}
func main() { query := `{ threatModels { name author } }`
reqBody, _ := json.Marshal(GraphQLRequest{Query: query})
resp, err := http.Post( "http://localhost:8080/graphql", "application/json", bytes.NewBuffer(reqBody), ) if err != nil { panic(err) } defer resp.Body.Close()
var result map[string]interface{} json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result)}File Watching
When the server is started with the -watch flag, it monitors the threat model directory for changes:
Monitored Events:
- File created (
.hcl,.json) - File modified (
.hcl,.json) - File deleted
- File renamed
Behavior:
- Changes are detected in real-time
- Cache is automatically updated
- No server restart required
- All active GraphQL connections continue to work
- Subsequent queries reflect the latest data
Example Output:
File modified: examples/tm1.hcl - reloading...Successfully reloaded examples/tm1.hcl (6 threat models loaded)Best Practices
Query Optimization
-
Request Only What You Need: GraphQL allows you to specify exactly which fields to return. Avoid over-fetching by requesting only the data you need.
-
Use Filters: Filter data at the server level rather than fetching everything and filtering client-side.
-
Avoid Deep Nesting: While the API supports deep nesting, excessive nesting can impact performance.
Error Handling
GraphQL returns errors in a structured format:
{ "errors": [ { "message": "Error message here", "path": ["fieldName"] } ], "data": null}Always check for the errors field in responses.
CORS
The server is configured to allow requests from any origin (*). For production use, consider restricting CORS origins.
Limitations
- Read-Only: The current API only supports queries. Mutations (create, update, delete) are not yet implemented.
- No Subscriptions: Real-time updates via GraphQL subscriptions are not supported. Use file watching (
-watchflag) for auto-reload instead. - No Pagination: Large result sets are returned in full. Consider filtering to reduce response size.
- In-Memory Only: All data is stored in memory. Server restart clears the cache and reloads from files.
Troubleshooting
Server Won’t Start
Error: Port already in use
Solution: Use a different port with -port flagError: Directory not found
Solution: Verify the -dir path exists and contains .hcl or .json filesQuery Returns Empty Results
- Verify threat models are loaded: Check server startup output for “Loaded X threat model(s)“
- Check the health endpoint:
http://localhost:8080/health - Verify filter criteria: Remove filters to see if data exists
- Check threat model names: Names are case-sensitive
File Watching Not Working
- Ensure
-watchflag is specified - Verify file extensions are
.hclor.json - Check file is in the watched directory or subdirectories
- Review server console output for error messages