ЁЯПл The SchoolтА║ЁЯЫбя╕П SecurityтА║ЁЯУж рдзрдбрд╛ 13 тАФ Dependencies: рддреБрдордЪреНрдпрд╛ app рдордзрд▓рд╛ рдЗрддрд░рд╛рдВрдЪрд╛ code
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУж рдзрдбрд╛ 13 тАФ Dependencies: рддреБрдордЪреНрдпрд╛ app рдордзрд▓рд╛ рдЗрддрд░рд╛рдВрдЪрд╛ code

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


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

рдзрдбреЗ 01тАУ12, рдЖрдгрд┐ supply chain рдЪреА рдкрд╣рд┐рд▓реА рдЦрд┐рдбрдХреА: dependencies. рдЖрдзреБрдирд┐рдХ app рдордзрд▓рд╛ рдмрд╣реБрддреЗрдХ code рджреБрд╕рд▒реНрдпрд╛ рдХреЛрдгреАрддрд░реА рд▓рд┐рд╣рд┐рд▓реЗрд▓рд╛ рдЕрд╕рддреЛ тАФ libraries, рдЖрдгрд┐ рддреНрдпрд╛рдВрдЪреНрдпрд╛ libraries. рддреБрдореНрд╣реА software composition analysis (SCA: versions рдирд╛ advisories рд╢реА рдЬреБрд│рд╡рдгреЗ), hashes рд╕рд╣ lockfiles, рдЖрдкреЛрдЖрдк рдпреЗрдгрд╛рд░реЗ update PRs, рдЖрдгрд┐ supply chain рдордзрд▓реНрдпрд╛ рджреЛрди рдпреБрдХреНрддреНрдпрд╛ тАФ typosquatting рдЖрдгрд┐ dependency confusion тАФ рдЖрдгрд┐ рддреНрдпрд╛ рдХрд╢рд╛ рдерд╛рдВрдмрд╡рд╛рдпрдЪреНрдпрд╛ рд╣реЗ рд╢рд┐рдХрддрд╛.

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

рд╢рд╛рд│реЗрдЪреНрдпрд╛ рдЬрддреНрд░реЗрд╕рд╛рдареА рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░ рд╕рдЧрд│реЗ рдХрд╛рд╣реА рд╕реНрд╡рддрдГ рдмрдирд╡рдд рдирд╛рд╣реА. рддреЗ рдмреНрд░реЗрдб, рдЬреЕрдо рдЖрдгрд┐ рд░рд╕ рдмрд╛рд╣реЗрд░реВрди рд╡рд┐рдХрдд рдШреЗрддреЗ. рд╡рд┐рддрд░рдг рдЦрд┐рдбрдХреАрд╕рд╛рдареА рджреАрдкрд┐рдХрд╛рдЪреЗ рдирд┐рдпрдо:

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

