mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
refactor: dedupe core trimmed readers
This commit is contained in:
@@ -209,10 +209,10 @@ export class ClawHubRequestError extends Error {
|
||||
|
||||
function normalizeBaseUrl(baseUrl?: string): string {
|
||||
const envValue =
|
||||
process.env.OPENCLAW_CLAWHUB_URL?.trim() ||
|
||||
process.env.CLAWHUB_URL?.trim() ||
|
||||
normalizeOptionalString(process.env.OPENCLAW_CLAWHUB_URL) ||
|
||||
normalizeOptionalString(process.env.CLAWHUB_URL) ||
|
||||
DEFAULT_CLAWHUB_URL;
|
||||
const value = (baseUrl?.trim() || envValue).replace(/\/+$/, "");
|
||||
const value = (normalizeOptionalString(baseUrl) || envValue).replace(/\/+$/, "");
|
||||
return value || DEFAULT_CLAWHUB_URL;
|
||||
}
|
||||
|
||||
@@ -235,14 +235,14 @@ function extractTokenFromClawHubConfig(value: unknown): string | undefined {
|
||||
|
||||
function resolveClawHubConfigPaths(): string[] {
|
||||
const explicit =
|
||||
process.env.OPENCLAW_CLAWHUB_CONFIG_PATH?.trim() ||
|
||||
process.env.CLAWHUB_CONFIG_PATH?.trim() ||
|
||||
process.env.CLAWDHUB_CONFIG_PATH?.trim(); // legacy misspelling from older clawhub CLI builds; keep for back-compat
|
||||
normalizeOptionalString(process.env.OPENCLAW_CLAWHUB_CONFIG_PATH) ||
|
||||
normalizeOptionalString(process.env.CLAWHUB_CONFIG_PATH) ||
|
||||
normalizeOptionalString(process.env.CLAWDHUB_CONFIG_PATH); // legacy misspelling from older clawhub CLI builds; keep for back-compat
|
||||
if (explicit) {
|
||||
return [explicit];
|
||||
}
|
||||
|
||||
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
||||
const xdgConfigHome = normalizeOptionalString(process.env.XDG_CONFIG_HOME);
|
||||
const configHome =
|
||||
xdgConfigHome && xdgConfigHome.length > 0 ? xdgConfigHome : path.join(os.homedir(), ".config");
|
||||
const xdgPath = path.join(configHome, "clawhub", "config.json");
|
||||
@@ -259,9 +259,9 @@ function resolveClawHubConfigPaths(): string[] {
|
||||
|
||||
export async function resolveClawHubAuthToken(): Promise<string | undefined> {
|
||||
const envToken =
|
||||
process.env.OPENCLAW_CLAWHUB_TOKEN?.trim() ||
|
||||
process.env.CLAWHUB_TOKEN?.trim() ||
|
||||
process.env.CLAWHUB_AUTH_TOKEN?.trim();
|
||||
normalizeOptionalString(process.env.OPENCLAW_CLAWHUB_TOKEN) ||
|
||||
normalizeOptionalString(process.env.CLAWHUB_TOKEN) ||
|
||||
normalizeOptionalString(process.env.CLAWHUB_AUTH_TOKEN);
|
||||
if (envToken) {
|
||||
return envToken;
|
||||
}
|
||||
@@ -366,7 +366,7 @@ async function clawhubRequest(
|
||||
params: ClawHubRequestParams,
|
||||
): Promise<{ response: Response; url: URL }> {
|
||||
const url = buildUrl(params);
|
||||
const token = params.token?.trim() || (await resolveClawHubAuthToken());
|
||||
const token = normalizeOptionalString(params.token) || (await resolveClawHubAuthToken());
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(
|
||||
() =>
|
||||
|
||||
@@ -145,6 +145,10 @@ function normalizeOptionFlag(token: string): string {
|
||||
return normalizeLowercaseStringOrEmpty(token.split("=", 1)[0]);
|
||||
}
|
||||
|
||||
function readTrimmedArgToken(argv: readonly string[], index: number): string {
|
||||
return normalizeNullableString(argv[index]) ?? "";
|
||||
}
|
||||
|
||||
const POSIX_SHELL_OPTIONS_WITH_VALUE = new Set([
|
||||
"--init-file",
|
||||
"--rcfile",
|
||||
@@ -333,7 +337,7 @@ function normalizePackageManagerExecToken(token: string): string {
|
||||
function unwrapPnpmExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -377,7 +381,7 @@ function unwrapPnpmExecInvocation(argv: string[]): string[] | null {
|
||||
function unwrapPnpmDlxInvocation(argv: string[]): string[] | null {
|
||||
let idx = 0;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -411,7 +415,7 @@ function unwrapPnpmDlxInvocation(argv: string[]): string[] | null {
|
||||
function unwrapDirectPackageExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -439,7 +443,7 @@ function unwrapDirectPackageExecInvocation(argv: string[]): string[] | null {
|
||||
function unwrapNpmExecInvocation(argv: string[]): string[] | null {
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -480,7 +484,7 @@ function resolvePosixShellScriptOperandIndex(argv: string[]): number | null {
|
||||
}
|
||||
let afterDoubleDash = false;
|
||||
for (let i = 1; i < argv.length; i += 1) {
|
||||
const token = argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -517,7 +521,7 @@ function resolveOptionFilteredFileOperandIndex(params: {
|
||||
}): number | null {
|
||||
let afterDoubleDash = false;
|
||||
for (let i = params.startIndex; i < params.argv.length; i += 1) {
|
||||
const token = params.argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(params.argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -549,7 +553,7 @@ function resolveOptionFilteredPositionalIndex(params: {
|
||||
}): number | null {
|
||||
let afterDoubleDash = false;
|
||||
for (let i = params.startIndex; i < params.argv.length; i += 1) {
|
||||
const token = params.argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(params.argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -583,7 +587,7 @@ function collectExistingFileOperandIndexes(params: {
|
||||
let afterDoubleDash = false;
|
||||
const hits: number[] = [];
|
||||
for (let i = params.startIndex; i < params.argv.length; i += 1) {
|
||||
const token = params.argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(params.argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -607,7 +611,7 @@ function collectExistingFileOperandIndexes(params: {
|
||||
hits.push(i);
|
||||
return { hits, sawOptionValueFile: true };
|
||||
}
|
||||
const nextToken = params.argv[i + 1]?.trim() ?? "";
|
||||
const nextToken = readTrimmedArgToken(params.argv, i + 1);
|
||||
if (!inlineValue && nextToken && resolvesToExistingFileSync(nextToken, params.cwd)) {
|
||||
hits.push(i + 1);
|
||||
return { hits, sawOptionValueFile: true };
|
||||
@@ -651,7 +655,7 @@ function resolveBunScriptOperandIndex(params: {
|
||||
if (directIndex === null) {
|
||||
return null;
|
||||
}
|
||||
const directToken = params.argv[directIndex]?.trim() ?? "";
|
||||
const directToken = readTrimmedArgToken(params.argv, directIndex);
|
||||
if (directToken === "run") {
|
||||
return resolveOptionFilteredFileOperandIndex({
|
||||
argv: params.argv,
|
||||
@@ -673,7 +677,7 @@ function resolveDenoRunScriptOperandIndex(params: {
|
||||
argv: string[];
|
||||
cwd: string | undefined;
|
||||
}): number | null {
|
||||
if ((params.argv[1]?.trim() ?? "") !== "run") {
|
||||
if (readTrimmedArgToken(params.argv, 1) !== "run") {
|
||||
return null;
|
||||
}
|
||||
return resolveOptionFilteredFileOperandIndex({
|
||||
@@ -687,7 +691,7 @@ function resolveDenoRunScriptOperandIndex(params: {
|
||||
function hasRubyUnsafeApprovalFlag(argv: string[]): boolean {
|
||||
let afterDoubleDash = false;
|
||||
for (let i = 1; i < argv.length; i += 1) {
|
||||
const token = argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -714,7 +718,7 @@ function hasRubyUnsafeApprovalFlag(argv: string[]): boolean {
|
||||
function hasPerlUnsafeApprovalFlag(argv: string[]): boolean {
|
||||
let afterDoubleDash = false;
|
||||
for (let i = 1; i < argv.length; i += 1) {
|
||||
const token = argv[i]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, i);
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
@@ -753,7 +757,7 @@ function resolveMutableFileOperandIndex(argv: string[], cwd: string | undefined)
|
||||
return shellIndex === null ? null : unwrapped.baseIndex + shellIndex;
|
||||
}
|
||||
if (MUTABLE_ARGV1_INTERPRETER_PATTERNS.some((pattern) => pattern.test(executable))) {
|
||||
const operand = unwrapped.argv[1]?.trim() ?? "";
|
||||
const operand = readTrimmedArgToken(unwrapped.argv, 1);
|
||||
if (operand && operand !== "-" && !operand.startsWith("-")) {
|
||||
return unwrapped.baseIndex + 1;
|
||||
}
|
||||
@@ -810,7 +814,7 @@ function shellPayloadNeedsStableBinding(shellCommand: string, cwd: string | unde
|
||||
if (snapshot.snapshot) {
|
||||
return true;
|
||||
}
|
||||
const firstToken = argv[0]?.trim() ?? "";
|
||||
const firstToken = readTrimmedArgToken(argv, 0);
|
||||
return resolvesToExistingFileSync(firstToken, cwd);
|
||||
}
|
||||
|
||||
@@ -843,7 +847,7 @@ function pnpmDlxInvocationNeedsFailClosedBinding(argv: string[], cwd: string | u
|
||||
|
||||
let idx = 1;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -876,7 +880,7 @@ function pnpmDlxInvocationNeedsFailClosedBinding(argv: string[], cwd: string | u
|
||||
function pnpmDlxTailNeedsFailClosedBinding(argv: string[], cwd: string | undefined): boolean {
|
||||
let idx = 0;
|
||||
while (idx < argv.length) {
|
||||
const token = argv[idx]?.trim() ?? "";
|
||||
const token = readTrimmedArgToken(argv, idx);
|
||||
if (!token) {
|
||||
idx += 1;
|
||||
continue;
|
||||
@@ -935,7 +939,7 @@ export function resolveMutableFileOperandSnapshotSync(params: {
|
||||
}
|
||||
return { ok: true, snapshot: null };
|
||||
}
|
||||
const rawOperand = params.argv[argvIndex]?.trim();
|
||||
const rawOperand = readTrimmedArgToken(params.argv, argvIndex);
|
||||
if (!rawOperand) {
|
||||
return {
|
||||
ok: false,
|
||||
|
||||
@@ -11,7 +11,9 @@ import { readJsonFileWithFallback, writeJsonFileAtomically } from "../plugin-sdk
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeNullableString,
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
|
||||
const PAIRING_CODE_LENGTH = 8;
|
||||
@@ -98,7 +100,7 @@ function resolveAllowFromPath(
|
||||
accountId?: string,
|
||||
): string {
|
||||
const base = safeChannelKey(channel);
|
||||
const normalizedAccountId = typeof accountId === "string" ? accountId.trim() : "";
|
||||
const normalizedAccountId = normalizeOptionalString(accountId) ?? "";
|
||||
if (!normalizedAccountId) {
|
||||
return path.join(resolveCredentialsDir(env), `${base}-allowFrom.json`);
|
||||
}
|
||||
@@ -279,7 +281,7 @@ function resolveAllowFromAccountId(accountId?: string): string {
|
||||
}
|
||||
|
||||
function normalizeId(value: string | number): string {
|
||||
return String(value).trim();
|
||||
return normalizeStringifiedOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
function normalizeAllowEntry(channel: PairingChannel, entry: string): string {
|
||||
@@ -292,7 +294,7 @@ function normalizeAllowEntry(channel: PairingChannel, entry: string): string {
|
||||
}
|
||||
const adapter = getPairingAdapter(channel);
|
||||
const normalized = adapter?.normalizeAllowEntry ? adapter.normalizeAllowEntry(trimmed) : trimmed;
|
||||
return String(normalized).trim();
|
||||
return normalizeOptionalString(normalized) ?? "";
|
||||
}
|
||||
|
||||
function normalizeAllowFromList(channel: PairingChannel, store: AllowFromStore): string[] {
|
||||
@@ -310,7 +312,7 @@ function dedupePreserveOrder(entries: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const out: string[] = [];
|
||||
for (const entry of entries) {
|
||||
const normalized = String(entry).trim();
|
||||
const normalized = normalizeOptionalString(entry) ?? "";
|
||||
if (!normalized || seen.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
@@ -749,7 +751,7 @@ export async function upsertChannelPairingRequest(params: {
|
||||
params.meta && typeof params.meta === "object"
|
||||
? Object.fromEntries(
|
||||
Object.entries(params.meta)
|
||||
.map(([k, v]) => [k, String(v ?? "").trim()] as const)
|
||||
.map(([k, v]) => [k, normalizeOptionalString(v) ?? ""] as const)
|
||||
.filter(([_, v]) => Boolean(v)),
|
||||
)
|
||||
: undefined;
|
||||
@@ -769,17 +771,12 @@ export async function upsertChannelPairingRequest(params: {
|
||||
return requestMatchesAccountId(r, normalizedMatchingAccountId);
|
||||
});
|
||||
const existingCodes = new Set(
|
||||
reqs.map((req) =>
|
||||
String(req.code ?? "")
|
||||
.trim()
|
||||
.toUpperCase(),
|
||||
),
|
||||
reqs.map((req) => (normalizeOptionalString(req.code) ?? "").toUpperCase()),
|
||||
);
|
||||
|
||||
if (existingIdx >= 0) {
|
||||
const existing = reqs[existingIdx];
|
||||
const existingCode =
|
||||
existing && typeof existing.code === "string" ? existing.code.trim() : "";
|
||||
const existingCode = normalizeOptionalString(existing?.code) ?? "";
|
||||
const code = existingCode || generateUniqueCode(existingCodes);
|
||||
const next: PairingRequest = {
|
||||
id,
|
||||
@@ -838,7 +835,7 @@ export async function approveChannelPairingCode(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): Promise<{ id: string; entry?: PairingRequest } | null> {
|
||||
const env = params.env ?? process.env;
|
||||
const code = params.code.trim().toUpperCase();
|
||||
const code = (normalizeNullableString(params.code) ?? "").toUpperCase();
|
||||
if (!code) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { SecretProviderConfig, SecretRef, SecretRefSource } from "../config/types.secrets.js";
|
||||
import { isSafeExecutableValue } from "../infra/exec-safety.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { runSecretsApply, type SecretsApplyResult } from "./apply.js";
|
||||
import { createSecretsConfigIO } from "./config-io.js";
|
||||
import {
|
||||
@@ -198,7 +202,7 @@ async function promptOptionalPositiveInt(params: {
|
||||
message: params.message,
|
||||
initialValue: params.initialValue === undefined ? "" : String(params.initialValue),
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -211,7 +215,10 @@ async function promptOptionalPositiveInt(params: {
|
||||
}),
|
||||
"Secrets configure cancelled.",
|
||||
);
|
||||
const parsed = parseOptionalPositiveInt(String(raw ?? ""), params.max);
|
||||
const parsed = parseOptionalPositiveInt(
|
||||
normalizeStringifiedOptionalString(raw) ?? "",
|
||||
params.max,
|
||||
);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -221,7 +228,7 @@ function configureCandidateKey(candidate: {
|
||||
agentId?: string;
|
||||
}): string {
|
||||
if (candidate.configFile === "auth-profiles.json") {
|
||||
return `auth-profiles:${String(candidate.agentId ?? "").trim()}:${candidate.path}`;
|
||||
return `auth-profiles:${normalizeOptionalString(candidate.agentId) ?? ""}:${candidate.path}`;
|
||||
}
|
||||
return `openclaw:${candidate.path}`;
|
||||
}
|
||||
@@ -285,7 +292,7 @@ async function promptNewAuthProfileCandidate(agentId: string): Promise<Configure
|
||||
await text({
|
||||
message: "Auth profile id",
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -312,13 +319,13 @@ async function promptNewAuthProfileCandidate(agentId: string): Promise<Configure
|
||||
const provider = assertNoCancel(
|
||||
await text({
|
||||
message: "Provider id",
|
||||
validate: (value) => (String(value ?? "").trim().length > 0 ? undefined : "Required"),
|
||||
validate: (value) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
}),
|
||||
"Secrets configure cancelled.",
|
||||
);
|
||||
|
||||
const profileIdTrimmed = String(profileId).trim();
|
||||
const providerTrimmed = String(provider).trim();
|
||||
const profileIdTrimmed = normalizeStringifiedOptionalString(profileId) ?? "";
|
||||
const providerTrimmed = normalizeStringifiedOptionalString(provider) ?? "";
|
||||
if (credentialType === "token") {
|
||||
return {
|
||||
type: "auth-profiles.token.token",
|
||||
@@ -349,7 +356,7 @@ async function promptProviderAlias(params: { existingAliases: Set<string> }): Pr
|
||||
message: "Provider alias",
|
||||
initialValue: "default",
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -364,7 +371,7 @@ async function promptProviderAlias(params: { existingAliases: Set<string> }): Pr
|
||||
}),
|
||||
"Secrets configure cancelled.",
|
||||
);
|
||||
return String(alias).trim();
|
||||
return normalizeStringifiedOptionalString(alias) ?? "";
|
||||
}
|
||||
|
||||
async function promptProviderSource(initial?: SecretRefSource): Promise<SecretRefSource> {
|
||||
@@ -404,7 +411,7 @@ async function promptFileProvider(
|
||||
message: "File path (absolute)",
|
||||
initialValue: base?.path ?? "",
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -442,7 +449,7 @@ async function promptFileProvider(
|
||||
|
||||
return {
|
||||
source: "file",
|
||||
path: String(filePath).trim(),
|
||||
path: normalizeStringifiedOptionalString(filePath) ?? "",
|
||||
mode,
|
||||
...(timeoutMs ? { timeoutMs } : {}),
|
||||
...(maxBytes ? { maxBytes } : {}),
|
||||
@@ -469,7 +476,7 @@ async function promptExecProvider(
|
||||
message: "Command path (absolute)",
|
||||
initialValue: base?.command ?? "",
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -490,7 +497,7 @@ async function promptExecProvider(
|
||||
message: "Args JSON array (blank for none)",
|
||||
initialValue: JSON.stringify(base?.args ?? []),
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -571,12 +578,12 @@ async function promptExecProvider(
|
||||
"Secrets configure cancelled.",
|
||||
);
|
||||
|
||||
const args = await parseArgsInput(String(argsRaw ?? ""));
|
||||
const args = await parseArgsInput(normalizeStringifiedOptionalString(argsRaw) ?? "");
|
||||
const trustedDirs = parseCsv(String(trustedDirsRaw ?? ""));
|
||||
|
||||
return {
|
||||
source: "exec",
|
||||
command: String(command).trim(),
|
||||
command: normalizeStringifiedOptionalString(command) ?? "",
|
||||
...(args && args.length > 0 ? { args } : {}),
|
||||
...(timeoutMs ? { timeoutMs } : {}),
|
||||
...(noOutputTimeoutMs ? { noOutputTimeoutMs } : {}),
|
||||
@@ -864,7 +871,7 @@ export async function runSecretsConfigureInteractive(
|
||||
message: "Provider alias",
|
||||
initialValue: providerInitialValue,
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -876,7 +883,7 @@ export async function runSecretsConfigureInteractive(
|
||||
}),
|
||||
"Secrets configure cancelled.",
|
||||
);
|
||||
const providerAlias = String(provider).trim();
|
||||
const providerAlias = normalizeStringifiedOptionalString(provider) ?? "";
|
||||
const suggestedIdFromExistingRef =
|
||||
existingRef?.source === source ? existingRef.id : undefined;
|
||||
let suggestedId = suggestedIdFromExistingRef;
|
||||
@@ -894,7 +901,7 @@ export async function runSecretsConfigureInteractive(
|
||||
message: "Secret id",
|
||||
initialValue: suggestedId,
|
||||
validate: (value) => {
|
||||
const trimmed = String(value ?? "").trim();
|
||||
const trimmed = normalizeStringifiedOptionalString(value) ?? "";
|
||||
if (!trimmed) {
|
||||
return "Required";
|
||||
}
|
||||
@@ -909,7 +916,7 @@ export async function runSecretsConfigureInteractive(
|
||||
const ref: SecretRef = {
|
||||
source,
|
||||
provider: providerAlias,
|
||||
id: String(id).trim(),
|
||||
id: normalizeStringifiedOptionalString(id) ?? "",
|
||||
};
|
||||
if (ref.source === "exec" && !allowExecInPreflight) {
|
||||
const staticError = getSkippedExecRefStaticError({
|
||||
|
||||
@@ -27,7 +27,10 @@ import type { AgentToolsConfig } from "../config/types.tools.js";
|
||||
import { readInstalledPackageVersion } from "../infra/package-update-utils.js";
|
||||
import { normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import {
|
||||
formatPermissionDetail,
|
||||
formatPermissionRemediation,
|
||||
@@ -77,7 +80,7 @@ function expandTilde(p: string, env: NodeJS.ProcessEnv): string | null {
|
||||
if (!p.startsWith("~")) {
|
||||
return p;
|
||||
}
|
||||
const home = typeof env.HOME === "string" && env.HOME.trim() ? env.HOME.trim() : null;
|
||||
const home = normalizeOptionalString(env.HOME) ?? null;
|
||||
if (!home) {
|
||||
return null;
|
||||
}
|
||||
@@ -104,7 +107,7 @@ async function readPluginManifestExtensions(pluginPath: string): Promise<string[
|
||||
if (!Array.isArray(extensions)) {
|
||||
return [];
|
||||
}
|
||||
return extensions.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
|
||||
return extensions.map((entry) => normalizeOptionalString(entry) ?? "").filter(Boolean);
|
||||
}
|
||||
|
||||
function formatCodeSafetyDetails(findings: SkillScanFinding[], rootDir: string): string {
|
||||
@@ -434,7 +437,7 @@ async function listWorkspaceSkillMarkdownFiles(workspaceDir: string): Promise<st
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
function normalizeDockerLabelValue(raw: string | undefined): string | null {
|
||||
const trimmed = raw?.trim() ?? "";
|
||||
const trimmed = normalizeOptionalString(raw) ?? "";
|
||||
if (!trimmed || trimmed === "<no value>") {
|
||||
return null;
|
||||
}
|
||||
@@ -490,8 +493,10 @@ async function readSandboxBrowserHashLabels(params: {
|
||||
}
|
||||
|
||||
function parsePublishedHostFromDockerPortLine(line: string): string | null {
|
||||
const trimmed = line.trim();
|
||||
const rhs = trimmed.includes("->") ? (trimmed.split("->").at(-1)?.trim() ?? "") : trimmed;
|
||||
const trimmed = normalizeOptionalString(line) ?? "";
|
||||
const rhs = trimmed.includes("->")
|
||||
? (normalizeOptionalString(trimmed.split("->").at(-1)) ?? "")
|
||||
: trimmed;
|
||||
if (!rhs) {
|
||||
return null;
|
||||
}
|
||||
@@ -1061,7 +1066,12 @@ export async function collectStateDeepFilesystemFindings(params: {
|
||||
|
||||
const agentIds = Array.isArray(params.cfg.agents?.list)
|
||||
? params.cfg.agents?.list
|
||||
.map((a) => (a && typeof a === "object" && typeof a.id === "string" ? a.id.trim() : ""))
|
||||
.map(
|
||||
(a) =>
|
||||
normalizeOptionalString(
|
||||
a && typeof a === "object" ? (a as { id?: unknown }).id : undefined,
|
||||
) ?? "",
|
||||
)
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const defaultAgentId = resolveDefaultAgentId(params.cfg);
|
||||
@@ -1132,8 +1142,7 @@ export async function collectStateDeepFilesystemFindings(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const logFile =
|
||||
typeof params.cfg.logging?.file === "string" ? params.cfg.logging.file.trim() : "";
|
||||
const logFile = normalizeOptionalString(params.cfg.logging?.file) ?? "";
|
||||
if (logFile) {
|
||||
const expanded = logFile.startsWith("~") ? expandTilde(logFile, params.env) : logFile;
|
||||
if (expanded) {
|
||||
|
||||
@@ -27,7 +27,11 @@ import {
|
||||
DEFAULT_DANGEROUS_NODE_COMMANDS,
|
||||
resolveNodeCommandAllowlist,
|
||||
} from "../gateway/node-command-policy.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { pickSandboxToolPolicy } from "./audit-tool-policy.js";
|
||||
|
||||
export type SecurityAuditFinding = {
|
||||
@@ -161,7 +165,11 @@ function hasConfiguredDockerConfig(
|
||||
}
|
||||
|
||||
function normalizeNodeCommand(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
return normalizeOptionalString(value) ?? "";
|
||||
}
|
||||
|
||||
function isWildcardEntry(value: unknown): boolean {
|
||||
return normalizeStringifiedOptionalString(value) === "*";
|
||||
}
|
||||
|
||||
function listKnownNodeCommands(cfg: OpenClawConfig): Set<string> {
|
||||
@@ -350,12 +358,12 @@ function listPotentialMultiUserSignals(cfg: OpenClawConfig): string[] {
|
||||
}
|
||||
|
||||
const allowFrom = Array.isArray(section.allowFrom) ? section.allowFrom : [];
|
||||
if (allowFrom.some((entry) => String(entry).trim() === "*")) {
|
||||
if (allowFrom.some((entry) => isWildcardEntry(entry))) {
|
||||
out.add(`${basePath}.allowFrom includes "*"`);
|
||||
}
|
||||
|
||||
const groupAllowFrom = Array.isArray(section.groupAllowFrom) ? section.groupAllowFrom : [];
|
||||
if (groupAllowFrom.some((entry) => String(entry).trim() === "*")) {
|
||||
if (groupAllowFrom.some((entry) => isWildcardEntry(entry))) {
|
||||
out.add(`${basePath}.groupAllowFrom includes "*"`);
|
||||
}
|
||||
|
||||
@@ -367,7 +375,7 @@ function listPotentialMultiUserSignals(cfg: OpenClawConfig): string[] {
|
||||
out.add(`${basePath}.dm.policy="open"`);
|
||||
}
|
||||
const dmAllowFrom = Array.isArray(dmSection.allowFrom) ? dmSection.allowFrom : [];
|
||||
if (dmAllowFrom.some((entry) => String(entry).trim() === "*")) {
|
||||
if (dmAllowFrom.some((entry) => isWildcardEntry(entry))) {
|
||||
out.add(`${basePath}.dm.allowFrom includes "*"`);
|
||||
}
|
||||
}
|
||||
@@ -475,8 +483,7 @@ export function collectSyncedFolderFindings(params: {
|
||||
|
||||
export function collectSecretsInConfigFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
const password =
|
||||
typeof cfg.gateway?.auth?.password === "string" ? cfg.gateway.auth.password.trim() : "";
|
||||
const password = normalizeOptionalString(cfg.gateway?.auth?.password) ?? "";
|
||||
if (password && !looksLikeEnvRef(password)) {
|
||||
findings.push({
|
||||
checkId: "config.secrets.gateway_password_in_config",
|
||||
@@ -489,7 +496,7 @@ export function collectSecretsInConfigFindings(cfg: OpenClawConfig): SecurityAud
|
||||
});
|
||||
}
|
||||
|
||||
const hooksToken = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
||||
const hooksToken = normalizeOptionalString(cfg.hooks?.token) ?? "";
|
||||
if (cfg.hooks?.enabled === true && hooksToken && !looksLikeEnvRef(hooksToken)) {
|
||||
findings.push({
|
||||
checkId: "config.secrets.hooks_token_in_config",
|
||||
@@ -512,7 +519,7 @@ export function collectHooksHardeningFindings(
|
||||
return findings;
|
||||
}
|
||||
|
||||
const token = typeof cfg.hooks?.token === "string" ? cfg.hooks.token.trim() : "";
|
||||
const token = normalizeOptionalString(cfg.hooks?.token) ?? "";
|
||||
if (token && token.length < 24) {
|
||||
findings.push({
|
||||
checkId: "hooks.token_too_short",
|
||||
@@ -550,7 +557,7 @@ export function collectHooksHardeningFindings(
|
||||
});
|
||||
}
|
||||
|
||||
const rawPath = typeof cfg.hooks?.path === "string" ? cfg.hooks.path.trim() : "";
|
||||
const rawPath = normalizeOptionalString(cfg.hooks?.path) ?? "";
|
||||
if (rawPath === "/") {
|
||||
findings.push({
|
||||
checkId: "hooks.path_root",
|
||||
@@ -562,8 +569,7 @@ export function collectHooksHardeningFindings(
|
||||
}
|
||||
|
||||
const allowRequestSessionKey = cfg.hooks?.allowRequestSessionKey === true;
|
||||
const defaultSessionKey =
|
||||
typeof cfg.hooks?.defaultSessionKey === "string" ? cfg.hooks.defaultSessionKey.trim() : "";
|
||||
const defaultSessionKey = normalizeOptionalString(cfg.hooks?.defaultSessionKey) ?? "";
|
||||
const allowedAgentIds = resolveAllowedAgentIds(cfg.hooks?.allowedAgentIds);
|
||||
const allowedPrefixes = Array.isArray(cfg.hooks?.allowedSessionKeyPrefixes)
|
||||
? cfg.hooks.allowedSessionKeyPrefixes
|
||||
|
||||
Reference in New Issue
Block a user