Przejdź do głównej zawartości

Testy 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.

  • 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.

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ŁapieNie łapieBlokuje merge?
Analiza statyczna (Semgrep)Miejsca podatne na wstrzyknięcia, niebezpieczne API, znane złe wzorceWszystkiego, co zależy od tego, kto jest właścicielem którego rekorduTak, przy ważności ERROR
Skanowanie sekretów (Gitleaks)Klucze i tokeny w diffie lub historiiSekretów wczytanych poprawnie, ale później zalogowanychTylko 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ściBłędów w twoim własnym kodzieTak, 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ówKlas błędów, dla których nikt nie napisał przypadkuTak
Recenzja bezpieczeństwa AIBłędy, do których nie pasuje żaden wzorzec, np. sprawdzenie uprawnień na niewłaściwym obiekciePowtarzalnoś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.

  1. 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.

  2. 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ś.

  3. 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.

  4. 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.

  5. 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.

  6. 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-review

Do 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):

Okno terminala
# Terminal, from the repository root
git 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.

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.

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.

.github/workflows/security.yml
name: security
on:
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/security

Oznacz 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.

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ę.