Ogma eklenti sistemi
Ogma eklentileri, araca özel backend mantığı, frontend arayüz panelleri ve iş akışı adımları ekler. Eklentiler diskteki bir dizinden yerel olarak yüklenir, proje bazında etkinleştirilir ve korumalı bir ortamda çalışır.
Bu belge, eklenti yazarları için temel başvuru kaynağıdır.
En hızlı başlangıç için Eklenti hızlı başlangıç kılavuzunu kullanın.
Hızlı başlangıç
En küçük backend eklentisi
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; bir paketleyici kullanırken import ifadeleri kullanılabilir):
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());
}
});
}Bu, yalnızca backend içeren çalışan bir eklenti için yeterlidir. Kodu backend/script.js içinde tek dosya olarak tutun ve manifest.json içinde doğrudan bu dosyayı gösterin.
Bir dakikalık derleme akışı (TypeScript kaynağı)
TypeScript yazıyorsanız şu yapıyı kullanın:
text
my-plugin/
manifest.json
backend/
src/index.tsDerleme:
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.jsYükleme: Eklentiler > Yükle bölümünde my-plugin/ dizinini seçin. Ardından eklentiyi etkinleştirin.
Manifest başvurusu
manifest.json, paketin kök dizininde bulunur. Tüm alanlar büyük/küçük harfe duyarlıdır.
Üst düzey alanlar
| Alan | Zorunlu | Tür | Notlar |
|---|---|---|---|
id | evet | string | Yalnızca küçük harfler, rakamlar ve kısa çizgiler. En fazla 64 karakter. Yüklü eklentiler arasında benzersiz olmalıdır. |
version | evet | string | Semver: MAJOR.MINOR.PATCH |
name | hayır | string | Arayüzde gösterilen ad. Varsayılanı id değeridir. |
description | hayır | string | Tek satırlık özet. |
author | hayır | object | { "name": "...", "email": "...", "url": "..." } |
homepage | hayır | string | Kaynak deposunun veya belgelerin URL'si. |
plugins | evet | array | Bir veya daha fazla eklenti bileşeni kaydı (aşağıya bakın). |
permissions | hayır | array | Gerekli izin adlarının listesi (İzinler bölümüne bakın). |
Eklenti bileşeni kaydı
plugins dizisindeki her nesne bir bileşeni tanımlar.
Backend bileşeni:
json
{
"kind": "backend",
"id": "my-plugin-backend",
"entrypoint": "backend/script.js",
"runtime": "javascript",
"assets": "backend/assets"
}Frontend bileşeni:
json
{
"kind": "frontend",
"id": "my-plugin-frontend",
"entrypoint": "frontend/script.js",
"style": "frontend/style.css",
"assets": "frontend/assets",
"backend": { "id": "my-plugin-backend" }
}| Alan | Zorunlu | Notlar |
|---|---|---|
kind | evet | "backend" veya "frontend" |
id | evet | Manifest içinde benzersizdir. Küçük harfler, kısa çizgiler. |
entrypoint | evet | JS giriş dosyasının göreli yolu. |
style | hayır | Eklenti iframe'inde yüklenen CSS dosyası. |
assets | hayır | /plugins/{id}/assets/ altında sunulan statik varlıkların dizini. |
backend.id | hayır | sdk.backend.* RPC için bir frontend bileşenini backend bileşenine bağlar. |
runtime | hayır (yalnızca backend) | "javascript" (varsayılan ve desteklenen tek değer). |
Backend eklenti API'si (sdk)
Backend sdk nesnesi init(sdk) işlevinize iletilir. async olarak işaretlenmedikçe tüm metotlar eşzamanlıdır.
sdk.console
js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")Eklentinin günlük tamponuna yazar (Günlükler sekmesinde görünür). En fazla 500 kayıt tutulur. Her mesaj 1 KB'de kesilir.
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(), eklentinin yazılabilir özel veri dizinini gösterir; örneğin ~/.local/share/ogma/plugins/my-plugin/data. Dizin otomatik oluşturulur ve eklenti verilerini yeniden başlatmalar arasında saklamak için kullanılabilir.
Durum ve dosya yardımcıları için sdk.storage, sdk.path ve sdk.fs de kullanılabilir.
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, eklenti kimliğiyle sınırlıdır ve eklenti yeniden başlatmaları arasında kalıcıdır.
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, sdk.meta.path() altındaki dosyalarla sınırlıdır.
read ve write, readFile ve writeFile için uyumluluk takma adları olarak korunur. Üzerine yazmak istemediğiniz bir dosyayı oluşturmadan önce exists veya existsSync kullanın. Bu API'ler eklenti çalışma ortamında eşzamanlı çalışır; tam Node.js fs modülü değildir. Eklentinin dosya erişimi plugin_storage iznini gerektirir ve eklentinin özel veri dizini içinde kalır. İş akışı JavaScript'inin dosya sistemi bağlamı farklıdır; İş akışında dosya erişimi bölümüne bakın.
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
Ogma olayları için geri çağırma işlevleri kaydedin. Tüm geri çağırma işlevleri QuickJS korumalı alanında eşzamanlı olarak çağrılır.
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, send_requests izninin manifestte bildirilmesini ve kullanıcı tarafından verilmesini gerektirir. İzinler bölümüne bakı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");sdk.findings.create hız sınırları: dakikada 10, eklenti oturumu başına 500, olay geri çağırması başına 3.
sdk.api
Frontend'in sdk.backend.* üzerinden çağırabileceği backend RPC işlevleri kaydedin:
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" });İşleyici, frontend'den iletilen argümanları alır (ek bir sdk argümanı enjekte edilmez). Dönüş değerleri JSON olarak serileştirilip çağırana geri gönderilir.
Frontend bunları sdk.backend.getScans(scanId) üzerinden çağırır; Frontend eklenti API'si bölümüne bakın.
sdk.api.send, olayları eklenti başına bir kuyruğa ekler (en fazla 200 kayıt). Frontend'ler bu kuyruğu sdk.backend.onEvent üzerinden sorgular.
sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace
Bunlar salt okunur sorgu ad alanlarıdır. Tam metot imzaları için backend SDK başvurusuna bakın.
İstek sınıfları
RequestSpecRaw — durdurulmuş bir isteği temsil eder. Bunu onInterceptRequest içinde alırsınız.
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 — salt okunur, yakalanmış bir istek (sdk.requests.get tarafından alınır).
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 — salt okunur, yakalanmış bir yanıt.
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 eklenti API'si (sdk)
Frontend eklenti kodu, /plugins/{id}/ui adresinden yüklenen korumalı bir iframe içinde çalışır. Iframe, çağrıları backend'e ileten Ogma ana uygulamasıyla iletişim kurmak için postMessage kullanır.
SDK'ya window.ogmaSDK üzerinden erişilir. Ana uygulama köprüsü kurulduğunda canlı SDK'yı almak için ogmaSDK.ready(cb) çağırın:
js
ogmaSDK.ready(function(sdk) {
// sdk is the live SDK - safe to call any method here
sdk.log.info("frontend ready");
});Tüm SDK metotları Promise döndürür.
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" });read_http_history iznini gerektirir (otomatik verilir; kullanıcı onayı gerekmez).
sdk.findings
js
var page = await sdk.findings.list({ limit: 20, offset: 0 });read_findings iznini gerektirir (otomatik verilir).
sdk.scope
js
var scope = await sdk.scope.getActive();sdk.projects
js
var project = await sdk.projects.getCurrent();sdk.backend — backend RPC
Backend'de sdk.api.register ile kaydedilen işlevleri çağırın:
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, dahili olarak 2 saniyelik bir sorgulama döngüsü kullanır. Döndürülen abonelikten çıkma işlevini çağırarak dinlemeyi durdurun:
js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();sdk.navigation
js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });Bir gezinme sayfası kaydeder. Şu anda ana uygulama tarafından alındığı onaylanır. Tam yönlendirici entegrasyonu geliştirme aşamasındadır.
sdk.sidebar
js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });Bir kenar çubuğu öğesi kaydeder. Şu anda eklenti arayüz paneliyle sınırlıdır; global kenar çubuğu bağlantısı geliştirme aşamasındadır.
sdk.commands
js
await sdk.commands.register("my-plugin:scan", {
name: "Scan with My Plugin",
handler: function(context) { /* ... */ }
});Ana uygulama tarafından alındığı onaylanır. Komut paleti entegrasyonu geliştirme aşamasındadır.
sdk.menu
js
await sdk.menu.registerItem({
type: "Request",
commandId: "my-plugin:scan",
leadingIcon: "shield"
});Ana uygulama tarafından alındığı onaylanır. Bağlam menüsüne ekleme geliştirme aşamasındadır.
sdk.window
js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });Eklenti panelinde geçici bir bildirim gösterir. Türler: 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İzinler
İzinleri manifest.json içinde bildirin:
json
{
"permissions": ["send_requests", "write_findings"]
}Otomatik verilen izinler (kullanıcı onayı gerekmez)
Bunlar her yüklü eklentiye her zaman verilir:
| İzin | İzin verdiği işlemler |
|---|---|
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 |
Korumalı izinler (kullanıcı onayı gerektirir)
Bunlar manifestte bildirilmeli ve İzinler sekmesinden kullanıcı tarafından açıkça verilmelidir:
| İzin | İzin verdiği işlemler |
|---|---|
send_requests | sdk.requests.send — dışarıya HTTP istekleri gönderme |
write_findings | sdk.findings.create, sdk.findings.update |
Korumalı izinler bildiren bir eklenti etkinleştirilirken kullanıcıya bir onay istemi gösterilir. Kullanıcı İzinler sekmesinden istediği zaman izin verebilir veya izinleri geri alabilir.
Eklenti paket yapısı
Bir eklenti paketi yerel bir dizinden ve tarayıcı akışlarında .zip dışa aktarımlarından da yüklenebilir.
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.)Backend betiği gereksinimleri
- Tek, kendi kendine yeterli bir JS dosyası olmalıdır.
require()ve dinamikimport()desteklenmez.- Bir
init(sdk)işlevini dışa aktarmalıdır (veya global olarak tanımlamalıdır). - QuickJS tarafından desteklenen ES2020 alt kümesi:
async/await,Promise,Map,Set,Symbol,Proxy,Date,RegExp,JSON.fetchveBufferyoktur. - Desteklenen statik import ifadeleri eklenti ön işlemesiyle çözümlenir:
@ogma/sdk,crypto,fs,path. - En büyük dosya boyutu: 256 KB.
Frontend betiği gereksinimleri
- Korumalı bir iframe içinde çalışır. Eklentinin
/plugins/{id}/api/*adresine POST isteği gönderebilmesi ve/plugins/{id}/events/polladresini sorgulayabilmesi içinconnect-src: 'self'izni vardır. - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'. - Iframe korumalı alanında
allow-same-originyoktur; eklenti Ogma'nın üst DOM'una veya çerezlerine erişemez. - SDK'ya erişmek için
ogmaSDK.ready(cb)kullanın; geri çağırma çalışmadan SDK metotlarını çağırmayın.
Ogma için eklenti derleme
Backend tek bir paketlenmiş JS dosyası olmak zorunda olduğundan TypeScript/ES modülü kaynak kodunu yüklemeden önce paketlemelisiniz.
Önerilen araç zinciri:
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/frontendCaido geliştirme araç zincirini (@caido-community/dev) kullanıyorsanız caido-dev build çalıştırın ve ardından çıktıyı, kökünde manifest.json bulunan Ogma uyumlu bir düzene kopyalayın.
Eklenti yükleme
- Sol kenar çubuğunda Eklentiler bölümünü açın.
- Yüklü sekmesinin üst kısmındaki Yükle düğmesine tıklayın.
- Masaüstü uygulamasında yerel klasör seçiciyi açmak için Göz at düğmesine tıklayın. Tarayıcıda, eklenti dizininin sunucu tarafındaki tam yolunu yazın.
- Manifesti ve dosya envanterini kontrol etmek için Doğrula düğmesine tıklayın.
- Doğrulama başarılıysa Yükle düğmesine tıklayın.
- Listeden eklentiyi seçin ve Etkinleştir düğmesine tıklayın.
- Eklenti korumalı izinler bildiriyorsa etkinleştirmeden önce İzinler sekmesinden bunları inceleyip verin.
Sorun giderme
Eklentinin başlatılması sessizce başarısız oluyor: Günlükler sekmesini kontrol edin. En yaygın nedenler:
sdk.meta.path()çağrılmıştır ancak veri dizini oluşturulamamıştır.init()içinde işlenmemiş bir istisna vardır.- Bir
sdk.*çağrısı eksiktir veya yanlış yazılmıştır.
Frontend boş görünüyor: Tarayıcı konsolunda CSP ihlallerini kontrol edin. Frontend betiğinizin herhangi bir SDK metoduna erişmeden önce ogmaSDK.ready(cb) çağırdığından emin olun.
sdk.requests.send, Permission denied hatası veriyor: send_requests izni manifestte bildirilmeli VE İzinler sekmesinde kullanıcı tarafından verilmelidir.
sdk.api.register işlevleri frontend'den çağrılamıyor: Backend yalnızca yüklenmiş değil, etkinleştirilmiş olmalıdır. İşlev adı, frontend'in sdk.backend.call çağrısına ilettiği adla tam olarak eşleşmelidir (büyük/küçük harfe duyarlı).
Uyumluluk uyarıları hata olarak görünüyor: Bunlar çalışmayı engellemez ancak API kapsamındaki eksikleri gösterir. API kapsamı için yukarıdaki SDK eşleme tablolarına bakın.