本文へ移動
cccskills
無料GitHub で公開日本語紹介

secrets

アプリが必要とするAPIキーやOAuth接続を登録し、設定画面や導入チェックリストに表示する方法を案内します。認証情報の保存先や読み出し方も扱います。

原文原文の説明を見る

Declaratively register API keys and service credentials a template needs so they appear on Settings › API keys and in the onboarding checklist. Use before adding any third-party credential or setup UI so API keys, OAuth connections, and scoped configuration use the correct shared primitive.

BuilderIO/agent-nativeスター 7,525(GitHub 7,525 + cccskills 0)

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • APIキーを設定画面に登録したいとき
  • 必須の認証情報を導入手順に出したいとき
  • OAuth接続を共通画面に表示したいとき
  • 実行時に認証情報を安全に読み出したいとき

日本語での紹介

できること

アプリが外部サービスを使うために必要なAPIキーや認証情報を登録し、設定画面や導入チェックリストに表示する方法を扱います。ユーザー単位またはワークスペース単位の保存範囲を選び、キーの入力・検証・テスト・削除や、OAuth接続の状態表示につなげます。実行時には登録した範囲に合った保管庫から値を読み出し、秘密の値をログやエージェントの文脈に出さずに利用します。

こんなときに便利

新しい外部サービス連携を追加するときや、独自の認証情報入力画面を作る前に、共通の設定・保存方法を確認したい場合に向いています。必須設定を導入手順に反映したいときや、既存の接続情報を別の場所に重複保存していないか確認するときにも使えます。

使い方の例

  • 「この連携に必要なAPIキーを設定画面と導入チェックリストに登録してください」
  • 「OAuth接続を共通の設定画面に表示し、実行時に安全に参照してください」

注意点

実際のキーやトークンをソースコード、テスト、例に書かないでください。OAuthの認可処理自体は専用のトークン管理機能を使います。プロセス内部だけで使う環境変数は対象外です。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md20.2 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Secrets Registry

Non-negotiable rule

Never hardcode credential values. Source, docs, tests, fixtures, prompts, seed data, and generated extension/app content may mention credential names such as OPENAI_API_KEY, but must not contain real API keys, tokens, webhook URLs, signing secrets, OAuth refresh tokens, or private Builder/customer data.

Never write real secrets to non-gitignored files. Put temporary secret material under the root .tmp/ or another explicitly gitignored path, then delete it when it is no longer needed.

Provider secret values are supplied at runtime through the encrypted app_secrets vault, saveCredential / resolveCredential, OAuth, or ${keys.NAME} substitution. Deployment configuration is reserved for deploy-level secrets and non-provider configuration. Examples must use obvious placeholders such as <OPENAI_API_KEY> or ${keys.SLACK_WEBHOOK}, not real-looking copied values.

Provider credentials and provider account identifiers are workspace data. Use standard workspace connections and org/workspace vault scopes; never put them in .env or deployment environment variables, and never add a provider-specific action or startup bootstrap just to write a credential for one organization.

Google OAuth triage

ObservationMeaningNext action
invalid_grant from a deliberately fake codeGoogle accepted the client pair and rejected only the codeDo not rotate credentials; check flow state and callback registration
invalid_clientGoogle rejected the client id/secret pairVerify the exact pair and deployment source before rotating
redirect_uri_mismatchThe client, host, callback path, and Google registration disagreeCompare that exact tuple in Google Cloud Console; publish a new deploy if site-scoped or build-time configuration changes

Do not reason about this from memory. The probe checks both contracts: the unqualified /health/google endpoint reports the sign-in contract, while /health/google?client=managed reports deployment-level managed OAuth. It asks Google directly whether each live (client_id, redirect_uri) pair is registered:

pnpm check:google-redirect-uris -- --env all

The sign-in health path calls checkGoogleSignInCredential(), which prefers the active Better Auth pair and otherwise uses resolveGoogleSignInCredentials() from packages/core/src/server/google-oauth-credentials.ts. Managed health calls checkGoogleManagedCredential(), which resolves ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"] through resolveSecretPair() in packages/core/src/server/credential-provider.ts. App provider handlers use resolveGoogleProviderCredentialCandidatesWithReader() with resolveSecret under request context. These are separate flows and can intentionally land on different Google clients, so a clean result for one says nothing about the other. Read both contracts before changing anything:

curl -s https://HOST/_agent-native/health/google | jq '{clientId,mismatchedPairs,credentialSource}'
curl -s "https://HOST/_agent-native/health/google?client=managed" | jq '{clientId,mismatchedPairs,credentialSource}'

Different clientId values across those two, or mismatchedPairs: true, is a divergence to understand, not damage to undo. Never repair it by rotating a secret: writing a fresh value into whichever namespace the failing flow does not read verifies clean and changes nothing. Do not collapse the namespaces without first confirming which flow uses which client; separate sign-in and managed clients on one host can be deliberate. For prebuilt Netlify deploys, uploaded Functions read site-scoped secrets at runtime, and the health route resolves them per request. The build may receive masked placeholder values. After changing a site-scoped env var, publish a new deploy before verifying live behavior. Call it a rebuild when the changed value is baked into build output or static assets; a runtime-only secret does not need to be baked into the bundle.

Credential Modeling Preflight

Before registering a provider's fields, inspect the workspace/provider connection catalog first. If a reusable connection exists, use its app grant and scoped resolveWorkspaceConnectionCredential(s)ForApp path instead of registering a parallel secret. Only classify fields for app-local setup when no reusable connection exists:

  • API or service key - register it as kind: "api-key" with the narrowest correct scope, a human label, a description, a docs link, and a validator.
  • OAuth authorization or refresh token - use the OAuth token store and register a kind: "oauth" entry so the shared UI renders Connect and the runtime owns status, refresh, and reauthorization.
  • Deploy- or app-level configuration - use deployment/runtime configuration, not a per-user secret row. For a non-secret public setting already represented by AgentNativeConfig, put the default in agent-native.config.ts and use its AGENT_NATIVE_CONFIG_<PATH> alias only for a deployment override. Never put a credential or provider key in that public namespace.
  • Account, customer, manager, or other non-secret identifier - store it as scoped connection metadata or app data, not as a masked secret field.

required: true is for a logical setup requirement. If a provider needs several values, do not automatically create one required checklist item per field; use one composite onboarding step or a registered connection readiness check.

Custom setup UI is allowed for provider-specific prerequisites, ordering, or health checks, but it must delegate credential storage and connection state to the shared vault/OAuth/settings surfaces.

When to use

Use this for any external credential your template needs: API keys, service tokens, webhook secrets. It gives you:

  • A sidebar UI entry for each credential (masked input, rotate, test, delete).
  • Automatic onboarding-checklist items for required: true secrets.
  • A stable server-side read API (readAppSecret) that decrypts values on demand.
  • Validator hooks for health-checking keys before save and from a Test button.

When NOT to use

  • OAuth flows that need to run the full authorization code exchange — use @agent-native/core/oauth-tokens directly to save/refresh tokens. The registry can still surface the OAuth connection in the sidebar by registering a secret with kind: "oauth" — that just delegates status lookup to oauth-tokens and renders a Connect button, no app_secrets row is written.
  • Purely process-level env vars that are never user-facing (e.g. NODE_ENV, deployment flags). Those belong in the onboarding form method or the envKeys list in core-routes-plugin.

Registering a secret

// server/plugins/register-secrets.ts
import { defineNitroPlugin } from "@agent-native/core/server";
import { registerRequiredSecret } from "@agent-native/core/secrets";

export default defineNitroPlugin(() => {
  registerRequiredSecret({
    key: "OPENAI_API_KEY",
    label: "OpenAI API Key",
    description: "Used for Whisper transcription of your recordings.",
    docsUrl: "https://platform.openai.com/api-keys",
    scope: "user",
    kind: "api-key",
    required: true,
    validator: async (value) => {
      const res = await fetch("https://api.openai.com/v1/models", {
        headers: { Authorization: `Bearer ${value}` },
      });
      return res.ok
        ? { ok: true }
        : { ok: false, error: `OpenAI rejected the key (HTTP ${res.status})` };
    },
  });
});

OAuth in the unified UI

