Sicurezza del server MCP per Craft CMS: il limite è un utente Craft.
Un client IA che può modificare un sito in funzione ha bisogno di limiti chiari. Questa pagina elenca ogni limite nell'ordine in cui una chiamata lo incontra. Dice anche cosa non copre ciascun limite.
Cosa attraversa ogni chiamata
In questo ordine. Una chiamata che non supera un passaggio non raggiunge mai lo strumento.
- Limite di frequenza — chiamate al minuto, contate per token. Un freno contro un modello che gira in loop, non una difesa contro un chiamante determinato che possiede un token valido.
- Ambito del token —
read,contentofull. L'ambito restringe soltanto ciò che l'utente associato può già fare. - Edizione — il plugin esce in un'unica edizione, quindi oggi questo passaggio va sempre a buon fine. Esiste perché una futura suddivisione in edizioni non possa concedere di nascosto uno strumento.
- Modalità sola lettura — finché è attiva, non viene eseguito nulla che modifichi qualcosa, qualunque cosa dicano le impostazioni seguenti.
- Attivazione degli strumenti — ogni strumento che modifica qualcosa è disattivato finché un amministratore non lo attiva, uno alla volta.
- Controllo amministratore — gli strumenti da amministratore richiedono inoltre che l'utente associato sia un amministratore.
- Schema degli argomenti — gli argomenti vengono validati rispetto allo schema dello strumento prima che lo strumento li veda.
- Il controllo dei permessi di Craft, per elemento — poi lo strumento chiede a Craft, per questo elemento e questo sito, se l'utente associato può vederlo, salvarlo o eliminarlo.
Due tipi di rifiuto, di proposito. Uno strumento che non esiste e uno strumento non coperto dall'ambito del token ricevono la stessa risposta — Unknown tool — perché la differenza rivelerebbe a un attaccante cos'altro c'è. Uno strumento disattivato, bloccato dalla modalità sola lettura o riservato agli amministratori non compare nemmeno nell'elenco, ma una chiamata diretta riceve il motivo in parole chiare, così chi gestisce il sito non cerca l'errore dalla propria parte.
Perché l'utente è il vero confine
Ogni token appartiene esattamente a un utente Craft. Si pongono due domande, e quella che conta è la seconda: prima quella generale, se questo tipo di azione è aperto a questo utente, e poi quella precisa — questo utente può fare questo, su questo elemento, su questo sito? Un redattore che può lavorare solo su un sito e vedere una sola sezione raggiunge esattamente quello tramite il client IA, e nient'altro.
Questo ha una conseguenza che vale la pena dire chiaramente: un token che appartiene a un amministratore può fare ciò che può fare un amministratore. Assegna a un token l'utente con meno permessi che basta ancora per il lavoro, e usa l'ambito per restringere ulteriormente.
Una sezione o un gruppo di categorie sconosciuti o vietati restituiscono un elenco vuoto, non un errore — un errore confermerebbe che la cosa esiste. Un handle di sito inesistente viene invece segnalato come errore, perché un handle sbagliato è un errore nella chiamata, non un confine.
Token
- Il testo in chiaro viene mostrato una sola volta, quando il token viene creato. Viene salvato solo un hash SHA-256, quindi non può essere mostrato di nuovo.
- I token stanno in una tabella propria, mai nella project config — che viene salvata in Git.
- La pagina dei token nel pannello di controllo è accessibile solo agli amministratori. Craft registra un permesso "Access AlphaBridge MCP" per ogni plugin con una voce nella barra laterale; concederlo non apre questa pagina ai non amministratori.
- Un token può avere una data di scadenza e può essere revocato in qualsiasi momento; la chiamata successiva con quel token fallisce.
- L'header
Authorizationviene oscurato nelle voci di log di Craft relative al contesto della richiesta, così un token non finisce instorage/logsquando una richiesta viene registrata.
Il registro delle attività, e cosa omette volutamente
Ogni chiamata agli strumenti viene registrata: quando, quale strumento, quale utente Craft e l'esito — consentita, rifiutata o fallita. Vengono registrate anche le chiamate rifiutate; sono quelle interessanti.
I nomi degli argomenti vengono registrati, i loro valori no. Puoi vedere che una voce è stata creata con una sezione e un titolo; il titolo non lo puoi leggere dal registro. È voluto: un registro che salvasse i valori sarebbe una seconda copia, meno protetta, dei tuoi contenuti.
Per leggere il registro serve l'ambito amministratore. Il registro viene ridotto a una dimensione fissa, quindi non può crescere senza limiti.
Un limite, detto onestamente: la scrittura della voce di registro avviene al meglio delle possibilità. Se quella scrittura fallisce — per esempio per un errore del database — la chiamata non viene interrotta; l'errore finisce invece nel log di Craft. Considera quindi il registro delle attività una traccia molto buona, non una garanzia che non manchi nulla.
Leggere i log di Craft è più limitato di quanto sembri
Lo strumento per i log restituisce le voci di log di Craft senza il contesto della richiesta che Craft vi allega. Quel contesto contiene $_POST, $_COOKIE e $_SESSION — compresa, in caso di login fallito, una password in chiaro. Istruzioni SQL e stack trace vengono troncati al primo marcatore.
Per questo lo strumento per i log è uno strumento da amministratore ed è disattivato di base.
Gli strumenti per il database, e i loro limiti
Due strumenti raggiungono il database, e solo uno esegue SQL scritto da te. craft_db_schema elenca i nomi delle tabelle e segna quelle protette come non leggibili. craft_db_query esegue una SELECT su una tabella e rifiuta, prima che qualcosa raggiunga il database:
- join, subquery, common table expression e
UNION - ogni funzione tranne
count,min,max,avgesum - tabelle protette — utenti, sessioni, token, la project config e altre che contengono credenziali o segreti
- espressioni che nominano l'account del server o una sequenza
Una barriera fatta di regole conosce solo ciò che le è stato detto. Per questo entrambi sono strumenti da amministratore, lo strumento di query è disattivato di base e nessuno dei due scrive mai.
Origini e DNS rebinding
Se una richiesta contiene un header Origin, deve essere nell'allowlist, altrimenti l'endpoint risponde 403. L'allowlist è ciò che hai configurato più l'URL del sito, ma solo dove questo si può determinare staticamente. Un URL del sito costruito dall'alias @web di Craft non conta: Craft ricava @web dall'header Host della richiesta in arrivo, quindi un attaccante verrebbe confrontato con un valore scelto da lui stesso.
In un'installazione di questo tipo l'elenco configurato è l'unica fonte e, se è vuoto, ogni richiesta che contiene un Origin viene rifiutata. I client server-to-server non inviano alcun Origin e non sono interessati.
Cosa non fa questo plugin
Ognuna di queste è una scelta, e ognuna elimina una categoria di rischio invece di gestirla.
- Nessuna esecuzione arbitraria di codice. Nessuno strumento esegue PHP, comandi shell o un template passato da te.
- Nessun deploy e nessun trasferimento di file. Il plugin non sposta file e non tocca mai un server via SSH o FTP.
- Nessuna gestione del ciclo di vita dei plugin. Niente installa, attiva, disattiva o rimuove plugin.
- Nessuna scrittura nella project config o negli utenti. Craft applica le regole per questi nei propri controller; questo plugin parla con l'API degli elementi, che non vede quelle regole — quindi lì non scrive affatto.
- Nessun tag. Craft non definisce permessi per i tag, quindi qualsiasi confine andrebbe inventato anziché applicato.
- Nessun valore delle impostazioni dei campi. Le impostazioni dei campi vengono restituite solo come nomi, mai come valori, perché un plugin può metterci qualsiasi cosa. Questa non è una promessa generale sui segreti: lo strumento per il database raggiunge ogni tabella fisica il cui nome non è nel suo elenco di tabelle protette, e una tabella di terze parti con un nome poco appariscente non vi compare. Proprio per questo lo strumento è riservato agli amministratori e disattivato di base.
Come lo manteniamo vero
Ogni modifica esegue una suite di unit test, l'analisi statica con PHPStan al livello 6 senza baseline e un test end-to-end su un'installazione di Craft appena creata con PostgreSQL 16 e MySQL 8.4. Quel test crea un redattore non amministratore che può vedere esattamente una sezione e lavorare esattamente su un sito, e dimostra che il filtro dei permessi taglia davvero — non soltanto che gli strumenti restituiscono qualcosa.
Hai trovato qualcosa? Segnalalo in privato a mail@cultureclub.dev, non con una segnalazione pubblica.