ai-agents-for-beginners

Obejrzyj wideo z lekcji: Zabezpieczanie agentów AI za pomocą kryptograficznych pokwitowań

(Wideo z lekcji i miniaturka zostaną dodane przez zespół ds. zawartości Microsoft po scaleniu, zgodnie ze schematem lekcji 14 / 15.)

Zabezpieczanie agentów AI za pomocą kryptograficznych pokwitowań

Wprowadzenie

W tej lekcji omówimy:

Cele nauki

Po ukończeniu tej lekcji będziesz potrafił:

Problem: Ścieżka audytu Twojego agenta

Wyobraź sobie, że wdrożyłeś agenta AI dla Contoso Travel. Agent odczytuje prośby klientów, wywołuje interfejs API lotów, aby sprawdzić opcje, i rezerwuje miejsca w imieniu klienta. W ostatnim kwartale agent obsłużył 50 000 rezerwacji.

Dziś pojawia się audytor. Zadaje proste pytanie: “Pokaż mi, co zrobił Twój agent.”

Wręczasz mu pliki dziennika. Audytor je przegląda i zadaje trudniejsze pytanie: “Skąd mam wiedzieć, że te dzienniki nie zostały zmienione?”

To jest problem ścieżki audytu. Większość dzisiejszych wdrożeń agentów opiera się na:

Żadne z nich nie potrafi odpowiedzieć na pytanie audytora bez zmuszania go do zaufania komuś (tobie, twojemu dostawcy chmury, dostawcy bazy danych). W zastosowaniach wewnętrznych takie zaufanie jest często akceptowalne. W przypadku obciążeń regulowanych (finanse, opieka zdrowotna, wszystko objęte europejską ustawą o AI) nie jest.

Kryptograficzne pokwitowania rozwiązują ten problem, umożliwiając niezależną weryfikację każdego działania agenta. Audytor nie musi ufać tobie. Potrzebuje tylko twojego klucza publicznego i samego pokwitowania.

Co to jest kryptograficzne pokwitowanie?

Pokwitowanie to obiekt JSON rejestrujący, co agent zrobił, podpisany podpisem cyfrowym.

flowchart LR
    A[Agent wywołuje narzędzie] --> B[Zbuduj ładunek potwierdzenia]
    B --> C[Kanonizacja JSON RFC 8785]
    C --> D[Skrót SHA-256]
    D --> E[Podpis Ed25519]
    E --> F[Potwierdzenie z podpisem]
    F --> G[Audytor weryfikuje offline]
    G --> H{Podpis ważny?}
    H -- yes --> I[Dowód wykazujący manipulację]
    H -- no --> J[Potwierdzenie odrzucone]

Minimalne pokwitowanie wygląda tak:

{
  "type": "agent.tool_call.v1",
  "agent_id": "contoso-travel-bot",
  "tool_name": "lookup_flights",
  "tool_args_hash": "sha256:a3f9c1...",
  "result_hash": "sha256:7b2e1d...",
  "policy_id": "contoso-travel-policy-v3",
  "timestamp": "2026-04-25T14:30:00Z",
  "sequence": 47,
  "previous_receipt_hash": "sha256:9d4e6a...",
  "signature": {
    "alg": "EdDSA",
    "sig": "c5af83...",
    "public_key": "8f3b2c..."
  }
}

Trzy cechy sprawiają, że to działa:

  1. Podpis. Pokwitowanie jest podpisane przez bramę agenta używając prywatnego klucza Ed25519. Każdy, kto ma odpowiedni klucz publiczny, może zweryfikować podpis offline. Każda modyfikacja jakiegokolwiek pola unieważnia podpis.

  2. Kanoniczne kodowanie. Przed podpisaniem pokwitowanie jest serializowane za pomocą JSON Canonicalization Scheme (JCS, RFC 8785). Zapewnia to, że dwie implementacje generujące ten sam logiczny pokwitowanie dają identyczne bajtowo wyniki. Bez kanoniczności różni serializatorzy JSON wygenerowaliby różne podpisy dla tej samej zawartości.

  3. Łączenie za pomocą hasha. Pole previous_receipt_hash łączy każde pokwitowanie z poprzednim. Usunięcie lub zmiana kolejności pokwitowania łamie każde pokwitowanie powstałe po nim. Manipulacja jest widoczna na poziomie łańcucha, nawet jeśli podpisy poszczególnych pokwitowań zostałyby obejściem.

