Testy bezpieczeństwa kodu pisanego przez agentów
ENTesty bezpieczeństwa kodu pisanego przez agentów łączą deterministyczne skanery z wykonywalnymi testami bezpieczeństwa: macierzą autoryzacji, testami nadużyć dla wstrzyknięć i obsługi wejścia oraz testem exploita dla każdego znaleziska zgłoszonego przez recenzenta AI. Znalezisko się liczy tylko wtedy, gdy jego test nie przechodzi przed poprawką i przechodzi po niej, więc wynik da się udowodnić bez czytania każdej linii.
Ta strona jest dla programistów, którzy wdrażają endpointy napisane przez agenta, i dla tech leadów, którzy odpowiadają za bramkę przed merge’em. Wyobraź sobie sytuację: agent dodał w tym tygodniu 12 tras API, Semgrep świeci na zielono, a recenzent AI pisze, że zmiana „wygląda bezpiecznie”. Potem klient otwiera /api/invoices/4812 i widzi fakturę innej firmy. Żaden skaner nie wie, że faktura 4812 należy do kogoś innego. Wie to tylko test, który koduje twój model autoryzacji.
Co dają testy bezpieczeństwa zmian agenta
Dział zatytułowany „Co dają testy bezpieczeństwa zmian agenta”- Zestaw testów bezpieczeństwa uruchamiany przy każdym pull requeście: jeden test autoryzacji na trasę i rolę oraz przypadki nadużyć dla wejść, które trasa przyjmuje.
- Regułę recenzji, która zamienia znaleziska AI z opinii w dowody: każde znalezisko przychodzi z nieprzechodzącym testem albo odpada.
- Test mutacyjny, który dowodzi, że testy bezpieczeństwa naprawdę coś łapią, więc zielony przebieg coś znaczy.
- Bramkę w CI, w której merge blokują kontrole deterministyczne, a recenzja AI pozostaje doradcza.
- Cztery prompty do skopiowania, działające w Claude Code, Codeksie i Cursorze.
Która kontrola bezpieczeństwa łapie który błąd?
Dział zatytułowany „Która kontrola bezpieczeństwa łapie który błąd?”Każda warstwa łapie inną klasę błędów i żadna nie zastępuje pozostałych. Skanery i ich konfiguracja są opisane na stronie bramki bezpieczeństwa dla kodu pisanego przez agentów. Ta strona dotyczy warstwy, której skanery nie zapewnią: testów.
| Warstwa | Łapie | Nie łapie | Blokuje merge? |
|---|---|---|---|
| Analiza statyczna (Semgrep) | Miejsca podatne na wstrzyknięcia, niebezpieczne API, znane złe wzorce | Wszystkiego, co zależy od tego, kto jest właścicielem którego rekordu | Tak, przy ważności ERROR |
| Skanowanie sekretów (Gitleaks) | Klucze i tokeny w diffie lub historii | Sekretów wczytanych poprawnie, ale później zalogowanych | Tylko po dodaniu kroku CI z warstwy 3 (nie ma go w jobie poniżej) |
Audyt zależności (npm audit, kontrola lockfile’a) | Znane CVE i opublikowane ostrzeżenia o złośliwym oprogramowaniu; zmyślone lub typosquattowane pakiety wymagają weryfikacji zależności | Błędów w twoim własnym kodzie | Tak, przy podatnościach o ważności high lub critical |
| Testy bezpieczeństwa (ta strona) | Brakującą lub błędną autoryzację (IDOR), mass assignment, nadużycia logiki biznesowej, regresje naprawionych błędów | Klas błędów, dla których nikt nie napisał przypadku | Tak |
| Recenzja bezpieczeństwa AI | Błędy, do których nie pasuje żaden wzorzec, np. sprawdzenie uprawnień na niewłaściwym obiekcie | Powtarzalności: dwa przebiegi mogą się różnić | Nie, jest doradcza, dopóki znalezisko nie stanie się testem |
Z tabeli wynikają dwie rzeczy. Błędna kontrola dostępu (broken access control) zależy od reguł własności, których nie zna żaden skaner, a zarazem jest kategorią A01 w OWASP Top 10:2025. Recenzent AI jest też jedyną warstwą, której wynik jest twierdzeniem, a nie werdyktem, dlatego ten workflow zamienia jego twierdzenia w testy. Ryzyko w zależnościach ma osobną stronę: zmyślone pakiety i slopsquatting.
Jak zbudować z agentem zestaw testów bezpieczeństwa, krok po kroku
Dział zatytułowany „Jak zbudować z agentem zestaw testów bezpieczeństwa, krok po kroku”Wykonaj te kroki przy każdej zmianie, która dodaje lub modyfikuje trasę, uprawnienie albo ścieżkę wejścia. Zakładają, że zmiana ma już kryteria akceptacji; lista zagrożeń z kroku 1 to ich część dotycząca bezpieczeństwa.
-
Spisz zagrożenia dla tej zmiany przed jakąkolwiek recenzją kodu. Dla każdej nowej lub zmienionej trasy zapisz, kto może ją wywołać, których rekordów może dotknąć i które wejścia przekraczają granicę zaufania (pola body, parametry ścieżki, przesyłane pliki, adresy URL pobierane przez serwer). Wystarczy pięć linii na trasę. Ta lista jest specyfikacją, którą kodują testy.
-
Wygeneruj macierz autoryzacji. Jeden test na trasę, rolę i przypadek własności: anonim, inny tenant, właściciel, użytkownik tego samego tenanta, który nie jest właścicielem, i administrator tenanta. Użyj pierwszego promptu poniżej. Oczekiwane kody statusu pochodzą z listy zagrożeń, a nie z lektury kodu przez agenta; inaczej test utrwali to, co kod robi dziś.
-
Wygeneruj testy nadużyć dla każdego wejścia. Ciągi wstrzyknięć SQL i NoSQL, path traversal, zbyt duże i zniekształcone body, nieoczekiwane typy, dodatkowe pola, które powinny zostać zignorowane (mass assignment), oraz cele server-side request forgery (SSRF) dla każdego adresu URL pobieranego przez serwer. Użyj drugiego promptu.
-
Uruchom recenzję bezpieczeństwa AI na diffie i wymagaj dowodu. Każde znalezisko musi przyjść z testem, który nie przechodzi na obecnym kodzie. Znalezisko bez takiego testu trafia na listę pytań do człowieka, a nie na listę błędów. Użyj trzeciego promptu i poleceń dla poszczególnych narzędzi z następnej sekcji.
-
Udowodnij, że testy łapią błędy. Tymczasowo usuń każde sprawdzenie autoryzacji i każdą walidację wejścia, których dotyczą testy, uruchom zestaw i potwierdź, że nie przechodzi. Test bezpieczeństwa, który zostaje zielony po usunięciu sprawdzenia, jest dekoracją. Użyj czwartego promptu.
-
Ustaw zestaw jako wymagany check. Umieść testy w
tests/security/, uruchamiaj je w CI obok skanerów i oznacz job jako wymagany. Układ CI opisuje sekcja Zablokuj merge testami bezpieczeństwa w CI.
Wygenerowany test autoryzacji wygląda tak. Jest celowo krótki: każdy wiersz to jedno twierdzenie o tym, kto może co zrobić, a recenzent czyta tę tabelę zamiast kodu handlera.
// tests/security/invoices.authz.test.ts (Vitest + supertest)import { describe, it, expect } from 'vitest';import request from 'supertest';import { app } from '../../src/app';import { tokenFor, seedInvoice } from '../helpers';
const cases = [ { who: 'anonymous', token: null, status: 401 }, { who: 'other tenant', token: () => tokenFor('acme-user'), status: 404 }, { who: 'owner', token: () => tokenFor('globex-user'), status: 200 }, { who: 'same-tenant non-owner', token: () => tokenFor('globex-other-user'), status: 404 }, { who: 'tenant admin', token: () => tokenFor('globex-admin'), status: 200 },];
describe('GET /api/invoices/:id', () => { it.each(cases)('$who gets $status', async ({ token, status }) => { const invoice = await seedInvoice({ tenant: 'globex' }); const req = request(app).get(`/api/invoices/${invoice.id}`); const res = token ? await req.set('Authorization', `Bearer ${token()}`) : await req; expect(res.status).toBe(status); });});Przypadek innego tenanta oczekuje 404, a nie 403, żeby odpowiedź nie potwierdzała istnienia rekordu. Ustal tę regułę raz, zapisz ją w liście zagrożeń i pozwól, by każdy wygenerowany test jej przestrzegał.
Recenzja bezpieczeństwa AI, która musi udowodnić swoje znaleziska
Dział zatytułowany „Recenzja bezpieczeństwa AI, która musi udowodnić swoje znaleziska”Wszystkie trzy narzędzia potrafią przejrzeć diff pod kątem błędów bezpieczeństwa. Polecenia się różnią, ale reguła, która czyni wynik wiarygodnym, jest ta sama: znalezisko staje się błędem dopiero wtedy, gdy odtwarza je test.
W sesji uruchom wbudowaną recenzję swojej gałęzi względem domyślnej gałęzi na origin:
/security-reviewDo skryptowego sprawdzenia przed pushem przekaż diff do trybu headless i poproś o wynik zgodny ze schematem. Z --json-schema zwalidowany obiekt trafia do pola structured_output w wyniku JSON (sprawdzone na Claude Code 2.1.283, kanał latest):
# Terminal, from the repository rootgit diff origin/main...HEAD | claude -p \ --tools "Read,Grep,Glob" \ --strict-mcp-config \ --output-format json \ --json-schema "$(cat .claude/security-findings.schema.json)" \ "Security-review the diff on stdin. Report only issues an attacker can trigger, each with the request or input that triggers it." \ | jq -e '[.structured_output.findings[] | select(.severity == "critical" or .severity == "high")] | length == 0'Plik schematu potrzebuje tablicy findings, której elementy mają pola severity (critical, high, medium, low), file, line i issue. Wywołanie jq -e kończy się kodem różnym od zera, gdy istnieje znalezisko o ważności high lub critical. --tools ogranicza wbudowane narzędzia do Read, Grep i Glob, a --strict-mcp-config (bez --mcp-config) pomija skonfigurowane serwery MCP, więc recenzent nie ma żadnego narzędzia, którym mógłby edytować to, co recenzuje (sprawdzone na Claude Code 2.1.283, kanał latest). --allowedTools tego nie zapewnia: tylko z góry zatwierdza wymienione narzędzia, a Edit i Bash zostają dostępne dla każdej reguły allow w twoich ustawieniach.
Żeby łapać błędy w trakcie pisania kodu przez agenta, a nie po fakcie, dodaj wtyczkę Anthropic security-guidance (claude plugin install security-guidance@claude-plugins-official); jej konfiguracja jest na stronie bramki bezpieczeństwa dla kodu pisanego przez agentów. Audyt całej bazy kodu zamiast diffa opisuje audyt bezpieczeństwa z Claude Code.
codex review przyjmuje własne instrukcje albo cel, np. --base main, ale nie oba naraz (Codex CLI 0.157.1). Do przeglądu bezpieczeństwa podaj instrukcje i nazwij w nich gałąź bazową:
# Terminalcodex review "Security pass only: review the changes on this branch againstmain for missing authorization, injection, mass assignment, SSRF, pathtraversal and hardcoded secrets. Report file:line, severity and the exactrequest that triggers each issue."Do sprawdzenia skryptowego codex exec waliduje końcową wiadomość względem schematu i zapisuje ją do pliku. Użyj schematu o tym samym kształcie co w zakładce Claude Code (tablica findings z polami severity, file, line, issue):
codex exec --ephemeral \ -c default_permissions=":read-only" \ --output-schema .codex/security-findings.schema.json \ -o findings.json \ "Security-review git diff origin/main...HEAD. Do not edit files. Report only issues an attacker can trigger."jq -e '[.findings[] | select(.severity == "critical" or .severity == "high")] | length == 0' findings.jsonProfile uprawnień są w Codex CLI 0.157.1 w wersji beta; --sandbox read-only to starszy odpowiednik (nie łącz obu). Werdykt bierz z -o (--output-last-message). --json wypisuje strumień zdarzeń jako JSONL, a to nie jest obiekt ze znaleziskami. Na GitHubie komentarz @codex security review w pull requeście zleca recenzję bezpieczeństwa (sprawdzone w dokumentacji OpenAI 2026-08-28). Skanowanie całego repozytorium opisuje strona Codex Security.
W czacie agenta wklej trzeci prompt z tej strony (poniżej), mając wybraną gałąź. Agent może uruchamiać testy, więc poproś, by w tej samej turze napisał i uruchomił test odtwarzający problem.
Na pull requestach Bugbot Cursora „reviews pull requests and identifies bugs, security issues, and code quality problems”, a Cursor opisuje też osobnych Security Agents. Obie nazwy sprawdzono na cursor.com 2026-08-28; dostępności w planach i cen nie, więc sprawdź stronę, zanim coś na nich oprzesz. Komentarze obu botów traktuj tak jak wynik promptu: znalezisko trafia do zestawu jako nieprzechodzący test, zanim ktokolwiek je naprawi.
Skąd wiesz, że testy bezpieczeństwa naprawdę działają?
Dział zatytułowany „Skąd wiesz, że testy bezpieczeństwa naprawdę działają?”Zestaw testów bezpieczeństwa, który przechodzi, niczego nie dowodzi, dopóki nie zobaczysz, jak nie przechodzi z właściwego powodu. Trzy kontrole dają tę pewność bez czytania handlerów.
- Najpierw czerwony, potem zielony. Każdy test napisany na podstawie znaleziska musi nie przechodzić na niepoprawionym kodzie. Zachowaj log CI z tego przebiegu albo commituj test przed poprawką, żeby historia pokazywała czerwony przebieg. Test, który nigdy nie był czerwony, może niczego nie testować.
- Usuń sprawdzenie i patrz, jak test pada. Poniższy test mutacyjny usuwa po jednej linii autoryzacji lub walidacji i oczekuje, że co najmniej jeden test bezpieczeństwa nie przejdzie. Mutacje, które przetrwały, to luki w zestawie i zarazem lista, którą dajesz agentowi jako następne zadanie.
- Licz macierz, nie testy. Macierz autoryzacji jest kompletna, gdy ma jeden wiersz na trasę, typ wywołującego i przypadek własności. Recenzent porównuje liczbę wierszy z listą tras w
docs/security/threats.md, co zajmuje minuty zamiast czytania całego diffa.
Kto zatwierdza: autor odpowiada za listę zagrożeń i macierz; drugi inżynier przegląda listę zagrożeń, liczbę wierszy macierzy i wszystkie pozycje „suspected, not reproduced”. Przy zmianach w uwierzytelnianiu, płatnościach lub dostępie do danych wielu tenantów tym drugim inżynierem jest ktoś z twojej listy eskalacji bezpieczeństwa. Czerwony przebieg, tabela mutacji i wynik recenzji trafiają do pull requesta w pakiecie dowodów.
Zablokuj merge testami bezpieczeństwa w CI
Dział zatytułowany „Zablokuj merge testami bezpieczeństwa w CI”Podziel pipeline na dwa joby. Job deterministyczny nie ma klucza API żadnego modelu, więc nic, co atakujący wpisze do diffa, nie dosięgnie sekretu; to on jest wymaganym checkiem. Job z recenzją AI jest doradczy: komentuje, a jego znaleziska stają się testami według opisanego wyżej workflow.
name: securityon: pull_request:permissions: contents: read
jobs: deterministic: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v7 with: node-version: 22 - run: npm ci - name: Dependency audit (production dependencies) run: npm audit --omit=dev --audit-level=high - name: Static analysis run: | pip install semgrep==1.178.0 semgrep scan --config p/owasp-top-ten --severity ERROR --error --metrics=off - name: Security tests run: npx vitest run tests/securityOznacz deterministic jako wymagany status check w regułach ochrony gałęzi. Przypnij Semgrepa do przetestowanej wersji (1.178.0 była aktualna na PyPI 2026-09-26), żeby aktualizacja reguł lub silnika nie zmieniła bramki na czerwono z dnia na dzień bez własnego pull requesta. --severity ERROR ogranicza skan do reguł o ważności ERROR, a --error oblewa job przy każdym takim znalezisku. Przy pull_request GitHub uruchamia ten workflow i wywoływany przez niego zestaw tests/security/ z samego pull requesta, więc pull request może usunąć krok i nadal przejść. Obejmij /.github/ i tests/security/ plikiem CODEOWNERS i włącz Require review from Code Owners, żeby taka zmiana nie weszła bez recenzji wskazanej osoby.
Ten job nie skanuje jeszcze sekretów. Dodaj skan z warstwy 3 na stronie o bramkach bezpieczeństwa: krok TruffleHog albo Gitleaks uruchamiany na zakresie commitów pull requesta. Bez niego wiersz Gitleaks z tabeli wyżej działa tylko na maszynach z hookiem pre-commit.
Doradczy job AI różni się w zależności od narzędzia:
Akcja Anthropic claude-code-security-review recenzuje diff pull requesta i go komentuje. Dodaj ją jako drugi job przypięty do pełnego SHA commita, tak jak Semgrepa wyżej: job ma twój klucz API, a ruchome @main uruchomiłoby to, co akurat jest na tej gałęzi w dniu przebiegu:
ai-review: if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} fetch-depth: 2 persist-credentials: false - uses: anthropics/claude-code-security-review@0c6a49f1fa56a1d472575da86a94dbc1edb78eda # main as of 2026-09-26 with: comment-pr: true claude-model: claude-opus-5-5 exclude-directories: tests,docs claude-api-key: ${{ secrets.CLAUDE_API_KEY }}README akcji mówi wprost, że „is not hardened against prompt injection attacks and should only be used to review trusted PRs”. W publicznym repozytorium włącz dla workflow opcję Require approval for all external contributors. Domyślny model akcji, claude-opus-4-1-20250805, został wycofany z Claude API, więc bez claude-model job się nie uruchomi; aktualne nazwy znajdziesz w przeglądzie modeli.
Akcja OpenAI openai/codex-action@v1 uruchamia codex exec na runnerze. Nadaj jej profil uprawnień tylko do odczytu (beta, tak jak ustawienie default_permissions wyżej) i schemat, żeby wynik dało się sprawdzić maszynowo:
ai-review: if: github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@v7 with: fetch-depth: 0 persist-credentials: false - uses: openai/codex-action@v1 with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} permission-profile: ":read-only" output-schema-file: .codex/security-findings.schema.json output-file: findings.json prompt: | Security-review git diff origin/${{ github.base_ref }}...HEAD. Report only issues an attacker can trigger, with the request that triggers each one. - run: cat findings.jsonTen job też ma klucz API, więc obowiązuje zasada przypinania z zakładki Claude: @v1 to ruchomy tag (2026-09-26 wskazywał v1.12; najnowsze wydanie to v1.13, 2026-10-05), więc przed skopiowaniem joba zastąp go pełnym SHA commita tego wydania z tagów codex-action.
Przed uruchomieniem akcja sprawdza, czy użytkownik, który wyzwolił workflow, ma prawo zapisu do repozytorium. Jeśli chcesz automatycznych komentarzy recenzji na każdym pull requeście, użyj integracji Codeksa z GitHubem (@codex security review) zamiast workflow.
Recenzja Cursora działa jako integracja z GitHubem, a nie krok workflow: włącz Bugbota dla repozytorium, a skomentuje każdy pull request. W każdym przypadku zostaw job deterministic jako wymagany check; komentarze Bugbota są wejściem do workflow „dowód przez test”, a nie werdyktem o merge’u.
Linia if: pomija job AI dla pull requestów z forków, które przy pull_request i tak nie dostają sekretów. Nigdy nie uruchamiaj żadnego z jobów AI na pull_request_target i nigdy nie dawaj mu prawa zapisu do zawartości repozytorium. Powody i bezpieczny wzorzec dla kontrybucji z forków opisuje strona agenci headless w CI.
Co się psuje przy testowaniu bezpieczeństwa kodu agenta?
Dział zatytułowany „Co się psuje przy testowaniu bezpieczeństwa kodu agenta?”Agent pisze testy, które przechodzą na podatnym kodzie. Zwykle oczekiwane statusy wzięto z kodu, a nie z listy zagrożeń, więc test utrwala błąd. Naprawa: wygeneruj testy ponownie wyłącznie na podstawie docs/security/threats.md i dodaj do promptu regułę „a failing test here is a finding, not a test bug”, tak jak robi to pierwszy prompt. Resztę wyłapie test mutacyjny.
Agent „naprawia” nieprzechodzący test bezpieczeństwa, zmieniając asercję. Tak zestaw testów bezpieczeństwa po cichu gnije. Naprawa: zabezpiecz tests/security/ tak, by zmiany wymagały wskazanego recenzenta, np. wpisem w CODEOWNERS, i zapisz w CLAUDE.md lub AGENTS.md, że testy bezpieczeństwa zmienia się tylko w zadaniu, które wprost o to prosi. Zobacz ochronę wyroczni testowej.
Recenzent AI zasypuje pull request teoretycznymi znaleziskami. Naprawa: wymagaj nieprzechodzącego testu (trzeci prompt), wyłącz z recenzji katalogi tests i docs i usuń kategorie, które świadomie akceptujesz, np. rady o limitach zapytań bez konkretnej ścieżki nadużycia. To, co zostanie, jest krótkie, a każdą pozycję da się odtworzyć.
Dwa przebiegi recenzji na tym samym diffie dają różne wyniki. Recenzja modelu nie jest deterministyczna. Naprawa: nigdy nie uzależniaj merge’a od werdyktu recenzenta. Uzależnij go od testów, które powstały z jego znalezisk i przy każdym przebiegu dają tę samą odpowiedź.
Test mutacyjny zostawia brudne drzewo robocze. Przerwany przebieg może zostawić usunięte sprawdzenie. Naprawa: prompt kończy się git status; przejrzyj ten wynik przed commitem, a sam test uruchamiaj na czystej gałęzi lub w osobnym worktree.
npm audit pada na tranzytywnym CVE, którego nie da się zaktualizować. Naprawa: sprawdź, czy podatna funkcja jest osiągalna z twojego kodu, zapisz decyzję z datą ważności i użyj overrides w package.json, żeby przypiąć poprawioną wersję tranzytywną, jeśli taka istnieje. Nie sięgaj po npm audit fix --force bez pełnego zestawu testów, bo może zainstalować nowe wersje major z niekompatybilnymi zmianami.
Skanery są zielone, a błąd i tak trafia na produkcję. To właśnie luka, dla której istnieje ta strona: skaner nie zna twoich reguł własności. Naprawa: dodaj incydent jako wiersz macierzy autoryzacji lub nowy przypadek nadużycia, potwierdź, że nie przechodzi na starym kodzie, i zostaw go na zawsze jako test regresji. Sklasyfikuj ucieczkę według taksonomii błędów, żeby naprawić najwcześniejszą brakującą kontrolę.