Encrypted Secrets & API Keys #

Custom Functions and Custom Listing Pages can access sensitive credentials (such as API keys, OAuth tokens, and webhook secrets) securely through context.secrets.


1. Overview & Architecture #

  • At-Rest Encryption: All secrets are encrypted using AES-256-GCM before being persisted in the database. Raw plaintext values are never returned to the browser in API responses.
  • Nested Dot-Notation: Organize credentials into logical namespaces (e.g. stripe.apiKey, stripe.webhookSecret, sendgrid.token).
  • Scoped Permissions: Secrets can be marked Global (available to every custom function and listing page in the workspace) or Scoped (restricted to specific selected functions or listing pages).
  • Ambient TypeScript Intellisense: Monaco editor automatically queries your workspace's configured secrets to generate exact ambient typings for context.secrets.

2. Accessing Secrets in Code #

In Custom Functions #

typescript
/**
 * Custom Function Triggered on Lead Action
 */
export async function run(context: Context, dolfin: DolfinApi): Promise<void> {
  const { lead, secrets } = context;

  // Fully typed with autocomplete:
  const stripeKey = secrets.stripe.apiKey;

  const response = await dolfin.http.post("https://api.stripe.com/v1/customers", {
    headers: {
      Authorization: `Bearer ${stripeKey}`,
    },
    body: {
      email: lead.email,
      name: lead.name,
    },
  });

  console.log("Stripe customer created with status:", response.status);
}

In Custom Listing Fetchers #

typescript
/**
 * Custom Listing Page Fetcher
 */
export async function run(
  context: CustomListContext,
  dolfin: DolfinSdk
): Promise<CustomListResponse> {
  const { filter, pagination, secrets } = context;

  const res = await dolfin.http.get("https://api.external-erp.com/orders", {
    headers: {
      "X-API-KEY": secrets.erp.apiKey,
    },
    query: {
      page: pagination.page,
      limit: pagination.limit,
      status: filter.status,
    },
  });

  return {
    items: res.body?.orders || [],
    total: res.body?.totalCount || 0,
  };
}

In Dynamic Filter Options #

typescript
/**
 * Filter definitions with secure remote option resolution
 */
export const filters = [
  {
    key: "warehouseId",
    label: "Warehouse Location",
    type: "select",
    options: async (parentVal, dolfin, context) => {
      const apiKey = context?.secrets?.erp?.apiKey;
      const res = await dolfin.http.get("https://api.external-erp.com/warehouses", {
        headers: { "X-API-KEY": apiKey },
      });
      return (res.body?.warehouses || []).map((w: any) => ({
        label: w.name,
        value: w.id,
      }));
    },
  },
] as const;

3. Automated Log & Error Redaction #

To prevent sensitive tokens from accidentally leaking into debugging traces:

  1. Console Redaction: Any calls to console.log(...), console.warn(...), or console.error(...) containing secret strings or their common encoded variants (URL-encoded, JSON-escaped, base64) are sanitized with [REDACTED_SECRET] before transmission to logs.
  2. Error Messages: If an external API or script throws an error containing the secret value in the message or stack trace, the message is automatically redacted before being logged or surfaced.
  3. Execution Payloads: Secrets are injected into the V8 sandbox separately from contextPayload and are never saved in historical audit logs.
ℹ️ Note

Automated redaction prevents accidental credential disclosure in logs and error responses. Code authors with edit access to a resource should follow least-privilege scoping by assigning secrets only to the functions that require them.