flowchart LR
    req["ЁЯУД requirements / package.json"] --> lock["ЁЯз╛ lockfile<br/>exact versions + sha256 hashes"]
    lock --> audit{"ЁЯУЛ audit vs advisories<br/>pip-audit ┬╖ npm audit ┬╖ Dependabot"}
    audit -->|"requests 2.28.1 тЖТ тЙе 2.31.0<br/>pyyaml 5.3.1 тЖТ тЙе 5.4"| fix["ЁЯФБ update PR + tests"]
    name{"ЁЯСА new package name"} -->|"reqeusts тЙИ requests"| stop["тЫФ typosquat: review"]
    idx["ЁЯПл one index / proxy<br/>internal names from internal only"] --> lock

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

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

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рддреБрдореНрд╣реА рддреБрдордЪреНрдпрд╛ dependencies рдЪреА рдкреНрд░рддреНрдпреЗрдХ рдУрд│ рддреБрдордЪреНрдпрд╛ app рдЪреНрдпрд╛ permissions рдЖрдгрд┐ рддреБрдордЪреНрдпрд╛ users рдЪреНрдпрд╛ data рд╕рд╣ production рдордзреНрдпреЗ рдкрд╛рдард╡рддрд╛. Library рдордзрд▓реА рдЬреНрдЮрд╛рдд, рдЖрдзреАрдЪ рджреБрд░реБрд╕реНрдд рдЭрд╛рд▓реЗрд▓реА vulnerability рд╣рд╛ рдЖрдд рд╢рд┐рд░рдгреНрдпрд╛рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рдиреЗрд╣рдореАрдЪрд╛ рдорд╛рд░реНрдЧ рдЖрд╣реЗ тАФ fix рдЖрдзреАрдЪ рдЙрдкрд▓рдмреНрдз рдЖрд╣реЗ, рдЖрдгрд┐ attackers рдЬреБрдиреЗ version рд╢реЛрдзрдд scan рдХрд░рддрд╛рдд. рдЖрдгрд┐ malicious packages рджреБрд░реНрдореАрд│ рдирд╛рд╣реАрдд: рд╕рд╛рд░рдЦреА рджрд┐рд╕рдгрд╛рд░реА рдирд╛рд╡реЗ рдЖрдгрд┐ рддрд╛рдмреНрдпрд╛рдд рдШреЗрддрд▓реЗрд▓реА maintainer accounts public registries рдордзреНрдпреЗ рджрд░ рдЖрдард╡рдбреНрдпрд╛рд▓рд╛ рджрд┐рд╕рддрд╛рдд. рдмрдЪрд╛рд╡ рдореНрд╣рдгрдЬреЗ "libraries рдХрдзреАрдЪ рд╡рд╛рдкрд░реВ рдирдХрд╛" рдЕрд╕рд╛ рдирд╛рд╣реА. рддреЛ рдореНрд╣рдгрдЬреЗ рддреБрдореНрд╣реА рдиреЗрдордХреЗ рдХрд╛рдп рд╡рд╛рдкрд░рддрд╛ рддреЗ рдорд╛рд╣реАрдд рдЕрд╕рдгреЗ, рддреЗ рдЕрджреНрдпрдпрд╛рд╡рдд рдареЗрд╡рдгреЗ, рдЖрдгрд┐ рдирд╡реЗ рдирд╛рд╡ рди рдкрд╛рд╣рддрд╛ рдЖрдд рдпреЗрдК рди рджреЗрдгреЗ.

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

sec/supply.py рдордзреНрдпреЗ:

sec/demo.py рдордзрд▓реЗ dependencies() рдЪрд╛рд░ installed packages рдЪрд╛ audit рдХрд░рддреЗ рдЖрдгрд┐ рдЪрд╛рд░ рдирд╛рд╡реЗ рддрдкрд╛рд╕рддреЗ.

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

python3 sec/demo.py dependencies
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from supply import vulnerable, typosquat
for installed in ({"requests": "2.30.0"}, {"requests": "2.31.0"}, {"jinja2": "3.1.2", "pyyaml": "6.0.1"}, {"flask": "3.0.0"}):
    print(f"{str(installed):<38} тЖТ {vulnerable(installed) or 'no known advisory'}")
for name in ("request", "reqests", "pandsa", "djnago", "numpy", "boto3", "leftpad"):
    print(f"typosquat check {name:<8} тЖТ {typosquat(name) or 'no close popular name'}")
EOF
python3 sec/test_sec.py

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

dependencies рд╣реЗ рдЫрд╛рдкрддреЗ:

тХРтХРтХР dependencies тХРтХРтХР
тФАтФА audit of {'requests': '2.28.1', 'jinja2': '3.1.4', 'pyyaml': '5.3.1', 'flask': '3.0.0'}
   requests 2.28.1: leaks Proxy-Authorization on redirect тЖТ upgrade to тЙе 2.31.0
   pyyaml 5.3.1: arbitrary code via full_load тЖТ upgrade to тЙе 5.4
   typosquat check reqeusts  тЖТ ['requests']
   typosquat check numpyy    тЖТ ['numpy']
   typosquat check flask     тЖТ no close popular name
   typosquat check boto4     тЖТ ['boto3']
   lockfiles with hashes ┬╖ automated updates ┬╖ an allow-list for new packages ┬╖ audit in CI

тЬЕ done тАФ every door checked

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

