Zum Inhalt springen

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.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 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.ts

Build:

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

Installation: 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 ​

FeldErforderlichTypHinweise
idjaZeichenketteNur Kleinbuchstaben, Ziffern und Bindestriche. Maximal 64 Zeichen. Unter den installierten Plugins eindeutig.
versionjaZeichenketteSemver: MAJOR.MINOR.PATCH
nameneinZeichenketteAnzeigename in der Oberfläche. Standardmäßig id.
descriptionneinZeichenketteEinzeilige Zusammenfassung.
authorneinObjekt{ "name": "...", "email": "...", "url": "..." }
homepageneinZeichenketteURL zum Quellcode-Repository oder zur Dokumentation.
pluginsjaArrayEin oder mehrere Plugin-Komponenteneinträge (siehe unten).
permissionsneinArrayListe 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" }
}
FeldErforderlichHinweise
kindja"backend" oder "frontend"
idjaInnerhalb des Manifests eindeutig. Kleinbuchstaben, Bindestriche.
entrypointjaRelativer Pfad zur JS-Einstiegsdatei.
styleneinCSS-Datei, die im Plugin-Iframe geladen wird.
assetsneinVerzeichnis statischer Assets, die unter /plugins/{id}/assets/ bereitgestellt werden.
backend.idneinVerknüpft eine Frontend-Komponente mit ihrer Backend-Komponente für sdk.backend.*-RPC.
runtimenein (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 plugin

sdk.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.sep

sdk.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: Response

sdk.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()       // > 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)

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.registerItem

Berechtigungen ​

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:

BerechtigungErlaubte Funktionen
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

Geschützte Berechtigungen (Zustimmung des Nutzers erforderlich) ​

Diese müssen im Manifest deklariert und vom Nutzer auf der Registerkarte Berechtigungen ausdrücklich erteilt werden:

BerechtigungErlaubte Funktionen
send_requestssdk.requests.send – ausgehende HTTP-Anfragen senden
write_findingssdk.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 dynamisches import() 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. Kein fetch, kein Buffer.
  • 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/poll abfragen kann.
  • CSP: default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'.
  • Kein allow-same-origin in 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/frontend

Wenn 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 ​

  1. Öffnen Sie Plugins in der linken Seitenleiste.
  2. Klicken Sie auf Installieren (oben auf der Registerkarte Installiert).
  3. 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.
  4. Klicken Sie auf Validieren, um das Manifest und den Dateibestand zu prüfen.
  5. Klicken Sie auf Installieren, wenn die Validierung erfolgreich ist.
  6. Wählen Sie das Plugin in der Liste aus und klicken Sie auf Aktivieren.
  7. 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.

Proprietäre Software. Alle Rechte vorbehalten.