İçeriğe geç

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

Derleme:

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

Yü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 ​

AlanZorunluTürNotlar
idevetstringYalnızca küçük harfler, rakamlar ve kısa çizgiler. En fazla 64 karakter. Yüklü eklentiler arasında benzersiz olmalıdır.
versionevetstringSemver: MAJOR.MINOR.PATCH
namehayırstringArayüzde gösterilen ad. Varsayılanı id değeridir.
descriptionhayırstringTek satırlık özet.
authorhayırobject{ "name": "...", "email": "...", "url": "..." }
homepagehayırstringKaynak deposunun veya belgelerin URL'si.
pluginsevetarrayBir veya daha fazla eklenti bileşeni kaydı (aşağıya bakın).
permissionshayırarrayGerekli 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" }
}
AlanZorunluNotlar
kindevet"backend" veya "frontend"
idevetManifest içinde benzersizdir. Küçük harfler, kısa çizgiler.
entrypointevetJS giriş dosyasının göreli yolu.
stylehayırEklenti iframe'inde yüklenen CSS dosyası.
assetshayır/plugins/{id}/assets/ altında sunulan statik varlıkların dizini.
backend.idhayırsdk.backend.* RPC için bir frontend bileşenini backend bileşenine bağlar.
runtimehayı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 plugin

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

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

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

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_requestssdk.requests.send — dışarıya HTTP istekleri gönderme
write_findingssdk.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 dinamik import() 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. fetch ve Buffer yoktur.
  • 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/poll adresini sorgulayabilmesi için connect-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-origin yoktur; 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/frontend

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

  1. Sol kenar çubuğunda Eklentiler bölümünü açın.
  2. Yüklü sekmesinin üst kısmındaki Yükle düğmesine tıklayın.
  3. 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.
  4. Manifesti ve dosya envanterini kontrol etmek için Doğrula düğmesine tıklayın.
  5. Doğrulama başarılıysa Yükle düğmesine tıklayın.
  6. Listeden eklentiyi seçin ve Etkinleştir düğmesine tıklayın.
  7. 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.

Tescilli yazılım. Tüm hakları saklıdır.