Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
authproof-sdk — Ricevute di delega firmate crittograficamente per agenti AI. Definisci esattamente ciò che un'IA può e non può fare — firmato, verificabile, a prova di manomissione. | Kitploit
Strumenti/GitHubGitHub/commonguy25/authproof-sdk
Autenticazione e AutorizzazioneCrittografiaGestione Identità e Accessi (IAM)Sicurezza della Supply ChainSicurezza delle APISicurezza dell'IA
GitHubcommonguy25/authproof-sdk

authproof-sdk

Ricevute di delega firmate crittograficamente per agenti AI. Definisci esattamente ciò che un'IA può e non può fare — firmato, verificabile, a prova di manomissione.

Vedi Repository
62 mesi faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Sito web

AuthProof SDK

AuthProof è un protocollo di delegazione crittografica per l'IA agentica. La maggior parte dei protocolli in questo campo impongono l'applicazione di una policy definita dall'operatore, dando all'operatore l'autorità di espandere o reinterpretare l'intento originale dell'utente dopo il fatto. AuthProof è costruito su un modello di fiducia diverso: la chiave privata dell'utente firma l'oggetto di autorizzazione che controlla l'esecuzione, e lo stato live del modello viene verificato sia al momento dell'autorizzazione che immediatamente prima dell'esecuzione. La combinazione di autorità firmata dall'utente e gating dello stato live del modello è la rivendicazione specifica, non una storia di enforcement generica.

Cosa lo rende diverso:

  • L'utente è l'autorità firmataria. Ogni protocollo concorrente (AIP, AITH, OAP, SAGA, AgentSpec) impone l'applicazione di una policy definita dall'operatore. In AuthProof, la chiave privata dell'utente firma direttamente l'oggetto di autorizzazione. L'operatore non può ampliare l'ambito dopo che l'utente ha firmato.

  • Commitment dello stato del modello in due fasi. Il modello viene misurato al momento dell'autorizzazione e rimisurato immediatamente prima dell'esecuzione. Se il modello è cambiato tra questi due punti, l'esecuzione viene bloccata nella verifica pre-esecuzione.

  • Distinzione tra aggiornamento del provider e sostituzione malevola. Il protocollo classifica le modifiche dello stato del modello in due categorie: aggiornamenti legittimi del provider (PROVIDER_UPDATE_REQUIRES_REAUTH) e sostituzioni non autorizzate (MALICIOUS_MODEL_SUBSTITUTION). Ciascuna produce un codice di motivazione di rifiuto leggibile dalla macchina che identifica quali componenti sono cambiati.

Verificatore Pre-Esecuzione

Il gate deterministico che viene eseguito prima che qualsiasi azione dell'agente venga eseguita.

Il PreExecutionVerifier si trova al di fuori del runtime dell'agente. Il runtime non ottiene mai il controllo finché il verificatore non supera. Un agente compromesso o malevolo non può saltarlo — viene eseguito prima che il runtime parta.

Perché è importante

I controlli di autorizzazione tradizionali avvengono all'interno del runtime dell'agente. Se il runtime è compromesso, questi controlli possono essere saltati, riordinati o bypassati. PreExecutionVerifier elimina questa superficie d'attacco spostando l'autorizzazione completamente al di fuori del runtime. L'agente esegue solo se — e solo se — tutti e sei i controlli sequenziali vengono prima superati.

