---
tags: [конспект, слушателю, БРПО]
курс: М БРПО — Специалист по процессам разработки безопасного ПО
занятие: Жизненный цикл безопасного ПО. Языки и стеки технологий
---

# Конспект: жизненный цикл безопасного ПО, языки и стеки

Этот конспект можно читать до занятия, во время и после. Он не заменяет практику: главное сегодня — не прочитать, а запустить, сломать и починить.

**Вопрос дня, к которому мы вернёмся вечером:**

> Чем «программа работает» отличается от «программу можно выпускать»?

---

## 1. Жизненный цикл программного обеспечения

В этой лекции жизненный цикл представлен как семь этапов — это авторская учебная модель для разговора о свидетельствах выпуска:

```
требования → проектирование → реализация → тестирование → выпуск → сопровождение → вывод из эксплуатации
```

Стандарты и организации могут называть, объединять и повторять этапы по-разному. Методологии отличаются длиной обратной связи, ролями, артефактами и способом выпуска, поэтому эта семиступенчатая схема не заменяет описание конкретного процесса.

| Подход | Как проходит цикл | Где применяется сегодня |
|---|---|---|
| Водопад | Один раз, целиком, последовательно | Госконтракты, сертифицируемые системы, там где требования зафиксированы заранее |
| Итеративный | Много раз, каждый раз целиком, но по кусочку функционала | Крупные корпоративные системы |
| Agile / Scrum | Циклами по 1–4 недели, приоритеты пересматриваются каждый цикл | Большая часть коммерческой разработки |
| DevOps | Плюс автоматизация выкладки: цикл замыкается на эксплуатацию | Сервисы, которые обновляются часто |
| DevSecOps | То же, но проверки безопасности встроены в автоматику, а не пристроены сбоку | То, к чему сейчас идёт отрасль и на что ориентирован ГОСТ Р 56939 |

### Сдвиг влево

«Сдвиг влево» (shift left) — если нарисовать жизненный цикл слева направо, то это перенос работ по безопасности из правой части (тестирование, выпуск) в левую (требования, проектирование, написание кода).

Зачем: **чем позже найден дефект, тем больше вокруг него успело нарасти.**

- Ошибка в требованиях — правится одним предложением.
- Она же в проектировании — переделка схемы.
- Она же в коде — переписывание модуля и всех тестов к нему.
- Она же в выпущенном продукте — плюс оповещение заказчиков, внеплановое обновление, разбор инцидента, объяснения регулятору.

Работа по сути одна и та же. Разница — в слое обвязки вокруг неё.

### Как это связано с ГОСТ Р 56939-2024

Стандарт описывает **25 процессов** разработки безопасного ПО (нумерация 5.1–5.25) — от планирования и обучения сотрудников до реагирования на уязвимости и вывода ПО из эксплуатации.

Полезно держать в голове разделение:

- **SDL (Security Development Lifecycle)** отвечает на вопрос *как делать*. Это инженерная практика.
- **ГОСТ Р 56939-2024** отвечает на вопрос *что должно быть сделано и чем это подтверждается*. Его требования могут входить в область оценки соответствия, когда стандарт применим; точный состав проверки задают схема оценки, договор и требования к продукту.

Один и тот же статический анализ кода в SDL называется практикой, а в ГОСТ — процессом 5.10, к которому нужен регламент, назначенный исполнитель и запись о выполнении.

Сегодняшний день так или иначе затрагивает процессы **5.8** (кодирование), **5.9** (экспертиза кода), **5.10** (статический анализ), **5.12** (безопасная сборка), **5.18** (функциональное тестирование).

---

## 2. Экстремальное программирование

Появилось во второй половине 1990-х, автор — Кент Бек, книга «Extreme Programming Explained» вышла в 1999 году.

Идея в названии: взять практики, которые считаются полезными, и довести их до предела.

- Ревью кода полезно? Тогда пусть код пишут двое за одним экраном — ревью станет непрерывным.
- Тесты полезны? Тогда пишем тест **до** кода.
- Интеграция полезна? Тогда интегрируем несколько раз в день, а не раз в квартал.

**Двенадцать практик XP:** планирование, малые релизы, метафора системы, простой дизайн, тестирование, рефакторинг, парное программирование, коллективное владение кодом, непрерывная интеграция, 40-часовая рабочая неделя, заказчик на площадке, стандарт кодирования.

Четыре из них — прямые меры безопасности:

