Skip to content

Commit 5e664f4

Browse files
izarsnyk-bot
andauthored
docs: align README examples with current Pydantic API (#336)
## Summary - **`controls.isHardened`**: `web.isHardened = True` and `db.isHardened = False` changed to `web.controls.isHardened = True` / `db.controls.isHardened = False`. `isHardened` lives on `Controls`, not on `Element`. Because `Element` has `extra="allow"`, the old form **failed silently** — it stored the value as an extra field and left `controls.isHardened` at its default. - **`controls.hasAccessControl`**: `my_lambda.hasAccessControl = True` → `my_lambda.controls.hasAccessControl = True`. Same silent-failure mechanism. - **`db.type = DatastoreType.SQL`**: `db.isSql = True` → `db.type = DatastoreType.SQL`. The actual field is `isSQL` (uppercase), not `isSql`; `isSql` was silently stored as an extra field. The shipped `tm.py` uses `DatastoreType.SQL`. - **`sourceFiles`**: `web.sourceCode` and `db.sourceCode` → `web.sourceFiles = [...]` / `db.sourceFiles = [...]`. `sourceFiles` is a `List[str]` on `Element`; `sourceCode` was silently ignored. - **`DatastoreType` import**: Added to the sample import list (required by `db.type = DatastoreType.SQL`). - **`--describe` block**: Replaced stale hand-written output (showing pre-Pydantic fields that no longer exist) with verbatim output of `./tm.py --describe Server`, which shows the full inherited attribute set including `controls`, `sourceFiles`, `minTLSVersion`, etc. - **Usage block**: Updated synopsis (added `--colormap` to synopsis line; changed `optional arguments:` to `options:` — Python 3.10+ argparse wording). - **Threat condition example**: `f.sink.isSQL` → `f.sink.type == DatastoreType.SQL`. `isSQL` is a valid bool field (still exists, defaults `True`) but is non-discriminating for all Datastores by default; `type == DatastoreType.SQL` matches the idiom in the shipped `tm.py`. ## Verified failure mode of old sample `extra="allow"` on Pydantic models means every stale attribute set (`web.isHardened`, `db.isSql`, `web.sourceCode`, etc.) was silently stored in `model_extra` rather than raising a `ValidationError`. No error was raised; the intended fields were simply never set. ## Validation ``` # Corrected sample python /tmp/sample.py --dfd > /dev/null # OK python /tmp/sample.py --report docs/basic_template.md > /dev/null # OK # Test suite uv run pytest tests/ -x -q # 243 passed # Diff touches only README.md git diff --stat HEAD~1 # README.md | 93 +++... ``` ## Hypotheses that turned out wrong - The hypothesis stated the README uses `from pytm.pytm import`; it already used `from pytm import` correctly. Only the missing `DatastoreType` in the import list needed fixing. - `docs/threats.md` hits from the `grep` sweep (`target.controls.isHardened`, `target.controls.hasAccessControl`) are already in correct form — no changes needed there. ## Out-of-scope finding `./tm.py --describe Element` errors with "No such class to describe: Element" because `Element` is not added to `_CLASS_REGISTRY` in `pytm/pytm.py` (only its subclasses are added via `_iter_subclasses`). Tracked separately. --------- Co-authored-by: snyk-bot <snyk-bot@snyk.io>
1 parent bc4e7be commit 5e664f4

1 file changed

Lines changed: 68 additions & 26 deletions

File tree

README.md

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ lower overhead and more convenient alternative to the OCI container approach.
7575
All available arguments:
7676

7777
```text
78-
usage: tm.py [-h] [--debug] [--dfd] [--report REPORT]
79-
[--exclude EXCLUDE] [--seq] [--list] [--describe DESCRIBE]
78+
usage: tm.py [-h] [--debug] [--dfd] [--report REPORT] [--exclude EXCLUDE]
79+
[--seq] [--list] [--colormap] [--describe DESCRIBE]
8080
[--list-elements] [--json JSON] [--levels LEVELS [LEVELS ...]]
8181
[--stale_days STALE_DAYS]
8282
83-
optional arguments:
83+
options:
8484
-h, --help show this help message and exit
8585
--debug print debug messages
8686
--dfd output DFD
@@ -109,21 +109,62 @@ Currently available elements are: TM, Element, Server, ExternalEntity, Datastore
109109
The available properties of an element can be listed by using `--describe` followed by the name of an element:
110110

111111
```text
112-
113-
(pytm) ➜ pytm git:(master) ✗ ./tm.py --describe Element
114-
Element class attributes:
115-
OS
116-
definesConnectionTimeout default: False
117-
description
118-
handlesResources default: False
119-
implementsAuthenticationScheme default: False
120-
implementsNonce default: False
121-
inBoundary
122-
inScope Is the element in scope of the threat model, default: True
123-
isAdmin default: False
124-
isHardened default: False
125-
name required
126-
onAWS default: False
112+
$ ./tm.py --describe Server
113+
Server class attributes:
114+
OS Operating system
115+
default: ''
116+
assumptions Assumptions about the element. These optionally allow to exclude threats with the given SIDs
117+
default factory: list
118+
controls Security controls for this element
119+
default factory: Controls
120+
data pytm.Data object(s) in incoming data flows
121+
default factory: DataSet
122+
description Description of the element
123+
default: ''
124+
findings Threats that apply to this element
125+
default factory: list
126+
handlesResources Does this asset handle resources?
127+
default: False
128+
inBoundary Trust boundary this element exists in
129+
default: None
130+
inScope Is the element in scope of the threat model
131+
default: True
132+
inputs incoming Dataflows
133+
default factory: list
134+
is_drawn default: False
135+
levels List of levels (0, 1, 2, ...) to be drawn in the model
136+
default factory: <lambda>
137+
maxClassification Maximum data classification this element can handle
138+
default: <Classification.UNKNOWN: 0>
139+
minTLSVersion Minimum TLS version required
140+
default: <TLSVersion.NONE: 0>
141+
name Name of the element
142+
required
143+
onAWS Is this asset on AWS?
144+
default: False
145+
outputs outgoing Dataflows
146+
default factory: list
147+
overrides Overrides to findings, allowing to set a custom response, CVSS score or override other attributes
148+
default factory: list
149+
port Default TCP port for incoming data flows
150+
default: -1
151+
protocol Default network protocol for incoming data flows
152+
default: ''
153+
severity Severity level of threats affecting this element
154+
default: 0
155+
sourceFiles Location of the source code that describes this element relative to the directory of the model script
156+
default factory: list
157+
usesCache Does this server use cache?
158+
default: False
159+
usesEnvironmentVariables Does this asset use environment variables?
160+
default: False
161+
usesSessionTokens Does this server use session tokens?
162+
default: False
163+
usesVPN Does this server use VPN?
164+
default: False
165+
usesXMLParser Does this server use XML parser?
166+
default: False
167+
uuid default factory: <lambda>
127168
128169
```
129170

@@ -146,7 +187,8 @@ that periodically cleans the Database.
146187

147188
#!/usr/bin/env python3
148189

149-
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, Lambda, LLM, Data, Classification
190+
from pytm import TM, Server, Datastore, Dataflow, Boundary, Actor, Lambda, LLM, Data, Classification, DatastoreType
191+
150192

151193
tm = TM("my test tm")
152194
tm.description = "another test tm"
@@ -160,16 +202,16 @@ user.inBoundary = User_Web
160202

161203
web = Server("Web Server")
162204
web.OS = "CloudOS"
163-
web.isHardened = True
164-
web.sourceCode = "server/web.cc"
205+
web.controls.isHardened = True
206+
web.sourceFiles = ["server/web.cc"]
165207

166208
db = Datastore("SQL Database (*)")
167209
db.OS = "CentOS"
168-
db.isHardened = False
210+
db.controls.isHardened = False
169211
db.inBoundary = Web_DB
170-
db.isSql = True
212+
db.type = DatastoreType.SQL
171213
db.inScope = False
172-
db.sourceCode = "model/schema.sql"
214+
db.sourceFiles = ["model/schema.sql"]
173215

174216
comments = Data(
175217
name="Comments",
@@ -196,7 +238,7 @@ results = Data(
196238
)
197239

198240
my_lambda = Lambda("cleanDBevery6hours")
199-
my_lambda.hasAccessControl = True
241+
my_lambda.controls.hasAccessControl = True
200242
my_lambda.inBoundary = Web_DB
201243

202244
llm_api = LLM("AI Writing Assistant")
@@ -393,7 +435,7 @@ If `target` is a Dataflow, remember you can access `target.source` and/or `targe
393435
Conditions on assets can analyze all incoming and outgoing Dataflows by inspecting
394436
the `target.input` and `target.output` attributes. For example, to match a threat only against
395437
servers with incoming traffic, use `any(target.inputs)`. A more advanced example,
396-
matching elements connecting to SQL datastores, would be `any(f.sink.oneOf(Datastore) and f.sink.isSQL for f in target.outputs)`.
438+
matching elements connecting to SQL datastores, would be `any(f.sink.oneOf(Datastore) and f.sink.type == DatastoreType.SQL for f in target.outputs)`.
397439

398440
## Importing from JSON
399441

0 commit comments

Comments
 (0)