Vai al contenuto
mcpd
nessun daemon
Codice

Sicurezza

Una pagina sola per rispondere a una revisione. Che cosa viene conservato, che cosa viene mandato altrove, chi lo può leggere, e i controlli che mcpd non ha.

Tutto quello che segue è tratto dal sorgente del daemon e da spec/mcpd.spec.json, che è ciò da cui l’API viene generata. Dove un controllo non esiste, questa pagina lo dice, invece di lasciarti il buco da scoprire dentro un questionario.

Che cosa archivia mcpd

mcpd archivia il testo delle conversazioni del tuo agente. È il suo scopo, ed è da lì che una revisione dovrebbe partire. Uno span di tipo model_generation porta input e output: il prompt esatto mandato al modello e il testo esatto che è tornato indietro. Per la maggior parte degli utenti sono i dati più delicati che hanno.

Nei sei segnali, i campi in formato libero archiviati sono questi:

SegnaleChe cosa contiene
Span input, output, attributes, metadata e il messaggio di errore
Logil messaggio, e i fields strutturati
Metrichelabels
Punteggireason e metadata
Feedbackil comment in testo libero, e metadata
Note di aspettola frase, in text

Accanto al payload, ogni segnale porta campi di correlazione che legano un’esecuzione al resto del tuo sistema: progetto, ambiente, nome e versione del servizio, sessione, thread, utente, risorsa, esecuzione, richiesta, esperimento e release. user_id viene sottoposto a hash in entrata, a meno che tu non spenga l’oscuramento. Gli altri vengono archiviati come arrivano, quindi tratta ognuno di essi come un campo che può identificare una persona, se dentro ci metti un’identità.

I conteggi dei token e una cifra di costo vengono calcolati all’ingest e archiviati insieme all’esecuzione. Sulla chiamata al modello non viene chiesto niente a nessun provider.

Dove viene scritto

Un backend solo, scelto all’avvio. Il predefinito è un file SQLite incorporato in ./mcpd.db, che imposti con --data. L’indice full-text sta nello stesso file, quindi lo storage è un file solo da salvare e un file solo da proteggere. Con --postgres-url o --clickhouse-url i dati finiscono in un database che gestisci tu.

Le esecuzioni durevoli, quando sono accese, vivono in un secondo file SQLite accanto al primo, qualunque sia lo storage delle tracce. Tiene l’input, lo stato, l’output e la correlazione di ogni esecuzione, e l’input, l’output e l’errore di ogni passo, esattamente come sono stati mandati: senza oscuramento, con user_id senza hash, perché un passo successivo li rilegge e un valore riscritto cambierebbe quello che fa l’esecuzione. Gli span che un’esecuzione scrive nello storage delle tracce sono oscurati come tutti gli altri. Proteggi il file delle esecuzioni come proteggi lo storage delle tracce, o di più. La conservazione e la cancellazione per soggetto lo raggiungono entrambe, per le esecuzioni finite; vedi Esecuzioni.

mcpd non cifra quello che scrive. La cifratura a riposo è compito del volume sullo storage incorporato, e del database sugli altri due.

Che cosa esce dalla macchina

Con la configurazione predefinita, niente. Il daemon fa richieste verso l’esterno esattamente in quattro punti, e ognuno di essi è spento finché non lo configuri. Non c’è controllo degli aggiornamenti, non c’è rendicontazione dell’uso e non c’è telemetria di nessun tipo.

DestinazioneChe cosa viene mandatoSpento a meno che
Backend di storage Tutto quello che mcpd archivia, sulla tua connessione. Imposti --postgres-url o --clickhouse-url. Lo storage predefinito è un file locale e non ha bisogno di rete.
Inoltro Ogni span archiviato, riemesso come OTLP JSON verso {endpoint}/v1/traces. Il payload è compreso: input e output escono come attributi input.value e output.value, ognuno con un tetto di 32 KiB. I valori già oscurati restano oscurati, e un user_id sottoposto a hash viene inoltrato come hash. Imposti --forward-endpoint, o un blocco forward nel file di configurazione.
Webhook di monitor e obiettivi Un corpo JSON fisso: l’id e il nome del monitor o dell’obiettivo, il progetto, la condizione, che cosa sorveglia, la soglia, la lettura, il numero di campioni, un dettaglio di una riga e un timestamp. Non c’è dentro nessun testo di prompt o di completion. Un monitor o un obiettivo nel file di configurazione nomina un webhook.
Modello giudice Il criterio, più la domanda dell’esecuzione e la sua risposta, ognuna troncata a 4000 caratteri, mandati in POST a {endpoint}/chat/completions. È l’unico percorso che manda testo archiviato di prompt e di completion a un provider di modelli. Un blocco judge nel file di configurazione nomina un endpoint, un modello e almeno un criterio, e un chiamante chiede quello scorer per nome.