| Практика | Что это в терминах РБПО |
|---|---|
| Парное программирование | Экспертиза кода, выполняемая непрерывно, а не отдельной процедурой |
| Тестирование до кода | Каждый исправленный дефект получает тест, который не даст ему вернуться |
| Непрерывная интеграция | Основа конвейера: проверки запускаются автоматически |
| Стандарт кодирования | Прямое требование процесса 5.8 |

XP в чистом виде сегодня применяют редко. Но непрерывная интеграция, разработка через тестирование и парное программирование ушли из него в общую практику и живут отдельно. Это тот случай, когда методология растворилась в отрасли.

---

## 3. Как устроена разработка сегодня

Когда говорят «стек технологий», имеют в виду набор выборов — по одному на каждом слое. Слоёв примерно десять.

| № | Слой | Что выбирается | Примеры |
|---|---|---|---|
| 1 | Язык и среда выполнения | На чём пишем | Python, TypeScript, Java, C#, Go, Rust |
| 2 | Пакеты и окружение | Откуда берём чужой код | pip / uv, npm, NuGet, Maven, Cargo |
| 3 | Фреймворк | Каркас приложения | Django, FastAPI, React, Spring Boot, ASP.NET Core |
| 4 | Хранилище | Где лежат данные | PostgreSQL, SQLite, Redis, ClickHouse |
| 5 | Упаковка | Как приложение переносится между машинами | Docker, Kubernetes |
| 6 | Контроль версий | Где живёт история кода | Git + GitLab / GitVerse / GitFlic |
| 7 | CI/CD | Что запускается автоматически | GitLab CI, GitHub Actions, Jenkins |
| 8 | Качество и безопасность | Чем проверяем | линтер, тесты, SAST, SCA, DAST |
| 9 | Наблюдаемость | Как понимаем, что происходит в работе | логи, метрики, трассировки |
| 10 | Помощь разработчику | Чем ускоряем работу | IDE, ИИ-ассистенты |

> **Граница модели.** Схема из **10 слоёв** — авторская дидактическая модель, а не структура стандарта и не статистика учебных программ. Она помогает увидеть, что выпуск требует не только кода, но и управления версиями, конвейера, проверок и эксплуатации. Точный объём проверки при оценке соответствия определяют применимая схема, требования заказчика и модель угроз продукта; его нельзя свести к фиксированным слоям 6–8.

### Где какой язык живёт

| Язык | Основные ниши | Типичные фреймворки |
|---|---|---|
| Python | Веб-бэкенд, анализ данных и ML, автоматизация, скрипты ИБ | Django, FastAPI, Flask |
| JavaScript / TypeScript | Веб-интерфейсы, серверная часть | React, Vue, Svelte, Node.js |
| Java | Крупные корпоративные системы, Android | Spring Boot |
| C# | Корпоративные системы, кроссплатформенные и настольные приложения, игры | ASP.NET Core, .NET MAUI, Unity |
| C / C++ | Операционные системы, драйверы, встраиваемые системы | Qt |
| Rust | То же, что C/C++, но с гарантиями безопасности памяти | Tokio, Axum |
| Go | Сетевые сервисы, инфраструктурное ПО | стандартная библиотека, Gin |
| Kotlin / Swift | Мобильные приложения | Jetpack Compose, SwiftUI |
| SQL | Работа с данными (декларативный язык — тема среды) | — |
| 1С | Учётные системы, в России очень широко | платформа 1С:Предприятие |
| Object Pascal (Delphi) | Сопровождение унаследованных систем, быстрая разработка интерфейсов | VCL, Lazarus |
| Ассемблер | Обратная разработка, загрузчики, оптимизация узких мест | — |

### Библиотека, фреймворк, платформа

Различие определяется одним признаком — кто кого вызывает:

- **Библиотеку вызываете вы.** Вы решаете, когда обратиться к `requests`, чтобы отправить запрос.
- **Фреймворк вызывает вас.** Django сам решает, когда вызвать вашу функцию обработки страницы. Это называется инверсией управления.
- **Платформа определяет, где вы вообще можете быть вызваны.** .NET, JVM, Docker.

Почему это важно для безопасности: **когда чужой код управляет вашим, уязвимость в нём становится вашей уязвимостью.** Вы о ней не узнаете, пока специально не начнёте следить. Отсюда композиционный анализ и SBOM — перечень всех компонентов продукта.

