mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-14 19:40:40 +00:00
* feat(secrets): expand secret target coverage and gateway tooling * docs(secrets): align gateway and CLI secret docs * chore(protocol): regenerate swift gateway models for secrets methods * fix(config): restore talk apiKey fallback and stabilize runner test * ci(windows): reduce test worker count for shard stability * ci(windows): raise node heap for test shard stability * test(feishu): make proxy env precedence assertion windows-safe * fix(gateway): resolve auth password SecretInput refs for clients * fix(gateway): resolve remote SecretInput credentials for clients * fix(secrets): skip inactive refs in command snapshot assignments * fix(secrets): scope gateway.remote refs to effective auth surfaces * fix(secrets): ignore memory defaults when enabled agents disable search * fix(secrets): honor Google Chat serviceAccountRef inheritance * fix(secrets): address tsgo errors in command and gateway collectors * fix(secrets): avoid auth-store load in providers-only configure * fix(gateway): defer local password ref resolution by precedence * fix(secrets): gate telegram webhook secret refs by webhook mode * fix(secrets): gate slack signing secret refs to http mode * fix(secrets): skip telegram botToken refs when tokenFile is set * fix(secrets): gate discord pluralkit refs by enabled flag * fix(secrets): gate discord voice tts refs by voice enabled * test(secrets): make runtime fixture modes explicit * fix(cli): resolve local qr password secret refs * fix(cli): fail when gateway leaves command refs unresolved * fix(gateway): fail when local password SecretRef is unresolved * fix(gateway): fail when required remote SecretRefs are unresolved * fix(gateway): resolve local password refs only when password can win * fix(cli): skip local password SecretRef resolution on qr token override * test(gateway): cast SecretRef fixtures to OpenClawConfig * test(secrets): activate mode-gated targets in runtime coverage fixture * fix(cron): support SecretInput webhook tokens safely * fix(bluebubbles): support SecretInput passwords across config paths * fix(msteams): make appPassword SecretInput-safe in onboarding/token paths * fix(bluebubbles): align SecretInput schema helper typing * fix(cli): clarify secrets.resolve version-skew errors * refactor(secrets): return structured inactive paths from secrets.resolve * refactor(gateway): type onboarding secret writes as SecretInput * chore(protocol): regenerate swift models for secrets.resolve * feat(secrets): expand extension credential secretref support * fix(secrets): gate web-search refs by active provider * fix(onboarding): detect SecretRef credentials in extension status * fix(onboarding): allow keeping existing ref in secret prompt * fix(onboarding): resolve gateway password SecretRefs for probe and tui * fix(onboarding): honor secret-input-mode for local gateway auth * fix(acp): resolve gateway SecretInput credentials * fix(secrets): gate gateway.remote refs to remote surfaces * test(secrets): cover pattern matching and inactive array refs * docs(secrets): clarify secrets.resolve and remote active surfaces * fix(bluebubbles): keep existing SecretRef during onboarding * fix(tests): resolve CI type errors in new SecretRef coverage * fix(extensions): replace raw fetch with SSRF-guarded fetch * test(secrets): mark gateway remote targets active in runtime coverage * test(infra): normalize home-prefix expectation across platforms * fix(cli): only resolve local qr password refs in password mode * test(cli): cover local qr token mode with unresolved password ref * docs(cli): clarify local qr password ref resolution behavior * refactor(extensions): reuse sdk SecretInput helpers * fix(wizard): resolve onboarding env-template secrets before plaintext * fix(cli): surface secrets.resolve diagnostics in memory and qr * test(secrets): repair post-rebase runtime and fixtures * fix(gateway): skip remote password ref resolution when token wins * fix(secrets): treat tailscale remote gateway refs as active * fix(gateway): allow remote password fallback when token ref is unresolved * fix(gateway): ignore stale local password refs for none and trusted-proxy * fix(gateway): skip remote secret ref resolution on local call paths * test(cli): cover qr remote tailscale secret ref resolution * fix(secrets): align gateway password active-surface with auth inference * fix(cli): resolve inferred local gateway password refs in qr * fix(gateway): prefer resolvable remote password over token ref pre-resolution * test(gateway): cover none and trusted-proxy stale password refs * docs(secrets): sync qr and gateway active-surface behavior * fix: restore stability blockers from pre-release audit * Secrets: fix collector/runtime precedence contradictions * docs: align secrets and web credential docs * fix(rebase): resolve integration regressions after main rebase * fix(node-host): resolve gateway secret refs for auth * fix(secrets): harden secretinput runtime readers * gateway: skip inactive auth secretref resolution * cli: avoid gateway preflight for inactive secret refs * extensions: allow unresolved refs in onboarding status * tests: fix qr-cli module mock hoist ordering * Security: align audit checks with SecretInput resolution * Gateway: resolve local-mode remote fallback secret refs * Node host: avoid resolving inactive password secret refs * Secrets runtime: mark Slack appToken inactive for HTTP mode * secrets: keep inactive gateway remote refs non-blocking * cli: include agent memory secret targets in runtime resolution * docs(secrets): sync docs with active-surface and web search behavior * fix(secrets): keep telegram top-level token refs active for blank account tokens * fix(daemon): resolve gateway password secret refs for probe auth * fix(secrets): skip IRC NickServ ref resolution when NickServ is disabled * fix(secrets): align token inheritance and exec timeout defaults * docs(secrets): clarify active-surface notes in cli docs * cli: require secrets.resolve gateway capability * gateway: log auth secret surface diagnostics * secrets: remove dead provider resolver module * fix(secrets): restore gateway auth precedence and fallback resolution * fix(tests): align plugin runtime mock typings --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
import type { SecretProviderConfig, SecretRef } from "../config/types.secrets.js";
|
|
import { SecretProviderSchema } from "../config/zod-schema.core.js";
|
|
import { isValidSecretProviderAlias } from "./ref-contract.js";
|
|
import { parseDotPath, toDotPath } from "./shared.js";
|
|
import {
|
|
isKnownSecretTargetType,
|
|
resolvePlanTargetAgainstRegistry,
|
|
type ResolvedPlanTarget,
|
|
} from "./target-registry.js";
|
|
|
|
export type SecretsPlanTargetType = string;
|
|
|
|
export type SecretsPlanTarget = {
|
|
type: SecretsPlanTargetType;
|
|
/**
|
|
* Dot path in the target config surface for operator readability.
|
|
* Examples:
|
|
* - "models.providers.openai.apiKey"
|
|
* - "profiles.openai.key"
|
|
*/
|
|
path: string;
|
|
/**
|
|
* Canonical path segments used for safe mutation.
|
|
* Examples:
|
|
* - ["models", "providers", "openai", "apiKey"]
|
|
* - ["profiles", "openai", "key"]
|
|
*/
|
|
pathSegments?: string[];
|
|
ref: SecretRef;
|
|
/**
|
|
* Required for auth-profiles targets so apply can resolve the correct agent store.
|
|
*/
|
|
agentId?: string;
|
|
/**
|
|
* For provider targets, used to scrub auth-profile/static residues.
|
|
*/
|
|
providerId?: string;
|
|
/**
|
|
* For googlechat account-scoped targets.
|
|
*/
|
|
accountId?: string;
|
|
/**
|
|
* Optional auth-profile provider value used when creating new auth profile mappings.
|
|
*/
|
|
authProfileProvider?: string;
|
|
};
|
|
|
|
export type SecretsApplyPlan = {
|
|
version: 1;
|
|
protocolVersion: 1;
|
|
generatedAt: string;
|
|
generatedBy: "openclaw secrets configure" | "manual";
|
|
providerUpserts?: Record<string, SecretProviderConfig>;
|
|
providerDeletes?: string[];
|
|
targets: SecretsPlanTarget[];
|
|
options?: {
|
|
scrubEnv?: boolean;
|
|
scrubAuthProfilesForProviderTargets?: boolean;
|
|
scrubLegacyAuthJson?: boolean;
|
|
};
|
|
};
|
|
|
|
const FORBIDDEN_PATH_SEGMENTS = new Set(["__proto__", "prototype", "constructor"]);
|
|
|
|
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
}
|
|
|
|
function isSecretProviderConfigShape(value: unknown): value is SecretProviderConfig {
|
|
return SecretProviderSchema.safeParse(value).success;
|
|
}
|
|
|
|
function hasForbiddenPathSegment(segments: string[]): boolean {
|
|
return segments.some((segment) => FORBIDDEN_PATH_SEGMENTS.has(segment));
|
|
}
|
|
|
|
export function resolveValidatedPlanTarget(candidate: {
|
|
type?: SecretsPlanTargetType;
|
|
path?: string;
|
|
pathSegments?: string[];
|
|
agentId?: string;
|
|
providerId?: string;
|
|
accountId?: string;
|
|
authProfileProvider?: string;
|
|
}): ResolvedPlanTarget | null {
|
|
if (!isKnownSecretTargetType(candidate.type)) {
|
|
return null;
|
|
}
|
|
const path = typeof candidate.path === "string" ? candidate.path.trim() : "";
|
|
if (!path) {
|
|
return null;
|
|
}
|
|
const segments =
|
|
Array.isArray(candidate.pathSegments) && candidate.pathSegments.length > 0
|
|
? candidate.pathSegments.map((segment) => String(segment).trim()).filter(Boolean)
|
|
: parseDotPath(path);
|
|
if (segments.length === 0 || hasForbiddenPathSegment(segments) || path !== toDotPath(segments)) {
|
|
return null;
|
|
}
|
|
return resolvePlanTargetAgainstRegistry({
|
|
type: candidate.type,
|
|
pathSegments: segments,
|
|
providerId: candidate.providerId,
|
|
accountId: candidate.accountId,
|
|
});
|
|
}
|
|
|
|
export function isSecretsApplyPlan(value: unknown): value is SecretsApplyPlan {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return false;
|
|
}
|
|
const typed = value as Partial<SecretsApplyPlan>;
|
|
if (typed.version !== 1 || typed.protocolVersion !== 1 || !Array.isArray(typed.targets)) {
|
|
return false;
|
|
}
|
|
for (const target of typed.targets) {
|
|
if (!target || typeof target !== "object") {
|
|
return false;
|
|
}
|
|
const candidate = target as Partial<SecretsPlanTarget>;
|
|
const ref = candidate.ref as Partial<SecretRef> | undefined;
|
|
const resolved = resolveValidatedPlanTarget({
|
|
type: candidate.type,
|
|
path: candidate.path,
|
|
pathSegments: candidate.pathSegments,
|
|
agentId: candidate.agentId,
|
|
providerId: candidate.providerId,
|
|
accountId: candidate.accountId,
|
|
authProfileProvider: candidate.authProfileProvider,
|
|
});
|
|
if (
|
|
!isKnownSecretTargetType(candidate.type) ||
|
|
typeof candidate.path !== "string" ||
|
|
!candidate.path.trim() ||
|
|
(candidate.pathSegments !== undefined && !Array.isArray(candidate.pathSegments)) ||
|
|
!resolved ||
|
|
!ref ||
|
|
typeof ref !== "object" ||
|
|
(ref.source !== "env" && ref.source !== "file" && ref.source !== "exec") ||
|
|
typeof ref.provider !== "string" ||
|
|
ref.provider.trim().length === 0 ||
|
|
typeof ref.id !== "string" ||
|
|
ref.id.trim().length === 0
|
|
) {
|
|
return false;
|
|
}
|
|
if (resolved.entry.configFile === "auth-profiles.json") {
|
|
if (typeof candidate.agentId !== "string" || candidate.agentId.trim().length === 0) {
|
|
return false;
|
|
}
|
|
if (
|
|
candidate.authProfileProvider !== undefined &&
|
|
(typeof candidate.authProfileProvider !== "string" ||
|
|
candidate.authProfileProvider.trim().length === 0)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (typed.providerUpserts !== undefined) {
|
|
if (!isObjectRecord(typed.providerUpserts)) {
|
|
return false;
|
|
}
|
|
for (const [providerAlias, providerValue] of Object.entries(typed.providerUpserts)) {
|
|
if (!isValidSecretProviderAlias(providerAlias)) {
|
|
return false;
|
|
}
|
|
if (!isSecretProviderConfigShape(providerValue)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (typed.providerDeletes !== undefined) {
|
|
if (
|
|
!Array.isArray(typed.providerDeletes) ||
|
|
typed.providerDeletes.some(
|
|
(providerAlias) =>
|
|
typeof providerAlias !== "string" || !isValidSecretProviderAlias(providerAlias),
|
|
)
|
|
) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function normalizeSecretsPlanOptions(
|
|
options: SecretsApplyPlan["options"] | undefined,
|
|
): Required<NonNullable<SecretsApplyPlan["options"]>> {
|
|
return {
|
|
scrubEnv: options?.scrubEnv ?? true,
|
|
scrubAuthProfilesForProviderTargets: options?.scrubAuthProfilesForProviderTargets ?? true,
|
|
scrubLegacyAuthJson: options?.scrubLegacyAuthJson ?? true,
|
|
};
|
|
}
|