L’interfaccia che il binario serve su / è compilata dentro di esso. I suoi font, i suoi stili e i suoi script arrivano dalla stessa origine, e non fa nessuna richiesta a terzi.

Chiavi e ambiti

L’autenticazione è a chiavi API. Non c’è un account, non c’è una sessione e non c’è un login. Una chiave si presenta come Authorization: Bearer … oppure nell’header X-Mcpd-Key, e i due lati vengono entrambi sottoposti a hash SHA-256 prima del confronto, così né la lunghezza della chiave configurata né la posizione del primo carattere sbagliato si leggono dai tempi.

Gli ambiti sono sei, in ordine di portata:

AmbitoChe cosa permettePerché è un ambito a sé
ingest Scrivere segnali. Non leggere niente. Questa chiave sta in ogni container che emette uno span, compresi quelli che eseguono codice di terzi. Non deve aprire anche tutti i prompt archiviati.
read Leggere i segnali archiviati. È in lettura che stanno i prompt.
write Modificare i dati archiviati: punteggi, feedback, dataset, esperimenti e conferme. Parecchi di quegli handler leggono un payload mentre lavorano. Catturare dentro un dataset un’esecuzione di produzione legge l’input e l’output di quell’esecuzione, che è più di quanto faccia emettere uno span.
execute Avviare, annullare e segnalare esecuzioni, e pubblicare eventi verso di esse. Avviare un’esecuzione spende soldi e arriva dentro i tuoi sistemi. Una chiave che può registrare un punteggio non dovrebbe poter anche lanciare lavoro.
worker Prendere in carico passi e riferire che cosa è successo. È la chiave dentro il processo che esegue i corpi dei passi. Una presa in carico restituisce l’input del passo e l’output di ogni passo precedente, quindi legge payload, e non dovrebbe poter anche avviare esecuzioni o cambiare dati archiviati.
admin Distruggere dati, e leggere il registro di audit. Cancellare non è leggere.

Quale ambito prende un endpoint è dichiarato endpoint per endpoint nella specifica e pubblicato nel riferimento generato dell’API HTTP, così alla domanda «che cosa chiede questo endpoint» c’è una risposta sola, invece di una per documento.

Il ripiego, e che cosa significa per una chiave che hai già

Un elenco write non impostato ricade sull’elenco ingest, e un elenco admin non impostato ricade sull’elenco read. Gli elenchi execute e worker non impostati ricadono sull’elenco write, e quindi sull’elenco ingest quando anche quello non è impostato. È questo che impedisce a un aggiornamento di revocare l’accesso su ogni deploy configurato prima che gli ambiti più stretti esistessero. Una chiave admin vale anche per execute e worker, così un operatore può guidare un’esecuzione a mano. Insieme, queste regole vogliono dire che, su un daemon che tiene solo una chiave di ingest e una chiave di lettura:

  • la chiave di ingest allega anche punteggi e feedback, avvia esecuzioni e prende in carico passi, e
  • la chiave di lettura cancella anche le tracce e, attraverso il ripiego admin, avvia esecuzioni e prende in carico passi.

Quindi una chiave di lettura è una chiave di cancellazione finché non imposti --admin-key, e tutte e due le chiavi raggiungono la superficie delle esecuzioni finché non imposti --execute-key, --worker-key e --admin-key. Le chiavi vanno consegnate su quella base. Niente si restringe da solo. Ogni ambito ha la sua opzione, --ingest-key, --read-key, --write-key, --admin-key, --execute-key e --worker-key, e ognuna prende un elenco; ognuna si può impostare anche per progetto nel file di configurazione.

Senza chiavi, ogni ambito è aperto

Quel comportamento predefinito è voluto: una prima esecuzione su un portatile non deve richiedere una credenziale. È anche la cosa più pericolosa di un’istanza esposta, quindi, quando il daemon si mette in ascolto su un indirizzo che non è di loopback, registra all’avvio un avvertimento per ogni superficie aperta a chi non dovrebbe raggiungerla. Ogni avvertimento nomina la superficie e le opzioni che la chiudono. Esegui mcpd config prima di esporre una porta: stampa la configurazione a cui si risolve un’invocazione, poi esce. Porta una riga per ambito, e nomina l’elenco su cui un ambito è ricaduto, quindi admin auth key required (read keys) ti dice che le chiavi di lettura possono ancora cancellare. Docker e Kubernetes ne mostra l’output.