### Кто ещё пишет код

Часть кода сегодня создаётся с помощью ИИ-ассистентов, и это уже обычная практика. Для РБПО важны три вещи:

1. **Ответственность не делится.** Отвечает тот, кто отправил код в репозиторий. Сгенерированный код проходит те же проверки, что и написанный руками.
2. **Объём кода растёт быстрее, чем внимание к нему.** Это усиливает роль автоматических проверок.
3. **Появился отдельный риск цепочки поставок:** ассистент может предложить несуществующий пакет, а злоумышленник — заранее занять это имя в репозитории пакетов. Проверять имена зависимостей стало обязательным.

Практическое правило: пользоваться можно, но не отправляйте в коммит строку, которую не можете объяснить.

---

## 4. От словаря к классу: почему это про безопасность

Первая версия нашего журнала хранит записи так:

```python
record = {"inv": "МНИ-001", "kind": "USB-флеш", "label": "ДСП", "owner": "Иванова А. П."}
```

Три проблемы, и все три — про безопасность:

1. **У словаря нет схемы.** Опечатка `record["Owner"]` вместо `record["owner"]` не вызовет ошибку — просто создастся новый ключ, и данные молча разойдутся.
2. **Ничто не мешает записать что угодно.** Тип «Дискета», гриф «Совершенно секретно», ФИО «asdf» — всё пройдёт.
3. **Ничто не мешает добавить один инвентарный номер дважды.**

Вторая версия использует класс:

```python
@dataclass(frozen=True)
class Medium:
    inv: str
    kind: str
    label: str
    owner: str

    def __post_init__(self) -> None:
        if not INV_PATTERN.match(self.inv):
            raise ValidationError(...)
```

Что изменилось и почему это меры безопасности:

- **Набор полей фиксирован** — опечатка в имени поля теперь ошибка, а не тихое расхождение.
- **`frozen=True`** — запись нельзя изменить после создания. Значит, изменения не могут пройти мимо журнала, значит все изменения можно записать.
- **Проверка в конструкторе** — невалидный объект нельзя создать вообще. Ни из формы, ни из импорта CSV, ни из теста, ни из кода, который ещё не написан.

> **Ключевая мысль.** Проверять данные при вводе — это охранять одну дверь. Проверять в конструкторе — сделать так, чтобы неправильное состояние было невозможно. Первое — контроль. Второе — свойство системы.

**Белый список против чёрного.** Мы перечисляем разрешённые типы носителей и разрешённые грифы, а не запрещённые. Перечислить всё разрешённое реально. Перечислить всё запрещённое — нет: всегда найдётся способ записать то же самое иначе.

---

## 5. Четыре языка, одна задача

Задача: скопировать строку в буфер размером ровно 16 символов. Строка длиннее буфера.

### Ассемблер

```asm
copy_name:
    lea     rdi, [rel buffer]
.loop:
    mov     al, [rsi]
    mov     [rdi], al
    inc     rsi
    inc     rdi
    test    al, al
    jnz     .loop
    ret
```

Цикл копирует до нулевого байта — сколько бы их ни было. Проверки размера нет нигде. Лишние байты лягут в соседнюю память. Процессор не считает это ошибкой: для него нет «буфера», есть адрес и инструкция записи. Понятие границы существует только в голове программиста.

Это **CWE-787, запись за границей буфера** — корневая причина классического переполнения буфера.

### Object Pascal (Delphi)

```pascal
{$RANGECHECKS ON}
type TBuffer = array[0..15] of Char;
```

При включённой директиве программа аварийно остановится на 17-м символе: `Runtime error 201`. При выключенной — молча продолжит писать за границу.

То есть **безопасность памяти здесь является настройкой сборки.** Её можно включить, а можно случайно отключить ради быстродействия. Именно поэтому в РБПО проверяется не только исходный код, но и параметры компиляции.

### C#

```csharp
char[] buffer = new char[16];
buffer[i] = source[i];   // IndexOutOfRangeException при i >= 16
```

Проверку границ управляемого массива отключить настройкой сборки нельзя — она встроена в среду выполнения. Отдельную границу создают явный `unsafe`-код, FFI/P/Invoke и нативные зависимости: они требуют разрешения сборки, ревью и собственных мер контроля.

### Python

```python
buffer = [""] * 16
buffer[i] = source[i]   # IndexError
```