registerRequiredSecret({
  key: "GOOGLE_CONNECTED",
  label: "Google account",
  description: "Grants access to Gmail / Calendar APIs.",
  scope: "user",
  kind: "oauth",
  required: true,
  oauthProvider: "google", // must match the provider id in oauth-tokens
  oauthConnectUrl: "/_agent-native/google/auth-url",
});

The sidebar shows a Connect button instead of a text input; no app_secrets row is written — status is derived from hasOAuthTokens("google").

Registered options

FieldTypePurpose
keystringEnv-var style name (OPENAI_API_KEY). Also the storage key.
labelstringHuman-readable title in the sidebar.
descriptionstring?Subtitle under the label.
docsUrlstring?"Get key" link rendered on the card.
scope"user" | "workspace"Per-user or shared across the active org.
kind"api-key" | "oauth"Drives UI and storage behavior.
requiredboolean?When true, an onboarding step is auto-injected.
validator(v) => Promise<boolean | {ok,error}>Runs on save and from the Test button. Never log v.
oauthProviderstring? (oauth-kind only)Provider id in oauth-tokens that backs this entry.
oauthConnectUrlstring? (oauth-kind only)URL the Connect button points at.

Reading a secret from an action

import { z } from "zod";
import { defineAction } from "@agent-native/core/action";
import { readAppSecret } from "@agent-native/core/secrets";
import { getRequestUserEmail } from "@agent-native/core/server";

export default defineAction({
  description: "Transcribe an audio file with Whisper",
  schema: z.object({ fileUrl: z.string() }),
  run: async ({ fileUrl }) => {
    const email = await getRequestUserEmail();
    if (!email) throw new Error("Not signed in");

    const stored = await readAppSecret({
      key: "OPENAI_API_KEY",
      scope: "user",
      scopeId: email,
    });
    const apiKey = stored?.value;
    if (!apiKey) {
      throw new Error(
        "OPENAI_API_KEY is not set. Add it in Settings.",
      );
    }

    // …call OpenAI. NEVER log the key or include it in error messages.
  },
});

Rules:

  • Never log the value. The read layer enforces this server-side; your code must do the same.
  • Use env vars only for deploy-level secrets. If a credential is user-scoped, org-scoped, or workspace-scoped, read the scoped vault/credential store. Do not add a process.env fallback that makes every user inherit one deployment's key.
  • Scope matches the registration. scope: "user" → pass the user email. scope: "workspace" → pass the active orgId from getOrgContext(event).orgId.
  • One resolver per key, and every runtime path goes through it. Before reading a credential, grep the app for the key name. If a resolver already exists (resolveXConfig, a connector, a client factory), call it — do not read process.env for that key a second time somewhere else.
  • Identity comes from the caller, not the module. A shared helper under server/lib/ takes the email as a parameter; only an entrypoint (action, route, cron) decides whose identity it is. A library that resolves its own identity from process.env.AGENT_USER_EMAIL authorizes the deployment rather than the caller.

The failure this prevents is not a leak — it is a split brain. When the config check reads the vault and the feature reads process.env, the settings UI and onboarding checklist report the integration as configured while every actual call fails with "env var is required". The credential is right there in the vault, so the error names the wrong cause and sends everyone hunting for a sync or redeploy problem that does not exist. This has now shipped four times in one app (BigQuery, Jira, Pylon, Academy).

Before finishing any change that touches a credential, run the guard from the app directory — it finds this whether the app lives in templates/ or in a workspace repo:

npx agent-native doctor --only no-env-credentials

resolveCredential sees exactly one organization

resolveCredential(key, { userEmail, orgId }) checks the user scope, then the one orgId you pass, then the solo workspace. That is the whole search. It is correct for a signed-in request, and wrong for two common cases:

  • The caller has no organization. A cron, a scheduled job, or any CLI run without a real member identity resolves no orgId, so only the user and solo scopes are ever consulted — and a shared key is in neither.
  • The key was synced under a different organization. app_secrets has no scope visible to every org (readAppSecret is strict equality on (scope, scope_id, key)), so a key the vault UI advertises as available to every app is unreadable from any org except the one that ran the sync.

Both produce the same misleading "not set" that the split brain above produces, which is why swapping process.env for resolveCredential can look like a fix and change nothing. A workspace app should read shared keys through a resolver that also sweeps the caller's other memberships and a designated vault org — see resolveConnectorSecret and AGENT_VAULT_ORG_ID in the builder-workspace repo for the shape, including the boot-time assertion that the deployment really is single-tenant before a non-membership-gated fallback is safe.