chiuso prima di essere raggiungibile

mcpd --ingest-key "$INGEST" --read-key "$READ"

Un progetto è un confine fra tenant

Una chiave configurata al livello più alto concede ogni progetto. Una chiave elencata invece sotto un progetto con nome, nel blocco projects del file di configurazione, concede quel progetto e nessun altro, e ogni progetto tiene il proprio elenco per ambito. Una stessa chiave può comparire sotto più progetti: è così che un team di piattaforma tiene una chiave che copre quello che gestisce senza tenere tutto il server.

Sul percorso di scrittura il controllo è per segnale, non per richiesta:

  • Un batch può nominare più progetti. I segnali che nominano un progetto che la chiave non raggiunge vengono scartati e riportati nella risposta come rifiutati, con il motivo, così una flotta puntata sul progetto sbagliato si scopre dalla risposta che già legge. Il resto del batch viene archiviato.

Sul percorso di lettura, ogni endpoint che nomina un progetto lo confronta con quello che la chiave concede: tracce, span, log, metriche, punteggi, feedback, analitiche, dataset, esperimenti e registro di audit. Gli elementi dentro un dataset si indirizzano con l’id del dataset, e il daemon stabilisce quale progetto possiede quel dataset e ci confronta la chiave, così una chiave che non può aprire il progetto non può leggere i casi catturati da lì. Un id che non esiste viene rifiutato allo stesso modo di uno che la chiave non può raggiungere, quindi l’endpoint non si può usare per scoprire quali id esistono. Tre endpoint non prendono nessun progetto e sono raggiungibili da qualunque chiave che tenga l’ambito di lettura, quali che siano i progetti a cui è limitata: l’elenco dei progetti, il catalogo degli scorer e l’insieme delle capacità dello storage (GET /v1/api/capabilities). L’elenco dei progetti è filtrato dalla portata della chiave, così una chiave limitata a un progetto vede solo i progetti che raggiunge. Su un’istanza condivisa, i nomi dei progetti sono spesso nomi di team o di clienti. Lo stato dei monitor e lo stato degli obiettivi erano in quell’elenco, finché non sono stati filtrati dalla portata di progetto della chiave, come sarebbe dovuto essere da sempre: un monitor nomina il progetto che sorveglia e porta il webhook a cui invia. L’API indica solo l’host di quel webhook, perché l’URL di un webhook è spesso essa stessa una credenziale.

Un rifiuto dice quale dei due problemi è

Una chiave mancante o non riconosciuta è 401, e una chiave riconosciuta che questo endpoint o questo progetto non ammette è 403. La distinzione è operativa: un 401 ti manda a cercare un errore di battitura in una credenziale, un 403 ti dice che la credenziale è giusta ed è sbagliata l’autorizzazione.

quattro rifiuti
$ curl -sw "%{http_code} " "$MCPD/v1/api/traces?project=acme"401  this endpoint needs an API key; send it as `Authorization: Bearer <key>`$ curl … -H "authorization: Bearer $TYPO"401  that key is not one this daemon knows$ curl … -H "authorization: Bearer $INGEST"403  that key is not valid for reading$ curl … -H "authorization: Bearer $BILLING_READ"403  that key does not reach the project "acme"

Quell’ultimo messaggio è lo stesso, che il progetto esista o no. Confermare quali nomi di progetto sono reali è una piccola perdita, e su un daemon condiviso quei nomi sono i nomi dei tuoi clienti.

Il registro di audit

Ogni payload archiviato che mcpd restituisce viene registrato, e così ogni cancellazione, quindi la domanda «chi ha letto questa esecuzione» ha una risposta. Una riga porta quando, quale chiave, quale azione, quale progetto, quale traccia o quale dataset ha nominato, e se la richiesta è stata servita o rifiutata. GET /v1/api/audit lo rilegge, dalla più recente, e prende l’ambito admin.

