ЁЯПл The SchoolтА║ЁЯЫбя╕П SecurityтА║ЁЯзн рдзрдбрд╛ 15 тАФ Delivery line рдЪреЗ threat modelling: рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд╡рд░ рдПрдХ рд░рдХреНрд╖рдХ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯзн рдзрдбрд╛ 15 тАФ Delivery line рдЪреЗ threat modelling: рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд╡рд░ рдПрдХ рд░рдХреНрд╖рдХ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 16 рдкреИрдХреА рдзрдбрд╛ 15 ┬╖ рдорд╛рдЧреАрд▓: lesson-14-artifacts ┬╖ рдкреБрдвреАрд▓: lesson-16-ai-security


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ14, рдЖрдгрд┐ рддреНрдпрд╛рдВрдирд╛ рдЬреЛрдбрдгрд╛рд░реА рд╕рд╡рдп: threat modelling. рддреБрдореНрд╣реА delivery line рд╡рд░реВрди рдЪрд╛рд▓рддрд╛ тАФ laptop тЖТ git тЖТ dependencies тЖТ CI build тЖТ container image тЖТ registry тЖТ deployment тАФ рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд╡рд░ рд╕рд╣рд╛ STRIDE рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рддрд╛. рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд▓рд╛ рдирд╛рд╡ рджрд┐рд▓реЗрд▓рд╛ рдПрдХ рд░рдХреНрд╖рдХ рдорд┐рд│рддреЛ. рдордЧ рддреА рдШрдЯрдирд╛ рдЬреА рдкреНрд░рддреНрдпреЗрдХрд╛рд▓рд╛ рдХрдзреА рдирд╛ рдХрдзреА рднреЗрдЯрддреЗ: public commit рдордзреНрдпреЗ secret leak рд╣реЛрддреЗ, рдЖрдгрд┐ рдкрд╣рд┐рд▓реНрдпрд╛ рдкрд╛рдпрд▒реНрдпрд╛рдВрдЪрд╛ рдХреНрд░рдо (рдЖрдзреА rotate).

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рд╕реНрд╡рдпрдВрдкрд╛рдХреА рдХрд╛рдо рд╕реБрд░реВ рдХрд░рддреЛ рддреЗрд╡реНрд╣рд╛рдкрд╛рд╕реВрди рдбрдмрд╛ рд╡рд░реНрдЧрд╛рдд рдкреЛрд╣реЛрдЪреЗрдкрд░реНрдпрдВрдд рджреАрдкрд┐рдХрд╛ рдПрдХрд╛ рдбрдмреНрдпрд╛рдЪреНрдпрд╛ рдорд╛рдЧреЗ рдЬрд╛рддреЗ, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкрд╛рдпрд░реАрд╡рд░ рддреА рд╡рд┐рдЪрд╛рд░рддреЗ: "рдЗрдереЗ рдХрд╛рдп рдЪреБрдХреВ рд╢рдХрддреЗ, рдЖрдгрд┐ рддреЗ рдХреЛрдг рдерд╛рдВрдмрд╡рддреЗ?"

