Ogma プラグインシステム
Ogma プラグインは、独自のバックエンドロジック、フロントエンド UI パネル、ワークフローのステップを追加してツールを拡張します。プラグインはディスク上のディレクトリからローカルにインストールし、プロジェクトごとに有効化して、サンドボックス環境で実行します。
このドキュメントは、プラグイン開発者向けの主要なリファレンスです。
最短で始めるには、プラグインクイックスタートをご覧ください。
クイックスタート
最小構成のバックエンドプラグイン
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。バンドラーを使用する場合は import を利用できます):
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());
}
});
}これだけでバックエンドのみのプラグインが動作します。backend/script.js を単一ファイルのままにして、manifest.json から直接参照してください。
一分で行うビルド手順(TypeScript ソース)
TypeScript で開発する場合は、次の構成を使用します。
text
my-plugin/
manifest.json
backend/
src/index.tsビルド:
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インストール:プラグイン > インストールで my-plugin/ ディレクトリを選択し、プラグインを有効にします。
マニフェストリファレンス
manifest.json はパッケージのルートディレクトリに配置します。すべてのフィールドで大文字と小文字が区別されます。
トップレベルのフィールド
| フィールド | 必須 | 型 | 説明 |
|---|---|---|---|
id | はい | string | 小文字の英字、数字、ハイフンのみ。最大 64 文字。インストール済みプラグインの間で一意である必要があります。 |
version | はい | string | セマンティックバージョニング:MAJOR.MINOR.PATCH |
name | いいえ | string | UI に表示する名前。既定値は id です。 |
description | いいえ | string | 一行の概要。 |
author | いいえ | object | { "name": "...", "email": "...", "url": "..." } |
homepage | いいえ | string | ソースリポジトリまたはドキュメントの URL。 |
plugins | はい | array | 一つ以上のプラグインコンポーネントのエントリ(下記参照)。 |
permissions | いいえ | array | 必要な権限名の一覧(権限参照)。 |
プラグインコンポーネントのエントリ
plugins 配列の各オブジェクトは、一つのコンポーネントを定義します。
バックエンドコンポーネント:
json
{
"kind": "backend",
"id": "my-plugin-backend",
"entrypoint": "backend/script.js",
"runtime": "javascript",
"assets": "backend/assets"
}フロントエンドコンポーネント:
json
{
"kind": "frontend",
"id": "my-plugin-frontend",
"entrypoint": "frontend/script.js",
"style": "frontend/style.css",
"assets": "frontend/assets",
"backend": { "id": "my-plugin-backend" }
}| フィールド | 必須 | 説明 |
|---|---|---|
kind | はい | "backend" または "frontend" |
id | はい | マニフェスト内で一意である必要があります。小文字とハイフンを使用します。 |
entrypoint | はい | JS エントリファイルへの相対パス。 |
style | いいえ | プラグインの iframe に読み込む CSS ファイル。 |
assets | いいえ | /plugins/{id}/assets/ 以下で配信する静的アセットのディレクトリ。 |
backend.id | いいえ | sdk.backend.* RPC を使用するため、フロントエンドコンポーネントをバックエンドコンポーネントに関連付けます。 |
runtime | いいえ(バックエンドのみ) | "javascript"(既定値であり、唯一サポートされる値)。 |
バックエンドプラグイン API(sdk)
バックエンドの sdk オブジェクトは init(sdk) 関数に渡されます。async と明記されているものを除き、すべてのメソッドは同期的に動作します。
sdk.console
js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")プラグインのログバッファーに書き込みます(ログタブで確認できます)。最大 500 件を保持し、各メッセージは 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() は、プラグイン専用の書き込み可能なデータディレクトリを示します。例:~/.local/share/ogma/plugins/my-plugin/data。ディレクトリは自動作成され、再起動をまたいでプラグインのデータを保存できます。
状態管理やファイル操作の補助には、sdk.storage、sdk.path、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 のスコープはプラグイン ID ごとに分離され、データはプラグインの再起動後も保持されます。
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() 以下のファイルのみです。
read と write は引き続き、それぞれ readFile と writeFile の互換エイリアスとして利用できます。上書きしたくないファイルを作成する前に、exists または existsSync で確認してください。これらの API はプラグインランタイムで同期的に動作し、Node.js の完全な fs モジュールではありません。プラグインからのファイルアクセスには plugin_storage 権限が必要で、プラグイン専用のデータディレクトリ内に制限されます。ワークフローの JavaScript ではファイルシステムのコンテキストが異なります。ワークフローのファイルアクセスをご覧ください。
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 のイベントに対するコールバックを登録します。すべてのコールバックは 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 を使用するには、マニフェストで send_requests 権限を宣言し、ユーザーから許可を得る必要があります。権限をご覧ください。
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 の回数制限:毎分 10 回、プラグインセッションあたり 500 回、イベントコールバックあたり 3 回。
sdk.api
フロントエンドから sdk.backend.* 経由で呼び出せるバックエンド RPC 関数を登録します。
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" });ハンドラーはフロントエンドから渡された引数を受け取ります(追加の sdk 引数は注入されません)。戻り値は JSON にシリアライズされ、呼び出し元に返されます。
フロントエンドは sdk.backend.getScans(scanId) でこれらを呼び出します。フロントエンドプラグイン APIをご覧ください。
sdk.api.send はプラグインごとのキューにイベントを追加します(最大 200 件)。フロントエンドは sdk.backend.onEvent でこのキューをポーリングします。
sdk.replay, sdk.projects, sdk.scope, sdk.workflows, sdk.matchReplace
これらは読み取り専用のクエリ名前空間です。メソッドの完全なシグネチャはバックエンド SDK リファレンスをご覧ください。
リクエストクラス
RequestSpecRaw:インターセプトされたリクエストを表します。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:キャプチャされた読み取り専用のリクエスト(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:キャプチャされた読み取り専用のレスポンス。
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(sdk)
フロントエンドプラグインのコードは、/plugins/{id}/ui から読み込まれるサンドボックス化された iframe で実行されます。iframe は postMessage で Ogma ホストと通信し、ホストがバックエンドへの呼び出しを仲介します。
SDK は window.ogmaSDK から利用できます。ogmaSDK.ready(cb) を呼び出すと、ホストブリッジが確立された後に、使用可能な SDK を受け取れます。
js
ogmaSDK.ready(function(sdk) {
// sdk is the live SDK - safe to call any method here
sdk.log.info("frontend ready");
});すべての SDK メソッドは 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" });read_http_history 権限が必要です(自動的に付与され、ユーザーの承認は不要です)。
sdk.findings
js
var page = await sdk.findings.list({ limit: 20, offset: 0 });read_findings 権限が必要です(自動付与)。
sdk.scope
js
var scope = await sdk.scope.getActive();sdk.projects
js
var project = await sdk.projects.getCurrent();sdk.backend:バックエンド RPC
バックエンドで 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 は内部で 2 秒間隔のポーリングループを使用します。返された購読解除関数を呼び出すと、監視を停止できます。
js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();sdk.navigation
js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });ナビゲーションページを登録します。現在はホストが操作を受け付ける段階です。ルーターへの完全な統合は開発中です。
sdk.sidebar
js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });サイドバー項目を登録します。現在はプラグイン UI パネル内に限定されています。グローバルサイドバーのスロットとの接続は開発中です。
sdk.commands
js
await sdk.commands.register("my-plugin:scan", {
name: "Scan with My Plugin",
handler: function(context) { /* ... */ }
});ホストが操作を受け付けます。コマンドパレットへの統合は開発中です。
sdk.menu
js
await sdk.menu.registerItem({
type: "Request",
commandId: "my-plugin:scan",
leadingIcon: "shield"
});ホストが操作を受け付けます。コンテキストメニューへの追加は開発中です。
sdk.window
js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });プラグインパネルにトースト通知を表示します。種類: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権限
権限は manifest.json で宣言します。
json
{
"permissions": ["send_requests", "write_findings"]
}自動付与される権限(ユーザーの承認不要)
次の権限は、インストールされたすべてのプラグインに常に付与されます。
| 権限 | 許可される操作 |
|---|---|
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 |
保護された権限(ユーザーの承認が必要)
これらはマニフェストで宣言し、権限タブからユーザーが明示的に許可する必要があります。
| 権限 | 許可される操作 |
|---|---|
send_requests | sdk.requests.send:外部への HTTP リクエスト送信 |
write_findings | sdk.findings.create, sdk.findings.update |
保護された権限を宣言するプラグインを有効にすると、ユーザーに確認が表示されます。また、権限タブからいつでも権限を付与または取り消せます。
プラグインパッケージの構成
プラグインパッケージはローカルディレクトリからインストールできます。ブラウザーでの操作では、エクスポートされた .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.)バックエンドスクリプトの要件
- 単一の自己完結した JS ファイルである必要があります。
require()と動的なimport()はサポートされません。init(sdk)関数をエクスポートする必要があります(またはグローバル関数として定義します)。- QuickJS がサポートする ES2020 のサブセット:
async/await、Promise、Map、Set、Symbol、Proxy、Date、RegExp、JSON。fetchとBufferは利用できません。 - サポートされる静的 import はプラグインの前処理で解決されます:
@ogma/sdk、crypto、fs、path。 - ファイルサイズの上限:256 KB。
フロントエンドスクリプトの要件
- サンドボックス化された iframe 内で実行されます。
connect-src: 'self'が許可されているため、プラグインは/plugins/{id}/api/*に POST し、/plugins/{id}/events/pollをポーリングできます。 - CSP:
default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'。 - iframe のサンドボックスには
allow-same-originがないため、プラグインは Ogma の親 DOM や Cookie にアクセスできません。 - SDK にアクセスするには
ogmaSDK.ready(cb)を使用してください。コールバックが呼ばれる前に SDK メソッドを呼び出さないでください。
Ogma 向けプラグインのビルド
バックエンドは単一のバンドル済み JS ファイルである必要があるため、インストール前に TypeScript/ES モジュールのソースをバンドルしてください。
推奨ツールチェーン:
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 の開発ツールチェーン(@caido-community/dev)を使用する場合は、caido-dev build を実行し、ルートに manifest.json を配置した Ogma 互換の構成に出力をコピーしてください。
プラグインのインストール
- 左サイドバーのプラグインを開きます。
- インストールをクリックします(インストール済みタブの上部)。
- デスクトップアプリでは、参照をクリックして OS 標準のフォルダー選択画面を開きます。ブラウザーでは、サーバー側のプラグインディレクトリへのフルパスを入力します。
- 検証をクリックし、マニフェストとファイル一覧を確認します。
- 検証に合格したら、インストールをクリックします。
- 一覧でプラグインを選択し、有効にするをクリックします。
- プラグインが保護された権限を宣言している場合は、有効化する前に権限タブで内容を確認して許可してください。
トラブルシューティング
プラグインの初期化が通知なく失敗する: ログタブを確認してください。よくある原因は次のとおりです。
sdk.meta.path()を呼び出したが、データディレクトリを作成できなかった。init()内の未処理の例外。sdk.*呼び出しの欠落またはスペルミス。
フロントエンドが空白になる: ブラウザーコンソールで CSP 違反を確認してください。フロントエンドスクリプトが SDK メソッドにアクセスする前に ogmaSDK.ready(cb) を呼び出していることを確認します。
sdk.requests.send が Permission denied をスローする: マニフェストで send_requests 権限を宣言し、かつユーザーが権限タブで許可する必要があります。
sdk.api.register の関数をフロントエンドから呼び出せない: バックエンドはインストールだけでなく、有効化されている必要があります。関数名はフロントエンドが sdk.backend.call に渡す名前と完全に一致する必要があります(大文字と小文字を区別)。
互換性の警告がエラーとして表示される: 動作を妨げるものではありませんが、API の対応範囲に不足があることを示しています。対応状況は上記の SDK 対応表をご覧ください。