# Seif (/ro/docs/api/gateway/vault)

Seif de credențiale, zero-knowledge în mod implicit: sincronizare criptată, broker pentru agenți, aprobări și chei de dispozitiv.

Kodwen Vault îți păstrează credențialele criptate pe propriile dispozitive: clienții sigilează fiecare element (XChaCha20-Poly1305, chei împachetate cu hibridul post-cuantic X-Wing) înainte de încărcare, deci în mod implicit `/v1/vault/*` stochează și sincronizează text cifrat pe care Kodwen nu îl poate citi. Seiful este **zero-knowledge în mod implicit**, cu o singură excepție, dezactivată implicit și setată pentru fiecare element: proprietarul unui seif poate marca un element ca **utilizabil în cloud** (`PUT /v1/vault/items/{id}/cloud`). Clientul trimite atunci elementul decriptat, iar gateway-ul păstrează o a doua copie, sigilată cu o cheie KMS Kodwen, pe care o poate deschide ca să servească cererile agenților (`/v1/vault/http`, `/v1/vault/resolve`, `/v1/vault/derive`) cât timp dispozitivele tale sunt offline. Doar proprietarul seifului o poate activa sau dezactiva; dezactivarea, modificarea elementului sau ștergerea lui elimină acea copie. Agenții nu primesc niciodată valori secrete. Ei văd un catalog de handle-uri (`{{vault:<slug>}}`), cer o decizie de politică (niveluri `t0`-`t4` sau `deny`), așteaptă aprobarea ta când nivelul o cere și apelează endpoint-uri HTTPS intermediate al căror răspuns este redactat.

SDK-urile expun toată suprafața ca `codai.vault` (TypeScript și Python), iar cheile de dispozitiv sub `codai.vault.devices`.

## Elemente: sincronizare, scriere și roluri [#elemente-sincronizare-scriere-și-roluri]

### Citirea și sincronizarea elementelor [#citirea-și-sincronizarea-elementelor]

`GET /v1/vault/items` răspunde `{ items, server_time, has_more }`. Fiecare element este rândul stocat: `id`, `vault_id`, `revision`, `wrapped_key`, `ciphertext`, `kind`, `slug`, `agent_meta`, `honeytoken`, `cloud_usable`, `deleted_at`, `last_used_at`, `created_at`, `updated_at`.

- **Lista completă** (fără `since`): doar elementele active, cele mai recent modificate primele, cel mult 5000. `has_more` este adevărat când există peste 5000 de elemente active.
- **Modificări** (`?since=<dată și oră ISO 8601>`): fiecare element modificat după `since`, inclusiv cele șterse (`deleted_at` este setat: elimină elementul local), cele mai vechi primele, cel mult 1000 pe apel.
- **Elemente șterse**: un element șters de mai mult de 30 de zile păstrează doar un rând de marcaj, iar `wrapped_key` și `ciphertext` ale lui sunt șiruri goale, deci un client nu trebuie să decripteze niciodată un rând care are `deleted_at`.
- **`has_more: true`**: apelează din nou imediat cu `since` = `server_time` primit, care este atunci `updated_at` al ultimului element întors. Bazează-te pe `has_more`, nu pe numărul de elemente: o pagină nu este tăiată niciodată în interiorul unei milisecunde, deci poate avea mai puțin de 1000 de elemente și are peste 1000 doar când mai mult de 1000 de elemente s-au modificat în aceeași milisecundă.
- **`has_more: false`**: ești la zi. `server_time` este ceasul bazei de date, citit înaintea elementelor, minus 5 secunde; păstrează-l ca `since` al următoarei sincronizări. Poate fi mai devreme decât `since`-ul trimis de tine. Un element modificat în acele ultime secunde poate sosi din nou: înlocuiește copia ta a acelui `id`.
- Un `since` care nu poate fi citit ca dată și oră, inclusiv o valoare goală, răspunde 400 `invalid_since`. Momentele se compară la milisecundă.

### Scrierea elementelor [#scrierea-elementelor]

`PUT /v1/vault/items/{id} { vault_id, revision, wrapped_key, ciphertext, kind, slug?, agent_meta?, honeytoken? }` răspunde `201 { id, revision }` pentru un element nou și `200 { id, revision }` pentru o actualizare. Corpul nu are `id`: calea numește elementul, iar o cheie pe care ruta nu o cunoaște răspunde 400.

- Un element nou trimite `revision: 1`; o actualizare trimite revizia stocată + 1. Orice altceva răspunde 409 `{ error: { code: 'revision_conflict' }, revision }` cu revizia stocată: reîncarcă elementul și reîncearcă.
- **409 `item_deleted`**: elementul a fost șters pe alt dispozitiv și rămâne șters. Elimină-l local; ca să îi păstrezi conținutul, creează un element nou cu un id nou.
- `kind` și seiful unui element sunt fixate la creare; o schimbare răspunde 400.
- Expunerea către agenți (`slug` și `agent_meta`, trimise mereu împreună) și `honeytoken` sunt decizii ale proprietarului sau ale unui manager al seifului. Un editor care creează un element cu ele sau le schimbă primește 403. Fiecare actualizare trimite înapoi neschimbate `slug`, `agent_meta` și `honeytoken` stocate; omiterea lor contează ca schimbare.
- Un `slug` deja folosit în același seif răspunde 400. O schimbare de conținut elimină copia pentru cloud a elementului.

`DELETE /v1/vault/items/{id}` păstrează rândul ca element șters pentru sincronizare, revocă toate permisiunile pe el și îi elimină copia pentru cloud. Ștergerea unui element deja șters răspunde același `200 { deleted: true }` și nu schimbă nimic.

### Înregistrarea unei folosiri locale [#înregistrarea-unei-folosiri-locale]

`POST /v1/vault/items/{id}/used { via }` înregistrează că un passkey sau o autentificare a fost folosită pe un dispozitiv (`via`: `windows_plugin`, `android_provider`, `ios_provider`, `desktop` sau `phone`) și răspunde `{ id, last_used_at }`. Se stochează doar momentul, niciodată conținutul. Ruta păstrează o singură marcare pe element la 60 de secunde: o repetare în acest interval răspunde 200 cu `last_used_at` stocat, nu scrie nimic și nu înregistrează niciun eveniment `item.used`. Nu răspunde niciodată 429. Un element necunoscut, șters sau inaccesibil răspunde 404.

### Ce poate face fiecare rol pe elemente [#ce-poate-face-fiecare-rol-pe-elemente]

| Acțiune | viewer | editor | manager | owner |
| --- | --- | --- | --- | --- |
| Citește și sincronizează elementele (`GET /v1/vault/items`) | da | da | da | da |
| Înregistrează o folosire locală (`POST /v1/vault/items/{id}/used`) | da | da | da | da |
| Creează, modifică și șterge elemente; setează limitele și programul de rotire | nu | da | da | da |
| Setează sau schimbă expunerea către agenți (`slug`, `agent_meta`) și `honeytoken` | nu | nu | da | da |
| Activează sau dezactivează utilizarea în cloud (`PUT /v1/vault/items/{id}/cloud`); setează sau elimină un derivator | nu | nu | nu | da |