{'requests': '2.30.0'}                 тЖТ [('requests', '2.30.0', 'upgrade to тЙе 2.31.0', 'leaks Proxy-Authorization on redirect')]
{'requests': '2.31.0'}                 тЖТ no known advisory
{'jinja2': '3.1.2', 'pyyaml': '6.0.1'} тЖТ [('jinja2', '3.1.2', 'upgrade to тЙе 3.1.3', 'XSS via xmlattr filter')]
{'flask': '3.0.0'}                     тЖТ no known advisory
typosquat check request  тЖТ ['requests']
typosquat check reqests  тЖТ ['requests']
typosquat check pandsa   тЖТ ['pandas']
typosquat check djnago   тЖТ ['django']
typosquat check numpy    тЖТ no close popular name
typosquat check boto3    тЖТ no close popular name
typosquat check leftpad  тЖТ no close popular name

Tests рдордзреНрдпреЗ тЬЕ L13 advisories and typosquats are caught рдЖрд╣реЗ рдЖрдгрд┐ рд╢реЗрд╡рдЯ 16/16 passed рдиреЗ рд╣реЛрддреЛ.

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

рджреБрд░реБрд╕реНрдд version (2.31.0) рдкрд╛рд╕ рд╣реЛрддреЗ рдЖрдгрд┐ рддреНрдпрд╛рдЪреНрдпрд╛ рдЕрдЧрджреА рдЖрдзреАрдЪреЗ (2.30.0) рд╣реЛрдд рдирд╛рд╣реА тАФ рд░реЗрд╖рд╛ рдиреЗрдордХреА рдЖрд╣реЗ. Demo рдордзреНрдпреЗ jinja2 3.1.4 рдкрд╛рд╕ рдЭрд╛рд▓реЗ, рдкрдг 3.1.2 fail рд╣реЛрддреЗ: package рдЪреЗ рдирд╛рд╡ рдирд╛рд╣реА, рддрд░ рддреБрдордЪреЗ version рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рдЖрд╣реЗ. рдПрдХ рдЕрдХреНрд╖рд░ рдХрдореА, рдЬреЛрдбреА рдЕрджрд▓рд╛рдмрджрд▓ рдХрд┐рдВрд╡рд╛ рдПрдХ рдЕрдХреНрд╖рд░ рдЬрд╛рд╕реНрдд тАФ рд╣реЗ рд╕рдЧрд│реЗ "рд▓реЛрдХрдкреНрд░рд┐рдп рдирд╛рд╡рд╛рдЪреНрдпрд╛ рдЬрд╡рд│" рдореНрд╣рдгреВрди рджрд┐рд╕рддрд╛рдд. рдЖрдгрд┐ leftpad рдкрд╛рд╕ рд╣реЛрддреЗ тАФ рддреЗ рд╕реБрд░рдХреНрд╖рд┐рдд рдЖрд╣реЗ рдореНрд╣рдгреВрди рдирд╛рд╣реА, рддрд░ рддреЗ рдпрд╛ рдпрд╛рджреАрддрд▓реНрдпрд╛ рдирд╛рд╡рд╛рдВрдЪреНрдпрд╛ рдЬрд╡рд│ рдирд╛рд╣реА рдореНрд╣рдгреВрди. рдирд╛рд╡рд╛рдВрдЪреА рддрдкрд╛рд╕рдгреА рд╕рд╛рд░рдЦреА рджрд┐рд╕рдгрд╛рд░реА рдирд╛рд╡реЗ рдкрдХрдбрддреЗ; package рдЪрд╛рдВрдЧрд▓реЗ рдЖрд╣реЗ рд╣реЗ рддреА рд╕рд┐рджреНрдз рдХрд░рдд рдирд╛рд╣реА. рддреНрдпрд╛рд╕рд╛рдареА рдЕрдЬреВрдирд╣реА рдПрдХ рд╡реНрдпрдХреНрддреА, рдПрдХ allow-list рдЖрдгрд┐ рдПрдХ lockfile рд▓рд╛рдЧрддреЗ.

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

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

рдЦрд▒реНрдпрд╛ account рд╡рд░ тАФ pinned versions рдЖрдгрд┐ hashes рд╕рд╣ Python (pip-tools), рдЕрд╢рд╛ рдкреНрд░рдХрд╛рд░реЗ install рдХреЗрд▓реЗрд▓реЗ рдХреА рдЗрддрд░ рдХреЛрдгрддреАрд╣реА file рдирд╛рдХрд╛рд░рд▓реА рдЬрд╛рддреЗ, рдордЧ audit:

