docs: document auth redaction helpers

This commit is contained in:
Peter Steinberger
2026-06-03 22:43:51 -04:00
parent 8f7808d1e6
commit 32e51f250f
6 changed files with 35 additions and 0 deletions

View File

@@ -20,6 +20,10 @@ import {
} from "./model-auth.js";
import { normalizeProviderId } from "./model-selection.js";
// Builds concise auth labels for UI/status surfaces without exposing credential
// values. Resolution follows profile override, provider profiles, env, CLI, then
// custom provider config.
/** Resolve the display label that describes how a provider is authenticated. */
export function resolveModelAuthLabel(params: {
provider?: string;
cfg?: OpenClawConfig;
@@ -106,6 +110,8 @@ export function resolveModelAuthLabel(params: {
return `api-key${label ? ` (${label})` : ""}`;
}
if (providerEntryProfileRef.kind === "profile-incompatible") {
// Preserve the fact that config pointed at a profile while avoiding a
// misleading auth mode for an incompatible provider/profile pairing.
return "unknown";
}

View File

@@ -1,5 +1,7 @@
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
// Shared provider-auth runtime types and errors. Provider calls use these helpers
// to fail with actionable auth provenance while keeping secret normalization local.
const AWS_BEARER_ENV = "AWS_BEARER_TOKEN_BEDROCK";
const AWS_ACCESS_KEY_ENV = "AWS_ACCESS_KEY_ID";
const AWS_SECRET_KEY_ENV = "AWS_SECRET_ACCESS_KEY";
@@ -14,6 +16,7 @@ export type ResolvedProviderAuth = {
export type ProviderAuthErrorCode = "missing-api-key" | "missing-provider-auth";
/** Base provider auth error with a stable code for retry/fallback logic. */
export class ProviderAuthError extends Error {
readonly code: ProviderAuthErrorCode;
readonly provider: string;
@@ -26,6 +29,7 @@ export class ProviderAuthError extends Error {
}
}
/** Auth error raised when a resolved provider auth source lacks usable material. */
export class MissingProviderAuthError extends ProviderAuthError {
readonly mode: ResolvedProviderAuth["mode"];
readonly source: string;
@@ -38,6 +42,7 @@ export class MissingProviderAuthError extends ProviderAuthError {
}
}
/** Narrow unknown errors to provider auth errors, optionally by code. */
export function isProviderAuthError(
err: unknown,
code?: ProviderAuthErrorCode,
@@ -45,10 +50,12 @@ export function isProviderAuthError(
return err instanceof ProviderAuthError && (!code || err.code === code);
}
/** Narrow unknown errors to missing-provider-auth failures. */
export function isMissingProviderAuthError(err: unknown): err is MissingProviderAuthError {
return err instanceof MissingProviderAuthError;
}
/** Return the AWS credential env var that proves SDK auth is configured. */
export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): string | undefined {
if (env[AWS_BEARER_ENV]?.trim()) {
return AWS_BEARER_ENV;
@@ -62,10 +69,12 @@ export function resolveAwsSdkEnvVarName(env: NodeJS.ProcessEnv = process.env): s
return undefined;
}
/** Format the user-facing missing-auth error from auth provenance. */
export function formatMissingAuthError(auth: ResolvedProviderAuth, provider: string): string {
return `No API key resolved for provider "${provider}" (auth mode: ${auth.mode}, checked: ${auth.source}).`;
}
/** Require a normalized API key or throw a provider-auth error. */
export function requireApiKey(auth: ResolvedProviderAuth, provider: string): string {
const key = normalizeSecretInput(auth.apiKey);
if (key) {

View File

@@ -4,8 +4,11 @@ import { buildTextObservationFields } from "./embedded-agent-error-observation.j
import type { FailoverReason } from "./embedded-agent-helpers.js";
import type { FallbackAttempt, ModelCandidate } from "./model-fallback.types.js";
// Structured logging for model fallback decisions. The log payload carries
// sanitized error observations plus step fields that make fallback chains auditable.
const decisionLog = createSubsystemLogger("model-fallback").child("decision");
/** Return whether fallback decision logging is enabled for warn-level events. */
export function isModelFallbackDecisionLogEnabled(): boolean {
return decisionLog.isEnabled("warn");
}
@@ -85,6 +88,8 @@ function buildFallbackStepFields(params: {
}): ModelFallbackStepFields | undefined {
const lastPreviousAttempt = params.previousAttempts?.at(-1);
if (params.decision === "candidate_succeeded") {
// Success records the previous failed candidate as the source and the current
// candidate as the successful fallback destination.
if (!lastPreviousAttempt) {
return undefined;
}
@@ -120,6 +125,7 @@ function buildFallbackStepFields(params: {
};
}
/** Log one model fallback decision and return structured fallback-step fields. */
export function logModelFallbackDecision(
params: ModelFallbackDecisionParams,
): ModelFallbackStepFields | undefined {

View File

@@ -1,12 +1,15 @@
import { normalizeStringEntries } from "@openclaw/normalization-core/string-normalization";
import { normalizeToolList, normalizeToolName } from "./tool-policy.js";
// Guardrails for explicit tool allowlists. They collect user/operator sources and
// explain when allowlist resolution leaves no callable tools.
type ExplicitToolAllowlistSource = {
label: string;
entries: string[];
enforceWhenToolsDisabled?: boolean;
};
/** Normalize explicit allowlist sources, dropping empty source entries. */
export function collectExplicitToolAllowlistSources(
sources: Array<{ label: string; allow?: string[]; enforceWhenToolsDisabled?: boolean }>,
): ExplicitToolAllowlistSource[] {
@@ -25,6 +28,7 @@ export function collectExplicitToolAllowlistSources(
});
}
/** Build an actionable error when explicit allowlists remove every callable tool. */
export function buildEmptyExplicitToolAllowlistError(params: {
sources: ExplicitToolAllowlistSource[];
callableToolNames: string[];

View File

@@ -1,3 +1,4 @@
// Common trace fields copied into provider/model diagnostic events.
type AgentTraceBase = {
runId?: string;
sessionId?: string;
@@ -8,6 +9,7 @@ type AgentTraceBase = {
workspaceDir?: string;
};
/** Build a trace base object while preserving undefined optional fields. */
export function buildAgentTraceBase(params: AgentTraceBase): AgentTraceBase {
return {
runId: params.runId,

View File

@@ -7,6 +7,9 @@ import {
} from "../logging/redact.js";
import type { AgentMessage } from "./runtime/index.js";
// Transcript redaction helpers for persisted agent messages. Text and structured
// fields share logging redaction config while preserving object identity when no
// redaction changes are needed.
function resolveTranscriptRedactPatterns(patterns?: string[]) {
return patterns && patterns.length > 0 ? [...patterns, ...getDefaultRedactPatterns()] : undefined;
}
@@ -79,9 +82,13 @@ function redactTranscriptStructuredValue(
return value;
}
if (seen.has(value)) {
// Avoid recursive transcript payloads from escaping redaction or crashing
// persistence; circular refs serialize as a stable marker.
return "[Circular]";
}
if (!isPlainTranscriptObject(value)) {
// Non-plain instances can carry runtime state; leave them untouched instead
// of cloning unexpected prototypes into transcripts.
return value;
}
@@ -100,6 +107,7 @@ function redactTranscriptStructuredValue(
return next ?? value;
}
/** Return a redacted transcript message according to logging config. */
export function redactTranscriptMessage(message: AgentMessage, cfg?: OpenClawConfig): AgentMessage {
if (cfg?.logging?.redactSensitive === "off") {
return message;