Interrogare il Data Lake Sophos Fusion con la Search GraphQL API
La Search GraphQL API v1 consente a un’applicazione propria di cercare nel Data Lake storico di Sophos Fusion con QL. Il flusso è asincrono: avviare la ricerca, conservare la sua Search-URN, verificare il completamento e l’esito dell’esecuzione, quindi leggere tutte le pagine dei risultati. COMPLETE da solo non significa che tutte le parti della ricerca siano riuscite.
L’API è pubblicata dal 12 ottobre 2026. Affianca le API esistenti e non impone un passaggio immediato dalla XDR Query API, che rimane disponibile. Non sostituisce nemmeno Live Endpoint Search (LES): LES interroga gli endpoint connessi con SQL di tipo osquery. La distinzione tra Classic Live Discover, Data Lake Search e LES aiuta a scegliere il percorso dati corretto.
Prima della prima richiesta
Servono un tenant contenente i dati storici cercati, API Credentials adeguate, l’UUID univoco del tenant e un client HTTP capace di inviare richieste POST JSON e salvare le risposte complete. Per l’esempio, il tenant deve contenere eventi di processo. In assenza di tali dati, anche una ricerca tecnicamente riuscita può essere vuota.
Gestire in sicurezza le credenziali API illustra identità macchina, flusso OAuth con client_credentials, associazione al tenant e rotazione. Un identificatore partner o organizzazione non è un UUID tenant. In presenza di più tenant, scegliere quello di destinazione tramite un’associazione approvata e univoca, non soltanto dal nome visualizzato.
Il ruolo minimo esatto e gli entitlement necessari per lo specifico client Search vanno chiariti prima dell’approvazione per la produzione. Il nome di un ruolo generico di service principal non dimostra un’autorizzazione Search confermata. In caso di rifiuto, non assegnare Super Admin per precauzione.
Prima dell’avvio, definire la domanda investigativa, l’intervallo temporale e i campi necessari. I dati di processo possono contenere nomi utente, percorsi e righe di comando riservati. Query ed esportazioni devono restare in un ambiente approvato con diritti di accesso e conservazione regolamentati. Token e Client Secret non devono comparire nei log delle richieste, nei ticket o nei file di esempio salvati. Non registrare senza controllo nemmeno le righe complete dei risultati.
Usare l’host di destinazione specifico dell’API
Le operazioni seguenti condividono lo stesso contratto di richiesta:
POST https://api.taegis.sophos.com/graphql
Authorization: Bearer <access-token>
X-Tenant-ID: <tenant-uuid>
X-GraphQL-Schema: default
Content-Type: application/json
Nel client HTTP, sostituire <access-token> con il token di accesso OAuth corrente e <tenant-uuid> con l’UUID verificato. URL e header restano uguali per ciascuno dei body JSON seguenti. Gli esempi sono modelli di richiesta, non test tenant eseguiti qui né output di successo registrati.
L’host REST regionale di apiHosts.dataRegion non è l’host di destinazione di questo contratto Search. Viceversa, non spostare globalmente i client REST esistenti sull’host GraphQL. Questo nome host non garantisce neppure il luogo di archiviazione o la residenza dei dati; tali requisiti vanno chiariti in base alle disposizioni contrattuali e del tenant applicabili.
Avviare e tracciare una ricerca in modo univoco
Creare una nuova esecuzione con createSearch
Il modello seguente cerca eventi di processo dell’ultima ora e limita i campi evento selezionati. -1h è una finestra relativa per una prima verifica funzionale; per un successivo confronto con un altro client, definire limiti temporali espliciti identici.
{
"operationName": "CreateSearch",
"query": "mutation CreateSearch($input: CreateSearchInput) { createSearch(input: $input) { urn status } }",
"variables": {
"input": {
"text": "from process where earliest = -1h | fields host_id, commandline, image_path"
}
}
}
Prima dell’invio, verificare tenant, intervallo temporale e perimetro dei dati. createSearch crea e avvia una nuova esecuzione di ricerca, ma non modifica la configurazione di protezione degli endpoint. L’esecuzione può generare lavoro nel servizio e risultati sensibili. Un errore client non deve quindi avviare automaticamente una serie illimitata di nuove ricerche.
Dopo la richiesta, valutare prima lo stato HTTP e l’array GraphQL errors di primo livello. Anche HTTP 200 può contenere errori GraphQL. Salvare data.createSearch.urn solo dopo questa verifica. Usare l’URN restituita senza modificarla per stato e risultati; non scomporne né ricostruirne il formato. Lo stato iniziale può variare.
Per ogni esecuzione, il client registra almeno UUID tenant, testo della query, riferimento temporale, Search-URN e ora di avvio. Una Search-URN identifica l’intera esecuzione. Una Partition-URN identifica invece un sottoincarico, mentre un Event Resource Name identifica un singolo evento. Questi identificatori non sono intercambiabili.
Riutilizzare consapevolmente ricerche esistenti
Se è consentito utilizzare una ricerca adeguata già in corso o completata, getOrCreateSearch è un’alternativa:
{
"operationName": "GetOrCreateSearch",
"query": "mutation GetOrCreateSearch($input: CreateSearchInput) { getOrCreateSearch(input: $input) { urn status } }",
"variables": {
"input": {
"text": "from process where earliest = -1h | fields host_id, commandline, image_path"
}
}
}
Qui si acquisisce l’URN da data.getOrCreateSearch.urn. L’associazione considera i dati di input e confronta le query tramite la rappresentazione strutturata canonica, non soltanto tramite testo identico. Il riutilizzo non garantisce risultati aggiornati e il client non deve presentarlo come una nuova esecuzione. Quando il compito richiede una nuova esecuzione, usare deliberatamente createSearch.
Se un timeout fa perdere la risposta dopo createSearch, la ricerca potrebbe essere comunque in corso. Un nuovo tentativo alla cieca può creare un’altra esecuzione. Registrare lo stato come «creazione incerta» e decidere la ripresa secondo i requisiti di riutilizzo e aggiornamento definiti in precedenza. getOrCreateSearch non dimostra a posteriori che sia stata trovata esattamente l’esecuzione persa.
Verificare completamento, errori parziali e scadenza
Interrogare lo stato con la Search-URN salvata. Sostituire <search-urn> esclusivamente con il valore realmente restituito:
{
"operationName": "SearchDiagnostics",
"query": "query SearchDiagnostics($urn: URN!) { search(urn: $urn) { urn status errors expireTime outcome { success errors { urn errors } } searchPartitions { urn schemaType status errors } } }",
"variables": {
"urn": "<search-urn>"
}
}
Finché status è QUEUED o IN_PROGRESS, il client attende tra le interrogazioni. outcome.success: UNKNOWN è previsto prima del completamento. Un intervallo di polling, ad esempio di cinque secondi, è una possibile scelta del client, non un limite garantito del servizio. Ogni richiesta necessita inoltre di un timeout e il flusso complessivo di una scadenza adeguata al carico. Superata tale scadenza, l’esecuzione rimane irrisolta; interrompere il polling locale non annulla la ricerca sul server.
Con status: COMPLETE, valutare separatamente outcome.success:
| Valore | Significato |
|---|---|
ALL | Tutte le partizioni sono riuscite. |
PARTIAL | Solo alcune partizioni sono riuscite. |
NONE | Nessuna partizione è riuscita. |
Per PARTIAL, stabilire prima dell’uso in produzione se e per quali scopi siano accettabili risultati parziali. Un rapporto investigativo completo non deve presentarli implicitamente come riscontro complessivo. Con NONE non esiste un perimetro di ricerca riuscito su cui basare un cessato allarme. outcome.errors associa i messaggi alle partizioni; searchPartitions ne fornisce schema, stato ed errori. Anche in caso di esecuzione riuscita, errors può contenere avvisi a livello di ricerca o partizione.
Usare anche expireTime per verificare il piano di recupero: leggere i risultati necessari entro quel momento e, se servono più a lungo, salvarli nel proprio sistema approvato. Conservare un’URN o un cursore non ne prolunga la disponibilità. Qui non viene dedotta alcuna TTL fissa da un singolo esempio.
Leggere tutte le pagine dei risultati
Dopo il completamento e la valutazione dell’esito, richiedere la prima pagina senza cursor nelle variabili:
{
"operationName": "SearchResults",
"query": "query SearchResults($urn: URN!, $limit: Int, $cursor: String) { searchResults(urn: $urn, limit: $limit, cursor: $cursor) { fields { name baseType originSchemaTypes } values totals { total { value } subtotals { schemaType value } } nextCursor } }",
"variables": {
"urn": "<search-urn>",
"limit": 2
}
}
limit: 2 facilita la verifica della sequenza delle pagine nel pilota. Limita le righe per pagina, non il numero totale di corrispondenze. Per un client di produzione, scegliere la dimensione delle pagine in base all’elaborazione, senza dedurne un limite massimo documentato o un limite complessivo dei risultati.
Dopo ogni recupero, verificare stato HTTP, errors GraphQL e percorso di risposta atteso. Elaborare la pagina solo dopo. Anche dopo COMPLETE, il recupero può richiedere tempo; un timeout di lettura non dimostra un errore di ricerca. Riprovare gli errori di lettura transitori con backoff limitato, usando la stessa Search-URN e lo stesso cursore. Il consumer deve impedire che una pagina riletta venga esportata due volte.
Se data.searchResults.nextCursor contiene un valore, riportarlo invariato nelle variabili della stessa richiesta SearchResults:
{
"urn": "<search-urn>",
"limit": 2,
"cursor": "<next-cursor>"
}
Questo è soltanto il nuovo contenuto della sezione variables, non un body GraphQL completo. <next-cursor> proviene dalla pagina immediatamente precedente elaborata con successo. Ogni cursore rimane legato alla Search-URN che lo ha fornito. Non interpretarlo, modificarlo o trasferirlo a un’altra esecuzione. Continuare finché nextCursor è null o assente. Un cursore ripetuto o il superamento della scadenza client è un errore di recupero, non una conclusione riuscita.
Distinguere valori, metadati e totali
values contiene oggetti JSON di riga, non un elenco di colonne SQL né una stringa additionalColumns codificata in JSON come nel formato dei risultati LES. Le voci possono contenere oggetti ed elenchi annidati. fields descrive nomi e tipi dei campi disponibili; una voce di metadati non garantisce un valore popolato in ogni riga. Gestire sia i campi mancanti sia i valori null espliciti, senza equipararli automaticamente a una stringa vuota o a zero corrispondenze.
baseType serve a interpretare i valori. Per esempio, TIMESTAMP_SECONDS distingue i secondi da TIMESTAMP_MILLISECONDS e TIMESTAMP_MICROSECONDS; TIMESTAMP_ISO_8601 indica una stringa ISO-8601. Elaborare i tipi elenco e mappa come raccolte, non come un singolo scalare. Anche expireTime è un timestamp ISO-8601. I metadati possono indicare campi annidati con notazione puntata, come process_file_hash.md5. Il parser non deve quindi presumere che ogni nome simile sia una chiave piatta nell’oggetto di riga.
Le indicazioni facoltative totals.total e totals.subtotals riguardano rispettivamente l’intero insieme dei risultati e i relativi schemi, non la lunghezza dell’elenco values corrente. Per un recupero completo, elaborare tutte le pagine e confrontare le righe raccolte con i totali disponibili. L’assenza di totali non giustifica l’invenzione di un numero; restano necessari il completamento dei cursori e la documentazione del perimetro recuperato. Nemmeno una paginazione completa rende un risultato PARTIAL pienamente riuscito.
Preservare proiezione QL e arricchimento
Nell’esempio, | fields host_id, commandline, image_path limita i campi evento della query QL. Il selection set GraphQL determina invece se includere sezioni di risposta come fields, totals o nextCursor. values è uno scalare JSON: i singoli campi delle righe non possono essere selezionati tramite un selection set GraphQL annidato.
L’arricchimento aggiunge informazioni derivate. hostname ed enrichSummary non devono comparire direttamente in | fields. Conservare host_id per la risoluzione del nome host. Anche così, un nome host risolvibile non è garantito. I campi di input per enrichSummary dipendono dall’evento; una proiezione più stretta può abbreviare il riepilogo. Prima di ridurla, confrontare eventi rappresentativi con gli arricchimenti richiesti dal consumer.
Una proiezione può ridurre la risposta. Non dimostra costi di scansione inferiori né un’esecuzione più rapida. Omettere le sezioni di risposta non necessarie non deve soprattutto comportare la perdita di nextCursor e quindi della possibilità di recuperare tutti i risultati.
Confrontare in modo controllato un client XDR Query esistente
Passare dallo SQL storico a Search-QL non equivale a sostituire un URL né a tradurre meccanicamente SQL. Inventariare prima query esistenti, campi richiesti e rapporti a valle. La distinzione generale delle API XDR resta separatamente rilevante per Cases, Detections e altri flussi.
Per ogni query storica, eseguire il confronto in questo ordine:
- Registrare la domanda funzionale e le fonti necessarie. Le tabelle SQL non giustificano un’associazione diretta con schemi QL omonimi; verificare singolarmente schemi e nomi dei campi.
- Definire limiti espliciti
earliestelatestidentici per entrambe le ricerche e documentare il riferimento temporale. Due query consecutive «ultima ora» non costituiscono un confronto di uguaglianza rigoroso. - Verificare confronti di stringhe, distinzione maiuscole/minuscole ed espressioni regolari. QL non supporta i join SQL; join e colonne derivate richiedono un flusso funzionale riprogettato. Non effettuare il passaggio senza un’equivalenza chiarita.
- Verificare inizialmente la query QL in modo interattivo in Security Operations > Data Lake Search. Eseguire poi la ricerca API con le stesse condizioni funzionali.
- Confrontare record rappresentativi, campi obbligatori, tipi, timestamp e arricchimento, non soltanto il numero di corrispondenze. Entrambi i client devono leggere integralmente i rispettivi risultati.
- Adattare il nuovo consumer a
values/fields, Search-URN e cursori. Non associare alla cieca i vecchi stati RESTfinishedesucceededa un unico campo di stato GraphQL:COMPLETEeoutcome.successrestano verifiche separate.
Prima del cutover devono funzionare anche la politica sugli errori parziali, i tentativi limitati, la gestione della creazione incerta, la scadenza e la conservazione degli export. Il pilota tenant approvato registra query, finestra temporale, campi richiesti e tutte le pagine, ma nessun secret. Una ricerca vuota è valutata correttamente solo dopo aver verificato fonte, periodo, filtri, esito e risultati letti integralmente; non è un cessato allarme generale.
Fino all’accettazione, il client XDR Query esistente rimane il percorso operativo concordato. Se il nuovo flusso fallisce, interrompere il passaggio oppure riportare l’applicazione al client precedentemente conservato. Ciò non elimina dati già esportati né colma lacune probatorie già create. Una nuova ricerca successiva può differire dalla prima per riferimenti temporali diversi o dati non più disponibili.
Isolare gli errori in modo mirato
- Richiesta rifiutata: Verificare URL,
X-GraphQL-Schema: default, body JSON, token Bearer valido e UUID tenant corretto. Chiarire separatamente autenticazione e autorizzazione specifica dell’operazione; non cercare la regione per tentativi. Escludere i secret dai diagnostici. - HTTP 200, ma nessun
datautilizzabile: Leggere glierrorsdi primo livello. Verificare query, tipi delle variabili e campi richiesti prima di elaborare i dati a valle. COMPLETEconPARTIALoNONE: Valutareoutcome.errors, errori di ricerca esearchPartitionscoinvolte. Trattare l’export come incompleto o trattenerlo secondo la regola sui risultati parziali approvata in precedenza, senza confonderlo con zero corrispondenze.- Nome host assente o riepilogo più breve: Verificare proiezione e tipo di evento. Conservare
host_idper risolvere il nome host; non sostituire gli input di arricchimento mancanti conhostnameoenrichSummaryin| fields. - Recupero bloccato o concluso troppo presto: Verificare timeout della richiesta, scadenza complessiva, URN invariata, ultima pagina elaborata con successo e associazione del cursore. Un piccolo
limitnon è un limite complessivo dei risultati. - Un’URN salvata non restituisce più risultati in seguito: Verificare
expireTimee i dati già salvati. Né URN né cursore è un archivio permanente. Se serve una nuova esecuzione, documentarla come tale e non come ripristino del risultato originale.
L’API continua a evolversi. Limitare il client alle operazioni documentate createSearch, getOrCreateSearch, search e searchResults e ai campi supportati. I campi aggiuntivi rilevati tramite introspezione non costituiscono un’approvazione automatica all’implementazione.