рдордЧ рдПрдХреЗ рджрд┐рд╡рд╢реА рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рд╢рд╛рд│реЗрдЪреНрдпрд╛ рд╡рд░реНрддрдорд╛рдирдкрддреНрд░рд╛рдд рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░рд╛рдЪреНрдпрд╛ рдЪрд╛рд╡реАрдЪрд╛ рдлреЛрдЯреЛ рджрд┐рд╕рддреЛ. рдЖрдзреА рдХрд╛рдп? "рд╡рд░реНрддрдорд╛рдирдкрддреНрд░ рдкрд░рдд рдШреНрдпрд╛" рдирд╛рд╣реА. рдХреБрд▓реВрдк рдмрджрд▓рд╛, рдЖрддреНрддрд╛рдЪ. рдордЧ рдХреЛрдг рдЖрдд рдЖрд▓реЗ рддреЗ рдкрд╛рд╣рд╛, рдордЧ рдлреЛрдЯреЛ рдХрд╛рдвреВрди рдЯрд╛рдХрд╛, рдордЧ рд╣реЗ рдХрд╕реЗ рдШрдбрд▓реЗ рддреЗ рджреБрд░реБрд╕реНрдд рдХрд░рд╛.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    l["ЁЯТ╗ laptop<br/>MFA ┬╖ hardware keys"] --> g["ЁЯУЦ git<br/>branch protection ┬╖ secret scanning"]
    g --> d["ЁЯел dependencies<br/>lockfiles ┬╖ audit"]
    d --> b["ЁЯПЧя╕П CI build<br/>OIDC ┬╖ isolated runners"]
    b --> i["ЁЯУж image<br/>minimal base ┬╖ SBOM"]
    i --> r["ЁЯЧДя╕П registry<br/>signed digests"]
    r --> k["тШ╕я╕П deploy<br/>admission checks signature + provenance"]
    leak["ЁЯЪи secret leaked<br/>1 rotate ┬╖ 2 find uses ┬╖ 3 purge history<br/>4 contain ┬╖ 5 fix cause ┬╖ 6 postmortem"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/security/lesson-diagrams.html#l15

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рддреБрдореНрд╣реА рдХрд╛рдп рдкрд╛рдард╡рддрд╛ рддреЗрдЪ рд╣рд▓реНрд▓реЗрдЦреЛрд░ рдмрджрд▓реВ рд╢рдХрдд рдЕрд╕реЗрд▓, рддрд░ рддреНрдпрд╛рдВрдирд╛ рддреБрдордЪреЗ app рдлреЛрдбрд╛рдпрдЪреА рдЧрд░рдЬрдЪ рдирд╛рд╣реА. рд╡рд┐рд╖рд╛рд░реА dependency, рдЪреЛрд░рд▓реЗрд▓рд╛ CI token рдХрд┐рдВрд╡рд╛ рдмрджрд▓рд▓реЗрд▓реА image production рдЪреНрдпрд╛ рдкреВрд░реНрдг рд╡рд┐рд╢реНрд╡рд╛рд╕рд╛рд╕рд╣ production рдордзреНрдпреЗ рдкреЛрд╣реЛрдЪрддреЗ. рдзрдбреЗ 07тАУ14 рдиреЗ рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд▓рд╛ рдПрдХ tool рджрд┐рд▓реЗ; threat model рдЦрд╛рддреНрд░реА рдХрд░рддреЛ рдХреА рдХреЛрдгрддрд╛рд╣реА рдЯрдкреНрдкрд╛ рд╡рд┐рд╕рд░рд▓рд╛ рдЬрд╛рдд рдирд╛рд╣реА рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ tool рдЪрд╛рд▓реВ рдЖрд╣реЗ. рдЖрдгрд┐ leak рд╣реА рд╢рд░реНрдпрдд рдЕрд╕рддреЗ: рдЬрд┐рд╡рдВрдд key рдЬрд┐рддрдХрд╛ рдкреНрд░рддреНрдпреЗрдХ рдорд┐рдирд┐рдЯ public рдЕрд╕рддреЗ, рддрд┐рддрдХрд╛ рдкреНрд░рддреНрдпреЗрдХ рдорд┐рдирд┐рдЯ рдХреЛрдгреАрддрд░реА рддреА рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ тАФ рдореНрд╣рдгреВрди рдкрд╣рд┐рд▓реА рдкрд╛рдпрд░реА рдиреЗрд╣рдореА рддреА рдирд┐рд░реБрдкрдпреЛрдЧреА рдХрд░рдгреЗ рдЕрд╕рддреЗ, рд▓рдкрд╡рдгреЗ рдирд╛рд╣реА.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

sec/supply.py рдордзреНрдпреЗ, stride_pipeline() рд╕рд╛рдд рдЯрдкреНрдкреЗ (stage, "threats тЖТ guards") рдЕрд╕реЗ рдкрд░рдд рджреЗрддреЗ, рдЖрдгрд┐ leak_response() рд╕рд╣рд╛ рдкрд╛рдпрд▒реНрдпрд╛ рдХреНрд░рдорд╛рдиреЗ рдкрд░рдд рджреЗрддреЗ. sec/demo.py рдордзрд▓реЗ pipeline() рджреЛрдиреНрд╣реА print рдХрд░рддреЗ.

Snippet рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд╕рд╛рдареА STRIDE рдЕрдХреНрд╖рд░реЗ рдЬреЛрдбрддреЛ (рдпрд╛рджреАрддрд▓реНрдпрд╛ рдзреЛрдХреНрдпрд╛рдВрдЪреЗ рдЖрдкрд▓реЗ рд╕реНрд╡рддрдГрдЪреЗ рд╡рд╛рдЪрди), рдЖрдгрд┐ рдзрдбрд╛ 07 рдЪреЗ find_secrets() рдПрдХрд╛ commit рд╡рд░ pre-commit рд░рдХреНрд╖рдХ рдореНрд╣рдгреВрди рдЪрд╛рд▓рд╡рддреЛ рдЬреНрдпрд╛рдд рдПрдХ key рдЖрдгрд┐ database URL рдордзреНрдпреЗ рдПрдХ password рдЖрд╣реЗ тАФ рджреЛрдиреНрд╣реА рдмрдирд╛рд╡рдЯ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 sec/demo.py pipeline
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from supply import stride_pipeline, leak_response; from cloud import find_secrets
STRIDE = {"developer laptop": "S E", "git": "T R I", "dependencies": "T", "build (CI)": "S T I E",
          "container image": "T I", "registry": "T", "deployment": "T E"}
for stage, line in stride_pipeline():
    print(f"{stage:<17} {STRIDE[stage]:<8} guard: {line.split(' тЖТ ')[1]}")
commit = {"deploy.sh": "export AWS_KEY=AKIAEXAMPLE0123456789", "README.md": "run make deploy",
          "ci.yml": "db: postgres://app:Winter2026pw@db.school.example/grades"}
found = find_secrets(commit)
print(f"pre-commit scan of {len(commit)} files тЖТ {found}")
print("тЖТ commit blocked" if found else "тЖТ commit allowed")
print("if a secret gets past the scan, step 1 is:", leak_response()[0])
EOF
python3 sec/test_sec.py

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

pipeline рд╣реЗ print рдХрд░рддреЗ:

тХРтХРтХР pipeline тХРтХРтХР
тФАтФА STRIDE along the delivery line, one guard per stage:
   developer laptop   stolen token, malware тЖТ hardware keys, MFA, short-lived credentials
   git                force-push, secret committed тЖТ branch protection, reviews, secret scanning
   dependencies       malicious or vulnerable package тЖТ lockfiles, audit, allow-list, pinned versions
   build (CI)         poisoned runner, stolen CI secret тЖТ OIDC to the cloud, isolated runners, least privilege
   container image    vulnerable base, unknown content тЖТ minimal base, scan, SBOM
   registry           image swapped тЖТ sign images, verify digests on deploy
   deployment         unsigned or unreviewed artifact тЖТ admission policy that checks signature and provenance
тФАтФА a secret leaked in a public commit тАФ in this order:
   1. revoke / rotate the secret first (minutes, not days)
   2. find every place it was used (logs, CloudTrail)
   3. remove it from the code AND the git history; assume forks and caches already have it
   4. look for what the attacker did with it; contain
   5. fix the cause: secret scanning in pre-commit and CI, short-lived credentials
   6. write the blameless postmortem

тЬЕ done тАФ every door checked

рддреБрдордЪрд╛ snippet рд╣реЗ print рдХрд░рддреЛ:

developer laptop  S E      guard: hardware keys, MFA, short-lived credentials
git               T R I    guard: branch protection, reviews, secret scanning
dependencies      T        guard: lockfiles, audit, allow-list, pinned versions
build (CI)        S T I E  guard: OIDC to the cloud, isolated runners, least privilege
container image   T I      guard: minimal base, scan, SBOM
registry          T        guard: sign images, verify digests on deploy
deployment        T E      guard: admission policy that checks signature and provenance
pre-commit scan of 3 files тЖТ [('deploy.sh', 'AWS access key id'), ('ci.yml', 'password in a URL')]
тЖТ commit blocked
if a secret gets past the scan, step 1 is: revoke / rotate the secret first (minutes, not days)

Tests рд╢реЗрд╡рдЯреА 16/16 passed рджрд╛рдЦрд╡рддрд╛рдд. рдзрдбрд╛ 15 рдЪреА рд╕реНрд╡рддрдГрдЪреА рддрдкрд╛рд╕рдгреА рдирд╛рд╣реА: рддреНрдпрд╛рдЪреЗ рд░рдХреНрд╖рдХ рдореНрд╣рдгрдЬреЗ рдзрдбреЗ 07 (find_secrets), 13 рдЖрдгрд┐ 14 рдЪреНрдпрд╛ рддрдкрд╛рд╕рдгреНрдпрд╛.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

T (tampering) рдкреНрд░рддреНрдпреЗрдХ рдЯрдкреНрдкреНрдпрд╛рд╡рд░ рджрд┐рд╕рддреЛ тАФ delivery line рд╣реА рд╕рдЧрд│реНрдпрд╛рдд рдЖрдзреА рдЕрд╢реА рдЬрд╛рдЧрд╛ рдЖрд╣реЗ рдЬрд┐рдереЗ рд╡рд╛рдЯреЗрдд рдЧреЛрд╖реНрдЯреА рдмрджрд▓рд▓реНрдпрд╛ рдЬрд╛рдК рд╢рдХрддрд╛рдд, рдореНрд╣рдгреВрдирдЪ signatures рдЖрдгрд┐ digests (рдзрдбрд╛ 14) рдЗрддрдХреЗ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗрдд. CI рдХрдбреЗ рд╕рд░реНрд╡рд╛рдзрд┐рдХ рдЕрдХреНрд╖рд░реЗ рдЖрд╣реЗрдд: рддреНрдпрд╛рдЪреНрдпрд╛рдХрдбреЗ credentials рдЕрд╕рддрд╛рдд, рддреЛ рдЕрдиреЗрдХ рдард┐рдХрд╛рдгрдЪрд╛ code рдЪрд╛рд▓рд╡рддреЛ рдЖрдгрд┐ production рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪреВ рд╢рдХрддреЛ. рдЖрдгрд┐ scanner рдиреЗ рджреЛрди secrets git рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪрдгреНрдпрд╛рдЖрдзреАрдЪ рдерд╛рдВрдмрд╡рд▓реА тАФ рд╕рдЧрд│реНрдпрд╛рдд рд╕реНрд╡рд╕реНрдд leak рдкреНрд░рддрд┐рд╕рд╛рдж. Scan рдЪреБрдХрд▓рд╛, рддрд░ рдпрд╛рджреА commit рд╣рдЯрд╡рдгреНрдпрд╛рдиреЗ рдирд╛рд╣реА, рддрд░ rotation рдиреЗ рд╕реБрд░реВ рд╣реЛрддреЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ GitHub OIDC рддреЗ AWS: workflow рд▓рд╛ рдЕрд▓реНрдкрд╛рдпреБрд╖реА credentials рдорд┐рд│рддрд╛рдд, рдЖрдгрд┐ рдХреЛрдгрддреАрд╣реА AWS key рдХреБрдареЗрд╣реА рд╕рд╛рдард╡рд▓реЗрд▓реА рдирд╕рддреЗ. IAM role рдлрдХреНрдд рдпрд╛рдЪ repo рдЪреНрдпрд╛ main branch рд╡рд░ рд╡рд┐рд╢реНрд╡рд╛рд╕ рдареЗрд╡рддреЛ:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:BaluRaut/school-app:ref:refs/heads/main"
      }
    }
  }]
}
name: deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4          # pin third-party actions to a full commit SHA
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/school-deploy
          aws-region: ap-south-1
      - run: aws sts get-caller-identity