Un apelant care nu este membru al seifului primește 404, același răspuns ca pentru un seif care nu există.

### Răspunsuri 429 [#răspunsuri-429]

Fiecare 429 are corpul `{ error: { message, type: 'rate_limit_error', code } }` și un antet `Retry-After` în secunde.

| `code` | Rute | Când | `Retry-After` |
| --- | --- | --- | --- |
| `vault_rate_limited` | `POST /v1/vault/use`, `/v1/vault/http`, `/v1/vault/resolve`, `/v1/vault/derive` | a fost atinsă o limită per handle a elementului | 60 pentru limita pe minut, 3600 pentru limitele zilnice |
| `rate_limit_exceeded` | `GET /v1/vault/hibp/range/{prefix}` | peste 300 de apeluri pe utilizator într-o oră | 600 |
| `rate_limit_exceeded` | `POST /v1/vault/add-requests` | există deja 20 de cereri de adăugare în așteptare | 60 |

## Chei de dispozitiv și confirmare dovedită [#chei-de-dispozitiv-și-confirmare-dovedită]

O aprobare dovedește cine a decis. Pe lângă semnătura v1 a cheii seifului, o decizie poate purta o **dovadă** v2 de la o **cheie de dispozitiv** protejată hardware (Android Keystore pe telefon și ceas, Windows Hello pe desktop) sau de la un **passkey**. Cheile de dispozitiv sunt doar chei publice; cheia privată nu părăsește niciodată hardware-ul securizat al dispozitivului.

### Înrolarea [#înrolarea]

1. `POST /v1/vault/devices/challenge` întoarce un `nonce` de unică folosință (32 de octeți aleatori, base64url), valabil 5 minute.
2. Dispozitivul își creează cheia, iar cheia seifului și cheia dispozitivului semnează amândouă mesajul de înrolare:
   `codai-vault/device-enroll/v1|<device_id>|<kind>|<spki_sha256>|<nonce>`, unde `spki_sha256` este base64url(SHA-256(SubjectPublicKeyInfo DER)).
3. `POST /v1/vault/devices` trimite `device_id` (`android-…`, `wear-…`, `desktop-…`, `ios-…` sau `cli-…`), `kind` (`android_keystore`, `windows_hello`, `ios_secure_enclave` sau `cli_ed25519`), `name`, `surface` (`phone`, `wear`, `desktop` sau `cli`), `public_key`, `nonce`, `vault_signature`, `device_signature` și, când există, o `attestation`. Răspunsul este `201 { device }`.

Reînrolarea aceluiași `device_id` îi înlocuiește cheia. `GET /v1/vault/devices` listează cheile tale (inclusiv cele revocate), `PATCH /v1/vault/devices/{id}` redenumește una, iar `DELETE /v1/vault/devices/{id}` o revocă imediat: următoarea ei dovadă primește 403 `device_revoked`.

Toate valorile binare sunt base64url fără padding. Android semnează cu ECDSA P-256 SHA-256 (DER); Windows Hello semnează cu RSASSA-PKCS1-v1_5 SHA-256; `cli_ed25519` (cheia software creată de `codaid vault enroll`) semnează cu Ed25519 (semnătură brută de 64 de octeți, `public_key` = SubjectPublicKeyInfo DER Ed25519).

### Tipuri de atestare [#tipuri-de-atestare]

| `attestation` | Semnificație |
| --- | --- |
| `android-strongbox` | Atestarea Google a cheii verificată, cheia ținută într-un element securizat StrongBox. |
| `android-tee` | Atestarea Google a cheii verificată, cheia ținută în TEE. |
| `tpm` | O atestare TPM a cheii Windows Hello, verificată față de rădăcinile TPM Microsoft. |
| `hello-unattested` | O cheie Windows Hello fără atestare TPM. |
| `ios-unattested` | O cheie P-256 din Secure Enclave pe iPhone (`ios_secure_enclave`, suprafața `phone`, Face ID la fiecare semnătură). Încă fără atestare, deci aprobă doar cereri T3; o aprobare T4 primește 403 `step_up_required`. |
| `software` | O cheie `cli_ed25519` ținută într-un fișier pe calculator. Poate dovedi doar apeluri `/v1/vault/resolve`; nu poate aproba și nu poate debloca niciodată (403 `device_not_allowed`). |

Cheile Android trebuie să trimită `attestation: { android_chain: [...] }` (frunza prima); provocarea de atestare este formată din octeții nonce-ului, iar cheia atestată trebuie să fie egală cu `public_key`. Cheile Windows Hello pot trimite `attestation: { tpm: { attestation, cert_chain } }`; fără ea cheia este `hello-unattested`, cu una invalidă înrolarea eșuează. Cheile de iPhone (`ios_secure_enclave`) nu trimit deloc `attestation` (cu una, înrolarea eșuează cu `attestation_invalid`); semnătura este ECDSA P-256 SHA-256 în DER, ca la Android. Erori: 400 `invalid_nonce`, `possession_failed`, `attestation_required`, `attestation_invalid`; 403 `invalid_signature`; 409 `device_id_taken`; 503 `revocation_unavailable` (lista de revocare Google nu a putut fi verificată, reîncearcă mai târziu).

### Mesajele de aprobare [#mesajele-de-aprobare]

