Ga naar de inhoud

Ogma-pluginsysteem ​

Ogma-plugins breiden de tool uit met eigen backendlogica, panelen in de frontendinterface en workflowstappen. Plugins worden lokaal geïnstalleerd vanuit een map op schijf, per project ingeschakeld en uitgevoerd in een afgeschermde omgeving.

Dit document is het primaire naslagwerk voor pluginauteurs.

Gebruik Snel aan de slag met plugins als je zo snel mogelijk wilt beginnen.


Snel aan de slag ​

Minimale backendplugin ​

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; imports zijn mogelijk wanneer je een bundler gebruikt):

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());
    }
  });
}

Dit volstaat voor een werkende plugin met alleen een backend. Houd de code in één bestand, backend/script.js, en laat manifest.json er rechtstreeks naar verwijzen.

Buildproces in één minuut (TypeScript-broncode) ​

Gebruik deze structuur als je in TypeScript schrijft:

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

Bouwen:

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

Installeren: Plug-ins > Installeren, selecteer de map my-plugin/. Schakel de plugin vervolgens in.


Manifestnaslagwerk ​

manifest.json staat in de hoofdmap van het pakket. Alle velden zijn hoofdlettergevoelig.

Velden op het hoogste niveau ​

VeldVerplichtTypeOpmerkingen
idjastringAlleen kleine letters, cijfers en koppeltekens. Maximaal 64 tekens. Uniek onder de geïnstalleerde plugins.
versionjastringSemver: MAJOR.MINOR.PATCH
nameneestringWeergavenaam in de interface. Standaard id.
descriptionneestringSamenvatting van één regel.
authorneeobject{ "name": "...", "email": "...", "url": "..." }
homepageneestringURL van de broncoderepository of documentatie.
pluginsjaarrayEén of meer vermeldingen van plugincomponenten (zie hieronder).
permissionsneearrayLijst met namen van vereiste machtigingen (zie Machtigingen).

Vermelding van een plugincomponent ​

Elk object in de array plugins beschrijft één component.

Backendcomponent:

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

Frontendcomponent:

json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
VeldVerplichtOpmerkingen
kindja"backend" of "frontend"
idjaUniek binnen het manifest. Kleine letters, koppeltekens.
entrypointjaRelatief pad naar het JS-toegangspuntbestand.
styleneeCSS-bestand dat in het plugin-iframe wordt geladen.
assetsneeMap met statische assets die worden aangeboden onder /plugins/{id}/assets/.
backend.idneeKoppelt een frontendcomponent aan zijn backendcomponent voor sdk.backend.*-RPC.
runtimenee (alleen backend)"javascript" (standaardwaarde en de enige ondersteunde waarde).

Backendplugin-API (sdk) ​

Het backendobject sdk wordt doorgegeven aan je functie init(sdk). Alle methoden zijn synchroon, tenzij ze als async zijn gemarkeerd.

sdk.console ​

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

Schrijft naar de logbuffer van de plugin (zichtbaar op het tabblad Logboeken). Er worden maximaal 500 items bewaard. Elk bericht wordt afgekapt op 1 KB.

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() verwijst naar de beschrijfbare privégegevensmap van de plugin, bijvoorbeeld ~/.local/share/ogma/plugins/my-plugin/data. De map wordt automatisch aangemaakt en kan worden gebruikt om plugingegevens tussen herstarts te bewaren.

sdk.storage, sdk.path en sdk.fs zijn ook beschikbaar als hulpfuncties voor toestand en bestanden.

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 slaat gegevens per plugin-ID op en bewaart ze wanneer de plugin opnieuw wordt gestart.

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 is beperkt tot bestanden onder sdk.meta.path().

read en write blijven compatibiliteitsaliassen voor readFile en writeFile. Gebruik exists of existsSync voordat je een bestand aanmaakt dat je niet wilt overschrijven. Deze API's werken synchroon in de pluginruntime; ze vormen niet de volledige Node.js-module fs. Bestandstoegang voor plugins vereist de machtiging plugin_storage en blijft binnen de privégegevensmap van de plugin. Workflow-JavaScript heeft een andere bestandssysteemcontext; zie Bestandstoegang voor workflows.

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 ​