Le azioni registrate sono diciotto: get_trace, list_spans, list_logs, list_feedback, list_dataset_items, capture_into_dataset, read_trace_aspect_notes, get_theme, get_theme_flow, list_theme_history, list_theme_examples, delete_trace, erase_subject, e cinque sulle esecuzioni durevoli: list_runs, get_run, list_run_events, get_step_cause e cancel_run. Il confine è il payload, non la richiesta. Elencare le tracce restituisce una proiezione di nomi, tempi, conteggi di token e costo, che non contiene nessun prompt e nessuna completion, quindi non viene registrato; aprire una di quelle tracce sì, perché è lì che sta il testo. Elencare le esecuzioni durevoli viene registrato, perché ogni esecuzione nella lista porta il suo input, il suo output e il suo stato così come sono stati mandati. Una lettura rifiutata di un’esecuzione viene registrata sotto il progetto a cui appartiene l’esecuzione.

Le chiamate del worker non vengono registrate: prendere in carico un passo, dichiararlo partito, l’heartbeat, complete e fail. Un lease porta sì l’input del passo e gli output dei passi che lo precedono, ma il ciclo del worker prende in carico molte volte al minuto, e una riga per ogni presa in carico seppellirebbe le letture fatte da una persona. Tratta la chiave worker come una chiave che legge ogni payload delle esecuzioni che serve. Nemmeno avviare un’esecuzione viene registrato; un nuovo invio con una chiave di idempotenza ancora tenuta risponde con l’esecuzione che quella chiave ha avviato, quindi tratta la chiave execute come una chiave che può leggere le esecuzioni che avvia.

Cinque proprietà che chi fa una revisione dovrebbe conoscere:

  • La chiave non viene mai messa per iscritto. Il principal è un digest SHA-256 troncato della chiave, che distingue una chiave da un’altra senza archiviarne un verificatore. Una richiesta senza chiave su un daemon aperto viene registrata come anonymous.
  • La persona arriva dal tuo ingress, oppure non arriva. mcpd non ha account utente, quindi da sola una riga attribuisce un’azione a una chiave e non a un essere umano. Il campo subject porta un’identità quando --trusted-identity-header e --trusted-proxy-cidr sono impostati entrambi, ed è vuoto altrimenti, che è il comportamento predefinito. mcpd non stabilisce mai quell’identità da sé; registra quello che ha affermato un indirizzo che hai nominato tu.
  • Anche un rifiuto viene registrato. La riga scende prima che il 403 sia restituito, perché un tentativo rifiutato sul progetto di un altro team è esattamente la riga per cui un registro di audit esiste. Leggere il registro non viene a sua volta registrato: crescerebbe senza che sia mai stato letto un payload, e la riga non direbbe niente che chi tiene la chiave admin non sappia già.
  • Registra quale chiave, e se vuoi anche chi. La chiave è archiviata come digest troncato, mai la chiave stessa, quindi il registro non è un posto dove si accumulano credenziali. Questo risponde a «una chiave admin ha cancellato questo» e non a «quale persona lo ha fatto», perché mcpd non ha un identity provider. Se vuoi la seconda risposta, dagli l’header che il tuo ingress imposta già e gli indirizzi autorizzati a impostarlo: --trusted-identity-header x-forwarded-user con --trusted-proxy-cidr 10.42.0.0/16. Servono tutte e due le metà. Un header è una stringa che chiunque raggiunga la porta può mandare, quindi mcpd lo legge solo da un indirizzo che hai nominato tu; una richiesta da qualunque altro posto viene servita normalmente e la sua riga non porta nessun subject, che è la risposta onesta invece di una supposizione. Senza nessuna delle due opzioni non viene mai registrato nessun subject, ed è il comportamento predefinito.
  • La conservazione non lo sfoltisce. La passata di conservazione toglie span, log, metriche, tracce, punteggi, feedback, note di aspetto ed esecuzioni durevoli finite. Lascia stare il registro di audit, perché un registro che scadesse insieme ai dati che descrive non potrebbe dire chi ha letto i dati cancellati.
  • Non fa mai fallire la richiesta che descrive. Una riga che non si riesce a scrivere finisce nei log ed è contata come mcpd_audit_write_failures_total su /metrics, e la lettura restituisce lo stesso. Un registro di audit capace di buttare giù il percorso di lettura è una responsabilità peggiore di quella che previene, quindi sorveglia quel contatore invece di contare su richieste che falliscono.

