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.jsmanifest.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.tsBouwen:
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.jsInstalleren: 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
| Veld | Verplicht | Type | Opmerkingen |
|---|---|---|---|
id | ja | string | Alleen kleine letters, cijfers en koppeltekens. Maximaal 64 tekens. Uniek onder de geïnstalleerde plugins. |
version | ja | string | Semver: MAJOR.MINOR.PATCH |
name | nee | string | Weergavenaam in de interface. Standaard id. |
description | nee | string | Samenvatting van één regel. |
author | nee | object | { "name": "...", "email": "...", "url": "..." } |
homepage | nee | string | URL van de broncoderepository of documentatie. |
plugins | ja | array | Eén of meer vermeldingen van plugincomponenten (zie hieronder). |
permissions | nee | array | Lijst 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" }
}| Veld | Verplicht | Opmerkingen |
|---|---|---|
kind | ja | "backend" of "frontend" |
id | ja | Uniek binnen het manifest. Kleine letters, koppeltekens. |
entrypoint | ja | Relatief pad naar het JS-toegangspuntbestand. |
style | nee | CSS-bestand dat in het plugin-iframe wordt geladen. |
assets | nee | Map met statische assets die worden aangeboden onder /plugins/{id}/assets/. |
backend.id | nee | Koppelt een frontendcomponent aan zijn backendcomponent voor sdk.backend.*-RPC. |
runtime | nee (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 pluginsdk.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.sepsdk.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: Responsesdk.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() // > DateBody:
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.registerItemMachtigingen
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:
| Machtiging | Wat deze toestaat |
|---|---|
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 |
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:
| Machtiging | Wat deze toestaat |
|---|---|
send_requests | sdk.requests.send - uitgaande HTTP-verzoeken versturen |
write_findings | sdk.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 dynamischeimport()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. Geenfetch, geenBuffer. - 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/pollkan pollen. - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - Geen
allow-same-originin 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/frontendAls 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
- Open Plug-ins in de linkerzijbalk.
- Klik op Installeren (bovenaan het tabblad Geïnstalleerd).
- 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.
- Klik op Valideren om het manifest en de bestandsinventaris te controleren.
- Klik op Installeren als de validatie slaagt.
- Selecteer de plugin in de lijst en klik op Inschakelen.
- 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.