Registreer callbacks voor Ogma-gebeurtenissen. Alle callbacks worden synchroon aangeroepen binnen de QuickJS-sandbox.

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 ​

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 vereist dat de machtiging send_requests in het manifest is gedeclareerd en door de gebruiker is verleend. Zie Machtigingen.

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");

Frequentielimieten voor sdk.findings.create: 10 per minuut, 500 per pluginsessie, 3 per gebeurteniscallback.

sdk.api ​

Registreer backend-RPC-functies die de frontend via sdk.backend.* kan aanroepen:

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" });

De handler ontvangt de argumenten die vanuit de frontend worden doorgegeven (er wordt geen extra sdk-argument geïnjecteerd). Retourwaarden worden als JSON geserialiseerd en teruggestuurd naar de aanroeper.

De frontend roept deze aan via sdk.backend.getScans(scanId) - zie Frontendplugin-API.

sdk.api.send plaatst gebeurtenissen in een wachtrij per plugin (maximaal 200 items). Frontends pollen deze wachtrij via sdk.backend.onEvent.

sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace ​

Deze naamruimten bieden alleen leesquery's. Zie het naslagwerk voor de backend-SDK voor de volledige methodesignaturen.

Verzoekklassen ​

RequestSpecRaw - vertegenwoordigt een onderschept verzoek. Je ontvangt dit 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 - een vastgelegd verzoek dat alleen kan worden gelezen (uit 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 - een vastgelegde respons die alleen kan worden gelezen.

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)

Frontendplugin-API (sdk) ​

De code van frontendplugins wordt uitgevoerd in een afgeschermd iframe dat wordt geladen vanuit /plugins/{id}/ui. Het iframe gebruikt postMessage om met de Ogma-host te communiceren, die aanroepen doorgeeft aan de backend.

De SDK is beschikbaar via window.ogmaSDK. Roep ogmaSDK.ready(cb) aan om de actieve SDK te ontvangen zodra de hostbridge tot stand is gebracht:

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

Alle SDK-methoden retourneren Promises.

sdk.log ​

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

sdk.meta ​

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

sdk.requests ​

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

Vereist de machtiging read_http_history (automatisch verleend; geen goedkeuring van de gebruiker nodig).

sdk.findings ​

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

Vereist de machtiging read_findings (automatisch verleend).

sdk.scope ​

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

sdk.projects ​

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

sdk.backend - backend-RPC ​

Roep functies aan die op de backend zijn geregistreerd met sdk.api.register:

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 gebruikt intern een pollinglus met een interval van 2 seconden. Stop met luisteren door de geretourneerde afmeldfunctie aan te roepen:

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

sdk.navigation ​

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

Registreert een navigatiepagina. Wordt momenteel door de host bevestigd. Volledige routerintegratie is in ontwikkeling.

sdk.sidebar ​

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

Registreert een zijbalkitem. Momenteel lokaal binnen het interfacepaneel van de plugin - de aansluiting op de globale zijbalk is in ontwikkeling.

sdk.commands ​

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

Wordt door de host bevestigd. Integratie met het opdrachtenpalet is in ontwikkeling.

sdk.menu ​

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

Wordt door de host bevestigd. Het invoegen van items in het contextmenu is in ontwikkeling.

sdk.window ​

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

Toont een toastmelding in het pluginpaneel. Varianten: info, success, warning, error.

sdk.ui ​

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

Machtigingen ​

Declareer machtigingen in manifest.json:

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

Automatisch verleende machtigingen (geen goedkeuring van de gebruiker nodig) ​

Deze worden altijd aan elke geïnstalleerde plugin verleend:

MachtigingWat deze toestaat
read_http_historysdk.requests.get, sdk.requests.search
read_findingssdk.findings.get, sdk.findings.list
read_scopesdk.scope.getActive
read_projectssdk.projects.getCurrent, sdk.projects.list
plugin_storagesdk.storage, sdk.fs, sdk.path

Beschermde machtigingen (vereisen goedkeuring van de gebruiker) ​

Deze moeten in het manifest worden gedeclareerd en expliciet door de gebruiker worden verleend via het tabblad Rechten:

MachtigingWat deze toestaat
send_requestssdk.requests.send - uitgaande HTTP-verzoeken versturen
write_findingssdk.findings.create, sdk.findings.update

De gebruiker wordt om toestemming gevraagd bij het inschakelen van een plugin die beschermde machtigingen declareert. Machtigingen kunnen ook op elk moment worden verleend of ingetrokken via het tabblad Rechten.