main рд╡рд░ Branch protection тАФ reviews, рдПрдХ рдЖрд╡рд╢реНрдпрдХ check, force-push рдирд╛рд╣реА, deletion рдирд╛рд╣реА, admins рд╕реБрджреНрдзрд╛ рдпрд╛рдд рдпреЗрддрд╛рдд:

gh api -X PUT repos/BaluRaut/school-app/branches/main/protection --input - <<'EOF'
{
  "required_status_checks": { "strict": true, "contexts": ["test", "dependency-review"] },
  "enforce_admins": true,
  "required_pull_request_reviews": { "required_approving_review_count": 1, "dismiss_stale_reviews": true },
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}
EOF

Push protection рд╕рд╣ secret scanning (рдУрд│рдЦреАрдЪреНрдпрд╛ key format рдЕрд╕рд▓реЗрд▓рд╛ push GitHub рдирд╛рдХрд╛рд░рддреЗ), рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ laptop рд╡рд░ рдПрдХ pre-commit hook:

gh api -X PATCH repos/BaluRaut/school-app \
    -f 'security_and_analysis[secret_scanning][status]=enabled' \
    -f 'security_and_analysis[secret_scanning_push_protection][status]=enabled'
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

AWS key leak рдЭрд╛рд▓реНрдпрд╛рдирдВрддрд░рдЪрд╛ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕ тАФ рдпрд╛ рдХреНрд░рдорд╛рдиреЗ:

