---
url: https://docs.ogmabox.com/it/plugins/README.md
description: >-
  Sviluppa, crea i pacchetti, installa, abilita e distribuisci plugin Ogma con
  logica backend, pannelli frontend, comandi, autorizzazioni e metadati del
  marketplace.
---

# Sistema di plugin Ogma {#ogma-plugin-system}

I plugin Ogma estendono lo strumento con logica backend personalizzata, pannelli dell’interfaccia frontend e passaggi dei flussi di lavoro. I plugin vengono installati localmente da una directory su disco, abilitati per progetto ed eseguiti in un ambiente con isolamento sandbox.

Questo documento è il riferimento principale per gli autori di plugin.

Per iniziare il più rapidamente possibile, usa la [Guida rapida ai plugin](/it/plugins/quickstart).

***

## Avvio rapido {#quick-start}

### Plugin backend minimo {#minimal-backend-plugin}

```
my-plugin/
  manifest.json
  backend/script.js
```

`manifest.json`:

```json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "plugins": [
    {
      "kind": "backend",
      "id": "my-plugin-backend",
      "entrypoint": "backend/script.js"
    }
  ]
}
```

`backend/script.js` (ES2020; puoi usare le importazioni se utilizzi un bundler):

```js
async function init(sdk) {
  sdk.console.log("my-plugin started");

  sdk.events.onInterceptResponse(function(req, res) {
    if (res.getCode() === 403) {
      sdk.console.warn("403 on " + req.getUrl());
    }
  });
}
```

Questo è sufficiente per avere un plugin solo backend funzionante. Mantienilo come singolo file in `backend/script.js` e fai puntare `manifest.json` direttamente a quel file.

### Processo di compilazione in un minuto (codice sorgente TypeScript) {#one-minute-build-flow-typescript-source}

Se sviluppi in TypeScript, usa questa struttura:

```text
my-plugin/
  manifest.json
  backend/
    src/index.ts
```

Compila:

```bash
pnpm add -D @ogmabox/ogma-sdk esbuild typescript
pnpm exec esbuild backend/src/index.ts --bundle --format=iife --platform=neutral --external:@ogma/sdk --external:@ogmabox/ogma-sdk --outfile=backend/script.js
```

Installa da **Plugin > Installa**, seleziona la directory `my-plugin/`. Poi abilita il plugin.

***

## Riferimento del manifesto {#manifest-reference}

`manifest.json` si trova nella directory radice del pacchetto. Tutti i campi distinguono maiuscole e minuscole.

### Campi di livello superiore {#top-level-fields}