Che cosa nessuna chiave copre

  • L’interfaccia. Il markup su / viene servito a chiunque raggiunga la porta. I dati che ci stanno dietro no: ogni richiesta che fa passa dall’API di query e incontra le stesse chiavi. Nel daemon non c’è nessuna schermata di login.
  • Le sonde e /metrics. Non autenticate di proposito, perché un kubelet e uno scraper non possono portare una chiave. Riportano stato, versione, uptime, il nome del backend di storage e dei contatori, mai payload.
  • L’origine. L’API di query e le sonde rispondono con Access-Control-Allow-Origin: *, quindi una pagina nel browser su qualunque origine le può chiamare. mcpd non usa cookie e non usa credenziali implicite, quindi una pagina così deve comunque tenere una chiave: una politica di origine permissiva non gliene dà una.

Che cosa fa l’oscuramento, e che cosa non può fare

L’oscuramento è attivo per impostazione predefinita e gira prima della scrittura, come terza fase della pipeline di ingest. Un valore che una regola intercetta non arriva mai al database, che è una garanzia diversa da quella di un valore cancellato dopo, ed è la differenza su cui un auditor fa domande. Quello che non può fare è tenere quei byte fuori dalla rete: quando la regola gira, l’hanno già attraversata. Per questo gli SDK portano MCPD_CAPTURE_PAYLOADS=false, che toglie input e output prima che uno span sia messo in coda e lascia dov’è tutto il resto.

Ingest pipeline
  1. Decode

    OTLP or native, gzip or plain

  2. Sample

    Decided once, at the root

  3. Redact

    Before anything is written

  4. Price

    From the configured price book

  5. Store

    One transaction per batch

Redaction is stage three, not a cleanup job. A value that a rule matches never reaches storage at all.

Una regola è un insieme di sottostringhe minuscole e una modalità. Una chiave corrisponde quando il suo nome, reso minuscolo, contiene una di quelle. I valori intercettati vengono mascherati, mascherati in parte, sottoposti a un digest con chiave, oppure scartati. Quando una chiave intercettata contiene un oggetto, viene oscurato anche tutto quello che sta sotto, comunque si chiamino le chiavi figlie, e un segreto mandato come numero JSON è trattato come uno mandato come stringa. La tabella completa delle regole sta su Oscuramento e conservazione.

Adesso il limite, quello su cui una revisione farà domande e che nessuna configurazione toglie. Le regole corrispondono a nomi di campo, non alla forma di un valore. Una chiave API che un utente ha incollato in mezzo a una frase, e che arriva sotto un campo chiamato content o question, non viene intercettata da niente nella pipeline. Il nome del campo è innocente, quindi il valore viene archiviato per intero.

L’oscuramento ferma quindi una cosa sola:

  • Ferma la cattura strutturata di credenziali e identificatori, dove una credenziale arriva in un campo che porta il nome di quello che è.
  • Non analizza il testo libero, e non è un classificatore di contenuti.
  • Non riconosce un segreto dalla sua forma né dalla sua entropia.
  • Non tocca il nome di uno span né il messaggio di un record di log. Quelli sono identificatori che servono a raggruppare, non payload, e riscriverli romperebbe il raggruppamento. Non costruire il nome di uno span con l’indirizzo email di un cliente.
  • Non tocca lo storage delle esecuzioni. I payload di un’esecuzione durevole sono il suo stato di lavoro e sono tenuti così come sono stati mandati; vengono oscurati solo gli span che un’esecuzione scrive nello storage delle tracce.

Un controllo che lavora sulla forma c’è, ed è rilevamento a cose fatte invece che prevenzione: lo scorer integrato no_leaked_secret abbassa il voto a un’esecuzione la cui risposta porta un token che sembra una credenziale, per prefisso noto o per una lunga sequenza alfanumerica con maiuscole e minuscole. Dà un punteggio a un’esecuzione che è già archiviata. Non toglie niente, non legge il prompt, e gira solo quando glielo chiedi. Consideralo un modo per scoprire che questo è successo, non una seconda linea di oscuramento.

Due limiti valgono che una regola abbia corrisposto o no. Qualunque singola stringa più lunga di 128 KiB viene troncata su un confine di carattere con un marcatore, e la discesa si ferma a una profondità di 32. Esistono entrambi perché un solo documento fuori controllo non possa decidere la dimensione del tuo database né inchiodare una CPU.

--no-redact spegne l’intera pipeline, il che ha senso solo quando qualcosa a monte oscura già. Dove l’oscuramento sottopone a hash un identificatore, imposta --redact-key: la chiave integrata è una costante in un sorgente pubblicato, e gli identificatori vengono da spazi piccoli, quindi un digest senza chiave si recupera provando i candidati.