# 1. make it useless тАФ now
aws iam update-access-key --user-name ci-deploy --access-key-id AKIAEXAMPLE0123456789 --status Inactive
# 2. what did it do? (CloudTrail event history covers the last 90 days)
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE0123456789 \
    --max-results 50 --query 'Events[].{time:EventTime,name:EventName,source:EventSource}'
# 3. after the investigation тАФ delete it, and move the job to OIDC so there is no key to replace
aws iam delete-access-key --user-name ci-deploy --access-key-id AKIAEXAMPLE0123456789
# 4. clean the history (then force-push, and ask GitHub support to clear cached views)
git filter-repo --replace-text <(echo 'AKIAEXAMPLE0123456789==>REMOVED')

ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: test account рдордзреНрдпреЗ рдПрдХрд╛ рдмрдирд╛рд╡рдЯ key рдиреЗ рджрд░ рддрд┐рдорд╛рд╣реАрдд рдПрдХрджрд╛ leak runbook рдЪрд╛ рд╕рд░рд╛рд╡ рдХрд░рд╛: рдкрд╛рдпрд░реА 1 рд▓рд╛ рдХрд┐рддреА рд╡реЗрд│ рд▓рд╛рдЧрддреЛ рддреЗ рдореЛрдЬрд╛. рд░рд╛рддреНрд░реА 11 рд╡рд╛рдЬрддрд╛ database password рдХреЛрдг rotate рдХрд░реВ рд╢рдХрддреЛ рд╣реЗ рдХреЛрдгрд╛рд▓рд╛рдЪ рдорд╛рд╣реАрдд рдирд╕реЗрд▓, рддрд░ рд╣рд▓реНрд▓реЗрдЦреЛрд░рд╛рдЖрдзреА рддреБрдореНрд╣рд╛рд▓рд╛ рдЦрд░реА рдлрдЯ рд╕рд╛рдкрдбрд▓реА рдЖрд╣реЗ.