Avvio rapido```js

import { PreExecutionVerifier, DelegationLog } from 'authproof-sdk/pre-execution-verifier' import { RevocationRegistry } from 'authproof-sdk'

// 1. Set up the gate const delegationLog = new DelegationLog() const revocationRegistry = new RevocationRegistry() await revocationRegistry.init({ privateKey, publicJwk })

const verifier = new PreExecutionVerifier({ delegationLog, revocationRegistry }) await verifier.init({ privateKey: verifierKey, publicJwk: verifierPub })

// 2. Register your delegation receipt delegationLog.add(receiptHash, receipt)

// 3. Gate every action — before the agent runs const result = await verifier.check({ receiptHash, action: { operation: 'read', resource: 'calendar' }, operatorInstructions: 'Summarize meetings. Stay within scope.', programHash, // optional: prevents code substitution attacks })

if (!result.allowed) { throw new Error(Blocked: ${result.blockedReason}) } // Agent runtime only reaches here after all six checks pass

root@kitploit:~
### Sei controlli sequenziali (si ferma al primo fallimento)

| # | Controllo | Blocca quando |
|---|-----------|---------------|
| 1 | Firma del ricevuta | Firma ECDSA P-256 non valida o ricevuta manomessa |
| 2 | Revoca | Il ricevuta è stata revocata tramite `RevocationRegistry` |
| 3 | Finestra temporale | Ricevuta scaduta o non ancora valida (oracolo timestamp del log, non orologio client) |
| 4 | Ambito | Azione non in `ScopeSchema.allowedActions` o fallisce corrispondenza ambito basata su testo |
| 5 | Istruzioni operatore | Le istruzioni attuali non corrispondono all'hash bloccato nella ricevuta al momento dell'emissione |
| 6 | Hash del programma | Il `programHash` fornito non corrisponde all'hash `executes` impegnato (prevenzione sostituzione codice) |

Ogni risultato del controllo — superato o fallito — viene automaticamente registrato in un `ActionLog` immutabile firmato con la propria chiave del verificatore.

### Integrazioni middleware

Wrapper plug-and-play per framework comuni. Ogni wrapper filtra ogni chiamata attraverso `PreExecutionVerifier` prima che il codice avvolto venga eseguito.

- **[LangChain](https://github.com/commonguy25/authproof-sdk/blob/HEAD/src/middleware/langchain.js)** — avvolge qualsiasi agente con un metodo `invoke()`
- **[Express/HTTP](https://github.com/commonguy25/authproof-sdk/blob/HEAD/src/middleware/express.js)** — middleware per richieste per qualsiasi framework compatibile con Express
- **[Generic function wrapper](https://github.com/commonguy25/authproof-sdk/blob/HEAD/src/middleware/generic.js)** — avvolge qualsiasi funzione asincrona```js
// LangChain
import { authproofMiddleware } from 'authproof-sdk/middleware/langchain'
const guardedAgent = authproofMiddleware(agent, { receiptHash, verifier })

// Express
import { authproofMiddleware } from 'authproof-sdk/middleware/express'
app.use(authproofMiddleware({ verifier, getReceiptHash: (req) => req.headers['x-receipt-hash'] }))

// Any function
import { guardFunction } from 'authproof-sdk/middleware/generic'
const guardedExecute = guardFunction(executeAction, { receiptHash, verifier, action })

Il Problema

Tutti i framework IETF esistenti per l'identità degli agenti -- AIP, draft-klrc-aiagent-auth, WIMSE -- affrontano la fiducia servizio-agente: come un servizio a valle verifica che un agente sia autorizzato a chiamarlo. Nessuno di essi affronta la fiducia utente-operatore.