Проверка всегда, отключить нельзя. Более того, у строк в Python вообще нет фиксированного размера — задача в таком виде обычно просто не возникает.

### Итог

| Язык | Кто отвечает за границы памяти | Можно ли отключить проверку |
|---|---|---|
| Ассемблер | Программист | Проверки нет вообще |
| Object Pascal | Программист + компилятор | Да, директивой |
| C# | Среда выполнения в управляемом коде | `unsafe` и нативные границы — отдельный контур |
| Python | Интерпретатор в обычном Python-коде | Нативные расширения — отдельный контур |

Языки, где безопасность памяти обеспечивается средой или компилятором, называют **memory-safe**: C#, Java, Python, Go, Rust. Языки, где она лежит на программисте: C, C++, ассемблер.

Насколько это существенно: в [исторической публикации Microsoft Security Response Center](https://www.microsoft.com/en-us/msrc/blog/2019/07/a-proactive-approach-to-more-secure-code/) указано, что около 70 % уязвимостей, которым Microsoft назначала CVE, относились к проблемам безопасности памяти. Это контекст продуктов Microsoft, а не универсальная доля для всех систем. Управляемые языки и безопасное подмножество Rust устраняют многие такие ошибки в обычном коде, но `unsafe`, FFI и нативные расширения требуют отдельного контроля.

> **Главный вывод.** Выбор языка — это архитектурное решение по безопасности, принятое до написания первой строки кода. Оно определяет, какие классы уязвимостей вообще возможны в вашей программе.
>
> Но ни один язык не защищает от SQL-инъекции, от ошибки в логике доступа и от секрета, попавшего в репозиторий. Это следующий раздел.

---

## 6. Четыре дефекта, которые не лечатся выбором языка

Все четыре живут в образце `step3_defect.py`, а править вы будете его копию — **`step3_student.py`**. Образец остаётся нетронутым: к нему возвращаются, чтобы сравнить «было / стало» и чтобы проверки в пятой паре было с чем сопоставлять.

> [!important] В каком порядке читать этот раздел
> Ниже разобран каждый дефект и показано, как он устраняется. **Сначала попробуйте сами** — по заданию 3, глядя только на подсказки. Разбор здесь нужен, когда вы застряли или когда уже сделали и хотите свериться.
>
> Скопировать отсюда готовые строки можно за минуту, но тогда занятие пройдёт мимо. Проверка `py -m unittest test_student.py` покажет зелёный результат в обоих случаях — она не отличает понимание от копирования. Отличаете только вы.

### Д3. Внедрение SQL-кода — CWE-89

```python
sql = ("SELECT inv, kind, label, owner FROM media "
       "WHERE label = 'Открыто' AND owner LIKE '%" + query + "%'")
```

Функция по замыслу обязана возвращать **только** записи с грифом «Открыто». Но если ввести строку `%' OR label LIKE '%`, получится вот такой запрос:

```sql
SELECT ... WHERE label = 'Открыто' AND owner LIKE '%%' OR label LIKE '%%'
```

`AND` связывает сильнее, чем `OR`. Условие распалось на «(гриф открыто И владелец любой) ИЛИ (гриф любой)». Второе слагаемое истинно для всех строк — и наружу уходят конфиденциальные записи.

**Суть дефекта:** данные, пришедшие от пользователя, стали частью команды.

**Исправление — параметризованный запрос:**

```python
sql = ("SELECT inv, kind, label, owner FROM media "
       "WHERE label = 'Открыто' AND owner LIKE ?")
return connection.execute(sql, (f"%{query}%",)).fetchall()
```

Текст запроса — константа. Значения передаются отдельно. База разбирает запрос **до** того, как увидит данные, поэтому данные уже не могут стать командой.

> **Почему нельзя «просто запретить кавычки».** Это фильтрация по чёрному списку: способов записать то же самое всегда больше, чем вы предусмотрели, а фамилии с апострофом при этом перестанут искаться. И запомните: инъекция — это не про SQL. Это про склейку команды из данных. То же самое бывает с командами оболочки, путями, шаблонами, LDAP-запросами.

### Д4. Выход за пределы каталога — CWE-22

```python
path = Path(os.path.join(EXPORT_DIR, filename))
```

Имя файла приходит снаружи. Передайте `../../отчёт.txt` — и файл уйдёт за пределы каталога выгрузки, куда программе писать не положено.

**Исправление — нормализация и проверка границы:**

```python
base = EXPORT_DIR.resolve()
candidate = (base / filename.replace("\\", "/")).resolve()
if not candidate.is_relative_to(base):
    raise ValueError(...)
```

`resolve()` приводит путь к каноническому виду и убирает `..`. Затем проверяем, что результат действительно лежит внутри разрешённого каталога.

Проверять исходную строку на наличие `..` ненадёжно: есть обратные слэши, процентное кодирование, символические ссылки. **Проверяйте результат, а не текст.**

### Д1. Секрет в исходном коде — CWE-798

```python
ADMIN_TOKEN = "s3cr3t-admin-token-training"
```

Такой токен уезжает в систему контроля версий и остаётся в её истории навсегда. Удаление строки следующим коммитом ничего не меняет: старые коммиты никуда не делись, и `git log -p` их покажет. Сменить токен можно только пересборкой и перевыпуском программы.

**Исправление:**

```python
token = os.environ.get("MEDIA_JOURNAL_ADMIN_TOKEN")
```

В коде остаётся только *имя* переменной. Сам секрет живёт в окружении процесса, в файле `.env` (добавленном в `.gitignore`) или в менеджере секретов.

### Д2. Секрет в журнале приложения — CWE-532

```python
logging.info("Попытка входа: пользователь=%s токен=%s", user, token)
```

Журналы читает больше людей, чем исходный код: их собирают в SIEM, выгружают в тикеты, прикладывают к обращениям в поддержку.

**Исправление — маскирование:**

```python
logging.info("Попытка входа: пользователь=%s токен=%s", user, mask(token))
```

Заодно в эталоне сравнение токенов сделано через `hmac.compare_digest`: обычное `==` завершается на первом несовпавшем символе, поэтому по времени ответа секрет можно подобрать посимвольно.

### Связь с общепринятыми перечнями

Все четыре дефекта попадают в категории OWASP Top 10 (редакция 2021 года): Д3 — в A03 «Injection», Д1 — в A07 «Identification and Authentication Failures», Д2 — в A09 «Security Logging and Monitoring Failures», Д4 — в A01 «Broken Access Control».

---

## 7. Проверки: тесты, линтер, анализатор

### Два вида тестов

- **Функциональные** проверяют, что программа делает то, что должна.
- **Регрессионные тесты безопасности** проверяют, что программа **не делает** того, чего не должна.

Второй вид — это память об уже найденной уязвимости. Пока тест зелёный, дефект не может вернуться в код незамеченным.

```python
    def test_d3_sql_injection_does_not_leak_confidential(self):
        payload = "%' OR label LIKE '%"
        rows = self.module.find_public(self.connection, payload)
        leaked = [row for row in rows if row[2] == "Конфиденциально"]
        self.assertEqual(leaked, [])
```

`self.module` — это проверяемый файл. В `test_journal.py` он равен эталону, в `test_student.py` — вашему `step3_student.py`. Благодаря этому один и тот же текст теста работает и там, и там: свои тесты пишите точно так же, через `self.module`.

> **Обратите внимание.** Этот тест дословно повторяет требование, которое мы сформулировали утром на доске: «поиск никогда не возвращает записи с грифом Конфиденциально». Требование безопасности, записанное через «никогда», превращается в тест почти механически. Поэтому и формулировать их стоит именно так.

### Красное перед зелёным

Тест, который никогда не падал, ничего не доказывает. Прежде чем поверить тесту, убедитесь, что он умеет краснеть: подставьте дефектную версию и посмотрите, упадёт ли он.

### Линтер и анализатор безопасности — разные инструменты

| Инструмент | Что ищет | Нашёл ли SQL-инъекцию |
|---|---|---|
| Тесты | Нарушение заданного поведения | Да, если тест написан |
| ruff (линтер) | Стиль, мёртвый код, опечатки | **Нет** |
| bandit (SAST) | Опасные конструкции языка | Кандидат: B608 |
| pip-audit (SCA) | Уязвимости в чужих зависимостях | Нет, это другой класс дефектов |

На занятии мы запускаем `ruff check step3_defect.py`, и он находит ровно одну проблему — неиспользуемую переменную. SQL-инъекцию он не видит и не должен: это не его работа. В текущем учебном файле Bandit B608 отмечает вероятное формирование SQL-строки, но это эвристический кандидат, а не доказательство эксплуатируемости или отсутствия дефекта: возможны ложные срабатывания и пропуски.

**Вывод: ни один инструмент не закрывает задачу в одиночку.** Поэтому в ГОСТ Р 56939-2024 статический анализ (5.10), динамический анализ (5.11), композиционный анализ (5.16) и функциональное тестирование (5.18) — четыре разных процесса.

### Конвейер

Конвейер РБПО — это те же самые команды, которые вы вводили руками, только запускаемые автоматически при каждом изменении кода:

```yaml
stages: [lint, test, security]

lint:
  script:
    - ruff check .

test:
  script:
    - python -m unittest -v test_journal.py

security:
  script:
    - bandit -r .
    - pip-audit
```

Шаг, который при отрицательном результате останавливает сборку, называется **quality gate**. Не каждая проверка обязана блокировать выпуск, но для каждого результата нужны владелец, порог, срок реакции и процедура принятия исключения; иначе он рискует остаться пассивной телеметрией.

---

## 8. Ответ на вопрос дня

> Чем «программа работает» отличается от «программу можно выпускать»?

Разницей в свидетельствах выпуска. В авторской модели — **14 проверяемых свидетельств**; работающий код — одно из них. Остальные:

- сформулированные требования, в том числе требования безопасности через «никогда»;
- модель угроз;
- контроль ввода, встроенный в структуру данных, а не приделанный сбоку;
- функциональные тесты;
- регрессионные тесты безопасности, проверенные на дефектной версии;
- отчёт линтера;
- отчёт анализатора безопасности;
- отчёт композиционного анализа зависимостей;
- история изменений в системе контроля версий;
- отсутствие секретов в коде и в истории репозитория;
- воспроизводимая сборка с зафиксированными параметрами компиляции;
- зафиксированная версия выпуска;
- честный перечень того, чего в продукте нет.

Многие свидетельства можно полностью или частично собирать и проверять автоматически. Требования, модель угроз, решения об исключениях и финальное принятие риска требуют ответственных людей. Сдвиг влево сокращает обратную связь, но не отменяет контроль перед выпуском и в эксплуатации.

---

## Термины занятия

| Термин | Значение |
|---|---|
| SDLC | Жизненный цикл разработки ПО |
| SDL | Жизненный цикл разработки безопасного ПО |
| Сдвиг влево (shift left) | Перенос работ по безопасности на ранние этапы цикла |
| XP | Экстремальное программирование, методология Кента Бека |
| TDD | Разработка через тестирование: тест пишется до кода |
| CI/CD | Непрерывная интеграция и непрерывная поставка |
| Quality gate | Проверка, отрицательный результат которой останавливает сборку |
| SAST | Статический анализ кода на безопасность |
| SCA | Композиционный анализ: проверка чужих зависимостей |
| DAST | Динамический анализ: проверка работающего приложения |
| SBOM | Перечень всех компонентов, входящих в продукт |
| CWE | Международный перечень типов дефектов ПО |
| Memory-safe | Язык, в котором безопасность памяти обеспечена средой или компилятором |
| Инверсия управления | Схема, при которой фреймворк вызывает ваш код, а не наоборот |
| Инвариант | Условие, которое верно для объекта всегда, с момента создания |
| Белый список | Перечень разрешённого. Надёжнее чёрного списка запрещённого |

## Что почитать дальше

- ГОСТ Р 56939-2024 — перечень 25 процессов, читать выборочно по процессам сегодняшнего дня: 5.8, 5.9, 5.10, 5.12, 5.18
- Банк данных угроз ФСТЭК России — `bdu.fstec.ru`
- OWASP Top 10 — перечень наиболее критичных рисков веб-приложений
- CWE — `cwe.mitre.org`, поиск по номерам из сегодняшнего занятия: CWE-89, CWE-22, CWE-798, CWE-532, CWE-787
- Kent Beck. Extreme Programming Explained — первоисточник по XP

## Связанные темы курса

| Тема | Как связана с этим конспектом |
|---|---|
| Тестирование ПО, автоматизация, CI/CD, GitLab CE | Продолжение раздела 7 |
| Хроника языков, декларативные языки (Prolog, SQL), сдвиг влево, фреймворки, архитектура | Продолжение разделов 3 и 5; SQL здесь выступает источником уязвимости, а в отдельном модуле рассматривается как язык |
| Конвейер РБПО: статический, композиционный, динамический анализ, фаззинг | Углубление раздела 7 |
| Подведение итогов и итоговое тестирование | Проверка усвоения материала |
