Train each role on the weaknesses it actually ships, with labs and fixes that have failing tests, not slides. Give everyone a short hands-on onboarding path, add role tracks for web and API, identity, platform, AI and cryptography work, and send engineers to the matching fix card when a finding with that CWE lands on them. Keep a dated record per person for PCI DSS 6.2.2 and SSDF PO.2.2, but judge the program by outcomes: fewer repeat findings of the trained CWE in each team, compared with that team's own history.
- Do make every module hands-on: a lab or project whose tests fail until the learner fixes the code.
- Do pick topics from your own findings by CWE and team, not from a generic top-10 list.
- Do train just in time: link the fix card for a finding's CWE in the ticket and ask for its test on the fix.
- Do keep per-person, dated training records with topics and evidence, because PCI DSS 6.2.2 requires yearly training for developers of bespoke and custom software.
- Don't report completion rates, quiz scores or hours as success; they are inputs. Report repeat findings and time to fix for trained weaknesses.
| Setting | Value | Note | Source |
|---|---|---|---|
| PCI DSS v4.0.1 6.2.2 | Developers of bespoke and custom software trained at least once every 12 months | On software security relevant to their job and languages, secure design and secure coding, and the security testing tools they use. | PCI DSS v4.0.1 |
| NIST SSDF 1.1 PO.2.2 | Role-based training for everyone who contributes to secure development; review proficiency and update training periodically | — | NIST SP 800-218 |
| ISO/IEC 27001:2022 | Annex A 6.3, information security awareness, education and training | Applies to all personnel; developer training is the role-specific part. | — |
| OWASP SAMM 2.2.0 | Education and Guidance: level 1 awareness for everyone with auditable sign-off; level 2 role- and technology-specific training | — | OWASP SAMM G-EG-A-1, G-EG-A-2 |
| Outcome window | 180 days before and after training, per team and CWE | Local choice; long enough to see repeat findings, short enough to act on. | — |
- Pick five developers at random and find, for each, a dated training record from the last 12 months with topics and evidence of a hands-on exercise.
- Check that each team's training topics match its own top CWEs from the last two quarters of findings.
- Run the repeat-findings comparison for every team trained more than six months ago and act on the teams that show no improvement.
- Open three recent security tickets and confirm each links the fix card or guide for its weakness.
- Confirm the program report leads with outcome measures and lists completion figures only as inputs.
- Applies to
- PCI DSS v4.0.1, Requirement 6.2.2
- NIST SSDF 1.1, task PO.2.2
- OWASP SAMM 2.2.0, Education and Guidance practice
- Script Python 3.14 standard library (tested)
Engineering organizations of any size that need developers to write fewer vulnerabilities, and those that must show training evidence for PCI DSS, NIST SSDF or ISO/IEC 27001.
- Part of
- Workflow: Build an AppSec program
Learning goals and prerequisites
What you will learn
- Build role-based, hands-on curricula from paths, labs and fix cards.
- Train just in time by linking fix guidance to findings.
- Keep training records that satisfy PCI DSS 6.2.2 and SSDF PO.2.2.
- Measure the effect with repeat findings per team and CWE instead of completion rates.
Before you start
AppSec leads, engineering managers and security champions who plan or run developer training
- Have access to your findings by CWE and team for the last two quarters.
- Know which obligations (PCI DSS, SSDF, ISO/IEC 27001) apply to your organization.
- For the script: Python 3.11 or later.
Developers change how they write code after they have broken and fixed the same mistake in code that looks like theirs. A lab with a failing test gives that experience in minutes; a slide deck about the OWASP Top 10 does not. OWASP SAMM makes the same distinction: its first Education and Guidance activity is awareness training for everyone, with an auditable sign-off, and its second is training customized to roles and technologies, including the security nuances of each language and platform.
This site is built for that second kind. The learning paths at /learn/ combine guides with labs that run in the browser and check the learner's fix against attack cases. The downloadable projects on /labs/ come with a starter that fails its tests and a reference fix that passes. The fix cards at /fix/ show a vulnerable, a fixed and a plausible but wrong fix for one weakness in Node.js, Python, Java and C#, each with a regression test.
Three common obligations ask for training, with different scope. None of them asks for slides; all of them accept hands-on modules if the records show who did what, when, and on which topics.
| Requirement | Who | What it asks | Evidence that satisfies it |
|---|---|---|---|
| PCI DSS v4.0.1 6.2.2 | Software development personnel working on bespoke and custom software | Training at least once every 12 months on software security relevant to their job and languages, secure design and secure coding, and the security testing tools they use | Dated record per developer listing topics, languages and tools; see /frameworks/pci-dss-requirement-6/ |
| NIST SSDF 1.1 PO.2.2 | Everyone whose role contributes to secure development | Role-based training; periodic review of proficiency and of the training itself | Curriculum per role with desired outcomes; review notes; records |
| ISO/IEC 27001:2022 A.6.3 | All personnel and relevant interested parties | Awareness, education and training appropriate to roles | Awareness plus role training records within the ISMS |
| OWASP SAMM 2.2.0 Education and Guidance | Everyone in development, then roles | Level 1: awareness for all with auditable sign-off. Level 2: role- and technology-specific training | Assessment answers backed by the same records |
Start every engineer with the same short, hands-on path, then add one role track. Keep each track to what that role ships. Choose the yearly refresh from the team's own top CWEs rather than repeating the onboarding path.
Store the curriculum as data so the training record, onboarding checklists and reports all read the same source. The file below maps roles to paths, labs and fix cards on this site and states the just-in-time rule. Review it when your top CWEs change and when SSDF PO.2.2's "periodically review" falls due.
# Role-based curriculum built from softwaresecurity.com paths, labs and fix cards.
# Paths and labs are hands-on; "evidence" is what the training record stores.
roles:
all-engineers:
onboarding: [/learn/developer-foundations/]
labs: [/labs/authorization/, /labs/webhooks/]
yearly_refresh: one lab and one fix card chosen from the team's top CWEs
evidence: lab verdict screenshots or project test runs, dated
web-and-api:
onboarding: [/learn/browser-api-defense/]
labs: [/labs/csrf/, /labs/archive-paths/, /labs/checkout-race/]
fix_cards: [/fix/xss/, /fix/sql-injection/, /fix/missing-authorization/, /fix/ssrf/, /fix/csrf/]
identity-and-accounts:
onboarding: [/learn/identity-lifecycle/]
labs: [/labs/token-validation/, /labs/recovery/]
fix_cards: [/fix/jwt-validation/, /fix/session-cookies/, /fix/password-hashing/]
platform-and-build:
onboarding: [/learn/supply-chain-assurance/, /learn/runtime-defense/]
labs: [/labs/sbom-inventory/, /labs/artifact-policy/, /labs/kubernetes-policy/]
fix_cards: [/fix/hardcoded-secrets/, /fix/tls-certificate-validation/]
ai-features:
onboarding: [/learn/ai-application-defense/]
labs: [/labs/rag-isolation/, /labs/agent-tool-gateway/, /labs/output-rendering/]
cryptography-owners:
onboarding: [/learn/cryptography-foundations/]
labs: [/labs/nonces/]
fix_cards: [/fix/insecure-randomness/]
tech-leads-and-champions:
onboarding: [/learn/appsec-practitioner/]
practice: [threat model one feature per quarter, review two security-relevant pull requests per sprint]
security-leads:
onboarding: [/learn/security-program-assurance/]
labs: [/labs/release-evidence/]
just_in_time:
trigger: a finding is assigned to an engineer
action: link the fix card for the finding's CWE in the ticket and ask for its test to pass on the fixThe best time to learn about SQL injection is when a SQL injection finding is assigned to you. When a scanner, review or report produces a finding, put the matching fix card or guide in the ticket, and ask for the card's regression test, adapted to the code, in the fix. The engineer learns the pattern, the fix arrives with a test, and the record shows training tied to a real weakness.
Look up the card by CWE on the CWE pages at /cwe/ or the Fix a finding router at /workflows/fix-a-finding/. Add the link automatically where your tracker allows it; a ticket template with a "Fix guidance" field filled from the CWE is enough to start.
- Finding with CWE-89 in a Node.js service: link /fix/sql-injection/node-express/ and ask for its test.
- Finding with CWE-862 or CWE-639 in an API: link /fix/missing-authorization/ and /domains/appsec/authorization/object-level-authorization/.
- Finding in a design review: link the threat pattern and run a 30-minute session with the team on it.
| Format | Good for | What makes it work |
|---|---|---|
| Browser labs | Onboarding and refreshers, 20 to 45 minutes each | Attack cases decide pass or fail; learners see the exploit stop |
| Downloadable projects | Deeper practice and proof of skill | Learners fix the starter until the tests pass; run the tests in CI on their branch for evidence |
| Fix cards | Just-in-time training in the learner's stack | A vulnerable, a fixed and a wrong fix side by side, with the test that tells them apart |
| Fix-a-real-finding sessions | Teams with repeat findings | Pair on a real (closed) finding from the team's code; write the regression test together |
| Code review practice | Tech leads and champions | Review seeded pull requests with /domains/appsec/code-review/security-code-review/ as the checklist |
| Threat modeling practice | Designers of new features | Model one upcoming feature using /domains/appsec/threat-modeling/threat-modeling-methods/ and keep the output |
An assessor for PCI DSS 6.2.2 will ask which developers are in scope, when each was last trained, on which topics, and whether the topics match their job and languages. Keep one record per person per year with dates, topics, languages, tools and the evidence of each module. Prefer evidence an outsider can check: a lab result, a CI run of a project's tests, a merged fix with its regression test.
This site's progress export (the "Export progress" button on learning path pages) downloads a list of completed item IDs stored in the learner's browser. It has no name or dates and is self-reported, so use it as input to your own record, not as the record.
{
"person": "e-1042",
"role": "web-and-api",
"languages": ["TypeScript", "Python"],
"period": {"start": "2026-01-01", "end": "2026-12-31"},
"requirement": "PCI DSS v4.0.1 6.2.2; NIST SSDF 1.1 PO.2.2",
"modules": [
{
"id": "learn/browser-api-defense",
"completed": "2026-03-12",
"topics": ["secure design", "secure coding", "security testing tools"],
"evidence": "lab csrf passed 2026-03-10; project express-api reference tests passed in CI run 81423"
},
{
"id": "fix/sql-injection/node-express",
"completed": "2026-06-02",
"trigger": "finding APPSEC-311 (CWE-89)",
"evidence": "fix merged in PR 2210 with the card's regression test"
}
],
"tools_covered": ["Semgrep rules used in CI", "dependency scanner"],
"recorded_by": "appsec-team",
"recorded_on": "2026-06-03"
}Completion rate, quiz score, hours and satisfaction show that training happened, not that code changed. Report them as inputs, and judge the program by outcomes in each team's code, compared with that team's own history: teams that volunteer early are often already stronger, so comparing teams with each other misleads.
| Measure | Type | Why |
|---|---|---|
| Findings of the trained CWE per team, before and after | Outcome | The direct question: does the team still ship this weakness? |
| Time to remediate findings of trained CWEs | Outcome | Trained engineers should fix faster; use the definitions in /frameworks/appsec-metrics-and-slas/ |
| Escapes: trained CWEs found in production or by external reporters | Outcome | Shows whether prevention or only detection improved |
| Security comments in code review per sensitive pull request | Leading indicator | More issues caught before merge, if the count of escapes falls too |
| Completion within 12 months of in-scope developers | Input (compliance) | Needed for PCI DSS 6.2.2, not a measure of effect |
| Quiz scores, hours, satisfaction | Input | Useful for course design only |
The script compares, for each team and trained CWE, the findings opened in a window before the training date with the same window after it. In the sample, checkout's SQL injection findings fell from 3 to 1 in 180 days, while search still had 3 XSS findings after training and billing had 1 IDOR finding in each window. Search is the team to visit: the training did not change what it ships, so look at its templates and code review before scheduling more training.
Small counts are noisy. Use the comparison to decide where to look, not to grade people, and keep the same finding sources (scanner rules, review checklist) across both windows, or the numbers will move for reasons unrelated to training.
"""Compare findings of a trained CWE before and after each team's training.
Usage: python repeat_findings.py findings.csv training.csv [window_days]
Counts findings with the trained CWE in the window before and after the training date.
"""
import csv
import sys
from datetime import date, timedelta
def rows(path):
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def compare(findings, training, window):
for t in training:
trained = date.fromisoformat(t["trained"])
dates = [
date.fromisoformat(f["opened"])
for f in findings
if f["team"] == t["team"] and f["cwe"] == t["cwe"]
]
before = sum(trained - window <= d < trained for d in dates)
after = sum(trained <= d < trained + window for d in dates)
yield t["team"], t["cwe"], before, after
if __name__ == "__main__":
window = timedelta(days=int(sys.argv[3]) if len(sys.argv) > 3 else 180)
print(f"{'team':10} {'cwe':8} before after ({window.days}-day windows)")
for team, cwe, before, after in compare(rows(sys.argv[1]), rows(sys.argv[2]), window):
flag = " <- no improvement" if after >= before else ""
print(f"{team:10} {cwe:8} {before:6} {after:5}{flag}")- Onboarding: the foundations path and one role track in the first weeks, about half a day of hands-on work in total.
- Just in time: a fix card or guide with every security ticket, as the default.
- Yearly: one refresher per person chosen from the team's top CWEs, which also satisfies the PCI DSS 12-month cycle when records are kept.
- Quarterly: a review of each team's repeat findings and an update to the curriculum file.
- Champions: a monthly session built from the organization's own findings, as in /frameworks/security-champions-program/.
- Buying a video library and reporting completion as success.
- Teaching the OWASP Top 10 in the abstract instead of the CWEs each team actually ships, in its languages.
- Training once a year with nothing linking a later finding back to the lesson.
- Ranking teams or people by training scores; it rewards gaming and hides the teams that need help.
- Keeping records that cannot answer "who, when, what topics" for an in-scope developer.
- Treating more training as the answer to every repeat finding when the cause is a missing library, framework default or review step.
Guided practice
Wren, a fictional payments company, has 60 developers. Its auditor asked for PCI DSS 6.2.2 evidence, and leadership wants to know whether last year's training paid off. Use the data to fix the program.
Supplied scenario material
Current program (fictional):
- All developers watch a 2-hour OWASP Top 10 video each January; completion 97%; average quiz score 88%
- Records: an LMS export with name, video title, completion date
Teams and languages: checkout (Java), api (Node.js), mobile (Kotlin, Swift), data (Python)
Findings by CWE, last 12 months:
checkout: CWE-89 x5, CWE-639 x2
api: CWE-639 x6, CWE-918 x2
mobile: CWE-312 x3
data: CWE-502 x2, CWE-89 x1
Security tools in CI: Semgrep and a dependency scanner. The video does not mention either.
Tickets: none link fix guidance.1. Check 6.2.2
Does the current program meet PCI DSS 6.2.2?
Expected result and reasoning
Partly. It is yearly and recorded, but 6.2.2 asks for training relevant to job function and languages, secure design and coding, and the security testing tools used; the generic video covers none of the languages or the Semgrep and dependency-scanner tools. The records also lack topics.
2. Re-plan by role and CWE
Choose a track and two modules for checkout and api.
Expected result and reasoning
Checkout (Java): /learn/browser-api-defense/ with /fix/sql-injection/java-spring/ and /fix/missing-authorization/java-spring/. API (Node.js): /labs/authorization/ and /fix/missing-authorization/node-express/, plus /fix/ssrf/node-express/ for CWE-918. Add a module on reading and triaging Semgrep results for both.
3. Add just-in-time links
What changes in the ticket workflow?
Expected result and reasoning
Every security ticket gets the fix card for its CWE and stack, and the fix must include the card's regression test adapted to the code; this becomes a dated training record entry.
4. Replace the success metric
Leadership cites 97% completion and an 88% quiz average. What do you report instead?
Expected result and reasoning
Those are inputs. Report, per team and trained CWE, findings in the 180 days before and after training (for example api CWE-639), time to remediate, and escapes. Keep completion in the compliance appendix.
5. Spot a non-training cause
Mobile keeps getting CWE-312 (cleartext storage of sensitive information). Is more training the answer?
Expected result and reasoning
Maybe not. Check whether the apps lack a shared secure-storage wrapper; a library and a lint rule may prevent the class better than a course. Train on the wrapper once it exists.
Wren and its data are fictional. PCI DSS applies to entities that store, process or transmit cardholder data and their service providers; confirm scope with your assessor.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- PCI SSC document library: PCI DSS v4.0.1. v4.0.1 (June 2024).
- NIST SP 800-218: SSDF 1.1 (PO.2.2 role-based training). Version 1.1 (February 2022).
- OWASP SAMM: Education and Guidance, stream A (Training and Awareness)
- OWASP SAMM: Education and Guidance practice
- BSIMM16 report: Training practice activities. BSIMM16 (January 2026).
- OWASP Security Champions Guide
- MITRE CWE: CWE-312 Cleartext Storage of Sensitive Information. CWE 4.20.
Reference information
- SDLC phase
- Security program