Razem te cechy dają trzy gwarancje:

Tworzenie pokwitowania w Pythonie

Nie potrzebujesz specjalnej biblioteki do tworzenia pokwitowania. Prymitywy kryptograficzne są powszechnie dostępne, a logika to kilka dziesiątek linii Pythona.

Ćwiczenia praktyczne w code_samples/18-signed-receipts.ipynb przeprowadzają przez cały proces. Podsumowanie:

import json
import hashlib
import base64
from nacl import signing
from jcs import canonicalize  # Kanoniczny JSON zgodny z RFC 8785

def b64url_nopad(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")

def sha256_canonical(obj) -> str:
    """SHA-256 of a Python object's JCS-canonical JSON form."""
    return f"sha256:{hashlib.sha256(canonicalize(obj)).hexdigest()}"

# Wygeneruj lub załaduj klucz do podpisu (w produkcji przechowuj w skarbcu kluczy)
signing_key = signing.SigningKey.generate()
verify_key = signing_key.verify_key

# Zbuduj ładunek potwierdzenia (jeszcze bez podpisu)
tool_args = {"origin": "SYD", "destination": "LAX"}
tool_result = [{"flight": "QF11", "price": 1850, "stops": 0}]

payload = {
    "type": "agent.tool_call.v1",
    "agent_id": "contoso-travel-bot",
    "tool_name": "lookup_flights",
    "tool_args_hash": sha256_canonical(tool_args),
    "result_hash": sha256_canonical(tool_result),
    "policy_id": "contoso-travel-policy-v3",
    "timestamp": "2026-04-25T14:30:00Z",
    "sequence": 0,
    "previous_receipt_hash": None,
}

# Kanonizuj, haszuj, podpisz.
canonical_bytes = canonicalize(payload)
message_hash = hashlib.sha256(canonical_bytes).digest()
signature_bytes = signing_key.sign(message_hash).signature

# Dołącz uporządkowany obiekt podpisu.
receipt = {
    **payload,
    "signature": {
        "alg": "EdDSA",
        "sig": b64url_nopad(signature_bytes),
        "public_key": b64url_nopad(bytes(verify_key)),
    },
}

To cały pipeline podpisywania. Ćwiczenia w notatniku przeprowadzają przez każdy krok.

Weryfikacja pokwitowania i wykrywanie manipulacji

Weryfikacja to operacja odwrotna:

import base64
import hashlib
from nacl import signing
from nacl.exceptions import BadSignatureError
from jcs import canonicalize

def b64url_decode(s: str) -> bytes:
    padding = "=" * ((4 - len(s) % 4) % 4)
    return base64.urlsafe_b64decode(s + padding)

def verify_receipt(receipt: dict) -> bool:
    # Sygnatura jest obiektem strukturalnym: {"alg", "sig", "public_key"}.
    sig_obj = receipt.get("signature")
    if not sig_obj or sig_obj.get("alg") != "EdDSA":
        return False

    # Odtwórz ładunek, który został faktycznie podpisany (wszystko oprócz sygnatury).
    payload = {k: v for k, v in receipt.items() if k != "signature"}

    canonical_bytes = canonicalize(payload)
    message_hash = hashlib.sha256(canonical_bytes).digest()

    try:
        verify_key = signing.VerifyKey(b64url_decode(sig_obj["public_key"]))
        verify_key.verify(message_hash, b64url_decode(sig_obj["sig"]))
        return True
    except BadSignatureError:
        return False

Funkcja przyjmuje pokwitowanie i zwraca True, jeśli podpis jest ważny, False w przeciwnym razie. Bez wywołań sieciowych, bez zależności od usług, bez konieczności zaufania osobie trzeciej.

Aby zobaczyć działanie wykrywania manipulacji, notatnik pokazuje:

  1. Wygenerowanie ważnego pokwitowania i potwierdzenie jego weryfikacji.
  2. Modyfikację jednego bajtu pola tool_args_hash.
  3. Ponowne uruchomienie weryfikacji i jej niepowodzenie.

To praktyczny dowód na to, że pokwitowania są odporne na manipulacje: każda zmiana, choćby najmniejsza, łamie podpis.

Łączenie pokwitowań dla agentów wieloetapowych

Pojedyncze podpisane pokwitowanie chroni jedno działanie. Łańcuch pokwitowań chroni sekwencję.

flowchart LR
    R0[Pokwitowanie 0<br/>geneza] --> R1[Pokwitowanie 1]
    R1 --> R2[Pokwitowanie 2]
    R2 --> R3[Pokwitowanie 3]
    R1 -. previous_receipt_hash .-> R0
    R2 -. previous_receipt_hash .-> R1
    R3 -. previous_receipt_hash .-> R2

Każde pokwitowanie zapisuje hash pokwitowania poprzedniego. Aby potajemnie usunąć pokwitowanie 2, atakujący musiałby:

Jeśli klucz prywatny jest w sprzętowym sejfie kluczy, a ty publikujesz klucz publiczny z każdym pokwitowaniem, żaden z tych ataków nie jest możliwy bez wykrycia.

Notatnik pokazuje:

  1. Budowę łańcucha trzech pokwitowań.
  2. Weryfikację, że previous_receipt_hash każdego pokwitowania odpowiada faktycznemu hashowi pokwitowania poprzedniego.
  3. Manipulację jednym pokwitowaniem pośrodku i widok przerwania łańcucha dokładnie w tym miejscu.

W ten sposób tworzysz ścieżkę audytu, którą zewnętrzny audytor może zweryfikować bez konieczności zaufania tobie.

Co pokwitowania dowodzą (a czego nie)

To najważniejsza część tej lekcji. Pokwitowania są potężne, ale ich moc jest ograniczona.

Pokwitowania dowodzą trzech rzeczy:

  1. Przypisanie: konkretny klucz podpisał określony ładunek.
  2. Integralność: ładunek nie zmienił się od momentu podpisania.
  3. Kolejność: to pokwitowanie jest potem w łańcuchu niż tamto.

Pokwitowania NIE dowodzą:

  1. Poprawności: że działanie agenta było właściwe. Pokwitowanie może podpisać zarówno właściwą, jak i błędną odpowiedź równie czysto.
  2. Zgodności z polityką: że polityka odwołana przez policy_id została faktycznie oceniona lub że dopuściłaby to działanie, gdyby została sprawdzona. Pokwitowanie odnotowuje, co zostało zgłoszone, a nie co zostało wymuszone.
  3. Tożsamości poza kluczem: pokwitowanie mówi “ten klucz podpisał tę zawartość”. Nie mówi “ta osoba to zatwierdziła”. Połączenie klucza z osobą lub organizacją wymaga odrębnej infrastruktury tożsamości (katalogu, rejestru kluczy publicznych itp.).
  4. Prawdziwości danych wejściowych: jeśli agent otrzyma zmodyfikowany prompt i na niego zareaguje, pokwitowanie wiernie rejestruje to działanie. Pokwitowania są downstream od walidacji wejścia, a nie jej substytutem.

Ta granica jest istotna z dwóch powodów:

Częstym błędem jest założenie, że “mam pokwitowania” oznacza “mam zarządzanie.” Tak nie jest. Pokwitowania są fundamentem. Zarządzanie to system, który z nich budujesz.

Dowodzenie, że człowiek zatwierdził konkretne działanie

Punkt 3 powyżej zasługuje na osobną sekcję: pokwitowanie działania mówi “ten klucz podpisał tę zawartość”, nigdy “człowiek to zatwierdził”. Dla działań wysokiego ryzyka (zwroty, usunięcia, przelewy) ramy zarządzania coraz częściej wymagają dokładnie tego brakującego oświadczenia, a można je wygenerować za pomocą tych samych prymitywów, które zbudowałeś w tej lekcji.

Notatnik „code_samples/human-authorization-receipts.ipynb” dodaje drugi rodzaj pokwitowania, human.approval.v1, w tej samej formie koperty jak pokwitowania z lekcji (typowany ładunek podpisany Ed25519 po kanonicznym SHA-256, z obiektem signature na zewnątrz podpisanych bajtów). Nazwany zatwierdzający podpisuje pełne kanoniczne działanie i jego skrót przed wykonaniem; pokwitowanie działania agenta zawiera ten sam skrót działania oraz parent_approval_ref, czyli receipt_hash zatwierdzenia, w tej samej konwencji co previous_receipt_hash w łańcuchu powyżej. Jedna funkcja verify_chain obsługuje oba artefakty pod oddzielnymi przypiętymi rejestrami kluczy (klucze zatwierdzających vs klucze agentów), więc ścieżka kodu jest wspólna, ale autorytety nigdy.

Efekt, wyrażony precyzyjnie: człowiek zatwierdził dokładnie to działanie, a agent wykonał dokładnie to zatwierdzone działanie. W notatniku przypadki odrzuceń sprawiają, że właściwość jest rzeczywista, a nie tylko deklarowana:

Każda awaria odrzuca z odrębnym powodem, więc audytor czytający odrzucenie może rozpoznać, czy uprawnienia wygasły, czy działanie się zmieniło. Zasada nauczana przez notatnik: podpisane zatwierdzenie nie jest samo w sobie autorytetem. Autorytet istnieje tylko wtedy, gdy oba pokwitowania nadal wiążą się z tym samym kanonicznym działaniem w momencie wykonania. Ścieżka współpodpisu w tym samym projekcie Internet-Draft, który ta lekcja śledzi (draft-farley-acta-signed-receipts), jest oficjalnym wzorem tego schematu.

Odniesienia produkcyjne

Kod Pythona z tej lekcji jest celowo zwięzły, abyś mógł przeczytać każdy wiersz i dokładnie zrozumieć, co się dzieje. W produkcji masz dwie opcje:

  1. Buduj bezpośrednio na prymitywach kryptograficznych. 50 linii, które widziałeś powyżej, wystarczy dla wielu zastosowań. Biblioteki PyNaCl (Ed25519) i jcs (kanoniczny JSON) są dobrze utrzymane i audytowane.

  2. Użyj biblioteki produkcyjnej do pokwitowań. Kilka projektów open-source implementuje ten sam wzór z dodatkowymi funkcjami (rotacja kluczy, weryfikacja masowa, dystrybucja zestawu JWK, integracja z silnikami polityk):

    • Format pokwitowań użyty w tej lekcji odpowiada IETF Internet-Draft (draft-farley-acta-signed-receipts, wersja 02) obecnie w procesie standaryzacji, z wspólnym zestawem testów zgodności (agent-governance-testvectors) pozwalającym niezależnym implementacjom na wzajemną weryfikację identyczności bajtowej wyjścia kanonicznego.
    • Microsoft Agent Governance Toolkit łączy pokwitowania z decyzjami polityki opartymi na Cedar; zobacz Tutorial 33 w tym repozytorium dla przykładu end-to-end.
    • Pakiety protect-mcp (npm) i @veritasacta/verify (npm) dostarczają implementacji Node do podpisywania pokwitowań i weryfikacji offline, przeznaczone do opakowania dowolnego serwera MCP w odporną na manipulacje ścieżkę audytu, w tym przepływ hold-for-co-sign, gdzie wstrzymane działanie generuje pokwitowanie zatwierdzające związane ze skrótem działania (WebAuthn wspierany w trybie desktop), ten sam wzór pokwitowania zatwierdzenia jak w notatniku zatwierdzania przez człowieka powyżej.
    • Python SDK nobulex (pip install nobulex) zapewnia ten sam wzór podpisu Ed25519 + JCS w Pythonie z integracją LangChain i CrewAI, w tym opublikowanymi wektorami testowymi do walidacji krzyżowej i mapowaniem zgodności dostarczonym poprzez OWASP PR #2210.

Decyzja między tworzeniem własnego rozwiązania a użyciem biblioteki przypomina wybór między napisaniem własnej biblioteki JWT a użyciem przetestowanej: obie opcje są rozsądne; biblioteka oszczędza czas i zmniejsza powierzchnię audytu; własne rozwiązanie zmusza do zrozumienia każdego prymitywu. Ta lekcja uczy od podstaw, abyś miał fundament pod obie decyzje.

Sprawdzenie wiedzy

Sprawdź swoje rozumienie przed przejściem do ćwiczenia praktycznego.

1. Pokwitowanie jest podpisane prywatnym kluczem Ed25519 agenta. Audytor ma tylko klucz publiczny. Czy audytor może zweryfikować pokwitowanie offline?

Odpowiedź Tak. Weryfikacja Ed25519 wymaga tylko klucza publicznego i podpisanych bajtów. Bez wywołań sieciowych, bez zależności od usług. To cecha, która czyni pokwitowania przydatnymi w środowiskach odciętych od sieci, wieloorganizacyjnych lub o niskim poziomie zaufania.

2. Atakujący modyfikuje pole policy_id pokwitowania, twierdząc, że było ono regulowane bardziej liberalną polityką. Podpis dotyczył oryginalnego ładunku. Co się dzieje podczas weryfikacji?

Odpowiedź Weryfikacja nie powiodła się. Podpis został obliczony na kanonicznych bajtach oryginalnej zawartości; zmiana jakiegokolwiek pola zmienia kanoniczne bajty, co zmienia hash SHA-256, a to powoduje unieważnienie podpisu. Atakujący potrzebowałby klucza prywatnego, aby wygenerować nowy ważny podpis, którego nie posiada.

3. Dlaczego paragon zawiera tool_args_hash i result_hash, zamiast surowych argumentów i wyniku?

Odpowiedź Dwa powody. Po pierwsze, paragon może wymagać archiwizacji lub przesłania w środowiskach, gdzie ujawnienie surowej zawartości (dane PII, dane biznesowe) stanowi problem. Hashowanie utrzymuje paragon niewielkim i chroni zawartość; audytor weryfikuje, czy hash zgadza się z osobno przechowywaną kopią faktycznej zawartości. Po drugie, hashe mają stały rozmiar; paragon z hashami ma ograniczony rozmiar bez względu na wielkość wejść i wyjść.

4. Pole previous_receipt_hash łączy każdy paragon z jego poprzednikiem. Co stanie się nieważne, jeśli atakujący potajemnie usunie paragon ze środka łańcucha?

Odpowiedź Każdy paragon, który wystąpił po usuniętym. Ich pola `previous_receipt_hash` nie będą się już zgadzać z faktycznym łańcuchem (ponieważ wskazywany paragon już nie istnieje lub łańcuch teraz wskazuje na innego poprzednika). Aby ukryć usunięcie, atakujący musiałby ponownie podpisać każdy późniejszy paragon, co wymaga klucza prywatnego.

5. Paragon weryfikuje się poprawnie. Czy to dowód, że działanie agenta było poprawne, zgodne lub zgodne z polityką?

Odpowiedź Nie. Ważny paragon dowodzi trzech rzeczy: przypisania (ten klucz podpisał tę zawartość), integralności (zawartość nie została zmieniona) oraz kolejności (ten paragon pojawił się po tamtym paragonie). NIE dowodzi, że działanie było poprawne, że polityka wskazana w `policy_id` została faktycznie oceniona, ani że agent przestrzegał każdej reguły. Paragony czynią zachowanie agenta audytowalnym, niekoniecznie poprawnym. To najważniejsza granica w lekcji.

Ćwiczenie praktyczne

Otwórz code_samples/18-signed-receipts.ipynb i ukończ wszystkie cztery sekcje:

  1. Sekcja 1: Podpisz swój pierwszy paragon i zweryfikuj go.
  2. Sekcja 2: Sfałszuj paragon i zaobserwuj, że weryfikacja nie powiodła się.
  3. Sekcja 3: Zbuduj łańcuch trzech paragonów i sprawdź integralność łańcucha.
  4. Sekcja 4: Zastosuj wzór do agenta zbudowanego w Microsoft Agent Framework: otocz wywołanie narzędzia podpisywaniem paragonu, następnie zweryfikuj paragon niezależnie.

Wyzwanie dodatkowe 1: rozszerz schemat paragonu o dodatkowe pole dowolnego wyboru (np. identyfikator żądania do śledzenia), zaktualizuj logikę kanonicznego podpisu, aby je uwzględnić, i potwierdź, że paragon nadal przechodzi weryfikację. Następnie zmodyfikuj pole po podpisaniu i potwierdź, że weryfikacja się nie powiodła. To wymusza zrozumienie, jak każdy bajt kodowania kanonicznego wpływa na podpis.

Wyzwanie dodatkowe 2: skróć dwie swoje paragony SHA-256 razem (połącz ich kanoniczne bajty w deterministycznej kolejności) i wstaw powstały skrót jako nowe pole w trzecim paragonie przed podpisaniem. Zweryfikuj, że wszystkie trzy paragony nadal przechodzą weryfikację. Właśnie zbudowałeś dowód włączenia w jednym kroku: każdy posiadający trzeci paragon może udowodnić, że pierwsze dwa istniały w momencie jego podpisania, bez konieczności ujawniania ich zawartości. To wzór, którego używają paragony z selektywnym ujawnianiem na dużą skalę (zob. Merkle commitments, RFC 6962).

Podsumowanie

Kryptograficzne paragony dają agentom AI ślad audytu, który jest:

Nie zastępują walidacji wejścia, egzekwowania polityki ani infrastruktury tożsamości. Są podstawą dla tych warstw. Gdy wdrażasz agentów w regulowanych środowiskach, wieloorganizacyjnych przepływach pracy lub jakimkolwiek miejscu, gdzie przyszły audytor nie może zakładać Twojego zaufania, paragony uczciwie tworzą ślad audytowy.

Najważniejsza nauka: paragony dowodzą, kto co i kiedy powiedział. Nie dowodzą, że to, co powiedział, było prawdziwe lub słuszne. Trzymaj tę różnicę mocno. To odróżnia uczciwy system pochodzenia od wprowadzającego w błąd.

Lista kontrolna produkcji

Gdy będziesz gotowy, by przejść z tej lekcji do wdrożenia agentów podpisujących paragony w realnym środowisku:

Masz więcej pytań o zabezpieczanie agentów AI?

Dołącz do Microsoft Foundry Discord, aby spotkać się z innymi uczącymi się, uczestniczyć w godzinach konsultacji i uzyskać odpowiedzi na pytania dotyczące agentów AI.

Poza tą lekcją

Ta lekcja obejmuje podpisywanie pojedynczego paragonu i sekwencje połączone hasłami. Te same prymitywy tworzą kilka bardziej zaawansowanych wzorców, które możesz spotkać, gdy Twoja postawa zarządzania dojrzeje:

Dodatkowe zasoby

Poprzednia lekcja

Tworzenie lokalnych agentów AI


Zastrzeżenie: Niniejszy dokument został przetłumaczony za pomocą usługi tłumaczenia AI Co-op Translator. Choć dążymy do dokładności, prosimy pamiętać, że automatyczne tłumaczenia mogą zawierać błędy lub niedokładności. Oryginalny dokument w jego języku źródłowym należy uznawać za autorytatywne źródło. W przypadku informacji krytycznych zalecane jest skorzystanie z profesjonalnego tłumaczenia wykonanego przez człowieka. Nie ponosimy odpowiedzialności za jakiekolwiek nieporozumienia lub błędne interpretacje wynikające z użycia tego tłumaczenia.