тПня╕П рдкреБрдвреЗ

рдПрдХ рдЦрд┐рдбрдХреА рдЙрд░рд▓реА рдЖрд╣реЗ, рдЖрдгрд┐ рддреА рд╕рдЧрд│реНрдпрд╛рдд рдирд╡реА рдЖрд╣реЗ: рд╢рд╛рд│реЗрдЪрд╛ AI assistant, рдЬреЛ рд╕реВрдЪрдирд╛, web pages рдЖрдгрд┐ upload рдХреЗрд▓реЗрд▓реНрдпрд╛ files рд╡рд╛рдЪрддреЛ тАФ рдЖрдгрд┐ tools рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ. рдкреБрдвреЗ: рдорд┐рд│рд╡рд▓реЗрд▓рд╛ text рдореНрд╣рдгрдЬреЗ data, рдЖрджреЗрд╢ рдирд╛рд╣реАрдд.

git checkout lesson-16-ai-security

ЁЯзн Lesson 15 тАФ Threat modelling the delivery line: a guard at every stage

ЁЯУН You are here: Lesson 15 of 16 ┬╖ Previous: lesson-14-artifacts ┬╖ Next: lesson-16-ai-security


ЁЯУж What's in this branch

Lessons 01тАУ14, plus the habit that joins them: threat modelling. You walk the delivery line тАФ laptop тЖТ git тЖТ dependencies тЖТ CI build тЖТ container image тЖТ registry тЖТ deployment тАФ and at each stage ask the six STRIDE questions. Each stage gets a named guard. Then the incident everyone meets one day: a secret leaks in a public commit, and the order of the first steps (rotate first).

ЁЯзТ Explain like I'm 5