La catena di delega negli attuali sistemi agentici è:``` User → Operator → Agent → Services

root@kitploit:~
L'utente istruisce l'operatore. L'operatore istruisce l'agente. Ma nessuna registrazione crittografica dell'intento originale dell'utente esiste al momento della delega. L'operatore diventa un terzo fidato con autorità incontrollata di espandere, distorcere o omettere le istruzioni dell'utente prima che raggiungano l'agente.

Le conseguenze:

- Gli utenti non possono dimostrare cosa hanno autorizzato.
- I regolatori non hanno una pista di audit.
- I tribunali non hanno una catena di prove.
- Gli agenti non possono distinguere le istruzioni legittime dell'operatore da quelle compromesse o maligne.

AuthProof colma questa lacuna.

---

## Il Primitivo Fondamentale: Ricevuta di Delega

Una **Ricevuta di Delega** è un Oggetto di Autorizzazione firmato ancorato a un registro decentralizzato append-only prima che inizi qualsiasi azione dell'agente. Contiene quattro campi obbligatori:

### Ambito

Un elenco esplicito di operazioni consentite. Tutto ciò che non è elencato è negato per impostazione predefinita. Espresso in formato strutturato — non in linguaggio naturale. Classi di operazioni:

| Class | Description |
|---|---|
| `reads` | Accesso in lettura a risorse specificate |
| `writes` | Accesso in scrittura a risorse specificate |
| `deletes` | Cancellazione di risorse specificate |
| `executes` | Esecuzione di un programma specifico, referenziato dal suo **static capability signature hash** |

`executes` è la classe più pericolosa. Deve referenziare l'hash crittografico del DAG di capacità statica di un programma Safescript — non un nome, URI o descrizione. Nessuna corrispondenza hash significa nessuna esecuzione.

### Limiti

Proibizioni esplicite che non possono essere sovrascritte dalle istruzioni dell'operatore in nessuna circostanza. Limiti rigidi definiti dall'utente che sopravvivono a qualsiasi istruzione successiva dell'operatore.

### Finestra Temporale

Periodo di validità dell'autorizzazione. Il **timestamp del log** è l'oracolo del tempo — non l'orologio del client. Gli orologi dei client sono esplicitamente esclusi dalla validazione temporale.

### Hash delle Istruzioni dell'Operatore

Un hash crittografico delle istruzioni dichiarate dall'operatore al momento della delega. Se l'operatore successivamente istruisce l'agente in modo diverso, la discrepanza è rilevabile dal log senza ulteriori presupposti di fiducia.

L'utente firma questo oggetto con la propria chiave privata tramite **WebAuthn/FIDO2 utilizzando l'enclave sicuro del dispositivo**. La firma viene pubblicata sul log prima di qualsiasi azione dell'agente. Ogni azione successiva dell'agente referenzia l'hash della ricevuta. Le azioni al di fuori dell'ambito sono crittograficamente non valide.

---

## Architettura dello Stack di Fiducia

Tre livelli di protocollo eliminano tre terze parti fidate:

### Livello 1 — Manifesto di Capacità Firmato *(rimuove la fiducia nel registro)*

Nell'attuale ecosistema MCP, non esiste un'attestazione crittografica che le descrizioni di un server di strumenti corrispondano a ciò che effettivamente fa. Un operatore può presentare uno schema arbitrario.

La soluzione: i server di strumenti pubblicano un **manifesto di capacità firmato crittograficamente** prima che avvenga qualsiasi autorizzazione utente. Il campo `scope` della Ricevuta di Delega referenzia l'**hash di questo manifesto** — non lo schema auto-dichiarato dall'operatore. La divergenza tra il comportamento del server e il manifesto è rilevabile a livello di log.

### Livello 2 — Ricevuta di Delega *(rimuove la fiducia nell'operatore)*

L'intento originale dell'utente viene registrato in modo immutabile prima che le istruzioni dell'operatore raggiungano l'agente. La deviazione dell'operatore è dimostrabile.

### Livello 3 — Esecuzione Safescript *(rimuove la fiducia nel codice)*

[Safescript](https://github.com/safescript) è un linguaggio open-source in sandbox per l'esecuzione di agenti AI. La sua struttura DAG statica significa che l'intera firma di capacità di ogni programma è calcolabile prima che venga eseguito — nessun dispatch dinamico, nessuna espansione di capacità a runtime.

La classe di ambito `executes` referenzia uno specifico hash della firma di capacità Safescript. Se il programma fornito dall'operatore non corrisponde all'hash impegnato, l'esecuzione viene bloccata. L'agente non può essere sostituito con un programma diverso dopo la delega.

---

## Avvio Rapido```js
import { AuthProof, Scope, KeyCustody } from 'authproof-sdk';

// Initialize with hardware-backed key custody (recommended)
const authproof = new AuthProof({
  custody: KeyCustody.HARDWARE, // WebAuthn/FIDO2 via device secure enclave
  log: 'https://log.authproof.dev',
});

// Define permitted operations — explicit allowlist, deny-by-default
const scope = new Scope()
  .allow('reads',  ['resource://calendar/events', 'resource://email/inbox'])
  .allow('writes', ['resource://calendar/events'])
  .deny('deletes', '*')
  .execute('sha256:a3f1c9d8...', { program: 'scheduler-v1.sg' }); // Safescript hash

// Hard limits that survive any operator instruction
const boundaries = {
  never: ['external-network', 'credential-store', 'payment-methods'],
};

