System wtyczek Ogma
Wtyczki Ogma rozszerzają narzędzie o własną logikę backendu, panele interfejsu frontendu i kroki przepływów pracy. Wtyczki są instalowane lokalnie z katalogu na dysku, włączane osobno dla każdego projektu i uruchamiane w środowisku piaskownicy.
Ten dokument stanowi główną dokumentację referencyjną dla autorów wtyczek.
Jeśli chcesz zacząć jak najszybciej, skorzystaj z Szybkiego startu z wtyczkami.
Szybki start
Minimalna wtyczka backendu
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; importy są dozwolone przy korzystaniu z narzędzia do łączenia modułów w jeden plik):
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 wystarczy do utworzenia działającej wtyczki zawierającej tylko backend. Zachowaj ją jako pojedynczy plik backend/script.js i wskaż go bezpośrednio w manifest.json.
Budowanie w minutę (źródła TypeScript)
Jeśli piszesz w TypeScript, użyj następującej struktury:
text
my-plugin/
manifest.json
backend/
src/index.tsBudowanie:
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.jsInstalacja: Wtyczki > Zainstaluj, wybierz katalog my-plugin/. Następnie włącz wtyczkę.
Dokumentacja manifestu
manifest.json znajduje się w katalogu głównym pakietu. We wszystkich polach wielkość liter ma znaczenie.
Pola najwyższego poziomu
| Pole | Wymagane | Typ | Uwagi |
|---|---|---|---|
id | tak | ciąg znaków | Tylko małe litery, cyfry i łączniki. Maksymalnie 64 znaki. Unikalny wśród zainstalowanych wtyczek. |
version | tak | ciąg znaków | Semver: MAJOR.MINOR.PATCH |
name | nie | ciąg znaków | Nazwa wyświetlana w interfejsie. Domyślnie id. |
description | nie | ciąg znaków | Podsumowanie w jednym wierszu. |
author | nie | obiekt | { "name": "...", "email": "...", "url": "..." } |
homepage | nie | ciąg znaków | Adres URL repozytorium źródeł lub dokumentacji. |
plugins | tak | tablica | Co najmniej jeden wpis komponentu wtyczki (zobacz poniżej). |
permissions | nie | tablica | Lista nazw wymaganych uprawnień (zobacz Uprawnienia). |
Wpis komponentu wtyczki
Każdy obiekt w tablicy plugins opisuje jeden komponent.
Komponent backendu:
json
{
"kind": "backend",
"id": "my-plugin-backend",
"entrypoint": "backend/script.js",
"runtime": "javascript",
"assets": "backend/assets"
}Komponent frontendu:
json
{
"kind": "frontend",
"id": "my-plugin-frontend",
"entrypoint": "frontend/script.js",
"style": "frontend/style.css",
"assets": "frontend/assets",
"backend": { "id": "my-plugin-backend" }
}| Pole | Wymagane | Uwagi |
|---|---|---|
kind | tak | "backend" lub "frontend" |
id | tak | Unikalny w obrębie manifestu. Małe litery, łączniki. |
entrypoint | tak | Ścieżka względna do pliku wejściowego JS. |
style | nie | Plik CSS ładowany w ramce iframe wtyczki. |
assets | nie | Katalog zasobów statycznych udostępnianych pod /plugins/{id}/assets/. |
backend.id | nie | Łączy komponent frontendu z jego komponentem backendu na potrzeby RPC sdk.backend.*. |
runtime | nie (tylko backend) | "javascript" (wartość domyślna i jedyna obsługiwana). |
API wtyczek backendu (sdk)
Obiekt sdk backendu jest przekazywany do funkcji init(sdk). Wszystkie metody są synchroniczne, chyba że oznaczono je jako async.
sdk.console
js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")Zapisuje do bufora dziennika wtyczki (widocznego na karcie Dzienniki). Przechowywanych jest maksymalnie 500 wpisów. Każdy komunikat jest obcinany do 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() wskazuje prywatny katalog danych wtyczki z prawem zapisu, np. ~/.local/share/ogma/plugins/my-plugin/data. Katalog jest tworzony automatycznie i może służyć do przechowywania danych wtyczki między ponownymi uruchomieniami.
Do obsługi stanu i plików dostępne są również sdk.storage, sdk.path i 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[]sdk.storage jest przypisany do identyfikatora wtyczki, a jego dane są zachowywane między ponownymi uruchomieniami wtyczki.
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 jest ograniczony do plików w katalogu sdk.meta.path().
read i write pozostają aliasami zgodności dla readFile i writeFile. Użyj exists lub existsSync przed utworzeniem pliku, którego nie chcesz nadpisać. Te API działają synchronicznie w środowisku wykonawczym wtyczki; nie stanowią pełnego modułu fs z Node.js. Dostęp wtyczki do plików wymaga uprawnienia plugin_storage i pozostaje ograniczony do jej prywatnego katalogu danych. JavaScript w przepływach pracy ma inny kontekst systemu plików; zobacz Dostęp do plików w przepływach pracy.
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
Rejestruj funkcje zwrotne dla zdarzeń Ogma. Wszystkie funkcje zwrotne są wywoływane synchronicznie wewnątrz piaskownicy 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 wymaga zadeklarowania uprawnienia send_requests w manifeście i przyznania go przez użytkownika. Zobacz Uprawnienia.
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 wywołań sdk.findings.create: 10 na minutę, 500 na sesję wtyczki, 3 na funkcję zwrotną zdarzenia.
sdk.api
Rejestruj funkcje RPC backendu, które frontend może wywoływać przez 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" });Procedura obsługi otrzymuje argumenty przekazane z frontendu (nie jest wstrzykiwany dodatkowy argument sdk). Zwracane wartości są serializowane do JSON i odsyłane do wywołującego.
Frontend wywołuje te funkcje przez sdk.backend.getScans(scanId) — zobacz API wtyczek frontendu.
sdk.api.send umieszcza zdarzenia w kolejce przypisanej do wtyczki (maksymalnie 200 wpisów). Frontendy odpytują tę kolejkę przez sdk.backend.onEvent.
sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace
Są to przestrzenie nazw zapytań tylko do odczytu. Pełne sygnatury metod znajdziesz w dokumentacji referencyjnej SDK backendu.
Klasy żądań
RequestSpecRaw — reprezentuje przechwycone żądanie. Otrzymujesz je w 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 — przechwycone żądanie tylko do odczytu (z 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 — przechwycona odpowiedź tylko do odczytu.
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 wtyczek frontendu (sdk)
Kod frontendu wtyczki działa w izolowanej ramce iframe ładowanej z /plugins/{id}/ui. Ramka iframe korzysta z postMessage do komunikacji z hostem Ogma, który pośredniczy w wywołaniach backendu.
SDK jest dostępne przez window.ogmaSDK. Wywołaj ogmaSDK.ready(cb), aby otrzymać gotowe do użycia SDK po nawiązaniu połączenia przez most hosta:
js
ogmaSDK.ready(function(sdk) {
// sdk is the live SDK - safe to call any method here
sdk.log.info("frontend ready");
});Wszystkie metody SDK zwracają obiekty 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" });Wymaga uprawnienia read_http_history (przyznawane automatycznie; zgoda użytkownika nie jest potrzebna).
sdk.findings
js
var page = await sdk.findings.list({ limit: 20, offset: 0 });Wymaga uprawnienia read_findings (przyznawane automatycznie).
sdk.scope
js
var scope = await sdk.scope.getActive();sdk.projects
js
var project = await sdk.projects.getCurrent();sdk.backend — RPC backendu
Wywołuj funkcje zarejestrowane na backendzie przez 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 korzysta wewnętrznie z pętli odpytywania co 2 sekundy. Zakończ nasłuchiwanie, wywołując zwróconą funkcję anulowania subskrypcji:
js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();sdk.navigation
js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });Rejestruje stronę nawigacji. Obecnie host potwierdza wywołanie. Pełna integracja z routerem jest w trakcie prac.
sdk.sidebar
js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });Rejestruje pozycję paska bocznego. Obecnie działa lokalnie w panelu interfejsu wtyczki — podłączenie do globalnego paska bocznego jest w trakcie prac.
sdk.commands
js
await sdk.commands.register("my-plugin:scan", {
name: "Scan with My Plugin",
handler: function(context) { /* ... */ }
});Host potwierdza wywołanie. Integracja z paletą poleceń jest w trakcie prac.
sdk.menu
js
await sdk.menu.registerItem({
type: "Request",
commandId: "my-plugin:scan",
leadingIcon: "shield"
});Host potwierdza wywołanie. Dodawanie pozycji do menu kontekstowego jest w trakcie prac.
sdk.window
js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });Wyświetla krótkie powiadomienie w panelu wtyczki. Warianty: 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.registerItemUprawnienia
Deklaruj uprawnienia w manifest.json:
json
{
"permissions": ["send_requests", "write_findings"]
}Uprawnienia przyznawane automatycznie (bez zgody użytkownika)
Są zawsze przyznawane każdej zainstalowanej wtyczce:
| Uprawnienie | Na co pozwala |
|---|---|
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 |
Chronione uprawnienia (wymagające zgody użytkownika)
Muszą być zadeklarowane w manifeście i jawnie przyznane przez użytkownika na karcie Uprawnienia:
| Uprawnienie | Na co pozwala |
|---|---|
send_requests | sdk.requests.send — wykonywanie wychodzących żądań HTTP |
write_findings | sdk.findings.create, sdk.findings.update |
Przy włączaniu wtyczki deklarującej chronione uprawnienia użytkownik widzi prośbę o zgodę. Może również w dowolnym momencie przyznać lub cofnąć uprawnienia na karcie Uprawnienia.
Struktura pakietu wtyczki
Pakiet wtyczki można zainstalować z lokalnego katalogu, a podczas pracy w przeglądarce także z eksportów .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.)Wymagania dotyczące skryptu backendu
- Musi być pojedynczym, samowystarczalnym plikiem JS.
require()i dynamiczneimport()nie są obsługiwane.- Musi eksportować funkcję
init(sdk)(lub definiować ją globalnie). - Podzbiór ES2020 obsługiwany przez QuickJS:
async/await,Promise,Map,Set,Symbol,Proxy,Date,RegExp,JSON. Bezfetchi bezBuffer. - Obsługiwane importy statyczne są rozwiązywane podczas wstępnego przetwarzania wtyczki:
@ogma/sdk,crypto,fs,path. - Maksymalny rozmiar pliku: 256 KB.
Wymagania dotyczące skryptu frontendu
- Działa w izolowanej ramce iframe. Dozwolone jest
connect-src: 'self', aby wtyczka mogła wysyłać POST do/plugins/{id}/api/*i odpytywać/plugins/{id}/events/poll. - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - Piaskownica iframe nie ma
allow-same-origin— wtyczka nie może uzyskać dostępu do nadrzędnego DOM Ogma ani ciasteczek. - Używaj
ogmaSDK.ready(cb), aby uzyskać dostęp do SDK; nie wywołuj metod SDK, zanim zostanie wywołana funkcja zwrotna.
Budowanie wtyczki dla Ogma
Ponieważ backend musi być pojedynczym plikiem JS z połączonym kodem, przed instalacją musisz połączyć źródła TypeScript lub modułów ES w jeden plik.
Zalecany zestaw narzędzi:
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/frontendJeśli korzystasz z narzędzi programistycznych Caido (@caido-community/dev), uruchom caido-dev build, a następnie skopiuj wynik do struktury zgodnej z Ogma, z manifest.json w katalogu głównym.
Instalowanie wtyczki
- Otwórz Wtyczki na lewym pasku bocznym.
- Kliknij Zainstaluj (u góry karty Zainstalowane).
- W aplikacji desktopowej kliknij Przeglądaj, aby otworzyć natywne okno wyboru folderu. W przeglądarce wpisz pełną ścieżkę do katalogu wtyczki po stronie serwera.
- Kliknij Sprawdź poprawność, aby sprawdzić manifest i wykaz plików.
- Kliknij Zainstaluj, jeśli sprawdzenie zakończy się pomyślnie.
- Wybierz wtyczkę na liście i kliknij Włącz.
- Jeśli wtyczka deklaruje chronione uprawnienia, przejrzyj je i przyznaj na karcie Uprawnienia przed włączeniem.
Rozwiązywanie problemów
Inicjalizacja wtyczki kończy się niepowodzeniem bez komunikatu: Sprawdź kartę Dzienniki. Najczęstsze przyczyny:
- Wywołano
sdk.meta.path(), ale nie udało się utworzyć katalogu danych. - Nieobsłużony wyjątek w
init(). - Brakujące lub błędnie zapisane wywołanie
sdk.*.
Frontend jest pusty: Sprawdź konsolę przeglądarki pod kątem naruszeń CSP. Upewnij się, że skrypt frontendu wywołuje ogmaSDK.ready(cb) przed użyciem jakiejkolwiek metody SDK.
sdk.requests.send zgłasza Permission denied: Uprawnienie send_requests musi być zadeklarowane w manifeście ORAZ przyznane przez użytkownika na karcie Uprawnienia.
Funkcji sdk.api.register nie można wywołać z frontendu: Backend musi być włączony (sama instalacja nie wystarczy). Nazwa funkcji musi dokładnie odpowiadać temu, co frontend przekazuje do sdk.backend.call (wielkość liter ma znaczenie).
Ostrzeżenia o zgodności są wyświetlane jako błędy: Nie blokują działania, ale wskazują luki w obsłudze API. Zakres obsługi API znajdziesz w powyższych tabelach mapowania SDK.