Skip to content

fix(secrets): align SecretRef inspect/strict behavior across preload/runtime paths#66818

Merged
joshavant merged 12 commits intomainfrom
feat/secretref-inspect-strict-runtime-snapshots
Apr 14, 2026
Merged

fix(secrets): align SecretRef inspect/strict behavior across preload/runtime paths#66818
joshavant merged 12 commits intomainfrom
feat/secretref-inspect-strict-runtime-snapshots

Conversation

@joshavant
Copy link
Copy Markdown
Contributor

Summary

  • Problem: SecretRef handling was inconsistent across plugin preload, registration/setup surfaces, and runtime auth/tool paths. Some read-only flows could fail early on unresolved refs.
  • Why it matters: non-runtime commands and status paths should stay resilient when config contains unresolved SecretRefs, while runtime should remain strict where credentials must actually resolve.
  • What changed:
    • Added a shared inspect/strict SecretRef resolution contract in src/config/types.secrets.ts, exported via src/plugin-sdk/secret-input.ts.
    • Routed status deferred plugin preload through explicit resolved/source snapshots (src/cli/plugin-registry-loader.ts, src/commands/status.scan.fast-json.ts).
    • Updated read-only provider status resolution to prefer inspectAccount and skip throwing accounts (src/commands/agents.providers.ts).
    • Removed credential resolution from Slack route registration (extensions/slack/src/http/plugin-routes.ts).
    • Normalized provider/tool SecretRef handling for custom provider auth, xAI tool auth, and Firecrawl config paths.
    • Added Exa web-search SecretRef target coverage and synced docs matrix/surface.
    • Updated Telegram runtime token resolution to support env SecretRefs while preserving strict behavior for unresolved non-env refs.
  • What did NOT change (scope boundary):
    • No unrelated channel/runtime behavior refactors outside SecretRef lifecycle and snapshot-threading surfaces.
    • No protocol or command-surface expansion.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: SecretRef reads were happening at multiple lifecycle points without one shared contract for read-only inspection vs strict runtime resolution.
  • Missing detection / guardrail: registration/read-only flows were not consistently guarded against unresolved refs, and status preload did not always reuse explicit resolved/source snapshots.
  • Contributing context (if known): plugin/channel/provider codepaths evolved with local helpers, causing divergence in when unresolved refs throw vs degrade gracefully.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • src/config/types.secrets.resolution.test.ts
    • src/cli/plugin-registry-loader.test.ts
    • src/commands/status.scan.test.ts
    • src/commands/agents.providers.test.ts
    • extensions/slack/src/http/plugin-routes.test.ts
    • src/agents/model-auth.test.ts
    • extensions/xai/src/tool-auth-shared.test.ts
    • extensions/firecrawl/src/firecrawl-tools.test.ts
    • src/secrets/target-registry.test.ts
    • src/cli/command-secret-targets.test.ts
    • extensions/telegram/src/token.test.ts
  • Scenario the test should lock in:
    • Read-only paths do not hard-fail on unresolved SecretRefs.
    • Runtime paths remain strict where non-env refs are unresolved.
    • Deferred plugin preload receives explicit resolved/source snapshots.
    • Secret target registry/docs stay in sync.
  • Why this is the smallest reliable guardrail:
    • These seams are exactly where resolution mode and snapshot threading decisions are made.
  • Existing test that already covers this (if any):
    • Existing status/agents/provider/channel tests were extended at those boundaries.
  • If no new test is added, why not:
    • N/A

User-visible / Behavior Changes

  • openclaw agents list and other read-only/status paths are more resilient when unresolved SecretRefs exist in channel/provider configs.
  • Slack route registration no longer resolves credential refs at registration time.
  • Status deferred plugin preload uses explicit resolved/source config snapshots.
  • Custom provider/xAI/Firecrawl SecretRef handling now treats env refs and unresolved refs consistently for runtime-safe behavior.
  • Exa web-search SecretRef target is now included in secret target registry/docs.
  • Telegram runtime can resolve env-backed SecretRefs for bot tokens while retaining strict handling for unresolved non-env refs.