pip install pip-tools pip-audit
pip-compile --generate-hashes --output-file requirements.txt requirements.in
pip install --require-hashes -r requirements.txt
pip-audit -r requirements.txt --strict

рддрдпрд╛рд░ рдЭрд╛рд▓реЗрд▓реНрдпрд╛ requirements.txt рдЪреА рдПрдХ рдУрд│ рдЕрд╢реА рджрд┐рд╕рддреЗ (hash рд╣рд╛ рдЦрд▒реНрдпрд╛ file рдЪрд╛ SHA-256 рдЖрд╣реЗ):

requests==2.32.4 \
    --hash=sha256:<sha256 of the requests-2.32.4 wheel>

Node.js тАФ npm ci рдиреЗрдордХреА lockfile install рдХрд░рддреЗ рдЖрдгрд┐ package.json рд╡ lock рдЬреБрд│рдд рдирд╕рддреАрд▓ рддрд░ fail рд╣реЛрддреЗ; high-severity issues рдЕрд╕рддреАрд▓ рддрд░ audit build fail рдХрд░рддреЛ:

npm ci --ignore-scripts        # add scripts back only for packages that need them
npm audit --audit-level=high

Dependabot тАФ Python, npm, Docker base images рдЖрдгрд┐ GitHub Actions рд╕рд╛рдареА рджрд░ рдЖрдард╡рдбреНрдпрд╛рдЪреЗ update PRs (.github/dependabot.yml):

version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule: { interval: "weekly" }
    groups:
      minor-and-patch: { update-types: ["minor", "patch"] }
  - package-ecosystem: "npm"
    directory: "/web"
    schedule: { interval: "weekly" }
  - package-ecosystem: "docker"
    directory: "/"
    schedule: { interval: "weekly" }
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule: { interval: "weekly" }

рдкреНрд░рддреНрдпреЗрдХ pull request рд╡рд░ Dependency review тАФ vulnerable package рдЬреЛрдбрдгрд╛рд░рд╛ PR block рдХрд░рд╛:

name: dependency-review
on: pull_request
permissions: { contents: read }
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with: { fail-on-severity: high }

Dependency confusion рдерд╛рдВрдмрд╡рд╛ тАФ рд╕рдЧрд│реНрдпрд╛рд╕рд╛рдареА рдПрдХрдЪ index (рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде public index рдХрдбреЗ upstream рдЕрд╕рд▓реЗрд▓реЗ AWS CodeArtifact), рдЖрдгрд┐ npm рдордзреНрдпреЗ рддреБрдордЪреНрдпрд╛ рд╕реНрд╡рддрдГрдЪреНрдпрд╛ registry рд╢реА рдЬреЛрдбрд▓реЗрд▓рд╛ scope (.npmrc):

@school:registry=https://school-111122223333.d.codeartifact.ap-south-1.amazonaws.com/npm/internal/
aws codeartifact login --tool pip --domain school --domain-owner 111122223333 --repository internal

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: main branch рд╡рд░ audit рд╣рд╛ required check рдХрд░рд╛, рдЖрдгрд┐ рдЕрдкрд╡рд╛рджрд╛рдВрд╕рд╛рдареА рд▓рд┐рдЦрд┐рдд рдирд┐рдпрдо рдареЗрд╡рд╛ (finding рдХреЛрдг рд╕реНрд╡реАрдХрд╛рд░реВ рд╢рдХрддреЛ, рдЖрдгрд┐ рдХрдзреАрдкрд░реНрдпрдВрдд). рдХреЛрдгреАрд╣реА рд╡рдЧрд│реВ рд╢рдХреЗрд▓ рдЕрд╕рд╛ audit рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд рдПрдХ рд╕реВрдЪрдирд╛.

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