The doctor guard does not catch this second form — it looks for process.env reads, and resolveCredential is not one. Grep for resolveCredential yourself and confirm each call sits somewhere a single-org lookup is genuinely the right question. Findings that pair it with a ?? process.env.KEY fallback are the sanctioned deploy-level escape hatch; the bugs are calls whose value can only live in another org's vault.

HTTP routes

Core routes plugin mounts these under /_agent-native/secrets/ automatically:

  • GET /_agent-native/secrets — list registered secrets with status (set / unset / invalid), metadata, and — for set api-keys — the last 4 characters. Values are never returned.
  • POST /_agent-native/secrets/:key — body { value, scope?, scopeId? }. Runs the registered validator; returns 400 with the error on failure.
  • DELETE /_agent-native/secrets/:key — remove the stored value.
  • POST /_agent-native/secrets/:key/test — re-run the validator against the currently stored value.

Storage & encryption

  • Values are stored in app_secrets (created on-demand; no migration needed).
  • Values are encrypted at rest with AES-256-GCM. Generic app-local secrets prefer <APP_NAME>_SECRETS_ENCRYPTION_KEY (for example, ANALYTICS_SECRETS_ENCRYPTION_KEY), then SECRETS_ENCRYPTION_KEY, then BETTER_AUTH_SECRET.
  • Workspace-shared app_secrets prefer WORKSPACE_SECRETS_ENCRYPTION_KEY, then the legacy shared SECRETS_ENCRYPTION_KEY, then a purpose-derived key from A2A_SECRET. Better Auth and app-scoped keys remain legacy read candidates; a successful legacy decrypt is compare-and-swap migrated to the preferred shared key without changing the row timestamp.
  • Set the same stable WORKSPACE_SECRETS_ENCRYPTION_KEY in every app that reads a shared vault. It is intentionally separate from app-local OAuth encryption. If the vault still relies on A2A_SECRET, rotating A2A material also rotates its encryption key. Add the stable vault key everywhere and read/migrate existing rows before rotating A2A.
  • To rotate the dedicated workspace key itself, deploy the new value together with WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS=<old value>, let reads migrate ciphertext, then remove the previous key after the migration window.
  • If no configured key material exists, development uses a machine-local fallback and logs a one-time warning. Production fails closed.

Ad-hoc Keys

Ad-hoc keys are user-created secrets that are not declared by the template. Users create them through the settings UI or the agent chat to use with automations and the web-request tool. They support ${keys.NAME} substitution in outbound HTTP requests.

Ad-hoc API

Core routes plugin mounts these under /_agent-native/secrets/adhoc:

  • GET /_agent-native/secrets/adhoc — list all ad-hoc keys (name, last 4 chars, URL allowlist). Values are never returned.
  • POST /_agent-native/secrets/adhoc — body { name, value, urlAllowlist? }. Creates or updates an ad-hoc key.
  • DELETE /_agent-native/secrets/adhoc/:name — remove an ad-hoc key.

URL Allowlists

Each ad-hoc key can have a URL allowlist — an array of origin URLs that restrict where the key's value can be sent. The check is origin-level (scheme + host + port). If no allowlist is configured, the key can be used with any URL.

// Creating a key with an allowlist
POST /_agent-native/secrets/adhoc
{
  "name": "SLACK_WEBHOOK",
  "value": "<SLACK_WEBHOOK_URL_FROM_SETTINGS>",
  "urlAllowlist": ["https://hooks.slack.com"]
}

${keys.NAME} Substitution

The web-request tool supports ${keys.NAME} placeholders in the URL, headers, and body. Substitution happens server-side after the agent emits the tool call — the raw secret value never enters the agent's context.

import {
  resolveKeyReferences,
  validateUrlAllowlist,
} from "@agent-native/core/secrets/substitution";

// Resolve all ${keys.NAME} references in a string
const { resolved, usedKeys } = await resolveKeyReferences(
  "Bearer ${keys.API_TOKEN}",
  "user",
  "user@example.com",
);