Dipika follows one lunch box from the moment the cook starts until it reaches the classroom, and at every step she asks: "What could go wrong here, and who stops it?"

Then one day Aishwarya sees the kitchen key photographed in the school newspaper. What first? Not "take the newspaper back". Change the lock, right now. Then look at who came in, then take the photo down, then fix how it happened.

ЁЯЧ║я╕П Diagram

flowchart LR
    l["ЁЯТ╗ laptop<br/>MFA ┬╖ hardware keys"] --> g["ЁЯУЦ git<br/>branch protection ┬╖ secret scanning"]
    g --> d["ЁЯел dependencies<br/>lockfiles ┬╖ audit"]
    d --> b["ЁЯПЧя╕П CI build<br/>OIDC ┬╖ isolated runners"]
    b --> i["ЁЯУж image<br/>minimal base ┬╖ SBOM"]
    i --> r["ЁЯЧДя╕П registry<br/>signed digests"]
    r --> k["тШ╕я╕П deploy<br/>admission checks signature + provenance"]
    leak["ЁЯЪи secret leaked<br/>1 rotate ┬╖ 2 find uses ┬╖ 3 purge history<br/>4 contain ┬╖ 5 fix cause ┬╖ 6 postmortem"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/security/lesson-diagrams.html#l15

тЭУ What

ЁЯдФ Why

Because attackers do not need to break your app if they can change what you ship. A poisoned dependency, a stolen CI token or a swapped image lands in production with all of production's trust. Lessons 07тАУ14 gave each stage a tool; the threat model makes sure no stage is forgotten and that each tool is on. And a leak is a race: every minute a live key is public is a minute someone can use it тАФ so the first step is always to make it useless, not to hide it.

ЁЯФз How (in this repo)

In sec/supply.py, stride_pipeline() returns the seven stages as (stage, "threats тЖТ guards"), and leak_response() returns the six steps in order. pipeline() in sec/demo.py prints both.

The snippet adds the STRIDE letters for each stage (our own reading of the threats in the list), and runs lesson 07's find_secrets() as a pre-commit guard on a commit with a key and a password in a database URL тАФ both made up.

ЁЯзк Try it

python3 sec/demo.py pipeline
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from supply import stride_pipeline, leak_response; from cloud import find_secrets
STRIDE = {"developer laptop": "S E", "git": "T R I", "dependencies": "T", "build (CI)": "S T I E",
          "container image": "T I", "registry": "T", "deployment": "T E"}
for stage, line in stride_pipeline():
    print(f"{stage:<17} {STRIDE[stage]:<8} guard: {line.split(' тЖТ ')[1]}")
commit = {"deploy.sh": "export AWS_KEY=AKIAEXAMPLE0123456789", "README.md": "run make deploy",
          "ci.yml": "db: postgres://app:Winter2026pw@db.school.example/grades"}
found = find_secrets(commit)
print(f"pre-commit scan of {len(commit)} files тЖТ {found}")
print("тЖТ commit blocked" if found else "тЖТ commit allowed")
print("if a secret gets past the scan, step 1 is:", leak_response()[0])
EOF
python3 sec/test_sec.py

тЬЕ Verify тАФ what you should see

pipeline prints:

тХРтХРтХР pipeline тХРтХРтХР
тФАтФА STRIDE along the delivery line, one guard per stage:
   developer laptop   stolen token, malware тЖТ hardware keys, MFA, short-lived credentials
   git                force-push, secret committed тЖТ branch protection, reviews, secret scanning
   dependencies       malicious or vulnerable package тЖТ lockfiles, audit, allow-list, pinned versions
   build (CI)         poisoned runner, stolen CI secret тЖТ OIDC to the cloud, isolated runners, least privilege
   container image    vulnerable base, unknown content тЖТ minimal base, scan, SBOM
   registry           image swapped тЖТ sign images, verify digests on deploy
   deployment         unsigned or unreviewed artifact тЖТ admission policy that checks signature and provenance
тФАтФА a secret leaked in a public commit тАФ in this order:
   1. revoke / rotate the secret first (minutes, not days)
   2. find every place it was used (logs, CloudTrail)
   3. remove it from the code AND the git history; assume forks and caches already have it
   4. look for what the attacker did with it; contain
   5. fix the cause: secret scanning in pre-commit and CI, short-lived credentials
   6. write the blameless postmortem

тЬЕ done тАФ every door checked

Your snippet prints:

developer laptop  S E      guard: hardware keys, MFA, short-lived credentials
git               T R I    guard: branch protection, reviews, secret scanning
dependencies      T        guard: lockfiles, audit, allow-list, pinned versions
build (CI)        S T I E  guard: OIDC to the cloud, isolated runners, least privilege
container image   T I      guard: minimal base, scan, SBOM
registry          T        guard: sign images, verify digests on deploy
deployment        T E      guard: admission policy that checks signature and provenance
pre-commit scan of 3 files тЖТ [('deploy.sh', 'AWS access key id'), ('ci.yml', 'password in a URL')]
тЖТ commit blocked
if a secret gets past the scan, step 1 is: revoke / rotate the secret first (minutes, not days)

The tests end with 16/16 passed. Lesson 15 has no check of its own: its guards are the checks of lessons 07 (find_secrets), 13 and 14.

ЁЯПБ What you just proved

T (tampering) appears at every stage тАФ the delivery line is, above all, a place where things can be changed on the way, which is why signatures and digests (lesson 14) matter so much. CI carries the most letters: it holds credentials, runs code from many places and can reach production. And the scanner stopped two secrets before they ever reached git тАФ the cheapest possible leak response. When a scan misses, the list starts with rotation, not with deleting the commit.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ GitHub OIDC to AWS: the workflow gets short-lived credentials, and no AWS key is stored anywhere. The IAM role trusts only this repo's main branch:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
        "token.actions.githubusercontent.com:sub": "repo:BaluRaut/school-app:ref:refs/heads/main"
      }
    }
  }]
}
name: deploy
on:
  push:
    branches: [main]