Structuur van een pluginpakket ​

Een pluginpakket kan vanuit een lokale map worden geïnstalleerd en, bij gebruik via de browser, ook vanuit .zip-exports.

my-plugin/
  manifest.json           - verplicht
  backend/
    script.js             - gebundelde backend-JS (ES2020)
  frontend/
    script.js             - gebundelde frontend-JS
    style.css             - optionele CSS
    assets/               - statische assets (afbeeldingen, lettertypen, enz.)

Vereisten voor het backendscript ​

  • Moet één zelfstandig JS-bestand zijn.
  • require() en dynamische import() worden niet ondersteund.
  • Moet een functie init(sdk) exporteren (of deze globaal definiëren).
  • ES2020-subset die QuickJS ondersteunt: async/await, Promise, Map, Set, Symbol, Proxy, Date, RegExp, JSON. Geen fetch, geen Buffer.
  • Ondersteunde statische imports worden door pluginvoorverwerking opgelost: @ogma/sdk, crypto, fs, path.
  • Maximale bestandsgrootte: 256 KB.

Vereisten voor het frontendscript ​

  • Wordt uitgevoerd in een afgeschermd iframe. connect-src: 'self' is toegestaan, zodat de plugin POST-verzoeken kan versturen naar /plugins/{id}/api/* en /plugins/{id}/events/poll kan pollen.
  • CSP: default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'.
  • Geen allow-same-origin in de iframe-sandbox - de plugin heeft geen toegang tot de bovenliggende DOM of cookies van Ogma.
  • Gebruik ogmaSDK.ready(cb) om toegang te krijgen tot de SDK; roep geen SDK-methoden aan voordat de callback wordt uitgevoerd.

Een plugin bouwen voor Ogma ​

Omdat de backend één gebundeld JS-bestand moet zijn, moet je je TypeScript-/ES-modulebroncode bundelen voordat je de plugin installeert.

Aanbevolen toolchain:

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

Als je de Caido-ontwikkeltoolchain (@caido-community/dev) gebruikt, voer dan caido-dev build uit en kopieer de uitvoer vervolgens naar een Ogma-compatibele structuur met manifest.json in de hoofdmap.


Een plugin installeren ​

  1. Open Plug-ins in de linkerzijbalk.
  2. Klik op Installeren (bovenaan het tabblad Geïnstalleerd).
  3. Klik in de desktopapp op Bladeren om de native mapkiezer te openen. Typ in de browser het volledige pad naar de pluginmap op de server.
  4. Klik op Valideren om het manifest en de bestandsinventaris te controleren.
  5. Klik op Installeren als de validatie slaagt.
  6. Selecteer de plugin in de lijst en klik op Inschakelen.
  7. Als de plugin beschermde machtigingen declareert, bekijk en verleen deze dan op het tabblad Rechten voordat je de plugin inschakelt.

Problemen oplossen ​

Plugininitialisatie mislukt zonder melding: Controleer het tabblad Logboeken. De meest voorkomende oorzaken:

  • sdk.meta.path() is aangeroepen, maar de gegevensmap kon niet worden aangemaakt.
  • Een niet-afgevangen uitzondering in init().
  • Een ontbrekende of verkeerd gespelde sdk.*-aanroep.

De frontend blijft leeg: Controleer de browserconsole op CSP-overtredingen. Zorg dat je frontendscript ogmaSDK.ready(cb) aanroept voordat het een SDK-methode gebruikt.

sdk.requests.send geeft de fout Permission denied: De machtiging send_requests moet in het manifest zijn gedeclareerd EN door de gebruiker zijn verleend op het tabblad Rechten.

Functies van sdk.api.register zijn niet aanroepbaar vanuit de frontend: De backend moet zijn ingeschakeld (alleen geïnstalleerd zijn is niet voldoende). De functienaam moet exact overeenkomen (hoofdlettergevoelig) met wat de frontend doorgeeft aan sdk.backend.call.

Compatibiliteitswaarschuwingen worden als fouten getoond: Deze blokkeren de werking niet, maar wijzen op ontbrekende onderdelen in de API-interface. Zie de SDK-koppelingstabellen hierboven voor de API-dekking.

Propriëtaire software. Alle rechten voorbehouden.