Diagram (if applicable)

Before:
[CLI/read-only preload] -> [strict secret read in mixed paths] -> [throws early on unresolved ref]

After:
[CLI/read-only preload] -> [inspect mode + resolved/source snapshot threading] -> [degrades safely]
[runtime send/start] -> [strict resolution at execution boundary] -> [throws only when truly required]

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (Yes)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:
    • Risk: accidentally making runtime secret resolution too permissive.
    • Mitigation: strict mode is preserved for runtime-required non-env refs; inspect mode is explicitly scoped to read-only/setup/preload surfaces.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22+, pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): Slack, Telegram, xAI, Firecrawl, model providers
  • Relevant config (redacted): configs containing structured SecretRefs (env + non-env)

Steps

  1. Add unresolved SecretRef values to affected config paths.
  2. Run read-only/status commands and plugin preload flows.
  3. Run runtime credential paths for affected providers/channels.

Expected

  • Read-only/status flows do not crash on unresolved refs.
  • Runtime env refs resolve from environment where applicable.
  • Runtime unresolved non-env refs remain strict failures.

Actual

  • Matches expected in updated tests and targeted command-path verification.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • Built project successfully (pnpm build).
    • Verified Plugin SDK API drift workflow (pnpm plugin-sdk:api:check, pnpm plugin-sdk:api:gen, re-check pass).
    • Ran targeted tests covering config resolver, status preload, provider auth/tool paths, Slack route registration, Exa target/docs sync, and Telegram token resolution.
  • Edge cases checked:
    • Env SecretRef availability vs missing env var.
    • Unresolved non-env SecretRef behavior in runtime paths.
    • Read-only surfaces with inspect-first handling.
  • What you did not verify:
    • Full repository pnpm test did not finish green due unrelated failing shards outside this PR surface.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:
    • N/A

Risks and Mitigations

  • Risk:

    • Inspect-mode use could expand unintentionally into runtime paths.
    • Mitigation:
      • Strict mode remains default for runtime helper paths; tests assert strict behavior for unresolved non-env refs.
  • Risk:

    • Secret target registry changes can drift from docs.
    • Mitigation:
      • Updated matrix + credential surface docs and docs-sync tests.

@joshavant joshavant requested a review from a team as a code owner April 14, 2026 21:23
@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 14, 2026

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Arbitrary environment variable read via models.json SecretRef for custom provider API keys
2 🟠 High Plaintext secrets forwarded to plugin registration during read-only CLI status scan
3 🟡 Medium Brittle error-message substring matching suppresses plugin failures in provider status resolution
1. 🟠 Arbitrary environment variable read via models.json SecretRef for custom provider API keys
Property Value
Severity High
CWE CWE-200
Location src/agents/model-auth.ts:118-149

Description

resolveUsableCustomProviderApiKey resolves models.providers.<name>.apiKey when configured as a SecretRef {source:"env", provider:"default", id:<ENV_NAME>} by directly indexing process.env[envVarName].

  • The env var name comes from models.json (apiKeyRef.id.trim()), and is not restricted to a known-safe set.
  • canResolveEnvSecretRefInReadOnlyPath allows resolution when the referenced secret provider config is missing and the SecretRef provider equals the default env provider alias (typically "default").
  • As a result, a (potentially attacker-controlled) models.json can cause the runtime to read any environment variable (e.g., AWS_SECRET_ACCESS_KEY, GITHUB_TOKEN, CI secrets).
  • The resolved value is then used as the provider API key (sent in Authorization headers) to the configured custom baseUrl, enabling exfiltration if that URL is attacker-controlled.

Vulnerable code:

const envVarName = apiKeyRef.id.trim();
...
const envValue = normalizeOptionalSecretInput((params.env ?? process.env)[envVarName]);
...
return { apiKey: envValue, source: ... };

Recommendation

Require an explicit allowlist (or a strict env-var-name policy) before resolving env SecretRefs from models.json, especially when using the default env provider.

Concrete options:

  1. Disallow provider: "default" env SecretRefs from models.json unless a provider allowlist is configured, forcing users to opt-in:
function canResolveEnvSecretRefInReadOnlyPath({ cfg, provider, id }: ...): boolean {
  const providerConfig = cfg?.secrets?.providers?.[provider];
  if (!providerConfig) return false; // do not auto-allow "default"
  if (providerConfig.source !== "env") return false;
  return Array.isArray(providerConfig.allowlist) && providerConfig.allowlist.includes(id);
}
  1. Additionally validate id against ENV_SECRET_REF_ID_RE and consider blocking common high-value envs by default (denylist), or limiting to variables matching an expected prefix for model keys.

This prevents a crafted models.json from reading arbitrary environment secrets and sending them to an attacker-controlled endpoint.

2. 🟠 Plaintext secrets forwarded to plugin registration during read-only CLI status scan
Property Value
Severity High
CWE CWE-200
Location src/commands/status.scan.fast-json.ts:38-45

Description

The CLI now forwards an explicit config snapshot into plugin registry loading for read-only commands (e.g., status --json). In the status scan path, this snapshot is overview.cfg, which is a resolved config (tests show secret values like a resolved Telegram botToken).

Because plugin loading executes third-party plugin code at import/registration time, this causes plaintext secret values to be exposed to any installed plugin during registry preload, even when the command intends showSecrets: false.

Data flow:

  • Input: overview.cfg (resolved config, may contain decrypted secret values)
  • Propagation: ensureCliPluginRegistryLoaded({ config: overview.cfg, ... })
  • Sink: loadOpenClawPlugins(...) builds a plugin API with config: cfg and calls the plugin's register(api) function, giving the plugin full access to the resolved config (including secrets).

Vulnerable code paths:

// status scan: forwards resolved snapshot
await ensureCliPluginRegistryLoaded({
  scope: "configured-channels",
  routeLogsToStderr: true,
  config: overview.cfg,
  activationSourceConfig: overview.sourceConfig,
});
// plugin loader: passes cfg into plugin API and executes plugin code
const api = buildPluginApi({
  ...,
  config: cfg,
  ...,
});
await register(api);

Impact:

  • Any plugin loaded in this read-only flow can read and exfiltrate secrets from api.config (e.g., via logging, network calls, writing files), despite the command surface intending not to expose secrets.

Recommendation

Avoid providing plugins a fully resolved config (with secret values) during read-only/preload flows.

Options (pick one based on intended design):

  1. Use only the activation source config (SecretRefs / unresolved values) for plugin registry preload:
await ensureCliPluginRegistryLoaded({
  scope: "configured-channels",
  routeLogsToStderr: true,// do NOT pass resolved snapshot here
  activationSourceConfig: overview.sourceConfig,
});
  1. Pass a sanitized config snapshot that strips/rewrites secret-bearing fields before plugin load (keep only booleans/metadata needed to decide which plugins to load).

  2. Add a strict “no secrets” contract to plugin preload mode: load only manifests/metadata without executing plugin register() (or execute in a restricted mode where api.config is redacted).

Also ensure plugins are never handed secret values unless the command explicitly requires them and the user has opted into secret exposure.

3. 🟡 Brittle error-message substring matching suppresses plugin failures in provider status resolution
Property Value
Severity Medium
CWE CWE-703
Location src/commands/agents.providers.ts:26-31

Description