Conservazione e cancellazione

--retention-days cancella i segnali più vecchi di quel numero di giorni. Non impostarlo e non viene cancellato mai niente, che è il comportamento predefinito giusto: uno strumento che scartasse in silenzio le tracce del mese scorso lo scopriresti nel momento peggiore.

La passata gira su un timer ogni ora dentro il daemon, mai dentro la richiesta di un utente. Copre ogni progetto nominato nel blocco projects, ognuno con il proprio retention_days dove ne imposta uno e con la cifra globale dove non lo fa, più il progetto predefinito. Un progetto che non ha una finestra da nessuna parte non viene sfoltito affatto.

Per una richiesta che riguarda una persona, DELETE /v1/api/subjects/{user_id}?project=… toglie ogni esecuzione registrata a nome di quella persona nel progetto, con gli span, i log, le metriche, i punteggi e i feedback che ne dipendono, e risponde con un conteggio per segnale. L’identificatore viene sottoposto a hash con la stessa chiave prima del confronto, così la richiesta funziona sugli identificatori già sottoposti a hash senza che tu ne debba sottoporre a hash nessuno, e la risposta dice quanti identificatori archiviati erano in una forma che la richiesta non poteva far corrispondere. Per togliere una sola esecuzione invece di una persona, DELETE /v1/api/traces/{trace_id} toglie quella e tutto quello che ne dipende in una transazione sola. Prendono entrambe l’ambito admin ed entrambe finiscono nel registro di audit. Oscuramento e conservazione ripercorre la richiesta di cancellazione e che cosa può e non può promettere.

Che cosa mcpd non fa

Niente di quello che segue esiste in questa build. È elencato perché una revisione che lo scopre più tardi è un esito peggiore di una revisione che lo legge qui.

Non c’è in mcpdChe cosa fare invece
Terminazione TLS. mcpd parla HTTP in chiaro. Prompt e completion attraversano quel collegamento per intero. Termina il TLS sul tuo ingress o nella tua mesh, e tieni la porta del daemon fuori da qualunque rete non fidata.
SSO. Nessuna integrazione con un identity provider, niente OIDC, niente SAML, nessuna schermata di login. Metti l’interfaccia dietro il proxy di identità che già gestisci. L’identità di una persona è quella che quel proxy stabilisce.
SCIM. Nessun provisioning di utenti o di gruppi, perché mcpd non ha il concetto di account utente. Fai il provisioning sul proxy. Dentro mcpd l’unità è una chiave, aggiunta a un elenco o tolta da un elenco.
Ruoli oltre i sei ambiti. L’autorizzazione è un ambito e un elenco di progetti. Non c’è un ruolo per endpoint e non ci sono gruppi. Separa per chiave e per progetto. Se una distinzione conta, falla diventare un progetto.
Stabilire chi è una persona. Il registro di audit può registrare un subject, ma mcpd non ricava mai da sé chi è il chiamante. Registra quello che il tuo ingress ha affermato, e solo da un indirizzo che hai nominato tu. Imposta --trusted-identity-header e --trusted-proxy-cidr, così l’header che il tuo proxy imposta già arriva nella riga di audit. Senza di loro una riga attribuisce un’azione a una chiave, ed emettere una chiave per team restringe il campo del digest.
Ciclo di vita delle chiavi. Le chiavi sono elenchi letti all’avvio. Non c’è scadenza, non c’è un calendario di rotazione e non c’è una quota per chiave. Il tetto che esiste è per progetto: --quota-per-minute limita quanti segnali un progetto può archiviare in un minuto, quale che sia la chiave che li ha mandati. Configura più chiavi, una per team o per flotta, così revocarne una è una modifica e un riavvio, invece del rideploy di tutto quello che emette uno span. Se una flotta ha bisogno del suo tetto, falla diventare un progetto.
Cifratura a riposo. mcpd scrive file in chiaro o righe in chiaro. Cifra il volume, oppure usa un database che lo fa.

Segnalare qualcosa

Le vulnerabilità sospette vanno a [email protected] con «Security» nell’oggetto, non in un issue pubblico. Il SECURITY.md del repository dichiara l’ambito e che cosa contiene una buona segnalazione. Un’istanza non autenticata che qualcuno ha scelto di esporre è un comportamento predefinito documentato, non una scoperta. Qualcosa che passa oltre una chiave che era configurata, oltre una regola di oscuramento configurata, o attraverso il confine fra progetti, lo è.

Poi