// Issue the Delegation Receipt — anchored to log before any agent action
const receipt = await authproof.delegate({
  scope,
  boundaries,
  window: { duration: '8h' },           // validated against log timestamp
  operatorInstructions: instructionText, // hashed and committed
});

// receipt.id    — unique receipt identifier
// receipt.hash  — reference in every agent action
// receipt.log   — append-only log anchor

// Agent-side: validate an action against the receipt
const check = await authproof.validate({
  receiptHash: receipt.hash,
  action: { class: 'writes', resource: 'resource://calendar/events' },
});

if (!check.authorized) {
  // Out-of-scope action: surface a micro-receipt request to the user
  const microReceipt = await authproof.requestMicroReceipt({
    action: check.requestedAction,
    parent: receipt.hash,
  });
}

Autorizzazione Dinamica: Micro-Ricevute

Per le chiamate agli strumenti non coperte dalla Ricevuta di Delega originale, l'agente non può procedere silenziosamente. Il protocollo impone:

  1. L'agente identifica un requisito di capacità fuori ambito.
  2. L'agente presenta una richiesta di capacità all'utente descrivendo l'azione specifica.
  3. L'utente firma una micro-ricevuta che copre solo quell'azione.
  4. L'agente procede, facendo riferimento all'hash della micro-ricevuta.

Azioni sconosciute richiedono una nuova autorizzazione esplicita dell'utente. La risoluzione delle dipendenze segue la stessa regola — le dipendenze vengono verificate rispetto all'hash del manifest delle dipendenze impegnato al momento della delega. Dipendenze inaspettate costituiscono una violazione dell'ambito.


Agenti Concorrenti

Ogni evento di delega porta un ID di ricevuta univoco. Gli agenti concorrenti fanno ciascuno riferimento al proprio hash di ricevuta. Sono distinguibili per ricevuta, non per identità dell'agente.


Gestione delle Chiavi

La custodia hardware è il predefinito consigliato. La chiave privata non lascia mai il secure enclave; la firma è protetta dai dati biometrici o dal PIN del dispositivo.


Registro delle Azioni

Una Ricevuta di Delega definisce cosa un agente AI è autorizzato a fare. Il Registro delle Azioni registra cosa ha effettivamente fatto — e rende qualsiasi deviazione immediatamente verificabile.