- v1 (cheia seifului): `codai-vault/approve/v1|<approval_id>|<action_hash>|<decision>|<scope>`, trimis ca `signature`.
- v2 (cheie de dispozitiv sau passkey): `codai-vault/approve/v2|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>`, trimis ca `proof: { type: 'device', device_id, signature }`.
- v3 (cheie de dispozitiv sau passkey + potrivirea numărului): `codai-vault/approve/v3|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>|<preview_digest>|<match_number>`, trimis cu `match_number` lângă `proof`. `preview_digest` este base64url(SHA-256) al textului canonic al `request.preview` (`codai-vault/preview/v1`, apoi `title`, `agent`, `mode`, `host`, `action`, `purpose`, `task` ca linii `cheie=valoare`, cu `\`, linie nouă și CR escapate), deci semnătura acoperă exact ce a văzut aprobatorul.
- passkey: provocarea WebAuthn este base64url(SHA-256(mesajul v2 cu id-ul de dispozitiv `passkey`)), trimisă ca `proof: { type: 'passkey', response }`.

Trimite cel mult unul dintre `signature` și `proof`. `step_up_acr` este încă acceptat, dar ignorat: o confirmare declarată de client nu dovedește nimic.

### Reguli pe niveluri [#reguli-pe-niveluri]

- **Refuzul** nu cere dovadă (butonul Refuză din notificare este suficient).
- **Aprobarea T3** acceptă o semnătură v1 validă, o dovadă de dispozitiv sau o dovadă passkey.
- **Aprobarea T4** cere o dovadă de dispozitiv sau passkey; o semnătură v1 primește 403 `step_up_required`. Toate tipurile de atestare de mai sus sunt acceptate astăzi.
- **Ceasul Wear aprobă doar T3**: o cheie de ceas nu poate fi atestată ca legată de biometrie, deci o dovadă de la un dispozitiv `wear` pe o aprobare T4 primește 403 `wear_t3_only`.
- **Potrivirea numărului**: fiecare cerere T3/T4 primește un `match_number` din 2 cifre, întors doar agentului (`/vault/use`, `/vault/http`, `/vault/resolve`). Aprobatorul vede `request.preview` și trei `request.match_choices`, niciodată numărul. O aprobare T4 pe o astfel de cerere cere `match_number` cu o dovadă v3: fără număr primește 403 `match_required`; un număr greșit refuză imediat cererea și primește 403 `match_mismatch`. Aprobările T3 și refuzurile acceptă în continuare v1/v2.

Alte erori de dovadă (403): `invalid_signature`, `unknown_device`, `device_revoked`, `passkey_invalid`.

### Dovada pentru resolve (brokeri locali) [#dovada-pentru-resolve-brokeri-locali]

`POST /v1/vault/resolve` întoarce valori unui broker local (codaid, desktop) care le injectează într-un proces copil și îi redactează ieșirea. Brokerul trebuie să dovedească faptul că deține o cheie de dispozitiv înrolată; antetul `x-codai-agent` singur nu este de încredere.

1. `POST /v1/vault/resolve/challenge` întoarce un `challenge` de unică folosință, valabil 120 s.
2. Brokerul semnează `codai-vault/resolve/v1|<challenge>|<device_id>|<digest>` cu cheia dispozitivului, unde `digest` = base64url(SHA-256(`handles` unite cu `\n`, apoi `\n\n`, apoi `action`)), cu handle-urile exact cum sunt trimise și în aceeași ordine.
3. Corpul cererii resolve poartă `proof: { device_id, challenge, signature }`.

Erori: 400 `proof_required` (fără dovadă), 400 `invalid_challenge` (necunoscut, folosit sau expirat), 403 `invalid_device_proof` (cheie care nu e a ta, revocată sau semnătură greșită), 403 `broker_only` (nu este un broker Kodwen). Evenimentul de audit `use.resolved` înregistrează `device_id`.

## Protecții la anomalii [#protecții-la-anomalii]

Fiecare utilizare de către un agent este verificată și față de protecțiile tale la anomalii, citite din `GET /v1/vault/account` (`guards`) și schimbate cu `PATCH /v1/vault/settings { default_tier?, guards? }` (trimite cel puțin unul; `guards` este parțial și se combină cu valorile curente, iar răspunsul este `{ default_tier, guards }` efectiv).

| Protecție | Implicit | Se declanșează când | Motiv |
| --- | --- | --- | --- |
| `new_host` | `true` | nicio utilizare permisă sau permisiune pentru acest element și host în ultimele 30 de zile | `anomaly:new_host` |
| `burst_max` / `burst_window_s` | `5` / `60` | cel puțin `burst_max` cereri ale agenților pentru element în fereastră (`burst_max: 0` = dezactivat) | `anomaly:burst` |
| `hour_start` / `hour_end` / `tz` | `7` / `23` / `Europe/Bucharest` | utilizarea cade în afara `[hour_start, hour_end)` în `tz` (ambele ore `null` = dezactivat) | `anomaly:hour` |

O anomalie forțează decizia la **t4**: utilizatorul aprobă cu o cheie de dispozitiv sau un passkey (inclusiv potrivirea numărului), iar permisiunile existente care nu au fost aprobate la t4 sunt ignorate pentru acea utilizare. Un `deny` rămâne `deny`.

## Blocare de urgență și deblocare [#blocare-de-urgență-și-deblocare]

`POST /v1/vault/lock` (corp opțional, `{ via? }`) este butonul de panică: revocă toate permisiunile active, refuză toate aprobările în așteptare și blochează seiful, răspunzând `{ locked_at, grants_revoked, approvals_denied }`. Cheile dispozitivelor rămân înrolate ca să poți debloca. Apelul este idempotent. Cât timp e blocat, `GET /v1/vault/account` arată `locked_at` și `lock_reason` (`panic` sau `honeytoken`), iar orice utilizare de către agenți este refuzată cu motivul `vault_locked` (`/v1/vault/http` și `/v1/vault/resolve` răspund 403 `vault_denied`).

Deblocarea cere o dovadă protejată hardware peste o provocare de unică folosință:

1. `POST /v1/vault/unlock/challenge` întoarce `{ challenge, expires_at, message_template }` (32 de octeți aleatori, base64url, valabilă 5 minute).
2. Construiește mesajul `codai-vault/unlock/v1|<challenge>|<device_id|passkey>`:
   - **cheie de dispozitiv** (telefon, desktop): cheia semnează mesajul UTF-8 cu id-ul dispozitivului tău, trimis ca `proof: { type: 'device', device_id, signature }`. Cheile de ceas sunt refuzate (`wear_t3_only`).
   - **passkey** (hub): provocarea WebAuthn este base64url(SHA-256(mesajul cu `passkey` în locul id-ului de dispozitiv)), trimisă ca `proof: { type: 'passkey', response }`.
3. `POST /v1/vault/unlock { challenge, proof, via? }` răspunde `{ unlocked: true }`. Erori (403): `challenge_invalid`, `unknown_device`, `device_revoked`, `wear_t3_only`, `invalid_signature`, `passkey_invalid`.

Jurnalul de audit înregistrează `vault.locked`, `vault.unlocked`, `vault.unlock_failed` și `use.locked`.

## Momeli (honeytokens) [#momeli-honeytokens]

Un element marcat `honeytoken` este o momeală pe care niciun agent legitim nu ar trebui să o atingă. O utilizare a lui este refuzată și declanșează o alarmă, iar acum **blochează și seiful** (`lock_reason: 'honeytoken'`) și trimite o notificare de date fiecărui dispozitiv activ: `{ kind: 'vault.alarm', reason: 'honeytoken', locked: '1', title?, agent?, host? }` (o blocare de urgență trimite aceeași alarmă cu `reason: 'panic'`). Deblochează ca mai sus după ce știi ce s-a întâmplat.

## Limite per handle [#limite-per-handle]

Limitează cât de des pot agenții folosi un element. `GET /v1/vault/limits` listează `{ limits: [{ item_id, per_minute, per_day, units_per_day }] }`; `PUT /v1/vault/items/{id}/limits { per_minute, per_day, units_per_day }` le setează (toate cele trei chei sunt obligatorii, `null` elimină o limită, toate `null` elimină limitele; rol de editor sau mai mare pe seiful elementului).

Se numără doar utilizările permise. O utilizare `http` costă **2 unități**, orice alt mod 1. Peste o limită, `/v1/vault/use`, `/v1/vault/http` și `/v1/vault/resolve` răspund **429** `{ error: { code: 'vault_rate_limited', type: 'rate_limit_error', message } }` cu `Retry-After` (60 s pentru limita pe minut, 3600 s pentru limitele zilnice), iar jurnalul de audit înregistrează `use.limited`.

## Permisiunile se încheie odată cu sarcina [#permisiunile-se-încheie-odată-cu-sarcina]

O permisiune creată pentru o sarcină (scope `task` sau orice permisiune care poartă `task`-ul trimis la `/v1/vault/use`) este revocată când sarcina se încheie: la `POST /v1/tasks/{id}/confirm`, la `DELETE /v1/sessions/{id}` și de un proces de curățare la fiecare 5 minute pentru sarcinile închise. Agenții și brokerii le pot încheia explicit cu `POST /v1/vault/grants/revoke-task { task }`, care răspunde `{ revoked: n }`. `GET /v1/vault/grants` rămâne neschimbat; clienții grupează rândurile după `task` ca să arate permisiunile active.

## Cereri de adăugare [#cereri-de-adăugare]

Când un agent are nevoie de o credențială pe care seiful nu o are, nu trebuie niciodată să-ți ceară s-o lipești în chat. În schimb creează o **cerere de adăugare**, iar tu introduci valoarea într-o pagină Kodwen unde este criptată în browser (sau în aplicația desktop) înainte de salvare.

- `POST /v1/vault/add-requests { kind, title, slug?, hosts?, env_var?, fields?, purpose, task? }` răspunde `201 { id, url, status, expires_at }`. `url` este `https://hub.codai.ro/vault/add?req=<token>`; tokenul apare doar în acest răspuns. `fields` sunt **numele** câmpurilor cerute (cel mult 10), niciodată valori. O cerere trăiește 10 minute; pot fi cel mult 20 de cereri în așteptare per utilizator.
- `GET /v1/vault/add-requests/{id}` răspunde `{ id, status, slug, handle, item_id, expires_at }`, unde `status` este `pending`, `completed`, `cancelled` sau `expired`, iar `handle` este `{{vault:<slug>}}` după finalizare.
- `DELETE /v1/vault/add-requests/{id}` o anulează.
- `POST /v1/vault/add-requests/lookup { token }` întoarce cererea doar utilizatorului autentificat căruia îi aparține (linkuri directe pe desktop și telefon); oricine altcineva primește 404.
- `POST /v1/vault/add-requests/{id}/complete { item_id }` o marchează finalizată; elementul trebuie să fie într-unul dintre seifurile tale. Repetarea apelului cu același element nu mai schimbă nimic.

Aplicația desktop deschide `codai://vault/add?req=<token>` când este instalată; telefonul deschide pagina din hub într-un Custom Tab. Jurnalul de audit înregistrează `add.requested`, `add.completed` și `add.cancelled`.

Din terminal, `codaid vault add --title <t> [--kind api_key] [--slug s] [--host h]… [--env-var NAME] [--field f]… [--purpose p] [--json]` creează aceeași cerere și afișează linkul de deschis.

### Din MCP: elicitare prin URL [#din-mcp-elicitare-prin-url]

Setul de unelte MCP `vault` (în gateway și în `codaid vault mcp`) are două unelte pentru asta: `vault_add_secret` (aceleași intrări ca apelul REST) și `vault_add_status { id }`.

- `initialize` răspunde cu un antet `Mcp-Session-Id` care reține dacă clientul tău a declarat `capabilities.elicitation.url`. Antetul este semnat și fără stare pe server (`v1.<payload>.<HMAC-SHA256>`, valabil 30 de zile); trimite-l înapoi la apelurile următoare.
- Dacă elicitarea prin URL este declarată, `tools/call vault_add_secret` răspunde cu eroarea JSON-RPC **`-32042`** și `data.elicitations = [{ mode: 'url', elicitationId, url, message }]`, așa cum o definește specificația MCP (2025-11-25). Clientul afișează linkul; după ce adaugi secretul, agentul apelează `vault_add_status` (sau din nou `vault_add_secret` cu același slug sau titlu în 10 minute, ceea ce refolosește cererea) și primește handle-ul.
- **Alternativă**: fără această capabilitate (sau fără id de sesiune), unealta întoarce un rezultat obișnuit cu linkul și instrucțiunea „deschide-l, apoi apelează vault_add_status”.

### Securitate [#securitate]

- URL-ul conține doar un token de unică folosință de 32 de octeți; serverul păstrează SHA-256 al lui, niciodată tokenul însuși.
- Linkul **nu** este preautentificat: pagina din hub cere autentificare și arată cererea doar utilizatorului pentru care a fost creată. Oricine altcineva vede „nu a fost găsită”.
- Pagina nu primește nimic secret de la server. Sigilează elementul în browser cu cheile seifului tău și îl salvează pe calea obișnuită pentru elemente; agentul vede doar handle-ul.

## Acces de urgență [#acces-de-urgență]

Numește un alt utilizator Kodwen care poate citi seifurile alese dacă tu nu mai poți, după o perioadă de așteptare pe care o controlezi.

| Stare | Înțeles | Pasul următor |
| --- | --- | --- |
| `invited` | Invitație trimisă pe email (valabilă 14 zile). | Contactul acceptă din hub-ul său. |
| `accepted` | Contactul a acceptat; nicio cheie partajată încă. | Clientul tău sigilează cheile seifurilor pentru contact. |
| `sealed` | Cheile seifurilor sunt sigilate pentru cheia X-Wing a contactului. | Contactul poate cere acces. |
| `requested` | Contactul a cerut; `release_at = acum + delay_hours`. | Refuzi sau aprobi, ori trece perioada de așteptare. |
| `granted` | Contactul poate citi seifurile alese. | Revoci. |
| `revoked` | Încheiat de oricare parte; cheile sigilate sunt șterse. | — |

- `GET /v1/vault/emergency` răspunde `{ outgoing, incoming }`.
- `POST /v1/vault/emergency { email, delay_hours, vault_ids? }` răspunde mereu `202 { id }`, cu aceeași formă indiferent dacă adresa are sau nu cont Kodwen, deci nu poate fi folosit pentru a descoperi conturi. `delay_hours` este 24, 48 sau 72; `vault_ids` este implicit seiful tău personal. Cel mult 5 contacte.
- `POST /v1/vault/emergency/accept { token }` (contactul): are nevoie de un cont de seif (404 `vault_not_setup`) și nu poate fi propria ta invitație.
- `PUT /v1/vault/emergency/{id}/seal { grantee_fingerprint, sealed_keys }` (tu): `sealed_keys` trebuie să acopere exact `vault_ids`. Dacă amprenta cheii contactului nu este cea pe care ai verificat-o, răspunsul este 409 `fingerprint_changed`. Compară amprenta afișată pe ambele dispozitive înainte de sigilare.
- `PATCH /v1/vault/emergency/{id} { delay_hours?, vault_ids? }` (tu): schimbarea seifurilor șterge cheile sigilate și readuce contactul în `accepted`.
- `DELETE /v1/vault/emergency/{id}`: tu revoci sau contactul refuză.
- `POST /v1/vault/emergency/{id}/request { reason? }` (contactul, stare `sealed`), apoi `POST /v1/vault/emergency/{id}/deny` sau `/approve` (tu, stare `requested`). Un proces de curățare acordă accesul după ce a trecut `release_at`.
- `GET /v1/vault/emergency/{id}/vault-data` (contactul, stare `granted`) întoarce textul cifrat al seifurilor alese și cheile sigilate; contactul decriptează local. Accesul este **doar pentru citire** și nu se extinde niciodată la agenți; fiecare citire apare ca `emergency.accessed` în jurnalul tău de audit.

Fiecare tranziție anunță cealaltă parte: notificare FCM de date `{ kind: 'vault.emergency', contact_id, event, other_name, release_at }` pe telefon, Web Push în hub și un email. Jurnalul de audit înregistrează `emergency.invited|accepted|sealed|requested|denied|approved|granted|revoked|accessed`.

## Rotire și monitorizarea scurgerilor [#rotire-și-monitorizarea-scurgerilor]

### Verificarea scurgerilor (HIBP) [#verificarea-scurgerilor-hibp]

`GET /v1/vault/hibp/range/{prefix}` intermediază API-ul de intervale Pwned Passwords cu k-anonimitate: clientul calculează local SHA-1 pentru fiecare parolă, trimite doar primele **5 caractere hex** și compară singur sufixul în răspuns. Gateway-ul nu vede niciodată o parolă sau un hash complet. Răspunsurile folosesc `Add-Padding: true`, ca mărimea lor să nu dezvăluie prefixul, sunt păstrate în cache 24 de ore și sunt limitate la 300 de apeluri per utilizator pe oră (429 peste limită, 503 `hibp_unavailable` când serviciul extern nu răspunde). Clientul raportează apoi `POST /v1/vault/rotation/breach-report { results: [{ item_id, count }] }` (cel mult 500); un element nou compromis trimite o notificare `vault.breach`.

### Coada de rotire [#coada-de-rotire]

- `GET /v1/vault/rotation` listează `{ items: [{ item_id, slug, title, kind, interval_days, rotated_at, due_at, overdue, breached, breach_count, breach_checked_at, snoozed_until, provider, create_url }] }`: întâi cele compromise, apoi cele întârziate, apoi cele cu termenul cel mai apropiat.
- `PUT /v1/vault/items/{id}/rotation { interval_days?, provider?, snoozed_until? }` setează programul (1–730 de zile) și furnizorul; `POST /v1/vault/items/{id}/rotated` înregistrează o rotire și șterge marcajul de compromis.
- **Valori implicite**: 90 de zile pentru elementele `api_key`, `oauth_token` și `env`; autentificările sunt doar verificate pentru scurgeri; elementele cu derivator sunt omise (credențialele lor sunt de scurtă durată).
- Un proces de curățare la fiecare 6 ore trimite câte o notificare `vault.rotation` per element întârziat per ciclu (nu cât timp este amânat).
- Cheile care nu pot fi rotite printr-un API rămân statice, iar lista trimite la pagina unde creezi una nouă:

| `provider` | `create_url` |
| --- | --- |
| `stripe` (chei restricționate) | `https://dashboard.stripe.com/apikeys` |
| `github_pat` (PAT cu permisiuni granulare) | `https://github.com/settings/personal-access-tokens/new` |
| `openai` | `https://platform.openai.com/api-keys` |
| `google_api_key` | `https://console.cloud.google.com/apis/credentials` |

## Credențiale JIT [#credențiale-jit]

Un **derivator** transformă o credențială rădăcină într-una de scurtă durată, cu drepturi restrânse, la fiecare utilizare, astfel încât secretul de lungă durată nu ajunge niciodată la comandă.

- `GET /v1/vault/derivers`, `PUT /v1/vault/items/{id}/deriver { provider, config, max_ttl_s }` (300–43200 s), `DELETE /v1/vault/items/{id}/deriver`. `config` nu conține secrete.

| `provider` | `config` | Câmpurile elementului rădăcină | Câmpuri derivate → env |
| --- | --- | --- | --- |
| `aws_sts` | `role_arn`, `region` (implicit `us-east-1`), `session_policy?` (≤ 2048 caractere), `external_id?`, `duration_s` 900–43200 | `access_key_id`, `secret_access_key` | `access_key_id`, `secret_access_key`, `session_token` → `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN` |
| `gcp_sa` | `mode` `key` \| `impersonate`, `target_service_account?`, `scopes` (implicit cloud-platform), `lifetime_s` 300–3600 | `json` (cheie de cont de serviciu) | `token` → `CLOUDSDK_AUTH_ACCESS_TOKEN`, `GOOGLE_OAUTH_ACCESS_TOKEN` |
| `cloudflare` | `account_id?`, `policies`, `ttl_s` 300–86400 | `token` (cu drept de a crea tokenuri) | `token` → `CLOUDFLARE_API_TOKEN` |
| `github_app` | `app_id`, `installation_id`, `repositories?`, `permissions?` | `private_key` (PEM) | `token` → `GH_TOKEN`, `GITHUB_TOKEN` |

`{{vault:slug}}` pe un element derivat este primul câmp derivat; `{{vault:slug.<câmp>}}` alege unul anume.

**`POST /v1/vault/derive`** este doar pentru brokerii Kodwen (codaid, desktop): corpul `{ handle, action, purpose?, provenance?, task?, ttl_s?, proof }`, cu aceeași dovadă de dispozitiv ca la `/v1/vault/resolve`, peste `handles = [handle]`. Dovada este verificată înaintea oricărei decizii de politică; fără ea răspunsul este 400 `proof_required`, iar un apelant care nu este broker primește 403 `broker_only`. După politica obișnuită (modul `env`, inclusiv aprobările), elementul trebuie să fie utilizabil în cloud (409 `not_cloud_usable`) și să aibă un derivator (409 `no_deriver`). Răspunsul este `{ provider, values, env, expires_at }` cu `Cache-Control: no-store`; o eroare a furnizorului răspunde 502 `derive_failed`, cu mesajul curățat de orice seamănă cu un token. Jurnalul de audit înregistrează `derive.issued { provider, expires_at, device_id }`, niciodată valoarea.

Elementele care nu sunt utilizabile în cloud sunt derivate în brokerul local (`codaid vault run`, desktop), cu aceiași furnizori; brokerul injectează variabilele de mediu în procesul copil și redactează orice valoare derivată din ieșirea lui.

**`/v1/vault/http`** pe un element cu derivator `gcp_sa`, `cloudflare` sau `github_app` injectează tokenul bearer derivat în locul secretului rădăcină. Tokenul este păstrat în memoria gateway-ului până cu 60 de secunde înainte să expire.

## Completare automată de către agent [#completare-automată-de-către-agent]

Browserul agentului din desktop se poate autentifica pe site-uri fără ca modelul să vadă parola. În `browser_type`, scrie `{{vault:slug.câmp}}` (`username`, `password`, `totp` sau orice alt câmp). Aplicația desktop apoi:

1. citește originea paginii prin CDP și verifică hosturile legate ale elementului (`agent_meta.hosts`); un host nelegat eșuează cu `vault_host_mismatch`;
2. cere o decizie de politică în modul `autofill`, cu `host` = hostul paginii (aprobare pe dispozitivul tău când nivelul o cere);
3. decriptează local, verifică dacă pagina este încă pe aceeași origine și injectează valoarea; unealta răspunde `{ ok, filled: true }` fără valoare.

Instantaneele nu arată niciodată valoarea unui câmp de parolă, iar ieșirea `browser_eval` și a clicurilor este redactată pentru fiecare valoare completată în acea sesiune de browser (`[vault:<slug>]`).

Extensia de browser acceptă și cereri `agent_fill` de la aplicația desktop prin native messaging și completează doar când fila activă este exact pe originea cerută.

## Extensia de browser și passkey-uri în Windows [#extensia-de-browser-și-passkey-uri-în-windows]

**Kodwen Vault pentru Chrome și Edge** (Manifest V3, distribuită nelistată în Chrome Web Store și ascunsă în Edge Add-ons):

- Funcționează singură: criptografia seifului este inclusă ca WASM (fără cod de la distanță). Deblochezi cu parola și Secret Key; sesiunea deblocată trăiește doar în memoria service worker-ului și se blochează implicit după 10 minute de inactivitate.
- Completează doar pe originea legată de autentificare: service worker-ul verifică originea filei, iar scriptul de conținut verifică din nou `location.origin` înainte să scrie un câmp.
- **Native messaging** opțional către aplicația desktop (host `ro.codai.vault`): deblocare cu Windows Hello pe desktop, completare din seiful desktop (`candidates`, `fill`) și completări cerute de agent. `allowed_origins` din manifestul hostului conține doar id-urile extensiei din magazine, iar aplicația desktop verifică din nou id-ul extensiei și originea paginii la fiecare cerere.
- Permisiuni: `storage`, `scripting`, `activeTab`, `contextMenus`, `alarms`, `nativeMessaging`, `idle`; accesul la toate site-urile este opțional și cerut la rulare pentru completarea în pagină. Apelurile către gateway folosesc `via: 'extension'`.

**Furnizor de passkey-uri în Windows 11**: versiunea din Microsoft Store (MSIX) a aplicației desktop se înregistrează ca furnizor de passkey-uri, astfel încât Windows poate crea și folosi passkey-uri păstrate ca elemente `passkey` în seiful tău, după Windows Hello. Fiecare cerere de la Windows este verificată cu cheia de semnare a operațiilor pe care Windows a înregistrat-o pentru plugin. Instalările NSIS nu îl înregistrează și afișează „disponibil în versiunea din Microsoft Store”.

## Android: parole și passkey-uri pentru alte aplicații [#android-parole-și-passkey-uri-pentru-alte-aplicații]

Aplicația Kodwen pentru Android descărcată de pe GitHub (nu versiunea din Play) este **furnizor Credential Manager** pe Android 14+ și **serviciu de completare automată** pe Android 11–13. Îl activezi din Setări › Parole și conturi (sau Serviciu de completare automată).

- **Completare:** autentificările potrivite (`androidapp://<pachet>` sau domeniul site-ului și subdomeniile lui) sunt oferite cât timp seiful e deblocat; fiecare completare cere o confirmare biometrică.
- **Salvare:** când îți creezi un cont sau schimbi parola într-o altă aplicație sau într-un browser, telefonul îți propune să o salvezi în seif: implicit în primul seif personal, sau în orice seif în care ești owner, manager sau editor. Dacă există deja o autentificare cu același nume de utilizator și aceeași adresă, îți propune să o actualizezi. Elementul este sigilat pe telefon și urcat doar ca text cifrat (`PUT /v1/vault/items/:id`); nu este niciodată vizibil agenților (fără handle).
- **Passkey-uri:** site-urile și aplicațiile pot crea și folosi passkey-uri (ES256) păstrate ca elemente `passkey` în seif, în același format ca furnizorul din Windows, deci un passkey creat pe telefon funcționează și pe desktop. Cheia privată rămâne în elementul din seif; semnarea are loc în nucleul seifului de pe dispozitiv după confirmarea biometrică, iar contorul de semnături este salvat înapoi în element. Cererile din browser sunt acceptate doar de la browserele din lista Google de browsere privilegiate și doar când id-ul relying party se potrivește cu originea paginii.
- Nucleul CTAP / WebAuthn (authenticator data, cheie COSE, atestare `none`, aserțiune) este comun pentru furnizorul din desktop și telefon (`crates/codai-vault`, funcționalitatea `passkey`).

## Seifurile organizației [#seifurile-organizației]

Un proprietar sau administrator al organizației creează un seif partajat cu `POST /v1/vault/vaults { id, encrypted_meta, sealed_key, org_id }` (`kind: shared`). `GET /v1/vault/vaults` arată apoi, pentru fiecare seif, `org_id`, `role`-ul tău (`owner`, `manager`, `editor`, `viewer`), `quorum`, `rotation_needed_at` și `key_epoch`. Serverul nu vede niciodată o cheie de seif: fiecare membru primește propria copie, sigilată pentru cheia lui publică X-Wing de clientul unui proprietar sau manager.

- `GET /v1/vault/vaults/{id}/members` (orice membru): `{ members: [{ user_id, email, name, role, fingerprint, key_epoch, added_at }] }`.
- `GET /v1/vault/vaults/{id}/candidates` (proprietar/manager): membrii organizației care au cont de seif și nu sunt încă în seif, cu `public_keys` și `fingerprint`.
- `PUT /v1/vault/vaults/{id}/members/{userId} { role, sealed_key, fingerprint }` (proprietar/manager): arată amprenta candidatului, cere confirmarea ei (pe alt canal, ca la contactele de urgență), sigilează cheia seifului pentru `public_keys` și încarc-o. Dacă cheile membrului s-au schimbat de când a fost citită amprenta, răspunsul este 409 `fingerprint_changed`; un utilizator din afara organizației primește 403. Rândul proprietarului nu poate fi modificat.
- `PATCH /v1/vault/vaults/{id}/members/{userId} { role }` schimbă rolul (`manager`, `editor` sau `viewer`).
- `DELETE /v1/vault/vaults/{id}/members/{userId}` (proprietar/manager sau membrul care pleacă) șterge imediat rândul și setează `rotation_needed_at`. Eliminarea cuiva din organizație face același lucru pe toate seifurile acelei organizații.

### Rotirea cheii [#rotirea-cheii]

Un membru eliminat poate avea încă o copie veche a cheii seifului, așa că după orice eliminare seiful afișează un banner până când un proprietar sau manager îl rotește. Clientul generează o cheie nouă de seif, o resigilează pentru fiecare membru rămas, reîmpachetează cheia fiecărui element și trimite totul într-un singur apel:

`POST /v1/vault/vaults/{id}/rotate { key_epoch, encrypted_meta, members: [{ user_id, sealed_key }], items: [{ id, revision, wrapped_key, ciphertext }] }`

`key_epoch` este epoca curentă + 1, iar `revision` al fiecărui element este revizia lui curentă + 1. Cererea trebuie să acopere fiecare membru actual și fiecare element activ (cel mult 2000): altfel răspunsul este 409 `rotation_incomplete` cu id-urile lipsă și nu se schimbă nimic. Totul rulează într-o singură tranzacție și șterge `rotation_needed_at`.

## Aprobări cu cvorum [#aprobări-cu-cvorum]

Pe un seif al organizației, proprietarul poate cere ca mai multe persoane să aprobe utilizările sensibile: `PUT /v1/vault/vaults/{id}/quorum { m, scope }`, cu `m` între 2 și 9 și `scope` `critical` (doar elementele critice) sau `high` (ridicate și critice); `{ m: null }` dezactivează cvorumul. `m` nu poate depăși numărul de membri proprietari/manageri (400).

Când un agent cere o utilizare t3 sau t4 a unui element potrivit, aprobarea este creată cu `quorum_m = m` și expiră după 24 de ore. Dacă există mai puțin de `m` aprobatori eligibili (proprietari și manageri, alții decât solicitantul, cu cont de seif), utilizarea este refuzată imediat cu motivul `quorum_unavailable`. Fiecare aprobator eligibil primește o notificare, iar `GET /v1/vault/approvals` listează cererea cu `quorum: { m, approvals, needed, voted }`.

- Votezi cu `POST /v1/vault/approvals/{id}/vote { decision, proof }`. Fiecare vot are propria dovadă peste **approve/v4**:
  `codai-vault/approve/v4|<approval_id>|<action_hash>|<decision>|once|<approver_user_id>|<device_id>|<preview_digest>`
  semnată de una dintre cheile de dispozitiv ale aprobatorului (`proof: { type: 'device', device_id, signature }`) sau ca passkey a cărui provocare WebAuthn este base64url(SHA-256(mesajul cu id-ul de dispozitiv `passkey`)). Id-ul utilizatorului care aprobă face parte din mesaj, deci dovada unui aprobator nu poate fi refolosită ca a altuia. Cheile software sunt refuzate; t4 cere un dispozitiv atestat sau un passkey. Nu există număr de potrivire: aprobatorii nu sunt lângă agent.
- **Orice refuz încheie cererea** (`denied`). A `m`-a aprobare distinctă o marchează `approved` și creează o singură permisiune `once` pentru solicitant; evenimentul de audit `approval.approved` listează dispozitivul fiecărui votant. Fiecare vot apare ca `approval.vote`.
- Erori: 409 `already_voted`, 409 `not_pending`, 403 pentru o dovadă greșită sau un aprobator neeligibil. `POST /v1/vault/approvals/{id}/decide` pe o aprobare cu cvorum răspunde 409 `quorum_required`.

## Ce semnează aprobatorul (previzualizare v2) [#ce-semnează-aprobatorul-previzualizare-v2]

Previzualizarea aprobării include acum **lanțul** de delegare al agentului (cine pe cine a rugat, cel mai exterior primul, unit prin ` > `, tăiat la 300 de caractere). Textul canonic este `codai-vault/preview/v2`, urmat de `title`, `agent`, `mode`, `host`, `action`, `purpose`, `task`, `chain` ca linii `key=value`, cu aceeași escapare ca la v1. Un obiect de previzualizare al cărui `chain` este un șir folosește v2; altfel v1. Hub-ul, desktopul și telefonul arată lanțul lângă cerere, iar fiecare eveniment de utilizare îl stochează.

Tranziție: până la următoarea versiune de desktop și telefon, gateway-ul stochează și rezumatul v1 al aceleiași previzualizări (fără `chain`). O dovadă v3 este verificată întâi cu rezumatul v2, apoi cu cel v1; o dovadă v4 trebuie să folosească rezumatul v2.

## Aprobări offline prin cod QR [#aprobări-offline-prin-cod-qr]

Când telefonul nu are rețea, desktopul poate transporta aprobarea în ambele sensuri prin coduri QR:

1. Desktopul afișează un QR de cerere (JSON compact, UTF-8, corecție de erori M):
   `{ "v": "codai-vault-qr/1", "t": "req", "id", "h": "<action_hash>", "tier": "t3|t4", "u": "<id-ul solicitantului>", "p": { ...previzualizare... }, "c": ["47","12","83"] }`.
2. Telefonul refuză un QR al cărui `u` nu este utilizatorul autentificat, recalculează singur rezumatul previzualizării din `p` (nu are încredere în niciun rezumat din QR), afișează `p` și îți cere să apeși numărul pe care ți-l arată agentul (unul dintre `c`; numărul nu apare în QR).
3. Telefonul semnează approve/v3 (v2 când nu există număr) cu cheia de dispozitiv și afișează un QR de răspuns:
   `{ "v": "codai-vault-qr/1", "t": "res", "id", "d": "approve|deny", "s": "once|task|1h|8h", "dev": "<id-ul dispozitivului>", "m": "<număr sau gol>", "sig": "<base64url>" }`.
4. Desktopul îl citește cu camera web și trimite `POST /v1/vault/approvals/{id}/decide { decision, scope, via: 'qr', match_number, proof: { type: 'device', device_id, signature } }`. Gateway-ul verifică dovada dispozitivului exact ca și cum ar fi trimis-o telefonul.

## Simulatorul de politici [#simulatorul-de-politici]

Înainte să-ți schimbi regulile, vezi ce ar fi făcut. `POST /v1/vault/policy/simulate { rules?, default_tier?, since?, limit? }` reevaluează utilizările trecute ale agenților tăi sub o politică propusă: `rules` înlocuiește regulile actuale, `default_tier` nivelul implicit; `since` este implicit acum 30 de zile, iar `limit` implicit 200 (cel mult 500). Este doar pentru citire și răspunde:

`{ evaluated, changed, grants: 'none', rows: [{ event_id, at, item_id, title, agent, chain, mode, host, current_tier, simulated_tier, reasons, source }] }`

Sursele sunt evenimentele `use.allowed`, `use.denied` și `approval.requested` (care stochează de acum `chain`, `mode`, `host`, `action_hash`, `purpose`, `task` și `provenance`, niciodată acțiunea brută), plus cererile stocate ale aprobărilor t3/t4 mai vechi (`source: 'approval'`). Sensibilitatea și hosturile legate provin din metadatele actuale ale fiecărui element. Permisiunile și protecțiile la anomalii **nu** sunt reevaluate. Aceeași verificare este disponibilă agenților prin unealta MCP doar pentru citire `vault_policy_simulate`.

## Chitanțe semnate [#chitanțe-semnate]

Evenimentele relevante pentru securitate sunt semnate: `use.allowed`, `use.denied`, `approval.approved`, `approval.denied`, `approval.vote` și `honeytoken`. Fiecare are un plic DSSE la `payload.receipt` în jurnalul de audit:

- `payloadType`: `application/vnd.codai.vault-receipt+json`;
- conținut: `{ v: 1, event, user_id, item_id, agent, tier, action_hash, reasons, device_id, approval_id, at }` (doar metadate, niciodată o valoare);
- `signatures: [{ keyid, sig }]`, ECDSA P-256 SHA-256 cu o cheie Cloud KMS.

Pentru verificare, citește `GET /v1/vault/receipts/keys` (`{ keys: [{ kid, alg: 'ES256', pem }] }`, păstrat în cache 1 oră), alege cheia al cărei `kid` este egal cu `keyid` și verifică semnătura peste codificarea de preautentificare DSSE `DSSEv1 <len(type)> <type> <len(payload)> <payload>`. Hub-ul face asta în browser și afișează un ecuson „semnat”. Dacă semnarea nu este disponibilă, evenimentul este stocat totuși, nesemnat; verificarea nu acceptă niciodată o chitanță nesemnată sau modificată.

## Rezumatul săptămânal [#rezumatul-săptămânal]

În fiecare luni la 07:10 (ora României), utilizatorii cu activitate în seif în acea săptămână primesc un email prin poșta Kodwen: utilizări, refuzuri, aprobări, dispozitive noi, elemente scadente pentru rotire. Îl oprești din linkul de dezabonare, din setarea din hub sau cu `PATCH /v1/vault/settings { digest_opt_out: true }`; `GET /v1/vault/account` arată `digest_opt_out`.

## Chei API cu scopuri [#chei-api-cu-scopuri]

O cheie API poate avea `scopes`; `null` (orice cheie existentă) înseamnă cheie completă. O cheie cu scopuri poate apela doar:

| Scop | Rute |
| --- | --- |
| `vault.read` | `GET /v1/vault/account`, `GET /v1/vault/vaults`, `GET /v1/vault/items`, `GET /v1/vault/catalog`, `GET /v1/vault/receipts/keys` |
| `vault.add` | `POST /v1/vault/add-requests`, `GET /v1/vault/add-requests/{id}`, `DELETE /v1/vault/add-requests/{id}` |

Orice altceva răspunde 403 `key_scope_forbidden`. **Conectează extensia de browser** din pagina de chei a hub-ului emite o cheie cu `vault.read` + `vault.add`, valabilă 180 de zile, după o autentificare recentă, și o afișează o singură dată. Extensia o păstrează criptată cu o cheie AES-GCM neexportabilă ținută în IndexedDB, niciodată în clar. `GET /v1/vault/account` întoarce `key_scopes` pentru cheia care apelează, iar extensia avertizează când rulează cu o cheie completă.

## Momeli în depozite [#momeli-în-depozite]

`codaid vault import --apply` plantează și **2 elemente-momeală pentru fiecare depozit importat** (numite după `--repo` sau după folder): elemente `api_key` vizibile agenților și marcate `honeytoken`, precum `aws-prod-legacy`, `stripe-live-old`, `github-deploy-old` sau `openai-prod-old`, legate de hostul real al furnizorului și completate cu valori aleatoare în formatul furnizorului (niciodată reale). Alegerea este deterministă pentru fiecare depozit și sare peste slug-urile pe care le ai deja; o rulare de probă le listează ca `decoy`. Fișierele din depozit nu sunt atinse niciodată. Orice utilizare declanșează calea de momeală de mai sus (refuz, blocare, notificare `vault.alarm`). Folosește `--no-honeytokens` ca să le omiți.

<OpenAPIPage document="./openapi/ro/gateway.yaml" operations={[{path: '/v1/vault/account',method: 'get'},{path: '/v1/vault/account',method: 'put'},{path: '/v1/vault/settings',method: 'patch'},{path: '/v1/vault/lock',method: 'post'},{path: '/v1/vault/unlock/challenge',method: 'post'},{path: '/v1/vault/unlock',method: 'post'},{path: '/v1/vault/limits',method: 'get'},{path: '/v1/vault/items/{id}/limits',method: 'put'},{path: '/v1/vault/vaults',method: 'get'},{path: '/v1/vault/vaults',method: 'post'},{path: '/v1/vault/items',method: 'get'},{path: '/v1/vault/items/{id}',method: 'put'},{path: '/v1/vault/items/{id}',method: 'delete'},{path: '/v1/vault/items/{id}/cloud',method: 'put'},{path: '/v1/vault/rules',method: 'get'},{path: '/v1/vault/rules',method: 'post'},{path: '/v1/vault/rules/{id}',method: 'delete'},{path: '/v1/vault/grants',method: 'get'},{path: '/v1/vault/grants/revoke-task',method: 'post'},{path: '/v1/vault/grants/{id}',method: 'delete'},{path: '/v1/vault/events',method: 'get'},{path: '/v1/vault/approvals',method: 'get'},{path: '/v1/vault/approvals/{id}',method: 'get'},{path: '/v1/vault/approvals/{id}/decide',method: 'post'},{path: '/v1/vault/devices/challenge',method: 'post'},{path: '/v1/vault/devices',method: 'get'},{path: '/v1/vault/devices',method: 'post'},{path: '/v1/vault/devices/{id}',method: 'patch'},{path: '/v1/vault/devices/{id}',method: 'delete'},{path: '/v1/vault/catalog',method: 'get'},{path: '/v1/vault/use',method: 'post'},{path: '/v1/vault/http',method: 'post'},{path: '/v1/vault/resolve/challenge',method: 'post'},{path: '/v1/vault/resolve',method: 'post'},{path: '/v1/vault/add-requests',method: 'post'},{path: '/v1/vault/add-requests/lookup',method: 'post'},{path: '/v1/vault/add-requests/{id}',method: 'get'},{path: '/v1/vault/add-requests/{id}',method: 'delete'},{path: '/v1/vault/add-requests/{id}/complete',method: 'post'},{path: '/v1/vault/emergency',method: 'get'},{path: '/v1/vault/emergency',method: 'post'},{path: '/v1/vault/emergency/accept',method: 'post'},{path: '/v1/vault/emergency/{id}/seal',method: 'put'},{path: '/v1/vault/emergency/{id}',method: 'patch'},{path: '/v1/vault/emergency/{id}',method: 'delete'},{path: '/v1/vault/emergency/{id}/request',method: 'post'},{path: '/v1/vault/emergency/{id}/deny',method: 'post'},{path: '/v1/vault/emergency/{id}/approve',method: 'post'},{path: '/v1/vault/emergency/{id}/vault-data',method: 'get'},{path: '/v1/vault/hibp/range/{prefix}',method: 'get'},{path: '/v1/vault/rotation',method: 'get'},{path: '/v1/vault/items/{id}/rotation',method: 'put'},{path: '/v1/vault/items/{id}/rotated',method: 'post'},{path: '/v1/vault/items/{id}/used',method: 'post'},{path: '/v1/vault/rotation/breach-report',method: 'post'},{path: '/v1/vault/derivers',method: 'get'},{path: '/v1/vault/items/{id}/deriver',method: 'put'},{path: '/v1/vault/items/{id}/deriver',method: 'delete'},{path: '/v1/vault/derive',method: 'post'},{path: '/v1/vault/vaults/{id}/members',method: 'get'},{path: '/v1/vault/vaults/{id}/candidates',method: 'get'},{path: '/v1/vault/vaults/{id}/members/{userId}',method: 'put'},{path: '/v1/vault/vaults/{id}/members/{userId}',method: 'patch'},{path: '/v1/vault/vaults/{id}/members/{userId}',method: 'delete'},{path: '/v1/vault/vaults/{id}/rotate',method: 'post'},{path: '/v1/vault/vaults/{id}/quorum',method: 'put'},{path: '/v1/vault/approvals/{id}/vote',method: 'post'},{path: '/v1/vault/policy/simulate',method: 'post'},{path: '/v1/vault/receipts/keys',method: 'get'}]} />
