A lightweight OWASP SAMM v2 toolkit: run a maturity assessment, compute scores per practice and business function, generate a prioritised improvement roadmap, and render a Markdown report with an SVG maturity radar.
Running an AppSec program the way a SAMM assessment actually works β not just operating scanners, but measuring maturity, setting targets, and producing an actionable roadmap. It models the full SAMM v2 structure:
- 5 business functions β 15 security practices β 2 streams each β 3 maturity levels
- Answers are fractions per practice/stream/level (
0 / 0.25 / 0.5 / 0.75 / 1.0) - Scores roll up: stream β practice β business function β overall (all on a 0β3 scale)
- A target profile drives gap analysis and a ranked roadmap
No heavy dependencies β pure Python + PyYAML. The radar chart is generated as hand-built SVG (no matplotlib), so output embeds cleanly on GitHub.
Requires Python 3.9+.
pip install -e . # exposes the `samm-tool` command
# or run without installing:
python -m samm_tool --help# 1. Create a blank assessment (+ a target profile)
samm-tool init -o assessment.yaml --target target.yaml --target-level 2.0
# 2. Fill in assessment.yaml, then see the scores
samm-tool score assessment.yaml
# 3. Get a prioritised improvement roadmap
samm-tool roadmap assessment.yaml --target target.yaml
# 4. Generate a Markdown report + SVG radar
samm-tool report assessment.yaml --target target.yaml --outdir reportsFor live interviews, use the browser-based interface instead of hand-editing
assessment.yaml. This section is a complete manual for it.
This is an additional way to use the toolkit β the YAML/CLI workflow above keeps working exactly as documented, and doesn't require Flask.
pip install -e ".[web]" # installs Flask (optional dependency)
samm-tool web # starts at http://127.0.0.1:5000web command options:
| Flag | Default | Description |
|---|---|---|
--host |
127.0.0.1 |
network address the server listens on |
--port |
5000 |
TCP port |
--debug |
disabled | enables Flask debug mode (auto-reload, detailed tracebacks) |
Go to http://127.0.0.1:5000 (redirects to /login if you're not logged
in) and click Sign up. Fill in:
- Name β your name
- Role β your role (e.g., AppSec Analyst)
- Organization β the company/team you belong to (not the organization being assessed β that's asked later, per assessment)
- Email β used as the login; must be unique
- Password β at least 8 characters, no other complexity rules
On signup, you're logged in automatically and taken straight to the dashboard (My assessments). An already-registered email produces an error message and the form is re-shown without creating the account.
- Log in (
/login): email and password. - Log out: the Log out button in the top-right corner, available on any page while you're logged in.
The session uses a Flask-signed cookie (key generated once and persisted to
instance/secret_key); it doesn't expire automatically beyond the browser's
default behavior.
The home screen (/) shows:
- An Assessment in progress card, with a Continue button, if you have an assessment that isn't finished yet.
- The New assessment button, to start a new interview.
- The list of Completed assessments, each linking to its result page.
Note: the dashboard only shows the most recent in-progress assessment. If you click "New assessment" while one is already in progress, the earlier one stays saved in the database but stops appearing on the dashboard β finish an interview before starting another to avoid losing access to it through the interface.
On New assessment (/assessments/new), fill in the interview details:
- Organization β the organization/company being assessed
- Project β the project or product in scope
- Interviewee β the name of the person being interviewed
- Date β the interview date
On confirming (Start interview), the assessment is created with "in progress" status and you're taken straight to the first question of the questionnaire.
The questionnaire covers the 15 SAMM practices, each with 2 streams (A and B) β 30 screens in total, always in the same order (practice Γ stream, never shuffled). Each screen shows:
- The practice's code and name (e.g.,
SM Β· Strategy & Metrics) and the current stream, with the question number at the top ("Question X of 30") - The practice/stream's 3 maturity levels, each with its objective described
- For each level, a choice among 5 answers:
| Answer | Numeric value |
|---|---|
| No | 0.0 |
| Slightly | 0.25 |
| Partially | 0.5 |
| Mostly | 0.75 |
| Yes | 1.0 |
Navigation:
- Next saves the current screen's 3 answers and advances to the next one. All 3 answers are required β the screen won't advance without them.
- Back (available from the 2nd question onward) returns to the previous screen without losing what's already been answered; answers already saved appear pre-selected when revisiting a screen.
Each answer is written to the database as soon as you click Next β there's no separate "save" button, and no risk of losing answers already given. If you close the browser mid-interview, you can come back later, log in again, and click Continue on the dashboard: you resume exactly at the next unanswered question.
After answering the last of the 30 screens, the assessment is automatically marked as completed and you're redirected to the result page.
Once completed, the assessment always shows (/assessments/<id>/result):
- Overall maturity β average of the 15 practices, on a 0β3 scale
- The maturity radar (SVG), with the current score overlaid on the
default target (level 2.0 across all practices β the same default used by
samm-tool roadmap/reportwithout--target) - A score by business function table (Governance, Design, Implementation, Verification, Operations)
- A score by practice table (the 15 practices, each 0β3)
- Improvement roadmap β a list prioritized by the largest gap to the target, with concrete actions (the level to reach in each stream) to close each gap; if all practices already meet the target, that's stated explicitly instead of showing an empty list
- Links to download report.md and radar.svg, generated by the same
modules used by the CLI's
samm-tool reportflow
Completed assessments are read-only β there's no way to edit answers through the interface once they're completed.
Everything lives under instance/, created in the folder you ran
samm-tool web from (git-ignored β never versioned):
instance/samm.dbβ SQLite database with users, assessments, and answersinstance/secret_keyβ key used to sign session cookies (generated once, with permissions restricted to the file's owner)instance/reports/<id>/report.mdandradar.svgβ generated the first time each assessment's result page is opened, and reused by the download links
- Passwords hashed with PBKDF2 (
werkzeug.security), never stored in plain text - CSRF protection on every form that changes data (signup, login, logout, new assessment, questionnaire)
- Each assessment is only accessible to the user who created it β any attempt to access another user's assessment returns 404
- Free-text user input (name, role, organization, project, interviewee) is never rendered as raw HTML β always auto-escaped by Jinja2, preventing XSS
A worked, fictional assessment lives in examples/; the generated
output is committed in reports/:
- π
reports/report.mdβ full report - πΈοΈ
reports/radar.svgβ maturity radar (current vs target)
Regenerate it with:
samm-tool report examples/example-assessment.yaml \
--target examples/target-profile.yaml --outdir reportsmeta:
organization: Acme FinTech
assessor: Jane Doe
date: 2026-07-20
answers:
SM: {A: {1: 1.0, 2: 0.25, 3: 0.0}, B: {1: 0.5, 2: 0.25, 3: 0.0}}
# ... all 15 practice codes (SM PC EG TA SR SA SB SD DM AA RT ST IM EM OM)The roadmap and report commands take an optional --target profile: a
per-practice maturity goal on the 0β3 scale. Generate one with
init --target, or hand-write it β any practice you omit falls back to
--target-level (default 2.0).
targets:
SM: 2.0
SB: 3.0 # aim higher where the team is strong
EG: 1.5 # pragmatic floor for lower-priority practices
# ... any of the 15 practice codes; omitted ones use --target-level| Level | Formula | Range |
|---|---|---|
| Stream | sum of its 3 level fractions | 0.0β3.0 |
| Practice | mean of streams A and B | 0.0β3.0 |
| Business function | mean of its 3 practices | 0.0β3.0 |
| Overall | mean of all 15 practices | 0.0β3.0 |
pip install -e ".[dev]"
pytest -q
ruff check .The SAMM model text is summarised from the OWASP SAMM v2 project
(CC BY-SA 4.0). See NOTICE. This toolkit's code is MIT-licensed
(see LICENSE) and is an independent, unofficial helper β not an
official OWASP release.