
A Pythonic framework for threat modeling
Traditional threat modeling too often comes late to the party, or sometimes not at all. In addition, creating manual data flows and reports can be extremely time-consuming. The goal of pytm is to shift threat modeling to the left, making threat modeling more automated and developer-centric.
Based on your input and definition of the architectural design, pytm can automatically generate the following items:
The tm.py is an example model. You can run it to generate the report and diagram image files that it references:
mkdir -p tm
./tm.py --report docs/basic_template.md | pandoc -f markdown -t html > tm/report.html
./tm.py --dfd | dot -Tpng -o tm/dfd.png
./tm.py --seq | java -Djava.awt.headless=true -jar $PLANTUML_PATH -tpng -pipe > tm/seq.png
There's also an example Makefile that wraps all these into targets that can be easily shared for multiple models. If you have GNU make installed (available by default on Linux distros but not on OSX), simply run:
make MODEL=the_name_of_your_model_minus_.py
You should either have plantuml.jar on the same directory as your model, or set PLANTUML_PATH.
To avoid installing all the dependencies, like pandoc or Java, the script can be run inside a container:
# do this only once
export USE_DOCKER=true
make image
# call this after every change in your model
make
To simplify the usage of pytm host dependencies can be completely isolated
using Devbox. This is usually a
lower overhead and more convenient alternative to the OCI container approach.
curl -fsSL https://get.jetify.com/devbox | bashdevbox version update~/.config/nix/nix.conf file file: access-tokens = github.com=YOUR_TOKEN_HEREdevbox.json file: devbox shellpython in your terminal by using the which python command. The output should be the following path: .devbox/nix/profile/default/bin/pythonsample.png: ./tm.py --dfd | dot -Tpng -o sample.pngexitAll available arguments:
usage: tm.py [-h] [--debug] [--dfd] [--report REPORT]
[--exclude EXCLUDE] [--seq] [--list] [--describe DESCRIBE]
[--list-elements] [--json JSON] [--levels LEVELS [LEVELS ...]]
[--stale_days STALE_DAYS]
optional arguments:
-h, --help show this help message and exit
--debug print debug messages
--dfd output DFD
--report REPORT output report using the named template file (sample
template file is under docs/template.md)
--exclude EXCLUDE specify threat IDs to be ignored
--seq output sequential diagram
--list list all available threats
--colormap color the risk in the diagram
--describe DESCRIBE describe the properties available for a given element
--list-elements list all elements which can be part of a threat model
--json JSON output a JSON file
--levels LEVELS [LEVELS ...]
Select levels to be drawn in the threat model (int
separated by comma).
--stale_days STALE_DAYS
checks if the delta between the TM script and the code
described by it is bigger than the specified value in
days
The stale_days argument tries to determine how far apart in days the model script (which you are writing) is from the code that implements the system being modeled. Ideally, they should be pretty close in most cases of an actively developed system. You can run this periodically to measure the pulse of your project and the 'freshness' of your threat model.
Currently available elements are: TM, Element, Server, ExternalEntity, Datastore, Actor, Process, SetOfProcesses, Dataflow, Boundary, Lambda, LLM and Agent.
The available properties of an element can be listed by using --describe followed by the name of an element:
(pytm) ➜ pytm git:(master) ✗ ./tm.py --describe Element
Element class attributes:
OS
definesConnectionTimeout default: False
description
handlesResources default: False
implementsAuthenticationScheme default: False
implementsNonce default: False
inBoundary
inScope Is the element in scope of the threat model, default: True
isAdmin default: False
isHardened default: False
name required
onAWS default: False
The colormap argument, used together with dfd, outputs a color-coded DFD where the elements are painted red, yellow or green depending on their risk level (as identified by running the rules).
devbox shellpytm usage as usualexitThe following is a sample tm.py file that describes a simple application where a User logs into the application
and posts comments on the app. The app server stores those comments into the database. There is an AWS Lambda
that periodically cleans the Database.
#!/usr/bin/env python3
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, Lambda, LLM, Data, Classification
tm = TM("my test tm")
tm.description = "another test tm"
tm.isOrdered = True
User_Web = Boundary("User/Web")
Web_DB = Boundary("Web/DB")
user = Actor("User")
user.inBoundary = User_Web
web = Server("Web Server")
web.OS = "CloudOS"
web.isHardened = True
web.sourceCode = "server/web.cc"
db = Datastore("SQL Database (*)")
db.OS = "CentOS"
db.isHardened = False
db.inBoundary = Web_DB
db.isSql = True
db.inScope = False
db.sourceCode = "model/schema.sql"
comments = Data(
name="Comments",
description="Comments in HTML or Markdown",
classification=Classification.PUBLIC,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)
results = Data(
name="results",
description="Results of insert op",
classification=Classification.SENSITIVE,
isPII=False,
isCredentials=False,
# credentialsLife=Lifetime.LONG,
isStored=True,
isSourceEncryptedAtRest=False,
isDestEncryptedAtRest=True
)
my_lambda = Lambda("cleanDBevery6hours")
my_lambda.hasAccessControl = True
my_lambda.inBoundary = Web_DB
llm_api = LLM("AI Writing Assistant")
llm_api.isThirdParty = True
llm_api.processesPersonalData = True
llm_api.hasContentFiltering = False
llm_api.hasSystemPrompt = True
llm_api.processesUntrustedInput = True