Ogma-Plugin-System
Ogma-Plugins erweitern das Tool um eigene Backend-Logik, Frontend-UI-Panels und Workflow-Schritte. Plugins werden lokal aus einem Verzeichnis auf dem Datenträger installiert, pro Projekt aktiviert und in einer Sandbox-Umgebung ausgeführt.
Dieses Dokument ist die zentrale Referenz für Plugin-Autoren.
Für den schnellstmöglichen Einstieg verwenden Sie den Plugin-Schnellstart.
Schnellstart
Minimales Backend-Plugin
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 sind mit einem Bundler möglich):
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());
}
});
}Das genügt für ein funktionierendes Plugin nur mit Backend. Belassen Sie es als einzelne Datei in backend/script.js und verweisen Sie in manifest.json direkt darauf.
Build-Ablauf in einer Minute (TypeScript-Quellcode)
Wenn Sie TypeScript verwenden, nutzen Sie diese Struktur:
text
my-plugin/
manifest.json
backend/
src/index.tsBuild:
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.jsInstallation: Plugins > Installieren, wählen Sie das Verzeichnis my-plugin/ aus. Aktivieren Sie anschließend das Plugin.
Manifest-Referenz
manifest.json befindet sich im Stammverzeichnis des Pakets. Bei allen Feldern wird die Groß-/Kleinschreibung beachtet.
Felder auf oberster Ebene
| Feld | Erforderlich | Typ | Hinweise |
|---|---|---|---|
id | ja | Zeichenkette | Nur Kleinbuchstaben, Ziffern und Bindestriche. Maximal 64 Zeichen. Unter den installierten Plugins eindeutig. |
version | ja | Zeichenkette | Semver: MAJOR.MINOR.PATCH |
name | nein | Zeichenkette | Anzeigename in der Oberfläche. Standardmäßig id. |
description | nein | Zeichenkette | Einzeilige Zusammenfassung. |
author | nein | Objekt | { "name": "...", "email": "...", "url": "..." } |
homepage | nein | Zeichenkette | URL zum Quellcode-Repository oder zur Dokumentation. |
plugins | ja | Array | Ein oder mehrere Plugin-Komponenteneinträge (siehe unten). |
permissions | nein | Array | Liste der Namen erforderlicher Berechtigungen (siehe Berechtigungen). |
Plugin-Komponenteneintrag
Jedes Objekt im Array plugins beschreibt eine Komponente.
Backend-Komponente:
json
{
"kind": "backend",
"id": "my-plugin-backend",
"entrypoint": "backend/script.js",
"runtime": "javascript",
"assets": "backend/assets"
}Frontend-Komponente:
json
{
"kind": "frontend",
"id": "my-plugin-frontend",
"entrypoint": "frontend/script.js",
"style": "frontend/style.css",
"assets": "frontend/assets",
"backend": { "id": "my-plugin-backend" }
}| Feld | Erforderlich | Hinweise |
|---|---|---|
kind | ja | "backend" oder "frontend" |
id | ja | Innerhalb des Manifests eindeutig. Kleinbuchstaben, Bindestriche. |
entrypoint | ja | Relativer Pfad zur JS-Einstiegsdatei. |
style | nein | CSS-Datei, die im Plugin-Iframe geladen wird. |
assets | nein | Verzeichnis statischer Assets, die unter /plugins/{id}/assets/ bereitgestellt werden. |
backend.id | nein | Verknüpft eine Frontend-Komponente mit ihrer Backend-Komponente für sdk.backend.*-RPC. |
runtime | nein (nur Backend) | "javascript" (Standard und einziger unterstützter Wert). |
Backend-Plugin-API (sdk)
Das Backend-Objekt sdk wird an Ihre Funktion init(sdk) übergeben. Alle Methoden sind synchron, sofern sie nicht mit async gekennzeichnet sind.
sdk.console
js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")Schreibt in den Protokollpuffer des Plugins (sichtbar auf der Registerkarte Protokolle). Maximal 500 Einträge werden aufbewahrt. Jede Nachricht wird auf 1 KB gekürzt.
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() verweist auf das beschreibbare private Datenverzeichnis des Plugins, z. B. ~/.local/share/ogma/plugins/my-plugin/data. Das Verzeichnis wird automatisch erstellt und kann verwendet werden, um Plugin-Daten über Neustarts hinweg dauerhaft zu speichern.
sdk.storage, sdk.path und sdk.fs stehen ebenfalls für Zustandsverwaltung und Dateihilfsfunktionen zur Verfügung.
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 ist auf die Plugin-ID beschränkt und bleibt über Plugin-Neustarts hinweg erhalten.
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 ist auf Dateien unterhalb von sdk.meta.path() beschränkt.
read und write bleiben als Kompatibilitätsaliase für readFile und writeFile erhalten. Verwenden Sie exists oder existsSync, bevor Sie eine Datei erstellen, die Sie nicht überschreiben möchten. Diese APIs arbeiten in der Plugin-Laufzeit synchron; sie entsprechen nicht dem vollständigen Node.js-Modul fs. Der Dateizugriff von Plugins erfordert die Berechtigung plugin_storage und bleibt auf das private Datenverzeichnis des Plugins beschränkt. Workflow-JavaScript hat einen anderen Dateisystemkontext; siehe Dateizugriff in 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
Registrieren Sie Callbacks für Ogma-Ereignisse. Alle Callbacks werden synchron innerhalb der QuickJS-Sandbox aufgerufen.
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 erfordert, dass die Berechtigung send_requests im Manifest deklariert und vom Nutzer erteilt wird. Siehe Berechtigungen.
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");Aufrufgrenzen für sdk.findings.create: 10 pro Minute, 500 pro Plugin-Sitzung, 3 pro Ereignis-Callback.
sdk.api
Registrieren Sie Backend-RPC-Funktionen, die das Frontend über sdk.backend.* aufrufen kann:
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" });Der Handler erhält die vom Frontend übergebenen Argumente (es wird kein zusätzliches sdk-Argument eingefügt). Rückgabewerte werden als JSON serialisiert und an den Aufrufer zurückgesendet.
Das Frontend ruft diese Funktionen über sdk.backend.getScans(scanId) auf – siehe Frontend-Plugin-API.
sdk.api.send stellt Ereignisse in eine Plugin-spezifische Warteschlange (maximal 200 Einträge). Frontends fragen diese Warteschlange über sdk.backend.onEvent ab.
sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace
Dies sind schreibgeschützte Abfragenamensräume. Vollständige Methodensignaturen finden Sie in der Backend-SDK-Referenz.
Anfrageklassen
RequestSpecRaw – stellt eine abgefangene Anfrage dar. Sie erhalten dieses Objekt 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 – eine schreibgeschützte aufgezeichnete Anfrage (aus 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 – eine schreibgeschützte aufgezeichnete Antwort.
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)Frontend-Plugin-API (sdk)
Frontend-Plugin-Code läuft in einem Iframe mit Sandbox, das von /plugins/{id}/ui geladen wird. Das Iframe verwendet postMessage zur Kommunikation mit dem Ogma-Host, der Aufrufe an das Backend weiterleitet.
Das SDK ist über window.ogmaSDK verfügbar. Rufen Sie ogmaSDK.ready(cb) auf, um das einsatzbereite SDK zu erhalten, sobald die Host-Bridge eingerichtet ist:
js
ogmaSDK.ready(function(sdk) {
// sdk is the live SDK - safe to call any method here
sdk.log.info("frontend ready");
});Alle SDK-Methoden geben Promises zurück.
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" });Erfordert die Berechtigung read_http_history (automatisch erteilt; keine Zustimmung des Nutzers erforderlich).
sdk.findings
js
var page = await sdk.findings.list({ limit: 20, offset: 0 });Erfordert die Berechtigung read_findings (automatisch erteilt).
sdk.scope
js
var scope = await sdk.scope.getActive();sdk.projects
js
var project = await sdk.projects.getCurrent();sdk.backend – Backend-RPC
Rufen Sie Funktionen auf, die im Backend mit sdk.api.register registriert wurden:
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 verwendet intern eine Polling-Schleife mit einem Intervall von 2 Sekunden. Beenden Sie das Abonnement durch Aufrufen der zurückgegebenen Abmeldefunktion:
js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();sdk.navigation
js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });Registriert eine Navigationsseite. Wird derzeit vom Host bestätigt. Die vollständige Router-Integration ist in Arbeit.
sdk.sidebar
js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });Registriert einen Seitenleisteneintrag. Derzeit auf das Plugin-UI-Panel beschränkt – die Anbindung an die globale Seitenleiste ist in Arbeit.
sdk.commands
js
await sdk.commands.register("my-plugin:scan", {
name: "Scan with My Plugin",
handler: function(context) { /* ... */ }
});Wird vom Host bestätigt. Die Integration in die Befehlspalette ist in Arbeit.
sdk.menu
js
await sdk.menu.registerItem({
type: "Request",
commandId: "my-plugin:scan",
leadingIcon: "shield"
});Wird vom Host bestätigt. Das Einfügen in das Kontextmenü ist in Arbeit.
sdk.window
js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });Zeigt eine Toast-Benachrichtigung im Plugin-Panel an. 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.registerItemBerechtigungen
Deklarieren Sie Berechtigungen in manifest.json:
json
{
"permissions": ["send_requests", "write_findings"]
}Automatisch erteilte Berechtigungen (keine Zustimmung des Nutzers erforderlich)
Diese werden jedem installierten Plugin immer erteilt:
| Berechtigung | Erlaubte Funktionen |
|---|---|
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 |
Geschützte Berechtigungen (Zustimmung des Nutzers erforderlich)
Diese müssen im Manifest deklariert und vom Nutzer auf der Registerkarte Berechtigungen ausdrücklich erteilt werden:
| Berechtigung | Erlaubte Funktionen |
|---|---|
send_requests | sdk.requests.send – ausgehende HTTP-Anfragen senden |
write_findings | sdk.findings.create, sdk.findings.update |
Beim Aktivieren eines Plugins, das geschützte Berechtigungen deklariert, wird dem Nutzer eine Abfrage angezeigt. Er kann Berechtigungen auch jederzeit auf der Registerkarte Berechtigungen erteilen oder entziehen.
Struktur eines Plugin-Pakets
Ein Plugin-Paket kann aus einem lokalen Verzeichnis und bei browserbasierten Abläufen auch aus .zip-Exporten installiert werden.
my-plugin/
manifest.json - required
backend/
script.js - bundled backend JS (ES2020)
frontend/
script.js - bundled frontend JS
style.css - optional CSS
assets/ - static assets (images, fonts, etc.)Anforderungen an Backend-Skripte
- Muss eine einzelne, in sich geschlossene JS-Datei sein.
require()und dynamischesimport()werden nicht unterstützt.- Muss eine Funktion
init(sdk)exportieren (oder als globale Funktion definieren). - Von QuickJS unterstützte ES2020-Teilmenge:
async/await,Promise,Map,Set,Symbol,Proxy,Date,RegExp,JSON. Keinfetch, keinBuffer. - Unterstützte statische Imports werden durch die Plugin-Vorverarbeitung aufgelöst:
@ogma/sdk,crypto,fs,path. - Maximale Dateigröße: 256 KB.
Anforderungen an Frontend-Skripte
- Läuft in einem Iframe mit Sandbox.
connect-src: 'self'ist erlaubt, damit das Plugin POST-Anfragen an/plugins/{id}/api/*senden und/plugins/{id}/events/pollabfragen kann. - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - Kein
allow-same-originin der Iframe-Sandbox – das Plugin kann nicht auf Ogmas übergeordnetes DOM oder Cookies zugreifen. - Verwenden Sie
ogmaSDK.ready(cb), um auf das SDK zuzugreifen; rufen Sie SDK-Methoden erst auf, wenn der Callback ausgelöst wurde.
Ein Plugin für Ogma bauen
Da das Backend eine einzelne gebündelte JS-Datei sein muss, müssen Sie Ihren TypeScript-/ES-Modul-Quellcode vor der Installation bündeln.
Empfohlene 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/frontendWenn Sie die Caido-Entwicklungs-Toolchain (@caido-community/dev) verwenden, führen Sie caido-dev build aus und kopieren Sie die Ausgabe anschließend in eine Ogma-kompatible Verzeichnisstruktur mit manifest.json im Stammverzeichnis.
Ein Plugin installieren
- Öffnen Sie Plugins in der linken Seitenleiste.
- Klicken Sie auf Installieren (oben auf der Registerkarte Installiert).
- Klicken Sie in der Desktop-Anwendung auf Durchsuchen, um die native Ordnerauswahl zu öffnen. Geben Sie im Browser den vollständigen serverseitigen Pfad zum Plugin-Verzeichnis ein.
- Klicken Sie auf Validieren, um das Manifest und den Dateibestand zu prüfen.
- Klicken Sie auf Installieren, wenn die Validierung erfolgreich ist.
- Wählen Sie das Plugin in der Liste aus und klicken Sie auf Aktivieren.
- Wenn das Plugin geschützte Berechtigungen deklariert, prüfen und erteilen Sie diese vor dem Aktivieren auf der Registerkarte Berechtigungen.
Problemlösung
Plugin-Initialisierung schlägt ohne Fehlermeldung fehl: Prüfen Sie die Registerkarte Protokolle. Die häufigsten Ursachen:
sdk.meta.path()wurde aufgerufen, aber das Datenverzeichnis konnte nicht erstellt werden.- Eine unbehandelte Ausnahme in
init(). - Ein fehlender oder falsch geschriebener
sdk.*-Aufruf.
Frontend bleibt leer: Prüfen Sie die Browserkonsole auf CSP-Verstöße. Stellen Sie sicher, dass Ihr Frontend-Skript ogmaSDK.ready(cb) aufruft, bevor es auf eine SDK-Methode zugreift.
sdk.requests.send löst Permission denied aus: Die Berechtigung send_requests muss im Manifest deklariert UND vom Nutzer auf der Registerkarte Berechtigungen erteilt sein.
Mit sdk.api.register registrierte Funktionen sind vom Frontend aus nicht aufrufbar: Das Backend muss aktiviert sein (nicht nur installiert). Der Funktionsname muss exakt mit dem Namen übereinstimmen, den das Frontend an sdk.backend.call übergibt (Groß-/Kleinschreibung beachten).
Kompatibilitätswarnungen erscheinen als Fehler: Diese verhindern den Betrieb nicht, weisen aber auf Lücken im API-Funktionsumfang hin. Die obigen SDK-Zuordnungstabellen zeigen die API-Abdeckung.