permissions:
  contents: read
  id-token: write
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4          # pin third-party actions to a full commit SHA
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::111122223333:role/school-deploy
          aws-region: ap-south-1
      - run: aws sts get-caller-identity

Branch protection on main тАФ reviews, a required check, no force-push, no deletion, admins included:

gh api -X PUT repos/BaluRaut/school-app/branches/main/protection --input - <<'EOF'
{
  "required_status_checks": { "strict": true, "contexts": ["test", "dependency-review"] },
  "enforce_admins": true,
  "required_pull_request_reviews": { "required_approving_review_count": 1, "dismiss_stale_reviews": true },
  "restrictions": null,
  "allow_force_pushes": false,
  "allow_deletions": false
}
EOF

Secret scanning with push protection (GitHub refuses a push that contains a known key format), and a pre-commit hook on every laptop:

gh api -X PATCH repos/BaluRaut/school-app \
    -f 'security_and_analysis[secret_scanning][status]=enabled' \
    -f 'security_and_analysis[secret_scanning_push_protection][status]=enabled'
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

The first hour after an AWS key leaks тАФ in this order:

# 1. make it useless тАФ now
aws iam update-access-key --user-name ci-deploy --access-key-id AKIAEXAMPLE0123456789 --status Inactive
# 2. what did it do? (CloudTrail event history covers the last 90 days)
aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE0123456789 \
    --max-results 50 --query 'Events[].{time:EventTime,name:EventName,source:EventSource}'
# 3. after the investigation тАФ delete it, and move the job to OIDC so there is no key to replace
aws iam delete-access-key --user-name ci-deploy --access-key-id AKIAEXAMPLE0123456789
# 4. clean the history (then force-push, and ask GitHub support to clear cached views)
git filter-repo --replace-text <(echo 'AKIAEXAMPLE0123456789==>REMOVED')

ЁЯПн Why this matters in production: practise the leak runbook once a quarter with a fake key in a test account: time how long step 1 takes. If nobody knows who can rotate the database password at 11 p.m., you have found the real gap before an attacker did.

тПня╕П Next

One desk is left, and it is the newest: the school's AI assistant, which reads notices, web pages and uploaded files тАФ and can use tools. Next: retrieved text is data, not orders.

git checkout lesson-16-ai-security
тЖР PreviousartifactsNext тЖТai security

This page is the lesson's README from the lesson-15-pipeline-threats branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.