buildProviderStatusIndex treats any thrown Error whose message matches /unresolved SecretRef/i as a benign “not configured” condition.

  • The check is based on substring matching of error.message, not a typed error (e.g., SecretRefResolutionError).
  • A plugin bug or malicious/compromised plugin can throw an error containing that substring for unrelated failures (or include user-influenced content in its message), causing the CLI to silently downgrade a real failure into state: "not configured".
  • This can mislead operators and monitoring by masking integrity/availability problems during agents.providers status reporting.

Vulnerable code:

function isUnresolvedSecretRefResolutionError(error: unknown): boolean {
  return (
    error instanceof Error &&
    typeof error.message === "string" &&/unresolved SecretRef/i.test(error.message)
  );
}
...
} catch (error) {
  if (!isUnresolvedSecretRefResolutionError(error)) {
    throw error;
  }
  map.set(..., { state: "not configured", configured: false });
  continue;
}

Recommendation

Avoid classifying resolution failures by error.message substrings. Prefer a typed error signal.

Option A (recommended): use the existing typed error class

import { SecretRefResolutionError } from "../secrets/resolve.js";

function isUnresolvedSecretRefResolutionError(error: unknown): boolean {
  return error instanceof SecretRefResolutionError;
}

Option B: If cross-package instanceof may fail, add a stable discriminator (e.g., name === "SecretRefResolutionError" plus required fields like scope === "ref") or a shared helper exported from the secrets module.

Also consider logging/including diagnostics when suppressing the error (at least at debug level), so unexpected failures aren’t silently hidden.


Analyzed PR: #66818 at commit 3ea81d6