рд╕рд╛рд╣рд┐рддреНрдп рддрдкрд╛рд╕рд▓реЗ рдЧреЗрд▓реЗ. рдкрдг рддрдпрд╛рд░ рдбрдмрд╛ рд╡рд░реНрдЧрд╛рдд рдкреЛрд╣реЛрдЪрд▓реНрдпрд╛рд╡рд░, рддреЛ рдЖрдкрд▓реНрдпрд╛рдЪ рд╕реНрд╡рдпрдВрдкрд╛рдХрдШрд░рд╛рдиреЗ рдмрдирд╡рд▓реЗрд▓рд╛ рдЖрд╣реЗ рдЖрдгрд┐ рддреНрдпрд╛рдд рдХрд╛рдп рдЖрд╣реЗ рд╣реЗ рд╢рд┐рдХреНрд╖рд┐рдХреЗрд▓рд╛ рдХрд╕реЗ рдХрд│рддреЗ? рдкреБрдвреЗ: signatures, SBOMs рдЖрдгрд┐ provenance.

git checkout lesson-14-artifacts

ЁЯУж Lesson 13 тАФ Dependencies: other people's code in your app

ЁЯУН You are here: Lesson 13 of 16 ┬╖ Previous: lesson-12-admission ┬╖ Next: lesson-14-artifacts


ЁЯУж What's in this branch

Lessons 01тАУ12, plus the first desk of the supply chain: dependencies. Most of the code in a modern app was written by someone else тАФ libraries, and their libraries. You learn software composition analysis (SCA: matching versions against advisories), lockfiles with hashes, automated update PRs, and two supply-chain tricks тАФ typosquatting and dependency confusion тАФ and how to stop them.

ЁЯзТ Explain like I'm 5

For the school fair, the kitchen does not make everything itself. It buys bread, jam and juice from outside. Dipika's rules for the delivery desk:

ЁЯЧ║я╕П Diagram

flowchart LR
    req["ЁЯУД requirements / package.json"] --> lock["ЁЯз╛ lockfile<br/>exact versions + sha256 hashes"]
    lock --> audit{"ЁЯУЛ audit vs advisories<br/>pip-audit ┬╖ npm audit ┬╖ Dependabot"}
    audit -->|"requests 2.28.1 тЖТ тЙе 2.31.0<br/>pyyaml 5.3.1 тЖТ тЙе 5.4"| fix["ЁЯФБ update PR + tests"]
    name{"ЁЯСА new package name"} -->|"reqeusts тЙИ requests"| stop["тЫФ typosquat: review"]
    idx["ЁЯПл one index / proxy<br/>internal names from internal only"] --> lock

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

тЭУ What

ЁЯдФ Why

Because you ship every line of your dependencies to production, with your app's permissions and your users' data. A known, fixed vulnerability in a library is one of the most common ways in тАФ the fix already exists, and attackers scan for the old version. And malicious packages are not rare: look-alike names and hijacked maintainer accounts appear in public registries every week. The defence is not "never use libraries". It is knowing exactly what you use, keeping it current, and not letting a new name in without a look.

ЁЯФз How (in this repo)

In sec/supply.py:

dependencies() in sec/demo.py audits four installed packages and checks four names.

ЁЯзк Try it

python3 sec/demo.py dependencies
python3 - <<'EOF'
import sys; sys.path.insert(0, "sec"); from supply import vulnerable, typosquat
for installed in ({"requests": "2.30.0"}, {"requests": "2.31.0"}, {"jinja2": "3.1.2", "pyyaml": "6.0.1"}, {"flask": "3.0.0"}):
    print(f"{str(installed):<38} тЖТ {vulnerable(installed) or 'no known advisory'}")
for name in ("request", "reqests", "pandsa", "djnago", "numpy", "boto3", "leftpad"):
    print(f"typosquat check {name:<8} тЖТ {typosquat(name) or 'no close popular name'}")
EOF
python3 sec/test_sec.py

тЬЕ Verify тАФ what you should see

dependencies prints:

тХРтХРтХР dependencies тХРтХРтХР
тФАтФА audit of {'requests': '2.28.1', 'jinja2': '3.1.4', 'pyyaml': '5.3.1', 'flask': '3.0.0'}
   requests 2.28.1: leaks Proxy-Authorization on redirect тЖТ upgrade to тЙе 2.31.0
   pyyaml 5.3.1: arbitrary code via full_load тЖТ upgrade to тЙе 5.4
   typosquat check reqeusts  тЖТ ['requests']
   typosquat check numpyy    тЖТ ['numpy']
   typosquat check flask     тЖТ no close popular name
   typosquat check boto4     тЖТ ['boto3']
   lockfiles with hashes ┬╖ automated updates ┬╖ an allow-list for new packages ┬╖ audit in CI