// Validate a URL against a key's allowlist
const allowed = validateUrlAllowlist(
  "https://hooks.slack.com/services/<WORKSPACE>/<CHANNEL>/<SECRET>",
  ["https://hooks.slack.com"],
);

Key resolution falls back from user scope to workspace scope, so users can override shared keys without breaking automations that reference workspace defaults.

Dispatch Vault Access

Dispatch workspaces have a vault access policy for workspace app credentials:

  • all-apps is the default. Every saved Dispatch vault key is available to every workspace app; sync-vault-to-app pushes all vault keys to the target app.
  • manual requires explicit per-app grants. Use create-vault-grant / grant-vault-secrets-to-app, then sync-vault-to-app.

Use get-vault-access-settings before deciding whether to create grants, and use set-vault-access-settings only when the user asks to change the policy.

Vault keys land in the shared app_secrets store at org scope, so an app's Settings → API keys tab reports them as Set · Vault through resolveSecretDetailed (source/scopeId) instead of the registered-scope row alone. Runtime precedence is personal (user) row → shared org row → legacy workspace row → designated vault org → deploy env. Never add a second place to enter a key that the Vault already provides; label the source instead.

Key Files (ad-hoc)

FilePurpose
packages/core/src/secrets/substitution.tsresolveKeyReferences(), validateUrlAllowlist()
packages/core/src/tools/fetch-tool.tsweb-request tool consuming key references

Related skills

  • onboarding — the setup checklist that required secrets show up in.
  • actions — where you'll read secrets when calling third-party APIs.
  • authentication — session scoping; scope: "user" uses the session email.
  • security — input validation and never logging secrets.
  • automations — ad-hoc keys power ${keys.NAME} in automation web requests.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

a2a-assets

無料概要あり

複数のアプリやMCPホストからブランド画像・動画を生成、調整、書き出し、挿入する作業を支援します。ライブラリ選択や修正依頼も扱います。

  • スライド用のブランド画像を作りたいとき
  • 既存素材をフィードバックに合わせて直したいとき
  • 動画素材を生成して書き出したいとき
BuilderIO/agent-native7,5252026年10月26日 更新

account-health

無料概要あり

特定の顧客企業について、契約と製品利用の数値を照らし合わせ、根拠や不足情報を明記した健全性レポートをまとめます。

  • 顧客の健全性を確認したいとき
  • 契約席数と実利用を比べたいとき
  • 更新前に利用状況を整理したいとき
BuilderIO/agent-native7,5252026年10月26日 更新

actions

無料概要あり

アプリの操作をエージェントと画面の両方から呼び出せるアクションとして実装し、API連携や画面のデータ取得を一貫した形で追加するスキルです。

  • 新しいアプリ操作を追加したいとき
  • 外部APIと連携したいとき
  • 画面からデータを取得したいとき
BuilderIO/agent-native7,5252026年10月26日 更新

adding-a-feature

無料概要あり

新機能をUI・エージェント用アクション・説明資料・画面状態の4領域でそろえ、利用者とエージェントのどちらからも一貫して使えるようにするチェックリストです。

  • 新機能追加の作業範囲を整理したいとき
  • UIとエージェント操作をそろえたいとき
  • 画面状態の同期漏れを防ぎたいとき
BuilderIO/agent-native7,5252026年10月26日 更新

adhoc-analysis

無料概要あり

複数のデータソースを照合して、傾向や指標、根拠をまとめた分析結果を作成します。必要に応じて、再実行できるダッシュボードとして保存できます。

  • 商談と通話記録を照合したいとき
  • 顧客の利用状況を横断して調べたいとき
  • 繰り返し更新できる分析を保存したいとき
BuilderIO/agent-native7,5252026年10月26日 更新

admin-surfaces

無料概要あり

Analyticsの管理画面で、機能フラグの状態確認と切り替え、ダッシュボード利用状況の調査、接続先アプリのデータベース管理を行います。

  • アプリごとの機能フラグを確認・変更したいとき
  • ダッシュボードの所有者や利用状況を調べたいとき
  • 接続済みアプリのテーブルを管理したいとき
BuilderIO/agent-native7,5252026年10月26日 更新

BuilderIO のスキルをすべて見る

このスキルの問題を報告する