Last updated on: 2026-04-14T23:06:32Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: slack Channel integration: slack channel: telegram Channel integration: telegram cli CLI command changes commands Command implementations agents Agent runtime and tooling size: L maintainer Maintainer-authored PR labels Apr 14, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc8df503fb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/model-auth.ts
Comment on lines +84 to +86
if (ref.source === "env") {
const envId = ref.id.trim();
return envId || NON_ENV_SECRETREF_MARKER;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat env SecretRefs as env lookups, not literal API keys

When models.providers.<id>.apiKey is a SecretRef object with source: "env", this branch returns the raw env var name (for example MY_CUSTOM_KEY). In resolveUsableCustomProviderApiKey, unknown env names are not recognized by isNonSecretApiKeyMarker, so they are treated as literal credentials and sent as the provider API key instead of reading process.env[MY_CUSTOM_KEY]. This breaks custom providers that use non-built-in env var names and causes authentication failures whenever the env-ref id is outside the known marker list.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f4003cd

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 14, 2026

Greptile Summary

This PR introduces a shared resolveSecretInputString contract with inspect/strict modes to consistently handle unresolved SecretRefs across read-only and runtime paths. The core design is sound: status/preload surfaces now degrade gracefully, while runtime execution paths remain strict where credential resolution is mandatory. Changes to Slack route registration, Telegram token resolution, Firecrawl config, and xAI tool auth all follow the new contract cleanly, and the new resolution test suite provides good baseline coverage.

Confidence Score: 5/5

Safe to merge; both findings are P2 style and edge-case behavioral notes that do not block the main use cases.

All findings are P2: one is dead code after a guaranteed throw (Telegram), and the other is a behavioral edge case for non-standard env var names in custom provider SecretRefs (model-auth) that was already broken before this PR. The primary paths — status resiliency, Slack route registration, Firecrawl, xAI, Telegram env refs — are well-implemented and tested.

src/agents/model-auth.ts — the getCustomProviderApiKey env-ref branch for non-standard env var names and the corresponding test coverage gap.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/telegram/src/token.ts
Line: 34-39

Comment:
**Dead code after strict-mode SecretRef throw**

`resolveSecretInputString` with `mode: "strict"` will always throw here because we already know the value is `configured_unavailable` from the first inspect-mode call on the same input — the ref is still unresolved. The `return undefined` on line 39 is unreachable.

If `createUnresolvedSecretInputError` were exported from `types.secrets.ts`, throwing it directly using the `ref` already in scope from the inspect call would be cleaner than a double-parse. As-is, the dead-code line should simply be removed.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/model-auth.ts
Line: 80-88

Comment:
**Non-standard env var IDs returned as literal values**

For env SecretRefs whose `id` is not in the known-marker set, this branch returns the raw env var name string. In `resolveUsableCustomProviderApiKey`, `isNonSecretApiKeyMarker` returns `false` for unknown names, so the function returns the env var name as if it were a literal API credential — not the resolved value.

The new test uses `OPENAI_API_KEY`, which is already in `listKnownEnvApiKeyMarkers()` and therefore takes the correct env-lookup path. A user with a custom env var name not in that set would get the var name itself used as the credential.

Before this PR, `normalizeOptionalSecretInput` stripped all SecretRef objects, so `getCustomProviderApiKey` returned `undefined` and the caller saw `null` (not configured). The behavior for this edge case has changed.

Consider returning `NON_ENV_SECRETREF_MARKER` for env refs whose ID is not in the known list, matching the non-env branch, to keep the "managed but unresolvable here" semantics uniform.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Telegram: resolve env SecretRef tokens a..." | Re-trigger Greptile

Comment thread extensions/telegram/src/token.ts Outdated
Comment on lines +34 to +39
resolveSecretInputString({
value: params.value,
path: params.path,
mode: "strict",
});
return undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead code after strict-mode SecretRef throw

resolveSecretInputString with mode: "strict" will always throw here because we already know the value is configured_unavailable from the first inspect-mode call on the same input — the ref is still unresolved. The return undefined on line 39 is unreachable.

If createUnresolvedSecretInputError were exported from types.secrets.ts, throwing it directly using the ref already in scope from the inspect call would be cleaner than a double-parse. As-is, the dead-code line should simply be removed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/telegram/src/token.ts
Line: 34-39

Comment:
**Dead code after strict-mode SecretRef throw**

`resolveSecretInputString` with `mode: "strict"` will always throw here because we already know the value is `configured_unavailable` from the first inspect-mode call on the same input — the ref is still unresolved. The `return undefined` on line 39 is unreachable.

If `createUnresolvedSecretInputError` were exported from `types.secrets.ts`, throwing it directly using the `ref` already in scope from the inspect call would be cleaner than a double-parse. As-is, the dead-code line should simply be removed.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/agents/model-auth.ts
Comment on lines +80 to +88
const ref = coerceSecretRef(entry?.apiKey);
if (!ref) {
return undefined;
}
if (ref.source === "env") {
const envId = ref.id.trim();
return envId || NON_ENV_SECRETREF_MARKER;
}
return NON_ENV_SECRETREF_MARKER;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-standard env var IDs returned as literal values

For env SecretRefs whose id is not in the known-marker set, this branch returns the raw env var name string. In resolveUsableCustomProviderApiKey, isNonSecretApiKeyMarker returns false for unknown names, so the function returns the env var name as if it were a literal API credential — not the resolved value.

The new test uses OPENAI_API_KEY, which is already in listKnownEnvApiKeyMarkers() and therefore takes the correct env-lookup path. A user with a custom env var name not in that set would get the var name itself used as the credential.

Before this PR, normalizeOptionalSecretInput stripped all SecretRef objects, so getCustomProviderApiKey returned undefined and the caller saw null (not configured). The behavior for this edge case has changed.

Consider returning NON_ENV_SECRETREF_MARKER for env refs whose ID is not in the known list, matching the non-env branch, to keep the "managed but unresolvable here" semantics uniform.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/model-auth.ts
Line: 80-88

Comment:
**Non-standard env var IDs returned as literal values**

For env SecretRefs whose `id` is not in the known-marker set, this branch returns the raw env var name string. In `resolveUsableCustomProviderApiKey`, `isNonSecretApiKeyMarker` returns `false` for unknown names, so the function returns the env var name as if it were a literal API credential — not the resolved value.

The new test uses `OPENAI_API_KEY`, which is already in `listKnownEnvApiKeyMarkers()` and therefore takes the correct env-lookup path. A user with a custom env var name not in that set would get the var name itself used as the credential.

Before this PR, `normalizeOptionalSecretInput` stripped all SecretRef objects, so `getCustomProviderApiKey` returned `undefined` and the caller saw `null` (not configured). The behavior for this edge case has changed.

Consider returning `NON_ENV_SECRETREF_MARKER` for env refs whose ID is not in the known list, matching the non-env branch, to keep the "managed but unresolvable here" semantics uniform.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4003cde86

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/telegram/src/token.ts Outdated
Comment on lines +30 to +31
if (resolved.ref.source === "env") {
return normalizeSecretInputString(process.env[resolved.ref.id]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce env SecretRef policy before reading process.env

This new runtime branch resolves env-backed SecretRefs by directly reading process.env[resolved.ref.id], which skips provider validation and secrets.providers.<name>.allowlist checks that the normal secret resolver enforces. As a result, a config can point channels.telegram.botToken at any env var name (including disallowed ids or providers that should not resolve env refs) and the value is accepted if present, which can silently bypass secret policy and leak unintended env values in Telegram API calls. The same direct-lookup pattern was also added in the xAI/Firecrawl helpers, so this should be fixed as a policy-consistent SecretRef resolution path rather than a raw env read.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 86fac76

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86fac767a3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/model-auth.ts
if (!envVarName) {
return null;
}
const envValue = normalizeOptionalSecretInput((params.env ?? process.env)[envVarName]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce SecretRef policy before custom-provider env lookup

resolveUsableCustomProviderApiKey now reads process.env[ref.id] directly for env SecretRefs, but this path never validates ref.provider (configured provider, source match, or allowlist). That means a config like models.providers.custom.apiKey = {source:"env", provider:"restricted", id:"ANY_VAR"} will be accepted whenever ANY_VAR is set, bypassing the secret-provider policy checks that src/secrets/resolve.ts enforces for SecretRef resolution.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 48bf142

if (envVarName !== XAI_API_KEY_ENV_VAR) {
return { status: "blocked" };
}
const envValue = normalizeSecretInputString(process.env[envVarName]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate xAI env SecretRef provider before reading process.env

readConfiguredRuntimeApiKey resolves env SecretRefs by directly reading process.env[envVarName] once the id matches XAI_API_KEY, but it does not verify that the referenced secret provider exists, is source: "env", or allowlists that id. In runtime paths without a resolved snapshot, this lets plugins.entries.xai.config.webSearch.apiKey bypass secrets.providers.* policy and still consume an env value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 48bf142

Comment on lines +126 to +130
const envVarName = resolved.ref.id.trim();
if (envVarName !== FIRECRAWL_API_KEY_ENV_VAR) {
return { status: "blocked" };
}
const envValue = normalizeSecretInput(process.env[envVarName]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce env SecretRef provider checks in Firecrawl API key path

resolveConfiguredSecret reads process.env[envVarName] for env SecretRefs (when the id is FIRECRAWL_API_KEY) without validating provider existence/source/allowlist. As a result, plugins.entries.firecrawl.config.webSearch.apiKey (or related Firecrawl key fields) can bypass secrets.providers restrictions in source-config runtime flows and still resolve from environment state.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 48bf142

@joshavant joshavant self-assigned this Apr 14, 2026
@joshavant joshavant force-pushed the feat/secretref-inspect-strict-runtime-snapshots branch from 48bf142 to 70f12cd Compare April 14, 2026 22:56
@joshavant joshavant force-pushed the feat/secretref-inspect-strict-runtime-snapshots branch from 70f12cd to 3ea81d6 Compare April 14, 2026 22:59
@joshavant joshavant merged commit 1769fb2 into main Apr 14, 2026
5 checks passed
@joshavant joshavant deleted the feat/secretref-inspect-strict-runtime-snapshots branch April 14, 2026 22:59
@joshavant
Copy link
Copy Markdown
Contributor Author

Landed via temp rebase onto main.

  • Gate: pnpm lint && pnpm build && pnpm test
  • Land commit: 3ea81d6
  • Merge commit: 1769fb2

Thanks @joshavant!

kvnkho pushed a commit to kvnkho/openclaw that referenced this pull request Apr 17, 2026
…runtime paths (openclaw#66818)

* Config: add inspect/strict SecretRef string resolver

* CLI: pass resolved/source config snapshots to plugin preload

* Slack: keep HTTP route registration config-only

* Providers: normalize SecretRef handling for auth and web tools

* Secrets: add Exa web search target to registry and docs

* Telegram: resolve env SecretRef tokens at runtime

* Agents: resolve custom provider env SecretRef ids

* Providers: fail closed on blocked SecretRef fallback

* Telegram: enforce env SecretRef policy for runtime token refs

* Status/Providers/Telegram: tighten SecretRef preload and fallback handling

* Providers: enforce env SecretRef policy checks in fallback auth paths

* fix: add SecretRef lifecycle changelog entry (openclaw#66818) (thanks @joshavant)
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request Apr 27, 2026
…runtime paths (openclaw#66818)

* Config: add inspect/strict SecretRef string resolver

* CLI: pass resolved/source config snapshots to plugin preload

* Slack: keep HTTP route registration config-only

* Providers: normalize SecretRef handling for auth and web tools

* Secrets: add Exa web search target to registry and docs

* Telegram: resolve env SecretRef tokens at runtime

* Agents: resolve custom provider env SecretRef ids

* Providers: fail closed on blocked SecretRef fallback

* Telegram: enforce env SecretRef policy for runtime token refs

* Status/Providers/Telegram: tighten SecretRef preload and fallback handling

* Providers: enforce env SecretRef policy checks in fallback auth paths

* fix: add SecretRef lifecycle changelog entry (openclaw#66818) (thanks @joshavant)
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…runtime paths (openclaw#66818)

* Config: add inspect/strict SecretRef string resolver

* CLI: pass resolved/source config snapshots to plugin preload

* Slack: keep HTTP route registration config-only

* Providers: normalize SecretRef handling for auth and web tools

* Secrets: add Exa web search target to registry and docs

* Telegram: resolve env SecretRef tokens at runtime

* Agents: resolve custom provider env SecretRef ids

* Providers: fail closed on blocked SecretRef fallback

* Telegram: enforce env SecretRef policy for runtime token refs

* Status/Providers/Telegram: tighten SecretRef preload and fallback handling

* Providers: enforce env SecretRef policy checks in fallback auth paths

* fix: add SecretRef lifecycle changelog entry (openclaw#66818) (thanks @joshavant)
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…runtime paths (openclaw#66818)

* Config: add inspect/strict SecretRef string resolver

* CLI: pass resolved/source config snapshots to plugin preload

* Slack: keep HTTP route registration config-only

* Providers: normalize SecretRef handling for auth and web tools

* Secrets: add Exa web search target to registry and docs

* Telegram: resolve env SecretRef tokens at runtime

* Agents: resolve custom provider env SecretRef ids

* Providers: fail closed on blocked SecretRef fallback

* Telegram: enforce env SecretRef policy for runtime token refs

* Status/Providers/Telegram: tighten SecretRef preload and fallback handling

* Providers: enforce env SecretRef policy checks in fallback auth paths

* fix: add SecretRef lifecycle changelog entry (openclaw#66818) (thanks @joshavant)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling channel: slack Channel integration: slack channel: telegram Channel integration: telegram cli CLI command changes commands Command implementations docs Improvements or additions to documentation maintainer Maintainer-authored PR size: XL

Projects

None yet

1 participant