тЬЕ done тАФ every door checked

Your snippet prints:

{'requests': '2.30.0'}                 тЖТ [('requests', '2.30.0', 'upgrade to тЙе 2.31.0', 'leaks Proxy-Authorization on redirect')]
{'requests': '2.31.0'}                 тЖТ no known advisory
{'jinja2': '3.1.2', 'pyyaml': '6.0.1'} тЖТ [('jinja2', '3.1.2', 'upgrade to тЙе 3.1.3', 'XSS via xmlattr filter')]
{'flask': '3.0.0'}                     тЖТ no known advisory
typosquat check request  тЖТ ['requests']
typosquat check reqests  тЖТ ['requests']
typosquat check pandsa   тЖТ ['pandas']
typosquat check djnago   тЖТ ['django']
typosquat check numpy    тЖТ no close popular name
typosquat check boto3    тЖТ no close popular name
typosquat check leftpad  тЖТ no close popular name

The tests include тЬЕ L13 advisories and typosquats are caught and end with 16/16 passed.

ЁЯПБ What you just proved

The fixed version (2.31.0) passes and the one just before it (2.30.0) does not тАФ the line is exact. jinja2 3.1.4 passed in the demo, but 3.1.2 fails: your version is what matters, not the package name. A missing letter, a swapped pair or an extra letter all come out as "close to a popular name". And leftpad passes тАФ not because it is safe, but because it is not near this list. Name checks catch look-alikes; they do not prove a package is good. That still needs a person, an allow-list and a lockfile.

тЪая╕П Common mistakes

ЁЯПн In production

On a real account тАФ Python with pinned versions and hashes (pip-tools), installed so that any other file is refused, then audited:

pip install pip-tools pip-audit
pip-compile --generate-hashes --output-file requirements.txt requirements.in
pip install --require-hashes -r requirements.txt
pip-audit -r requirements.txt --strict

A line of the resulting requirements.txt looks like this (the hash is the real file's SHA-256):

requests==2.32.4 \
    --hash=sha256:<sha256 of the requests-2.32.4 wheel>

Node.js тАФ npm ci installs exactly the lockfile and fails if package.json and the lock do not agree; the audit fails the build on high-severity issues:

npm ci --ignore-scripts        # add scripts back only for packages that need them
npm audit --audit-level=high

Dependabot тАФ weekly update PRs for Python, npm, Docker base images and GitHub Actions (.github/dependabot.yml):

version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule: { interval: "weekly" }
    groups:
      minor-and-patch: { update-types: ["minor", "patch"] }
  - package-ecosystem: "npm"
    directory: "/web"
    schedule: { interval: "weekly" }
  - package-ecosystem: "docker"
    directory: "/"
    schedule: { interval: "weekly" }
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule: { interval: "weekly" }

Dependency review on every pull request тАФ block a PR that adds a vulnerable package:

name: dependency-review
on: pull_request
permissions: { contents: read }
jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/dependency-review-action@v4
        with: { fail-on-severity: high }

Stop dependency confusion тАФ one index for everything (for example AWS CodeArtifact with an upstream to the public index), and in npm a scope mapped to your own registry (.npmrc):

@school:registry=https://school-111122223333.d.codeartifact.ap-south-1.amazonaws.com/npm/internal/
aws codeartifact login --tool pip --domain school --domain-owner 111122223333 --repository internal

ЁЯПн Why this matters in production: make the audit a required check on the main branch, with a written rule for exceptions (who may accept a finding, and until when). An audit that anyone can skip is only a suggestion.

тПня╕П Next

The ingredients are checked. But when the finished lunch box reaches the classroom, how does the teacher know it is the one our kitchen made, and what is inside it? Next: signatures, SBOMs and provenance.

git checkout lesson-14-artifacts
тЖР PreviousadmissionNext тЖТartifacts

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