Ogni azione intrapresa da un agente produce una voce firmata e con timestamp collegata alla ricevuta attiva. Le voci formano una catena a prova di manomissione: ogni voce incorpora l'hash SHA-256 della precedente, quindi qualsiasi modifica retroattiva è rilevabile senza una terza parte fidata. Il metodo diff() è il primitivo di audit — allinea l'ambito autorizzato della ricevuta con ogni azione registrata e restituisce eventuali deviazioni.```js import AuthProof, { ActionLog } from 'authproof-sdk';

// 1. Issue a delegation receipt as normal const { privateKey, publicJwk } = await AuthProof.generateKey();

const { receipt, receiptId } = await AuthProof.create({ scope: 'Search the web for competitor pricing. Read calendar events.', boundaries: 'Do not send emails. Do not make purchases.', instructions: 'Cite sources. Keep under 500 words.', ttlHours: 4, privateKey, publicJwk, });

// 2. Initialize the action log with the agent's signing key const log = new ActionLog(); await log.init({ privateKey, publicJwk });

// Register the receipt so diff() knows what was authorized log.registerReceipt(receiptId, receipt);

// 3. Record each action the agent takes const e1 = await log.record(receiptId, { operation: 'Search competitor pricing', resource: 'web/search', parameters: { query: 'rival.com pricing 2024' }, });

const e2 = await log.record(receiptId, { operation: 'Read calendar events', resource: 'calendar/events', parameters: { range: 'this_week' }, });

// 4. Verify an individual entry (signature + chain integrity) const check = await log.verify(e1.entryId); // { valid: true, reason: 'Signature and chain integrity verified' }

// 5. Diff: authorized scope vs. everything that was done const report = log.diff(receiptId); // { // clean: true, // totalEntries: 2, // compliant: [{ entry: {...}, reason: '"Search competitor pricing" matches authorized scope' }, ...], // violations: [], // }

// A scope violation surfaces immediately await log.record(receiptId, { operation: 'Send email', resource: 'email/outbox' }); const auditReport = log.diff(receiptId); // auditReport.violations[0].reason → // '"Send email" outside authorized scope (0% scope match, 92% boundary overlap)'

root@kitploit:~
### API del Registro Azioni

| Metodo | Descrizione |
|---|---|
| `new ActionLog()` | Crea una nuova istanza del registro. Lo stato è in memoria. |
| `log.init({ privateKey, publicJwk })` | Inizializza con la chiave ECDSA P-256 dell'agente. Richiesto prima di `record()`. |
| `log.registerReceipt(receiptHash, receipt)` | Registra una ricevuta per il confronto dell'ambito. Richiesto prima di `diff()`. |
| `log.record(receiptHash, action)` | Aggiunge una voce firmata concatenata. Restituisce la voce sigillata. |
| `log.verify(entryId)` | Verifica la firma e la posizione nella catena di una voce. Restituisce `{ valid, reason }`. |
| `log.getEntries(receiptHash)` | Tutte le voci per una ricevuta in ordine cronologico. |
| `log.diff(receiptHash)` | Confronta tutte le voci con l'ambito della ricevuta. Restituisce `{ compliant, violations, clean }`. |

### Avviso di Produzione

I timestamp nella v1 utilizzano l'orologio del client. Per contesti normativi o legali in cui i timestamp devono essere verificabili in modo indipendente, sostituisci con un'autorità di timestamp fidato RFC 3161 prima di distribuire in produzione.

### Importante

Definisci sempre l'ambito utilizzando array `allowedActions` espliciti. La corrispondenza dell'ambito basata su testo è disponibile solo per lo sviluppo e non è adatta a contesti di produzione o conformità.

---

## Distribuzione Confidenziale

Esegui gli agenti AuthProof all'interno di Ambienti di Esecuzione Fidati (TEE) attestati dall'hardware. La classe `ConfidentialRuntime` associa le ricevute di delega alle misurazioni dell'enclave, quindi qualsiasi sostituzione dei pesi del modello, del codice di verifica o della piattaforma è rilevabile prima dell'esecuzione.

### Requisiti hardware

- **Intel TDX** — Intel Ice Lake Xeon o successivi (Xeon Scalable di 4a generazione). Azure DCdsv3-series, GCP C3 Confidential VMs.
- **AMD SEV-SNP** — AMD EPYC 3a generazione (Milan) o successivi. Azure DCasv5-series, AWS m6a con Nitro Enclaves.

### Crea una ricevuta con associazione della misurazione TEE```javascript
import { AuthProofClient } from 'authproof-sdk';

const client = new AuthProofClient();
const { receipt } = await client.delegate({
  scope: 'Summarize calendar events',
  operatorInstructions: 'Stay within scope.',
  expiresIn: '2h',
  privateKey,
  publicJwk,
  teeConfig: {
    platform:     'intel-tdx',
    verifierHash: verifierCodeHash,   // SHA-256 of your verifier binary
    modelHash:    modelWeightsHash,   // SHA-256 of model weights
  },
});
// receipt.teeMeasurement.expectedMrenclave is now bound to the receipt

Distribuzione su Azure Confidential Computing (Intel TDX)```javascript

import { ConfidentialRuntime } from 'authproof-sdk';

// Generate deployment configuration const config = ConfidentialRuntime.azureTDXConfig({ receiptHash, verifierHash, modelHash, region: 'eastus', }); // config.vmSize === 'Standard_DC4ds_v3' // config.attestationEndpoint === 'https://sharedeus.eus.attest.azure.net' // config.receiptBinding binds the receipt to the VM measurement

// At runtime inside the VM: const runtime = new ConfidentialRuntime({ platform: 'intel-tdx', verifier, actionLog, }); const result = await runtime.launch({ receiptHash, agentFn, operatorInstructions, verifierHash, modelHash, teeMeasurement: receipt.teeMeasurement, // mismatch blocks execution });

root@kitploit:~
Requisiti SKU di Azure: `Standard_DC4ds_v3` o superiore della serie DCdsv3. Abilita la crittografia del disco del sistema operativo riservato. Utilizza l'endpoint condiviso di Microsoft Azure Attestation (MAA) per la verifica delle attestazioni.

### Distribuzione su AWS Nitro Enclaves```javascript
const config = ConfidentialRuntime.awsNitroConfig({
  receiptHash,
  verifierHash,
  modelHash,
  region: 'us-east-1',
});
// config.instanceType === 'c6a.xlarge'
// config.enclaveOptions.enabled === true
// config.pcr0 is the combined receipt+verifier+model measurement

