Практический курс по прикладной безопасности приложений: от Git до полноценного DevSecOps-конвейера.
Что изучаем:
- Инфраструктура:
Git,CI/CD,Docker,Docker Compose,GitHub Actions,YAML - Языки:
Python,Shell(JavaиGo— в контексте SCA и анализа зависимостей) - AppSec инструменты:
Semgrep,Checkov,Bandit,OWASP Dependency-Check,Trivy,Docker Bench,OWASP ZAP,Gitleaks,TruffleHog,Hadolint - Стандарты: OWASP Top 10, CIS Docker Benchmark, CVSS, ISO 27005, NIST SP 800-30, PCI DSS, ГОСТ 57580
- Анализ рисков: оценка, приоритизация, стратегии снижения рисков ИБ
Как устроен курс:
- 8 intro-руководств + 10 лабораторных работ + итоговый pet-project + 7 тестов (5 базовых + 2 лекционных)
- Сверх основной линии — углублённый трек по Docker: 2 работы про безопасный запуск контейнера и цепочку поставки образа, с намеренно заложенными ложными срабатываниями
- Каждая лабораторная — отдельный репозиторий с исходным кодом и отчётом в формате
gistup - Все работы выполняются в ветке
develop→pull request→ approve от geminishkv - Прогрессия:
Git→Linux→Nmap→Risk Analysis→Docker→CIS Benchmark→SAST/SCA→DAST→CI/CD→Итоговый Risk Analysis→Pet-project
Замечания:
- Лабораторные обязательны для всех — вне зависимости от уровня подготовки
- Каждая работа разбивается на атомарные коммиты для трекинга изменений
- Отчёт сдаётся индивидуально с защитой: каждая команда — с описанием флагов и выводом из терминала
- В отчётах — вывод из консоли, не скриншоты
- Часть инструментов требует установки дополнительных
open-sourceпакетов
- Выполнить подготовительные инструкции:
- Подготовка рабочего окружения — VirtualBox, установка Linux, что учесть на Windows
- Настройка Git, GPG и GitHub CLI — git config, SSH, GnuPG, gh
- Оформление отчётов gistup — формат, структура, правила
- Введение в сети и TCP/IP — OSI, порты, DNS, HTTP
- Основы Docker — VM vs Container, Dockerfile, Compose
- Dockerfile: как устроен и как его писать — инструкции, кэш сборки, CMD и ENTRYPOINT, от наивного к правильному
- Введение в CI/CD — GitHub Actions, workflow, секреты
- Установка AppSec-инструментов — Semgrep, Trivy, ZAP, Gitleaks, Checkov
- Каждый репозиторий должен содержать
.gitignore,CODE_OF_CONDUCT,CONTRIBUTING,LICENSE,NOTICE,SECURITY - Выполнить лабораторные работы по порядку:
-
lab01 — Git: окружение и первый коммит
-
lab02 — Linux: права доступа и процессы
-
lab03 — Nmap: сканирование сети и NSE
-
lab04 — Анализ и снижение рисков ИБ
-
lab05 — Docker: образы и контейнеры
-
lab06 — Docker CIS Benchmark и Trivy
-
lab07 — SAST, SCA и поиск секретов
-
lab08 — DAST: OWASP ZAP
-
lab10 — Итоговая оценка рисков ИБ
По желанию, после lab05, lab06 и lab09 — углублённый трек по Docker:
-
docker01 — Рантайм: hardening и сегментация
-
docker02 — Поставка: сборка, SBOM, подпись
- Реализовать итоговую работу:
- pet_project — Индивидуальный проект: полный стек AppSec/DevSecOps
%%{init: {"flowchart": {"curve": "step"}}}%%
flowchart TB
accTitle: Маршрут прохождения курса
accDescr: Курс начинается с восьми intro-гайдов и репозитория с обвязкой, затем идёт цикл по десяти лабораторным: работа атомарными коммитами в develop, отчёт gistup, pull request и approve преподавателя с доработкой при отказе; после лабораторных — по желанию углублённый трек по Docker из двух работ, затем pet-project и завершение курса.
course_start([Старт курса])
prep_guides[[Пройти 8 intro-гайдов]]
repo_scaffold["Завести репозиторий<br/>с обвязкой"]
lab_order[/"Порядок лаб 01–10:<br/>01–04 основы<br/>05–06 контейнеры<br/>07–08 AppSec<br/>09–10 DevSecOps"/]
subgraph lab_cycle [Цикл по каждой лабораторной]
direction TB
next_join((" "))
lab_work["Выполнить следующую<br/>лабораторную в develop<br/>атомарными коммитами"]
lab_report[/"Отчёт gistup: команды,<br/>флаги, вывод терминала"/]
pr_join((" "))
lab_pr[Открыть pull request]
review_gate{"Approve<br/>преподавателя?"}
review_fork((" "))
lab_rework["Доработать<br/>по замечаниям"]
labs_left{"Остались<br/>лабораторные?"}
labs_fork((" "))
end
docker_track[["По желанию: углублённый<br/>трек Docker, 2 работы"]]
pet_project[["Pet-project:<br/>весь стек AppSec/DevSecOps"]]
course_end([Курс завершён])
course_start --> prep_guides
prep_guides --> repo_scaffold
repo_scaffold --> lab_order
lab_order --> next_join
next_join --> lab_work
lab_work --> lab_report
lab_report --> pr_join
pr_join --> lab_pr
lab_pr --> review_gate
review_gate --- review_fork
review_fork -->|Да| labs_left
review_fork -->|Нет| lab_rework
lab_rework --> pr_join
labs_left --- labs_fork
labs_fork -->|Да| next_join
labs_fork -->|Нет| docker_track
docker_track --> pet_project
pet_project --> course_end
classDef junction fill:#374151,stroke:#374151,stroke-width:1px,color:#374151,font-size:1px
classDef stage fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
classDef gate fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12
classDef done fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
class next_join,pr_join,review_fork,labs_fork junction
class prep_guides,repo_scaffold,lab_work,lab_pr,lab_rework,docker_track stage
class review_gate,labs_left gate
class pet_project,course_end done
- Единый стиль кода
- Код разбит на функции, без дублирования
- Оформление
README.mdв соответствии с содержанием проекта - Оформление
.gitignoreв соответствии с содержанием проекта - Оформление
.dockerignoreв соответствии с содержанием проекта - Использовать подходящий тип
LICENSEдля проекта иNOTICE - Создать и использовать скрипты для автоматизации сборки проекта, примеров, тестов, пакетирования
- Обеспечить непрерывный процесс сборки проекта с использованием сервиса
GitHub Actions - В
README.mdописаны запуск и проверка проекта - Для pet-project — релиз на
GitHub Releasesпо подписанному тегу - Рефакторинг и поддержка лабораторных работ в процессной деятельности
- Все команды выполняться строго из
терминала/ консолибез использованияWebUIза исключениям работы с токенами, ключами и специфичными настройками
- Подготовка окружения
$ uv sync --frozen # .venv из uv.lock (pyproject.toml — единственный источник версий)
$ uv run mkdocs serve --livereload
# or
$ uv run mkdocs serve -a 0.0.0.0:8000 # открыть с телефона по IP ноутбука- Проверки как в CI (каждая команда запускается локально теми же версиями, что в
uv.lock/package-lock.json)
$ uv run mkdocs build --strict # сборка с предупреждениями как ошибками
$ uv run --frozen --only-group lint yamllint --no-warnings .github/workflows mkdocs.yml
$ .github/scripts/pip-audit.sh # pip-audit по экспорту лока
$ .github/scripts/sbom.sh # CycloneDX SBOM рантайм-набора (как на шаге релиза)
$ uv run --frozen --only-group sast bandit -r labs -ll # + исключения см. ci.yml
$ npm ci && npx stylelint "docs/stylesheets/**/*.css" && npx eslint docs/javascripts/
$ npx markdownlint-cli2 "docs/**/*.md" "labs/**/*.md" README.md- Схемы — блоки кода
mermaidпрямо в Markdown: сайт рендерит их через Material, GitHub — сам. Оформление по ГОСТ 19.701-90:flowchart TB, ортогональные линии, развилки и слияния через точку. Проверка синтаксиса локально (скопируйте блок в файл.mmd):
$ npx --yes @mermaid-js/mermaid-cli@11.17.0 -i schema.mmd -o /tmp/schema.svg-
Mermaid на сайте свой: CSP (
script-src:'self', Telegram, Метрика) не пускает unpkg, поэтому Material не может взять его оттуда. Файлdocs/artifacts/vendor/mermaid/11.17.2/mermaid.min.js(npmmermaid@11.17.2, MIT, sha256581ed7d74bd9048d0e3a91363927d72ef22942d7722546b27f7cc29e35390eb8) подгружаетjavascripts/mermaid-loader.js: он заранее объявляетwindow.mermaid, которым пользуется Material, и тянет файл при первом рендере схемы, так что на страницах без схем 3,5 МБ не грузятся. Он же передаёт Mermaid шрифт сайта (иначе рамки SVG-текста считаются шрифтом по умолчанию, а рисуются Roboto), красит линии жизни sequence-диаграмм и задаёт широким схемам минимальную ширину 70% — на телефоне они прокручиваются внутри блока, а не сжимаются до нечитаемых 7 px. Тег<script>внутри контента не годится: при instant navigation Material ждёт только последний скрипт контейнера, схема остаётся кодом до жёсткой перезагрузки. Обновление: положитьdist/mermaid.min.jsновой версии в каталог с её номером, сверить sha256 с опубликованным пакетом и поменять путь вmermaid-loader.js. Проверка после правок — переход по ссылке со страницы без схем на страницу со схемой, а не только прямое открытие. Сами схемы проверяются в рендере сайта, а не только вmmdc: в sequence-диаграммахNote over A,Bне раздвигает участников, длинную подпись переносят вручную через<br/>. -
Очистка локального репозитория
$ rm -rf __pycache__ scripts/__pycache__
$ rm -rf .venv
$ lsof -i :8000
$ kill <PID>- Release — версия в
pyproject.tomlиpackage.json, запись вRELEASE_NOTES.md, подписанный тег;release-from-notes.ymlпо тегу собирает релиз из записи и прикладывает SBOM
$ git tag -s vX.Y.Z -m "vX.Y.Z"
$ git push origin vX.Y.Z
$ git tag -d vX.Y.Z # удалить локальный тег
$ git push --delete origin vX.Y.Z # удалить тот же тег на GitHubСайт — статическая сборка MkDocs Material. Первая схема показывает, из чего и в каком порядке собирается site/,
вторая (в конце раздела) — как сборка попадает на GitHub Pages и в релизы.
%%{init: {"flowchart": {"curve": "step"}}}%%
flowchart TB
accTitle: Сборка сайта курса
accDescr: MkDocs Material собирает статический сайт из исходников лабораторных, страниц docs, слоя темы и конфигурации: готовит Markdown, рендерит страницы, сжимает ресурсы и дописывает sitemap.
build_start([Запуск mkdocs build])
subgraph sources [Исходники в репозитории]
direction TB
labs_src[/"labs/: intro-гайды,<br/>лабы 01–10, pet-project,<br/>тесты"/]
docs_pages[/"docs/: обёртки лаб,<br/>materials, glossary.md"/]
theme_layer[/"docs/: overrides,<br/>stylesheets, javascripts,<br/>artifacts"/]
build_config[/"mkdocs.yml, hooks.py,<br/>pyproject.toml, uv.lock"/]
labs_src --> docs_pages
end
prepare_markdown["Подготовить Markdown:<br/>include-markdown — labs/,<br/>hooks.py — цифры hero,<br/>snippets — глоссарий"]
render_pages["Отрендерить страницы:<br/>шаблоны overrides, CSP,<br/>шрифты, свой Mermaid"]
optimize_assets["Сжать HTML, CSS и JS,<br/>собрать social-карточки"]
enrich_sitemap["Дописать sitemap:<br/>priority и changefreq"]
site_dir[/"site/: статический сайт"/]
build_done([Сайт собран])
build_start --> sources
sources --> prepare_markdown
prepare_markdown --> render_pages
render_pages --> optimize_assets
optimize_assets --> enrich_sitemap
enrich_sitemap --> site_dir
site_dir --> build_done
classDef stage fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
classDef done fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
class prepare_markdown,render_pages,optimize_assets,enrich_sitemap stage
class build_done done
Контент. Исходники лаб, intro и тестов живут в labs/, страницы сайта в docs/ подключают их через
include-markdown. Шапку README с заголовком и бейджами обёртка пропускает параметром start="</div>": на странице остаётся один h1 из hero,
а бейджи со стороннего сервиса не загружаются. docs/glossary.md не страница, а список аббревиатур, который pymdownx.snippets
дописывает к каждой странице (тултипы). docs/overrides/ и glossary.md исключены из сборки (exclude_docs).
docs/materials/index.md собирает материалы в карточки по типу: руководства, шпаргалки, справочники, OWASP Top 10, кейсы,
лекции, troubleshooting; лабы ссылаются на них из See-also. Вводные руководства — страницы docs/materials/guides/
(обёртки над исходниками labs/intro/); раньше они жили по адресу /labs/intro/…, со старых адресов стоят редиректы.
Шаблоны. overrides/main.html — общий <head> (CSP-meta, fonts.css, JSON-LD: WebPage и BreadcrumbList на каждой странице,
LearningResource на страницах лабораторных, Course, Person и организация на главной; блок htmltitle отдаёт заголовок из front matter
без повторного названия сайта, поэтому title пишется не длиннее 65 символов, а description не длиннее 160; Метрики в шаблоне нет,
её после согласия подключает banners.js). partials/copyright.html —
строка футера со ссылкой на уведомление об ответственности в политике конфиденциальности. overrides/home.html —
главная без сайдбаров (hide: [navigation, toc]), подключает home.css. partials/header.html — шапка
в стиле gpages поверх Material 9.7.x: логотип с кольцом, пилюли разделов с активным состоянием, внешние ссылки
(GitHub, Telegram, сайт автора), штатный поиск и бургер; ссылки от корня сайта, потому что instant navigation не подменяет шапку.
Навигация. Одно меню: пилюли шапки переключают разделы, левый сайдбар (navigation.tabs) показывает
только текущий раздел, панель табов Material не рендерится; в шторке на телефоне всё дерево. Страницы без
раздела («О проекте», релизы, политики) идут во всю ширину.
CSS. Порядок каскада задан extra_css: fonts.css (Roboto, Roboto Mono, Unbounded из artifacts/fonts/,
woff2 по unicode-range, OFL; внешних шрифтов нет, font-src 'self') → tokens.css (палитра gpages, --ink-*, токены Material) →
header.css → sidebar.css → typeset.css (типографика, код, списки, сетка 88rem; грузится после header.css,
поэтому шапка снимает кап .md-grid селектором из обоих классов) → components.css
(hero, нумерованные заголовки, таблицы, карточки лаб, карточки-справки lab-card-facts со списком «термин — описание», футер) → banners.css. home.css подключается
только главной (css_files плагина minify → home.min.css) и может переопределять токены на body. Hover кнопок расширяет трекинг букв, поэтому подпись кнопки (.btn__label с data-label) заранее занимает ширину состояния hover:
раскладка при наведении не двигается. Ряд кнопок hero стоит либо в одну строку, либо блоком два на два через container query по ширине колонки;
контейнер безымянный, потому что минификатор склеивает @container имя (…) в имя(, и браузер отбрасывает правило.
Блоки кода. Подсветку делает Pygments (pymdownx.highlight). У каждого блока есть метка языка: auto_title подставляет имя лексера,
auto_title_map сводит его к привычному короткому имени (bash, yaml, python, dockerfile), а явный title="…" у блока побеждает и показывает имя файла.
Блок без языка получает метку text. Для .gitignore, .dockerignore и похожих файлов лексера в Pygments нет, поэтому у таких блоков стоит лексер bash и title с именем файла.
Адаптив главной ярусами 1600 / 1220 / 960 / 700 px; всё в rem, чтобы масштабироваться с корневым шрифтом
Material. Сетки карточек в контенте тоже в rem — minmax(min(14rem, 100%), 1fr): на широких экранах Material
увеличивает корневой шрифт, и колонка в px перестаёт вмещать заголовок; min(…, 100%) держит одну колонку
на телефоне 320 px. !important только в print. У поля поиска в шапке — брендовая рамка в 1 px: вращать «пилюлю», как кольцо логотипа, нельзя, поэтому анимируется
угол conic-gradient через @property, а середину вырезает маска; при prefers-reduced-motion рамка статична.
Значения @property не нулевые (360deg): минификатор превращает
0deg в 0 и молча отбрасывает регистрацию.
JS. Пять модулей без зависимостей: mermaid-loader.js (свой Mermaid по требованию, см. Tutorial),
header.js (стекло шапки при скролле), typewriter-target.js
(hero, уважает prefers-reduced-motion), banners.js (карточка согласия, класс ata-consent, чтобы
антибаннеры не резали; Метрика грузится только после «Принять», выбор хранится 180 дней в ata_consent,
сменить его можно со страницы политики; текст уведомления об ответственности — раздел privacy.md#notice,
а не плашка поверх страницы), effects.js (появление hero и карточек лаб, живой конвейер, имена
landmark-навигаций, которые Material оставляет безымянными). Интерактивные элементы на тач-экранах не меньше 44 px.
Статистику репозитория в шторке рисует сам Material. Всё подписано на document$ для instant navigation.
Сборка и зависимости. pyproject.toml + uv.lock (хэши), группы инструментов CI (lint, audit,
sast, sbom). hooks.py считает цифры главной из дерева docs/ и labs/ (лабы, работы углублённого трека, руководства, тесты, материалы по типам, схемы, карточки troubleshooting)
и ставит слово в нужную форму («36 материалов», «82 схемы»); неизвестный токен {{ stats.* }} даёт warning,
и build --strict падает, так что сырой токен на сайт не попадает (mkdocs serve кэширует хук — после правки hooks.py сервер перезапускают); дописывает sitemap и кладёт редиректы для переехавших страниц
(свой код вместо mkdocs-redirects: с версии 1.2.3 плагин принадлежит другому проекту и тянет за собой properdocs). CI ставит всё через
uv sync --frozen, сканеры из лока, hadolint с проверкой sha256. Dependabot: actions / npm / uv, cooldown 7 дней.
Релиз (release-from-notes.yml, тег v*.*.*) берёт текст из RELEASE_NOTES.md и прикладывает CycloneDX SBOM
рантайм-набора из лока (.github/scripts/sbom.sh).
Доставка. ci.yml запускается на push и pull request в develop: линтеры → pip-audit → bandit → сборка;
на push сайт уходит на GitHub Pages, на pull request остаётся артефакт site-preview. hadolint идёт параллельно
с bandit и в needs сборки не входит: его статус виден в Checks, но публикацию он не останавливает.
release-from-notes.yml срабатывает на тег v*.*.*.
%%{init: {"flowchart": {"curve": "step"}}}%%
flowchart TB
accTitle: Доставка сайта и релизы
accDescr: Workflow ci.yml на push и pull request в develop прогоняет линтеры, аудит зависимостей и сканеры, собирает сайт и на push публикует его на GitHub Pages, а на pull request отдаёт артефакт site-preview; release-from-notes.yml по тегу версии выпускает релиз с архивом сайта и SBOM.
subgraph ci_flow [ci.yml]
direction TB
change_pushed(["Push или pull request<br/>в develop"])
subgraph lint_stage [Линтеры]
lint_docs["Проверить YAML и MD:<br/>yamllint, markdownlint"]
lint_layout["Проверить JS и CSS:<br/>eslint, stylelint"]
end
audit_deps["Проверить зависимости:<br/>pip-audit по uv.lock"]
audit_fork((" "))
scan_bandit["Проверить Python лаб: bandit"]
scan_hadolint["Проверить Dockerfile лаб: hadolint"]
hadolint_status(["Статус в Checks,<br/>сборку не гейтит"])
build_site[["Собрать сайт:<br/>mkdocs build --strict"]]
event_gate{"Событие —<br/>push в develop?"}
event_fork((" "))
deploy_pages["Опубликовать<br/>на GitHub Pages"]
site_live([course.geminishkv.tech])
preview_artifact[/"Артефакт site-preview"/]
preview_done([Превью для ревью PR])
change_pushed --> lint_stage
lint_stage --> audit_deps
audit_deps --- audit_fork
audit_fork --> scan_bandit
audit_fork --> scan_hadolint
scan_hadolint --> hadolint_status
scan_bandit --> build_site
build_site --> event_gate
event_gate --- event_fork
event_fork -->|Да| deploy_pages
event_fork -->|Нет| preview_artifact
deploy_pages --> site_live
preview_artifact --> preview_done
end
subgraph release_flow [release-from-notes.yml]
direction TB
tag_pushed([Подписанный тег vX.Y.Z])
release_build[["Собрать сайт:<br/>mkdocs build --strict"]]
verify_site[Проверить site/ и sitemap]
pack_release[/"Архив сайта<br/>и CycloneDX SBOM"/]
publish_release["Выпустить GitHub Release<br/>из RELEASE_NOTES.md"]
release_done([Релиз опубликован])
tag_pushed --> release_build
release_build --> verify_site
verify_site --> pack_release
pack_release --> publish_release
publish_release --> release_done
end
classDef junction fill:#374151,stroke:#374151,stroke-width:1px,color:#374151,font-size:1px
classDef stage fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
classDef gate fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#713f12
classDef done fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d
class audit_fork,event_fork junction
class lint_docs,lint_layout,audit_deps,scan_bandit,scan_hadolint,build_site,deploy_pages,release_build,verify_site,publish_release stage
class event_gate gate
class site_live,preview_done,release_done done
├── docs/ # MkDocs source (обёртки + материалы)
│ ├── index.md # Главная: hero, бейджи, о курсе, конвейер лаб с материалами, правила курса, материалы по типам, ссылки на канал и сайт автора
│ ├── about.md # О проекте
│ ├── privacy.md # Политика конфиденциальности
│ ├── Security.md # Политика безопасности
│ ├── RELEASE_NOTES.md # Релизы
│ ├── glossary.md # 40 аббревиатур AppSec (тултипы через snippets)
│ ├── robots.txt # Robots + AI-bot blocking
│ ├── llms.txt # Описание для AI-поисковиков
│ ├── CNAME # course.geminishkv.tech
│ ├── _headers # HTTP-заголовки безопасности для хостинга с поддержкой _headers
│ ├── labs/
│ │ ├── basic/lab01-10.md # 10 docs-обёрток лабораторных
│ │ ├── advanced/ # углублённый трек Docker: страница трека + 2 обёртки
│ │ ├── pet_project.md # Итоговый проект
│ │ └── tests/
│ │ ├── basic/ # 5 вариантов базовых тестов
│ │ └── lectures/ # 2 варианта теста Fintech
│ ├── materials/
│ │ ├── index.md # Индекс материалов: карточки по типу материала
│ │ ├── guides/ # 8 вводных руководств (обёртки над labs/intro/); семь из них раньше жили в /labs/intro/…
│ │ ├── lectures/fintech_ru.md # Лекция Fintech по-русски
│ │ ├── examples/ # 5 кейсов ИБ
│ │ ├── OWASPTOP10/ # 7 OWASP материалов; у каждого класса атак и каждого риска CI/CD своя схема хода атаки
│ │ ├── cheatsheet/ # 11 шпаргалок: Git, Docker, CI/CD, Web, Linux, Nmap
│ │ ├── ports.md # Справочник портов: сервис, риск, проверка, защита + схема разбора открытого порта
│ │ ├── appsec_tt.md # 29 классов инструментов
│ │ ├── licenses.md # 32 карточки лицензий
│ │ ├── risk_scoring.md # CVSS и реестр рисков: вектор, контекст, шкалы, поля реестра
│ │ ├── findings_triage.md # Разбор находок сканеров: три исхода, отчёты шести сканеров, файл триажа
│ │ ├── diagrams_legend.md # Как читать схемы курса: символы ГОСТ 19.701-90, развилки, цвета
│ │ ├── commands_*.md # Справочники команд по темам: Git, окружение, Docker, сканеры и конвейер
│ │ ├── APPENDIX.md # Приложение: входная страница справочников команд (адрес сохранён)
│ │ └── troubleshooting.md # FAQ: схема диагностики и 80 карточек по 18 темам
│ ├── stylesheets/ # fonts → tokens → header → sidebar → typeset → components → banners; home.css только на главной
│ ├── javascripts/ # mermaid-loader (свой Mermaid по требованию), header (стекло), typewriter-target, banners (согласие на Метрику), effects (fade-in, конвейер, landmarks)
│ ├── overrides/ # main.html (head: CSP, шрифты, JSON-LD), home.html, 404.html, partials/header.html, partials/copyright.html
│ └── artifacts/
│ ├── assets/ # логотипы (SVG, PNG 512), favicon (ICO, PNG 16/32, apple-touch-icon)
│ ├── vendor/mermaid/11.17.2/ # Mermaid для схем (MIT), его подгружает mermaid-loader.js при первой схеме
│ ├── exmpls/ # иллюстрация к кейсу анализа рисков
│ └── fonts/ # Roboto, Roboto Mono, Unbounded (woff2 + OFL)
├── labs/
│ ├── intro/ # 8 intro-руководств (исходники; Linux, macOS и Windows)
│ ├── basic/lab01-10/ # 10 лабораторных (README + код; у части — docker-compose)
│ ├── advanced/docker01-02/ # углублённый трек: стенд, проверяющий скрипт с исключениями, шаблон отчёта
│ ├── pet_project/ # Итоговый проект
│ └── tests/
│ ├── basic/ # 5 базовых тестов (исходники)
│ └── lectures/ru_fintech/ # 2 варианта теста Fintech (исходники)
├── .github/
│ ├── workflows/ci.yml # yamllint + markdownlint, eslint + stylelint → pip-audit → bandit / hadolint → build (на PR — артефакт site-preview) → deploy
│ ├── workflows/release-from-notes.yml # релиз из RELEASE_NOTES.md по тегу v*.*.* + CycloneDX SBOM
│ ├── scripts/pip-audit.sh # аудит экспорта лока, одинаково в CI и локально
│ ├── scripts/sbom.sh # SBOM рантайм-набора из лока (шаг релиза)
│ ├── dependabot.yml # actions / npm / uv, cooldown 7 дней
│ └── CODEOWNERS # ревью workflow и конфигов CI
├── hooks.py # цифры главной при сборке (со склонением) + sitemap (priority, changefreq) + редиректы переехавших страниц
├── mkdocs.yml
├── pyproject.toml # зависимости сайта и группы инструментов CI
├── uv.lock # лок с хэшами, ставится через uv sync --frozen
├── package.json / package-lock.json # линтеры docs: stylelint, eslint, markdownlint-cli2
├── eslint.config.js, stylelint.config.cjs, .markdownlint.yaml, .yamllint # конфиги линтеров
├── .gitattributes # jar / war / ear / class — binary в diff и merge
├── .gitleaksignore # fingerprint учебного токена лабы 07 для pre-commit с gitleaks
├── CODE_OF_CONDUCT.md, CONTRIBUTING.md, LICENSE.md, NOTICE.md, SECURITY.md
└── RELEASE_NOTES.md