| Campo | Obbligatorio | Tipo | Note |
|-------|----------|------|-------|
| `id` | sì | string | Solo lettere minuscole, cifre e trattini. Massimo 64 caratteri. Univoco tra tutti i plugin installati. |
| `version` | sì | string | Semver: `MAJOR.MINOR.PATCH` |
| `name` | no | string | Nome visualizzato nell’interfaccia. Il valore predefinito è `id`. |
| `description` | no | string | Riepilogo di una riga. |
| `author` | no | object | `{ "name": "...", "email": "...", "url": "..." }` |
| `homepage` | no | string | URL del repository del codice sorgente o della documentazione. |
| `plugins` | sì | array | Una o più voci di componenti del plugin (consulta sotto). |
| `permissions` | no | array | Elenco dei nomi delle autorizzazioni richieste (consulta [Autorizzazioni](#permissions)). |

### Voce di componente del plugin {#plugin-component-entry}

Ogni oggetto nell’array `plugins` descrive un componente.

**Componente backend:**

```json
{
  "kind": "backend",
  "id": "my-plugin-backend",
  "entrypoint": "backend/script.js",
  "runtime": "javascript",
  "assets": "backend/assets"
}
```

**Componente frontend:**

```json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
```

| Campo | Obbligatorio | Note |
|-------|----------|-------|
| `kind` | sì | `"backend"` o `"frontend"` |
| `id` | sì | Univoco all’interno del manifesto. Minuscole e trattini. |
| `entrypoint` | sì | Percorso relativo del file di ingresso JS. |
| `style` | no | File CSS caricato nell’iframe del plugin. |
| `assets` | no | Directory delle risorse statiche servite sotto `/plugins/{id}/assets/`. |
| `backend.id` | no | Collega un componente frontend al suo componente backend per RPC di `sdk.backend.*`. |
| `runtime` | no (solo backend) | `"javascript"` (valore predefinito e unico supportato). |

***

## API dei plugin backend (sdk) {#backend-plugin-api-sdk}

L’oggetto `sdk` del backend viene passato alla tua funzione `init(sdk)`. Tutti i metodi sono sincroni, salvo quelli contrassegnati come `async`.

### `sdk.console` {#sdk-console}

```js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")
```

Scrive nel buffer dei log del plugin (visibile nella scheda Registri). Vengono conservate al massimo 500 voci. Ogni messaggio viene troncato a 1 KB.

### `sdk.meta` {#sdk-meta}

```js
sdk.meta.id()          // > string: plugin id (e.g. "my-plugin")
sdk.meta.packageId()   // > string: same as id
sdk.meta.version()     // > string: semver (e.g. "1.0.0")
sdk.meta.path()        // > string: writable data directory for this plugin
```

`sdk.meta.path()` punta alla directory privata dei dati del plugin con accesso in scrittura, ad esempio `~/.local/share/ogma/plugins/my-plugin/data`. La directory viene creata automaticamente e può essere usata per conservare i dati del plugin tra i riavvii.

Sono disponibili anche `sdk.storage`, `sdk.path` e `sdk.fs` come utilità per stato e file.

### `sdk.storage` {#sdk-storage}

```js
sdk.storage.get("key")      // > string | null
sdk.storage.set("key", "value")
sdk.storage.delete("key")
sdk.storage.clear()
sdk.storage.keys()          // > string[]
```

`sdk.storage` è limitato all’ID del plugin e persiste tra i riavvii del plugin.

### `sdk.fs` {#sdk-fs}

```js
sdk.fs.readFile("relative/file.txt")      // > string
sdk.fs.writeFile("relative/file.txt", "text")
sdk.fs.appendFile("relative/file.txt", "more")
sdk.fs.exists("relative/file.txt")        // > boolean
sdk.fs.existsSync("relative/file.txt")    // > boolean
sdk.fs.list("relative/dir")              // > string[]
sdk.fs.mkdir("relative/dir")
```

`sdk.fs` è limitato ai file sotto `sdk.meta.path()`.

`read` e `write` restano alias di compatibilità di `readFile` e `writeFile`. Usa `exists` o `existsSync` prima di creare un file che non vuoi sovrascrivere. Queste API operano in modo sincrono nell’ambiente di esecuzione del plugin; non costituiscono il modulo `fs` completo di Node.js. L’accesso ai file del plugin richiede l’autorizzazione `plugin_storage` e resta all’interno della directory privata dei dati del plugin. Il JavaScript dei flussi di lavoro ha un contesto di file system diverso; consulta [Accesso ai file dei flussi di lavoro](../app/workflows.md#javascript-and-files).

### `sdk.path` {#sdk-path}

```js
sdk.path.join("a", "b", "c")
sdk.path.basename("/tmp/file.txt")
sdk.path.dirname("/tmp/file.txt")
sdk.path.extname("file.txt")
sdk.path.resolve("/a", "b")
sdk.path.isAbsolute("/tmp/file.txt")
sdk.path.sep
```

### `sdk.events` {#sdk-events}

Registra callback per gli eventi Ogma. Tutti i callback vengono chiamati in modo sincrono all’interno della sandbox QuickJS.

```js
sdk.events.onInterceptRequest(function(req) {
  // req: RequestSpecRaw
  // Return a modified RequestSpecRaw to mutate the request.
  // Return null/undefined to pass through unchanged.
});

sdk.events.onInterceptResponse(function(req, res) {
  // req: Request (read-only), res: Response (read-only)
  // Return value is ignored.
});

sdk.events.onProjectChange(function() {
  // no callback args
});

sdk.events.onFindingCreated(function(finding) {
  // finding: { id, title, reporter }
});
```

### `sdk.requests` {#sdk-requests}

```js
// Get a single HTTP entry by id
var entry = sdk.requests.get("entry-id");
// entry: { id, method, host, path, query, tls, ... } or null

// Search HTTP history
var results = sdk.requests.search({ limit: 20, offset: 0 });
// results: { entries: [...], total: N }

// Send an HTTP request (requires send_requests permission)
var response = await sdk.requests.send(spec);
// spec: RequestSpecRaw (see below)
// response: Response
```

`sdk.requests.send` richiede che l’autorizzazione `send_requests` sia dichiarata nel manifesto e concessa dall’utente. Consulta [Autorizzazioni](#permissions).

### `sdk.findings` {#sdk-findings}

```js
// Create a finding (requires write_findings permission)
sdk.findings.create({
  title: "SSRF via redirect",
  reporter: "my-plugin",
  dedupeKey: "ssrf-" + request.getId(),
  request: { id: request.getId() }
});

// Check if a finding already exists (dedup check)
var exists = sdk.findings.exists({ dedupeKey: "ssrf-abc" });

// List findings
var page = sdk.findings.list({ limit: 20, offset: 0 });

// Get a single finding
var finding = sdk.findings.get("finding-id");
```

Limiti di frequenza di `sdk.findings.create`: 10 al minuto, 500 per sessione del plugin e 3 per callback di evento.

### `sdk.api` {#sdk-api}

Registra funzioni RPC backend che il frontend possa chiamare tramite `sdk.backend.*`:

```js
// In backend init:
sdk.api.register("getScans", function(scanId) {
  return { scans: [] };
});

// Emit an event to connected frontends:
sdk.api.send("scan:complete", { scanId: 1, status: "ok" });
```

Il gestore riceve gli argomenti passati dal frontend (non viene iniettato alcun argomento `sdk` aggiuntivo). I valori restituiti vengono serializzati come JSON e inviati a chi ha effettuato la chiamata.

Il frontend effettua queste chiamate tramite `sdk.backend.getScans(scanId)`; consulta [API dei plugin frontend](#frontend-plugin-api-sdk).

`sdk.api.send` inserisce eventi in una coda per plugin (massimo 200 voci). I frontend interrogano periodicamente questa coda tramite `sdk.backend.onEvent`.

### `sdk.replay`, `sdk.projects`, `sdk.scope`, `sdk.workflows`, `sdk.matchReplace` {#sdk-replay-sdk-projects-sdk-scope-sdk-workflows-sdk-matchreplace}

Questi sono namespace di query di sola lettura. Consulta il [riferimento dell’SDK backend](./backend-sdk.md) per le firme complete dei metodi.

### Classi delle richieste {#request-classes}

**`RequestSpecRaw`** rappresenta una richiesta intercettata. La ricevi in `onInterceptRequest`.

```js
spec.getMethod()          // > string
spec.setMethod("POST")
spec.getHost()            // > string
spec.setHost("example.com")
spec.getPort()            // > number
spec.getPath()            // > string
spec.setPath("/new/path")
spec.getQuery()           // > string
spec.getTls()             // > boolean
spec.getHeaders()         // > Record<string, string[]>
spec.setHeader("X-Foo", "bar")
spec.getBody()            // > Body | null
spec.setBody("new body")
spec.getRaw()             // > Uint8Array (raw bytes) or []
spec.setRaw(bytes)        // set raw bytes

// Create a new spec from a URL string:
var spec = new RequestSpecRaw("https://example.com/path?q=1");
```

**`Request`** è una richiesta acquisita di sola lettura (da `sdk.requests.get`).

```js
req.getId()
req.getMethod()
req.getHost()
req.getPort()
req.getTls()
req.getPath()
req.getQuery()
req.getUrl()           // > full URL string
req.getHeaders()       // > Record<string, string>
req.getHeader("name")
req.getBody()          // > Body | null
req.getCreatedAt()     // > Date
req.toSpec()           // > RequestSpec (mutable copy)
```

**`Response`** è una risposta acquisita di sola lettura.

```js
res.getCode()            // > number (HTTP status)
res.getHeaders()         // > Record<string, string>
res.getHeader("name")
res.getBody()            // > Body | null
res.getRoundtripTime()   // > number (ms)
res.getCreatedAt()       // > Date
```

**`Body`**:

```js
body.toText()    // > string
body.toJson()    // > parsed object or null
body.toRaw()     // > Uint8Array
body.length      // > number (original size, may differ from toText() if truncated)
```

***

## API dei plugin frontend (sdk) {#frontend-plugin-api-sdk}

Il codice frontend del plugin viene eseguito in un iframe con isolamento sandbox caricato da `/plugins/{id}/ui`. L’iframe usa `postMessage` per comunicare con l’applicazione principale Ogma, che funge da intermediaria per le chiamate al backend.

L’SDK è disponibile tramite `window.ogmaSDK`. Chiama `ogmaSDK.ready(cb)` per ricevere l’SDK attivo una volta stabilito il bridge con l’applicazione principale:

```js
ogmaSDK.ready(function(sdk) {
  // sdk is the live SDK - safe to call any method here
  sdk.log.info("frontend ready");
});
```

Tutti i metodi dell’SDK restituiscono promesse.

### `sdk.log` {#sdk-log}

```js
sdk.log.info("message")
sdk.log.warn("message")
sdk.log.error("message")
```

### `sdk.meta` {#sdk-meta-1}

```js
var meta = await sdk.meta.get();
// { pluginId, packageId, name, version, ogmaVersion }
```

### `sdk.requests` {#sdk-requests-1}

```js
var entry = await sdk.requests.get({ id: "entry-id" });
var result = await sdk.requests.search({ limit: 20, offset: 0, query: "host:example.com" });
```

Richiede l’autorizzazione `read_http_history` (concessa automaticamente; non serve l’approvazione dell’utente).

### `sdk.findings` {#sdk-findings-1}

```js
var page = await sdk.findings.list({ limit: 20, offset: 0 });
```

Richiede l’autorizzazione `read_findings` (concessa automaticamente).

### `sdk.scope` {#sdk-scope}

```js
var scope = await sdk.scope.getActive();
```

### `sdk.projects` {#sdk-projects}

```js
var project = await sdk.projects.getCurrent();
```

### `sdk.backend` - RPC backend {#sdk-backend-backend-rpc}

Chiama le funzioni registrate con `sdk.api.register` nel backend:

```js
// Call a named backend function
var result = await sdk.backend.call("getScans", [scanId]);

// Poll for backend-emitted events (sdk.api.send on the backend side)
var { events, next_since } = await sdk.backend.poll(since);
// events: [{ event: "scan:complete", args: [...] }]

// Register an event listener (uses polling internally)
sdk.backend.onEvent("scan:complete", function(data) {
  console.log("scan done", data);
});
```

`sdk.backend.onEvent` usa internamente un ciclo di interrogazione ogni 2 secondi. Interrompi l’ascolto chiamando la funzione di annullamento dell’iscrizione restituita:

```js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();
```

### `sdk.navigation` {#sdk-navigation}

```js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });
```

Registra una pagina di navigazione. Attualmente l’applicazione principale conferma la ricezione. L’integrazione completa con il router è in corso.

### `sdk.sidebar` {#sdk-sidebar}

```js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });
```

Registra una voce della barra laterale. Attualmente è locale al pannello dell’interfaccia del plugin; il collegamento agli slot della barra laterale globale è in corso.

### `sdk.commands` {#sdk-commands}

```js
await sdk.commands.register("my-plugin:scan", {
  name: "Scan with My Plugin",
  handler: function(context) { /* ... */ }
});
```

L’applicazione principale conferma la ricezione. L’integrazione con la tavolozza dei comandi è in corso.

### `sdk.menu` {#sdk-menu}

```js
await sdk.menu.registerItem({
  type: "Request",
  commandId: "my-plugin:scan",
  leadingIcon: "shield"
});
```

L’applicazione principale conferma la ricezione. L’inserimento nel menu contestuale è in corso.

### `sdk.window` {#sdk-window}

```js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });
```

Mostra una notifica temporanea nel pannello del plugin. Varianti: `info`, `success`, `warning`, `error`.

### `sdk.ui` {#sdk-ui}

```js
sdk.ui.resize(600);                              // request iframe height change
sdk.ui.sidebar.registerItem("name", "/path");    // alias for sdk.sidebar.registerItem
```

***

## Autorizzazioni {#permissions}

Dichiara le autorizzazioni in `manifest.json`:

```json
{
  "permissions": ["send_requests", "write_findings"]
}
```

### Autorizzazioni concesse automaticamente (senza approvazione dell’utente) {#auto-granted-permissions-no-user-approval-needed}

Queste autorizzazioni vengono sempre concesse a qualsiasi plugin installato:

| Autorizzazione | Cosa consente |
|------------|----------------|
| `read_http_history` | `sdk.requests.get`, `sdk.requests.search` |
| `read_findings` | `sdk.findings.get`, `sdk.findings.list` |
| `read_scope` | `sdk.scope.getActive` |
| `read_projects` | `sdk.projects.getCurrent`, `sdk.projects.list` |
| `plugin_storage` | `sdk.storage`, `sdk.fs`, `sdk.path` |

### Autorizzazioni protette (richiedono l’approvazione dell’utente) {#protected-permissions-require-user-approval}

Devono essere dichiarate nel manifesto e concesse esplicitamente dall’utente dalla scheda Autorizzazioni:

| Autorizzazione | Cosa consente |
|------------|----------------|
| `send_requests` | `sdk.requests.send` - effettuare richieste HTTP in uscita |
| `write_findings` | `sdk.findings.create`, `sdk.findings.update` |

L’utente vede una richiesta di autorizzazione quando abilita un plugin che dichiara autorizzazioni protette. Può anche concedere o revocare le autorizzazioni in qualsiasi momento dalla scheda Autorizzazioni.

***

## Struttura del pacchetto del plugin {#plugin-package-structure}

Un pacchetto del plugin può essere installato da una directory locale e, nei flussi del browser, anche da esportazioni `.zip`.

```
my-plugin/
  manifest.json           - obbligatorio
  backend/
    script.js             - JS backend in bundle (ES2020)
  frontend/
    script.js             - JS frontend in bundle
    style.css             - CSS opzionale
    assets/               - risorse statiche (immagini, font, ecc.)
```

### Requisiti dello script backend {#backend-script-requirements}

* Deve essere un singolo file JS autonomo.
* `require()` e `import()` dinamico non sono supportati.
* Deve esportare una funzione `init(sdk)` (o definirla come globale).
* Sottoinsieme ES2020 supportato da QuickJS: `async/await`, `Promise`, `Map`, `Set`, `Symbol`, `Proxy`, `Date`, `RegExp`, `JSON`. Senza `fetch` e senza `Buffer`.
* Le importazioni statiche supportate vengono risolte dal pre-elaboratore del plugin: `@ogma/sdk`, `crypto`, `fs`, `path`.
* Dimensione massima del file: 256 KB.

### Requisiti dello script frontend {#frontend-script-requirements}

* Viene eseguito in un iframe con isolamento sandbox. È consentito `connect-src: 'self'` affinché il plugin possa inviare POST a `/plugins/{id}/api/*` e interrogare periodicamente `/plugins/{id}/events/poll`.
* CSP: `default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'`.
* Senza `allow-same-origin` nella sandbox dell’iframe: il plugin non può accedere al DOM della pagina principale Ogma né ai suoi cookie.
* Usa `ogmaSDK.ready(cb)` per accedere all’SDK; non chiamare i metodi dell’SDK prima che il callback venga eseguito.

***

## Compilazione di un plugin per Ogma {#building-a-plugin-for-ogma}

Poiché il backend deve essere un singolo file JS in bundle, devi creare il bundle del tuo codice sorgente TypeScript o dei moduli ES prima dell’installazione.

Catena di strumenti consigliata:

```bash
# Install dependencies
pnpm install

# Bundle backend (outputs a single CJS/IIFE file):
esbuild packages/backend/src/index.ts \
  --bundle \
  --platform=neutral \
  --format=iife \
  --global-name=_plugin \
  --outfile=dist/backend/script.js \
  --external:@ogma/sdk --external:@ogmabox/ogma-sdk

# Bundle frontend:
vite build packages/frontend --outDir ../../dist/frontend
```

Se usi la catena di strumenti di sviluppo Caido (`@caido-community/dev`), esegui `caido-dev build` e poi copia l’output in una struttura compatibile con Ogma con `manifest.json` nella radice.

***

## Installazione di un plugin {#installing-a-plugin}

1. Apri **Plugin** nella barra laterale sinistra.
2. Fai clic su **Installa** (in alto nella scheda Installato).
3. Nell’applicazione desktop, fai clic su **Sfoglia** per aprire un selettore di cartelle nativo. Nel browser, digita il percorso completo della directory del plugin sul server.
4. Fai clic su **Valida** per controllare il manifesto e l’inventario dei file.
5. Fai clic su **Installa** se la convalida ha esito positivo.
6. Seleziona il plugin nell’elenco e fai clic su **Attiva**.
7. Se il plugin dichiara autorizzazioni protette, esaminale e concedile dalla scheda **Autorizzazioni** prima di abilitarlo.

***

## Risoluzione dei problemi {#troubleshooting}

**L’inizializzazione del plugin fallisce senza mostrare errori:** Controlla la scheda **Registri**. Le cause più comuni sono:

* È stato chiamato `sdk.meta.path()`, ma non è stato possibile creare la directory dei dati.
* Un’eccezione non gestita in `init()`.
* Una chiamata `sdk.*` mancante o scritta in modo errato.

**Il frontend appare vuoto:** Controlla la console del browser per individuare violazioni CSP. Assicurati che il tuo script frontend chiami `ogmaSDK.ready(cb)` prima di accedere a qualsiasi metodo dell’SDK.

**`sdk.requests.send` genera `Permission denied`:** L’autorizzazione `send_requests` deve essere dichiarata nel manifesto E concessa dall’utente nella scheda Autorizzazioni.

**Le funzioni di `sdk.api.register` non sono richiamabili dal frontend:** Il backend deve essere abilitato (non solo installato). Il nome della funzione deve corrispondere esattamente, distinguendo maiuscole e minuscole, a quello che il frontend passa a `sdk.backend.call`.

**Gli avvisi di compatibilità appaiono come errori:** Non bloccano il funzionamento, ma indicano lacune nell’interfaccia dell’API. Consulta le tabelle di corrispondenza dell’SDK sopra per la copertura dell’API.