Requisiti AWS: c6a.xlarge o superiore con --enclave-options Enabled. Usa nitro-cli per creare ed eseguire l'immagine enclave. PCR0 nel documento di attestazione deve corrispondere a config.pcr0 affinché il binding del ricevimento sia valido.

Distribuisci su Kubernetes```javascript

const manifest = ConfidentialRuntime.kubernetesConfig({ receiptHash, platform: 'intel-tdx', namespace: 'production', }); // manifest is a full K8s List containing: // - Pod with TDX node selector and attestation sidecar // - ServiceAccount with minimal RBAC // - ConfigMap with receipt binding

root@kitploit:~
Applicare con `kubectl apply -f` dopo aver serializzato in YAML. Il selettore di nodo `intel.feature.node.kubernetes.io/tdx: "true"` richiede l'Intel Device Plugin for Kubernetes.

### Modulo kernel eBPF — aiuto richiesto

Il livello di enforcement TEE è completo dal lato userspace (`ConfidentialRuntime`, `TokenPreparer`). Il passo finale di enforcement — convalidare il token di capability firmato su ogni syscall tramite un hook eBPF LSM — richiede un modulo kernel aperto ai contributi.

Ingegneri con esperienza in eBPF LSM (Isovalent, Red Canary, o simili) sono particolarmente benvenuti. Apri un issue o PR su https://github.com/Commonguy25/authproof-sdk/issues

---

## Esempi

Due esempi eseguibili si trovano nella directory `examples/`.

**[`examples/langchain-example.js`](https://github.com/commonguy25/authproof-sdk/blob/HEAD/examples/langchain-example.js)** — Mostra il percorso completo di integrazione con LangChain: genera una coppia di chiavi, emette un receipt di delega, inizializza `PreExecutionVerifier` e avvolge qualsiasi agente con `authproofMiddleware` in modo che ogni chiamata a `invoke()` sia bloccata prima che il runtime dell'agente ottenga il controllo. Include un agente fittizio che puoi eseguire immediatamente e il pattern di codice esatto per un vero `AgentExecutor`. Esegui con `npm run example:langchain`.

**[`examples/webauthn-example.html`](https://github.com/commonguy25/authproof-sdk/blob/HEAD/examples/webauthn-example.html)** — Una demo browser autonoma a tre carte dell'intero flusso di delega. La Carta 1 ti permette di definire ambito, confini e istruzioni dell'operatore, quindi firma una ricevuta con una chiave locale (sostituisci con `navigator.credentials` per un vero WebAuthn). La Carta 2 mostra l'ID della ricevuta, la scadenza, il prompt di sistema e il JSON grezzo. La Carta 3 ti permette di digitare qualsiasi azione proposta e verificarla in tempo reale rispetto alla ricevuta, mostrando ciascun risultato del controllo. Apri direttamente in un browser — nessun passo di build richiesto.

---

## Installazione```bash
npm install authproof-sdk
  • npm: https://www.npmjs.com/package/authproof-sdk
  • GitHub: https://github.com/Commonguy25/authproof-sdk
  • Specifica del protocollo: WHITEPAPER.md
Scarica lo strumento
ModelloDescrizioneConsigliato
HardwareWebAuthn/FIDO2 tramite secure enclave del dispositivo. La chiave privata non lascia mai l'hardware.Sì — predefinito
DelegatoUn gestore di chiavi fidato detiene la chiave per conto dell'utente.Ambienti senza supporto FIDO2
Auto-custodiaL'utente detiene e gestisce la propria chiave privata.Utenti avanzati, flussi di lavoro air-gapped