Skip to content

Plugin SDK 與沙箱

Triggerfish 外掛讓您可以用自訂程式碼擴展代理,與外部系統互動——CRM 查詢、資料庫操作、API 整合、多步驟工作流程——同時在雙層沙箱中執行,防止程式碼做任何未被明確允許的事情。

執行環境

外掛在 Deno + Pyodide(WASM)上執行。不需要 Docker、容器或 Triggerfish 安裝以外的先決條件。

  • TypeScript 外掛直接在 Deno 沙箱中執行
  • Python 外掛在 Pyodide(編譯為 WebAssembly 的 Python 直譯器)中執行,而 Pyodide 本身在 Deno 沙箱中執行
外掛沙箱:Deno 沙箱包裝 WASM 沙箱,外掛程式碼在最內層執行

這種雙層沙箱架構意味著即使外掛包含惡意程式碼,它也無法存取檔案系統、進行未宣告的網路呼叫或逃逸到主機系統。

外掛能做什麼

外掛在嚴格邊界內有靈活的內部空間。在沙箱內,您的外掛可以:

  • 對目標系統執行完整的 CRUD 操作(使用使用者的權限)
  • 執行複雜查詢和資料轉換
  • 編排多步驟工作流程
  • 處理和分析資料
  • 跨呼叫維護外掛狀態
  • 呼叫任何已宣告的外部 API 端點

外掛不能做什麼

約束如何執行
存取未宣告的網路端點沙箱封鎖所有不在允許清單上的網路呼叫
發送無分類標籤的資料SDK 拒絕未分類的資料
讀取資料而不進行汙染傳播SDK 在資料被存取時自動汙染 session
在 Triggerfish 外部持久化資料沙箱內無檔案系統存取
透過旁路通道洩漏資料執行資源限制,無原始 socket 存取
使用系統憑證SDK 封鎖 get_system_credential();僅限使用者憑證

安全 sdk.get_system_credential()被封鎖的設計。外掛必須始終透過 sdk.get_user_credential() 使用委派的使用者憑證。這確保代理只能存取使用者能存取的內容——永遠不會更多。 :::

Plugin SDK 方法

SDK 為外掛提供了與外部系統和 Triggerfish 平台互動的受控介面。

憑證存取

typescript
// 取得使用者對服務的委派憑證
const credential = await sdk.get_user_credential("salesforce");

// 檢查使用者是否已連接某個服務
const connected = await sdk.has_user_connection("notion");

sdk.get_user_credential(service) 取得使用者對指定服務的 OAuth token 或 API 金鑰。如果使用者尚未連接該服務,呼叫會回傳 null,外掛應該優雅地處理這種情況。

資料操作

typescript
// 使用使用者的權限查詢外部系統
const results = await sdk.query_as_user("salesforce", {
  query: "SELECT Name, Amount FROM Opportunity WHERE StageName = 'Closed Won'",
});

// 將資料發送回代理——分類標籤是必要的
sdk.emitData({
  classification: "CONFIDENTIAL",
  payload: results,
  source: "salesforce",
});

每次呼叫 sdk.emitData() 都需要一個 classification 標籤。如果您省略它,SDK 會拒絕該呼叫。這確保所有從外掛流向代理上下文的資料都被正確分類。 :::

連線檢查

typescript
// 檢查使用者是否與某個服務有活躍連線
if (await sdk.has_user_connection("github")) {
  const repos = await sdk.query_as_user("github", {
    endpoint: "/user/repos",
  });
  sdk.emitData({
    classification: "INTERNAL",
    payload: repos,
    source: "github",
  });
}

外掛生命週期

每個外掛都遵循一個確保啟用前進行安全審查的生命週期。

1. 外掛建立(由使用者、代理或第三方)
       |
       v
2. 使用 Plugin SDK 建構外掛
   - 必須實作必要介面
   - 必須宣告端點和能力
   - 必須通過驗證
       |
       v
3. 外掛進入 UNTRUSTED 狀態
   - 代理無法使用它
   - 擁有者/管理員收到通知:「待分類」
       |
       v
4. 擁有者(個人)或管理員(企業)審查:
   - 此外掛存取什麼資料?
   - 它可以採取什麼動作?
   - 指定分類等級
       |
       v
5. 外掛在指定分類下啟用
   - 代理可以在策略約束內呼叫
   - 所有呼叫都通過策略鉤子

在個人方案中,您就是擁有者——您審查和分類自己的外掛。在企業方案中,管理員管理外掛登錄並指定分類等級。 :::

資料庫連接

原生資料庫驅動程式(psycopg2、mysqlclient 等)在 WASM 沙箱中不起作用。外掛改為透過基於 HTTP 的 API 連接到資料庫。

資料庫基於 HTTP 的選項
PostgreSQLPostgREST、Supabase SDK、Neon API
MySQLPlanetScale API
MongoDBAtlas Data API
SnowflakeREST API
BigQueryREST API
DynamoDBAWS SDK(HTTP)

這是一個安全優勢,而非限制。所有資料庫存取都通過可檢查、可控制的 HTTP 請求流動,沙箱可以執行,稽核系統可以記錄。

撰寫 TypeScript 外掛

一個查詢 REST API 的最小 TypeScript 外掛:

typescript
import type { PluginResult, PluginSdk } from "triggerfish/plugin";

export async function execute(sdk: PluginSdk): Promise<PluginResult> {
  // 檢查使用者是否已連接該服務
  if (!await sdk.has_user_connection("acme-api")) {
    return {
      success: false,
      error: "User has not connected Acme API. Please connect it first.",
    };
  }

  // 使用使用者的憑證查詢
  const data = await sdk.query_as_user("acme-api", {
    endpoint: "/api/v1/tasks",
    method: "GET",
  });

  // 將分類後的資料發送回代理
  sdk.emitData({
    classification: "INTERNAL",
    payload: data,
    source: "acme-api",
  });

  return { success: true };
}

撰寫 Python 外掛

一個最小的 Python 外掛:

python
async def execute(sdk):
    # 檢查連線
    if not await sdk.has_user_connection("analytics-db"):
        return {"success": False, "error": "Analytics DB not connected"}

    # 使用使用者的憑證查詢
    results = await sdk.query_as_user("analytics-db", {
        "endpoint": "/rest/v1/metrics",
        "method": "GET",
        "params": {"period": "7d"}
    })

    # 帶有分類的資料發送
    sdk.emit_data({
        "classification": "CONFIDENTIAL",
        "payload": results,
        "source": "analytics-db"
    })

    return {"success": True}

Python 外掛在 Pyodide WASM 執行環境中執行。標準函式庫模組可用,但原生 C 擴充不可用。使用基於 HTTP 的 API 進行外部連接。

外掛安全摘要

  • 外掛在雙層沙箱(Deno + WASM)中以嚴格隔離執行
  • 所有網路存取必須在外掛清單中宣告
  • 所有發送的資料必須帶有分類標籤
  • 系統憑證被封鎖——只有使用者委派的憑證可用
  • 每個外掛以 UNTRUSTED 進入系統,使用前必須分類
  • 所有外掛呼叫都通過策略鉤子並被完整稽核