Systém pluginů Ogma
Pluginy Ogma rozšiřují nástroj o vlastní backendovou logiku, frontendové panely rozhraní a kroky pracovních postupů. Pluginy se instalují místně z adresáře na disku, zapínají se pro jednotlivé projekty a běží v izolovaném prostředí.
Tento dokument je hlavní referenční příručkou pro autory pluginů.
Pokud chcete začít co nejrychleji, použijte Rychlý start s pluginy.
Rychlý start
Minimální backendový 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; při použití nástroje pro tvorbu balíčků lze používat importy):
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());
}
});
}To stačí pro funkční plugin pouze s backendem. Ponechte jej jako jediný soubor v backend/script.js a odkažte na něj přímo z manifest.json.
Postup sestavení za minutu (zdrojový kód v TypeScriptu)
Pokud píšete v TypeScriptu, použijte tuto strukturu:
text
my-plugin/
manifest.json
backend/
src/index.tsSestavení:
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.jsInstalace: Zásuvné moduly > Nainstalovat, vyberte adresář my-plugin/. Poté plugin zapněte.
Referenční dokumentace manifestu
manifest.json se nachází v kořenovém adresáři balíčku. Všechna pole rozlišují velikost písmen.
Pole nejvyšší úrovně
| Pole | Povinné | Typ | Poznámky |
|---|---|---|---|
id | ano | řetězec | Pouze malá písmena, číslice a spojovníky. Maximálně 64 znaků. Jedinečné mezi nainstalovanými pluginy. |
version | ano | řetězec | Semver: MAJOR.MINOR.PATCH |
name | ne | řetězec | Zobrazovaný název v rozhraní. Výchozí hodnota je id. |
description | ne | řetězec | Jednořádkové shrnutí. |
author | ne | objekt | { "name": "...", "email": "...", "url": "..." } |
homepage | ne | řetězec | URL repozitáře se zdrojovým kódem nebo dokumentace. |
plugins | ano | pole | Jedna nebo více položek komponent pluginu (viz níže). |
permissions | ne | pole | Seznam názvů požadovaných oprávnění (viz Oprávnění). |
Položka komponenty pluginu
Každý objekt v poli plugins popisuje jednu komponentu.
Backendová komponenta:
json
{
"kind": "backend",
"id": "my-plugin-backend",
"entrypoint": "backend/script.js",
"runtime": "javascript",
"assets": "backend/assets"
}Frontendová komponenta:
json
{
"kind": "frontend",
"id": "my-plugin-frontend",
"entrypoint": "frontend/script.js",
"style": "frontend/style.css",
"assets": "frontend/assets",
"backend": { "id": "my-plugin-backend" }
}| Pole | Povinné | Poznámky |
|---|---|---|
kind | ano | "backend" nebo "frontend" |
id | ano | Jedinečné v rámci manifestu. Malá písmena, spojovníky. |
entrypoint | ano | Relativní cesta ke vstupnímu souboru JS. |
style | ne | Soubor CSS načítaný v iframe pluginu. |
assets | ne | Adresář statických prostředků poskytovaných pod /plugins/{id}/assets/. |
backend.id | ne | Propojuje frontendovou komponentu s její backendovou komponentou pro RPC sdk.backend.*. |
runtime | ne (pouze backend) | "javascript" (výchozí a jediná podporovaná hodnota). |
API backendových pluginů (sdk)
Backendový objekt sdk se předává vaší funkci init(sdk). Všechny metody jsou synchronní, pokud nejsou označeny jako async.
sdk.console
js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")Zapisuje do vyrovnávací paměti protokolů pluginu (viditelné na kartě Protokoly). Uchovává se maximálně 500 záznamů. Každá zpráva je omezena na 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() odkazuje na soukromý datový adresář pluginu s možností zápisu, například ~/.local/share/ogma/plugins/my-plugin/data. Adresář se vytváří automaticky a lze jej použít k trvalému ukládání dat pluginu mezi restarty.
Pro práci se stavem a soubory jsou dostupné také sdk.storage, sdk.path a sdk.fs.
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[]Data v sdk.storage jsou vázána na ID pluginu a uchovávají se mezi restarty pluginu.
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 je omezeno na soubory pod sdk.meta.path().
read a write zůstávají aliasy pro readFile a writeFile kvůli kompatibilitě. Před vytvořením souboru, který nechcete přepsat, použijte exists nebo existsSync. Tato API pracují v běhovém prostředí pluginu synchronně; nejde o úplný modul fs z Node.js. Přístup pluginu k souborům vyžaduje oprávnění plugin_storage a zůstává uvnitř soukromého datového adresáře pluginu. JavaScript pracovních postupů má jiný kontext souborového systému; viz Přístup k souborům v pracovních postupech.
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
Registrujte obslužné funkce pro události Ogma. Všechny obslužné funkce se volají synchronně uvnitř sandboxu 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
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 vyžaduje, aby bylo oprávnění send_requests deklarováno v manifestu a uděleno uživatelem. Viz Oprávnění.
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");Limity volání sdk.findings.create: 10 za minutu, 500 za relaci pluginu, 3 na jedno volání obslužné funkce události.
sdk.api
Registrujte backendové funkce RPC, které může frontend volat prostřednictvím 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" });Obslužná funkce dostává argumenty předané z frontendu (žádný další argument sdk se nevkládá). Návratové hodnoty se serializují do JSON a posílají zpět volajícímu.
Frontend je volá prostřednictvím sdk.backend.getScans(scanId) – viz API frontendových pluginů.
sdk.api.send vkládá události do fronty pro daný plugin (maximálně 200 záznamů). Frontendové komponenty z této fronty pravidelně načítají události prostřednictvím sdk.backend.onEvent.
sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace
Jde o jmenné prostory pro dotazy pouze pro čtení. Úplné signatury metod najdete v referenční dokumentaci backendového SDK.
Třídy požadavků
RequestSpecRaw – představuje zachycený požadavek. Dostáváte jej v 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 – zachycený požadavek pouze pro čtení (ze 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 – zachycená odpověď pouze pro čtení.
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)API frontendových pluginů (sdk)
Frontendový kód pluginu běží v izolovaném iframe načítaném z /plugins/{id}/ui. Iframe používá postMessage ke komunikaci s hostitelskou aplikací Ogma, která zprostředkovává volání backendu.
SDK je dostupné prostřednictvím window.ogmaSDK. Zavolejte ogmaSDK.ready(cb) a získejte aktivní SDK, jakmile se naváže můstek k hostitelské aplikaci:
js
ogmaSDK.ready(function(sdk) {
// sdk is the live SDK - safe to call any method here
sdk.log.info("frontend ready");
});Všechny metody SDK vracejí objekty Promise.
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" });Vyžaduje oprávnění read_http_history (udělováno automaticky; schválení uživatelem není potřeba).
sdk.findings
js
var page = await sdk.findings.list({ limit: 20, offset: 0 });Vyžaduje oprávnění read_findings (udělováno automaticky).
sdk.scope
js
var scope = await sdk.scope.getActive();sdk.projects
js
var project = await sdk.projects.getCurrent();sdk.backend – backendové RPC
Volejte funkce zaregistrované na backendu pomocí 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 interně používá dotazovací smyčku s intervalem 2 sekund. Naslouchání ukončíte zavoláním vrácené funkce pro odhlášení:
js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();sdk.navigation
js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });Registruje navigační stránku. Hostitelská aplikace v současnosti potvrzuje přijetí. Úplná integrace směrovače je ve vývoji.
sdk.sidebar
js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });Registruje položku postranního panelu. V současnosti je omezena na panel rozhraní pluginu – propojení s globálním postranním panelem je ve vývoji.
sdk.commands
js
await sdk.commands.register("my-plugin:scan", {
name: "Scan with My Plugin",
handler: function(context) { /* ... */ }
});Hostitelská aplikace potvrzuje přijetí. Integrace palety příkazů je ve vývoji.
sdk.menu
js
await sdk.menu.registerItem({
type: "Request",
commandId: "my-plugin:scan",
leadingIcon: "shield"
});Hostitelská aplikace potvrzuje přijetí. Vkládání do kontextové nabídky je ve vývoji.
sdk.window
js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });Zobrazuje krátké oznámení v panelu pluginu. Varianty: 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.registerItemOprávnění
Deklarujte oprávnění v manifest.json:
json
{
"permissions": ["send_requests", "write_findings"]
}Automaticky udělovaná oprávnění (bez nutnosti schválení uživatelem)
Tato oprávnění jsou vždy udělena každému nainstalovanému pluginu:
| Oprávnění | Co umožňuje |
|---|---|
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 |
Chráněná oprávnění (vyžadují schválení uživatelem)
Tato oprávnění musí být deklarována v manifestu a uživatel je musí explicitně udělit na kartě Oprávnění:
| Oprávnění | Co umožňuje |
|---|---|
send_requests | sdk.requests.send – odesílání odchozích požadavků HTTP |
write_findings | sdk.findings.create, sdk.findings.update |
Při zapínání pluginu, který deklaruje chráněná oprávnění, se uživateli zobrazí výzva. Oprávnění může také kdykoli udělit nebo odebrat na kartě Oprávnění.
Struktura balíčku pluginu
Balíček pluginu lze nainstalovat z místního adresáře a při práci v prohlížeči také z exportů .zip.
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.)Požadavky na backendový skript
- Musí jít o jediný samostatný soubor JS.
require()a dynamickéimport()nejsou podporovány.- Musí exportovat funkci
init(sdk)(nebo ji definovat globálně). - Podmnožina ES2020 podporovaná QuickJS:
async/await,Promise,Map,Set,Symbol,Proxy,Date,RegExp,JSON. Bezfetcha bezBuffer. - Podporované statické importy se řeší při předzpracování pluginu:
@ogma/sdk,crypto,fs,path. - Maximální velikost souboru: 256 KB.
Požadavky na frontendový skript
- Běží v izolovaném iframe.
connect-src: 'self'je povoleno, aby plugin mohl odesílat POST na/plugins/{id}/api/*a pravidelně získávat události z/plugins/{id}/events/poll. - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - Sandbox iframe nemá
allow-same-origin– plugin nemá přístup k nadřazenému DOM ani cookies Ogma. - Pro přístup k SDK použijte
ogmaSDK.ready(cb); nevolejte metody SDK před spuštěním obslužné funkce.
Sestavení pluginu pro Ogma
Protože backend musí být jediný soubor JS se vším potřebným kódem, musíte před instalací sloučit zdrojový kód v TypeScriptu nebo modulech ES do balíčku.
Doporučená sada nástrojů:
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/frontendPokud používáte vývojové nástroje Caido (@caido-community/dev), spusťte caido-dev build a poté zkopírujte výstup do struktury kompatibilní s Ogma, která má v kořenovém adresáři manifest.json.
Instalace pluginu
- Otevřete Zásuvné moduly v levém postranním panelu.
- Klikněte na Nainstalovat (v horní části karty Nainstalované).
- V desktopové aplikaci klikněte na Procházet a otevřete nativní dialog pro výběr složky. V prohlížeči zadejte úplnou cestu k adresáři pluginu na straně serveru.
- Klikněte na Ověřit a zkontrolujte manifest a seznam souborů.
- Pokud validace projde, klikněte na Nainstalovat.
- Vyberte plugin v seznamu a klikněte na Zapnout.
- Pokud plugin deklaruje chráněná oprávnění, před zapnutím je zkontrolujte a udělte na kartě Oprávnění.
Řešení problémů
Inicializace pluginu selže bez oznámení: Zkontrolujte kartu Protokoly. Nejčastější příčiny:
- Bylo zavoláno
sdk.meta.path(), ale datový adresář se nepodařilo vytvořit. - Neošetřená výjimka v
init(). - Chybějící volání
sdk.*nebo překlep v něm.
Frontend je prázdný: Zkontrolujte konzoli prohlížeče, zda nehlásí porušení CSP. Ujistěte se, že frontendový skript volá ogmaSDK.ready(cb) před přístupem k jakékoli metodě SDK.
sdk.requests.send vyvolá Permission denied: Oprávnění send_requests musí být deklarováno v manifestu A ZÁROVEŇ uděleno uživatelem na kartě Oprávnění.
Funkce sdk.api.register nelze volat z frontendu: Backend musí být zapnutý (nestačí jej pouze nainstalovat). Název funkce se musí přesně shodovat (s rozlišováním velikosti písmen) s tím, co frontend předává do sdk.backend.call.
Upozornění na kompatibilitu se zobrazují jako chyby: Nebrání fungování pluginu, ale upozorňují na nedostatky v pokrytí API. Pokrytí API najdete ve výše uvedených tabulkách mapování SDK.