mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
fix(qa): isolate packaged mock auth bootstrap (#126247)
* fix(qa): isolate packaged mock auth config
Punchcard-Session: frost-orchard-lantern-ze
(cherry picked from commit 648bd40a4f)
* fix(qa): scrub inherited shell startup env
* fix(qa): block exported Bash functions
This commit is contained in:
@@ -1530,9 +1530,19 @@ jobs:
|
||||
"$RUNTIME_ROOT"/tmp/openclaw-"$RUNNER_UID"/openclaw-qa-suite-*) ;;
|
||||
*) echo "SUT temp root escaped the workflow runtime root." >&2; exit 1 ;;
|
||||
esac
|
||||
requested_config_path="${OPENCLAW_CONFIG_PATH:?}"
|
||||
config_path="${temp_root}/openclaw.json"
|
||||
[[ -f "$config_path" && ! -L "$config_path" ]]
|
||||
[[ "$(realpath -e "$config_path")" == "$config_path" ]]
|
||||
case "$requested_config_path" in
|
||||
"$config_path") ;;
|
||||
"${temp_root}/state/qa-auth-bootstrap/openclaw.json")
|
||||
[[ "${1:-}" == "models" && "${2:-}" == "auth" ]]
|
||||
[[ -f "$requested_config_path" && ! -L "$requested_config_path" ]]
|
||||
[[ "$(realpath -e "$requested_config_path")" == "$requested_config_path" ]]
|
||||
;;
|
||||
*) echo "SUT config path escaped the canonical or auth-bootstrap roots." >&2; exit 1 ;;
|
||||
esac
|
||||
capture_live_model_config "$config_path"
|
||||
|
||||
export OPENCLAW_QA_TEMP_ROOT="$temp_root"
|
||||
@@ -1540,7 +1550,7 @@ jobs:
|
||||
export OPENCLAW_HOME="$HOME"
|
||||
export OPENCLAW_STATE_DIR="${temp_root}/state"
|
||||
export OPENCLAW_OAUTH_DIR="${OPENCLAW_STATE_DIR}/credentials"
|
||||
export OPENCLAW_CONFIG_PATH="$config_path"
|
||||
export OPENCLAW_CONFIG_PATH="$requested_config_path"
|
||||
export XDG_CACHE_HOME="${temp_root}/xdg-cache"
|
||||
export XDG_CONFIG_HOME="${temp_root}/xdg-config"
|
||||
export XDG_DATA_HOME="${temp_root}/xdg-data"
|
||||
@@ -1575,6 +1585,9 @@ jobs:
|
||||
chown -R "$SUT_UID:$SUT_GID" "$path"
|
||||
chmod -R u=rwX,go= "$path"
|
||||
done
|
||||
if [[ "$requested_config_path" != "$config_path" ]]; then
|
||||
[[ "$(stat -c '%F:%a:%u:%g' "$requested_config_path")" == "regular file:600:${SUT_UID}:${SUT_GID}" ]]
|
||||
fi
|
||||
if [[ -n "${OPENCLAW_BUNDLED_PLUGINS_DIR:-}" ]]; then
|
||||
chown -R root:root "$OPENCLAW_BUNDLED_PLUGINS_DIR"
|
||||
chmod -R a+rX,go-w "$OPENCLAW_BUNDLED_PLUGINS_DIR"
|
||||
@@ -1640,6 +1653,7 @@ jobs:
|
||||
export SUT_UID SUT_GID RUNNER_UID RUNNER_GID RUNNER_HOME RUNNER_TEMP_DIR
|
||||
export CANDIDATE_ROOT CANDIDATE_ARTIFACTS_DIR RUNTIME_ROOT NODE_BIN
|
||||
export PRELOAD_PATH RUNNER_SENTINEL TRUSTED_WORKSPACE EVIDENCE_ROOT
|
||||
export CANONICAL_CONFIG_PATH="$config_path"
|
||||
export boundary_mode generation command_file identity_file sandbox_file
|
||||
export command_sha256 expected_env_keys_b64 sandbox_payload_b64
|
||||
|
||||
@@ -1801,8 +1815,12 @@ jobs:
|
||||
runtime_stage=verify-runtime-files
|
||||
[[ -r "$CANDIDATE_ROOT/dist/index.js" &&
|
||||
! -w "$CANDIDATE_ROOT/dist/index.js" &&
|
||||
-r "${OPENCLAW_CONFIG_PATH:?}" &&
|
||||
! -w "$OPENCLAW_CONFIG_PATH" ]]
|
||||
-r "${CANONICAL_CONFIG_PATH:?}" &&
|
||||
! -w "$CANONICAL_CONFIG_PATH" ]]
|
||||
if [[ "$OPENCLAW_CONFIG_PATH" != "$CANONICAL_CONFIG_PATH" ]]; then
|
||||
[[ "${1:-}" == "models" && "${2:-}" == "auth" &&
|
||||
-r "$OPENCLAW_CONFIG_PATH" && -w "$OPENCLAW_CONFIG_PATH" ]]
|
||||
fi
|
||||
[[ -d "${CANDIDATE_ARTIFACTS_DIR:?}" && -r "$CANDIDATE_ARTIFACTS_DIR" && -x "$CANDIDATE_ARTIFACTS_DIR" && ! -w "$CANDIDATE_ARTIFACTS_DIR" ]]
|
||||
for writable_path in \
|
||||
"${OPENCLAW_QA_TEMP_ROOT:?}/workspace" \
|
||||
@@ -1839,6 +1857,7 @@ jobs:
|
||||
unset \
|
||||
CANDIDATE_ROOT \
|
||||
CANDIDATE_ARTIFACTS_DIR \
|
||||
CANONICAL_CONFIG_PATH \
|
||||
EVIDENCE_ROOT \
|
||||
NODE_BIN \
|
||||
PRELOAD_PATH \
|
||||
@@ -1880,9 +1899,6 @@ jobs:
|
||||
else
|
||||
runtime_node_args=("$runtime_candidate_root/dist/index.js" "$@")
|
||||
fi
|
||||
# Login Bash reads /etc/bash.bashrc with inherited nounset.
|
||||
# Add PS1 only after the attested inbound env-key comparison.
|
||||
export PS1=
|
||||
runtime_stage=exec-runtime
|
||||
exec "$runtime_node_bin" "${runtime_node_args[@]}"
|
||||
'\'' openclaw-sut "$@"
|
||||
|
||||
@@ -17,19 +17,29 @@ import {
|
||||
import { listMockCodexModelInfos } from "./providers/shared/mock-model-config.js";
|
||||
import type { RuntimeId } from "./runtime-parity.js";
|
||||
|
||||
const QA_GATEWAY_CHILD_BLOCKED_SECRET_ENV_VARS = Object.freeze([
|
||||
const QA_GATEWAY_CHILD_BLOCKED_ENV_VARS = Object.freeze([
|
||||
"BASH_ENV",
|
||||
"BASHOPTS",
|
||||
"ENV",
|
||||
"OPENCLAW_QA_CONVEX_SECRET_CI",
|
||||
"OPENCLAW_QA_CONVEX_SECRET_MAINTAINER",
|
||||
"OPENCLAW_QA_SUT_FORBIDDEN_SENTINEL",
|
||||
"OPENCLAW_QA_TELEGRAM_GROUP_ID",
|
||||
"OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN",
|
||||
"OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN",
|
||||
"SHELLOPTS",
|
||||
]);
|
||||
|
||||
function scrubQaGatewayChildSecretEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
for (const envKey of QA_GATEWAY_CHILD_BLOCKED_SECRET_ENV_VARS) {
|
||||
function scrubQaGatewayChildEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
for (const envKey of QA_GATEWAY_CHILD_BLOCKED_ENV_VARS) {
|
||||
delete env[envKey];
|
||||
}
|
||||
// Bash imports exported functions before the launcher can apply its allowlist.
|
||||
for (const envKey of Object.keys(env)) {
|
||||
if (envKey.startsWith("BASH_FUNC_")) {
|
||||
delete env[envKey];
|
||||
}
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
@@ -112,10 +122,12 @@ export function buildQaRuntimeEnv(params: {
|
||||
delete normalizedEnv.OPENCLAW_SKIP_CHANNELS;
|
||||
delete normalizedEnv.OPENCLAW_SKIP_PROVIDERS;
|
||||
Object.assign(normalizedEnv, params.runtimeEnvPatch);
|
||||
// Parent shell startup controls must be removed after caller patches so no
|
||||
// launcher or runtime child can import them before its own allowlist runs.
|
||||
normalizedEnv.OPENCLAW_BUILD_PRIVATE_QA = "1";
|
||||
delete normalizedEnv[QA_LIVE_ANTHROPIC_SETUP_TOKEN_ENV];
|
||||
delete normalizedEnv[QA_LIVE_SETUP_TOKEN_VALUE_ENV];
|
||||
return scrubQaGatewayChildSecretEnv(scrubQaGatewayChildTestRunnerEnv(normalizedEnv));
|
||||
return scrubQaGatewayChildEnv(scrubQaGatewayChildTestRunnerEnv(normalizedEnv));
|
||||
}
|
||||
|
||||
export async function stageQaCodexMockModelCatalog(params: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
// Qa Lab tests cover gateway child plugin behavior.
|
||||
import { EventEmitter, once } from "node:events";
|
||||
import { lstat, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||
@@ -160,22 +160,32 @@ if (!recordPath || !configPath || !stateDir) {
|
||||
throw new Error("missing fixture environment");
|
||||
}
|
||||
const record = (value) => fs.appendFileSync(recordPath, JSON.stringify(value) + "\\n");
|
||||
const authDbPath = path.join(stateDir, "agents", "qa", "agent", "openclaw-agent.sqlite");
|
||||
if (args[0] === "models") {
|
||||
let stdin = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
for await (const chunk of process.stdin) stdin += chunk;
|
||||
const provider = args[args.indexOf("--provider") + 1];
|
||||
const configStat = fs.lstatSync(configPath);
|
||||
record({
|
||||
kind: "auth",
|
||||
args,
|
||||
stdin,
|
||||
dbExists: fs.existsSync(path.join(stateDir, "agents", "qa", "agent", "openclaw-agent.sqlite")),
|
||||
authDbPath,
|
||||
dbExists: fs.existsSync(authDbPath),
|
||||
configPath,
|
||||
configMode: configStat.mode & 0o777,
|
||||
configRegular: configStat.isFile(),
|
||||
configSymlink: configStat.isSymbolicLink(),
|
||||
stateDir,
|
||||
env: {
|
||||
OPENCLAW_CLI: process.env.OPENCLAW_CLI,
|
||||
OPENCLAW_CONFIG_PATH: configPath,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
},
|
||||
});
|
||||
fs.mkdirSync(path.dirname(authDbPath), { recursive: true });
|
||||
fs.writeFileSync(authDbPath, "fixture auth");
|
||||
if (process.env.QA_FAIL_PROVIDER === provider) {
|
||||
process.stderr.write("Authorization: Bearer " + stdin.trim());
|
||||
process.exit(9);
|
||||
@@ -186,7 +196,16 @@ if (args[0] === "models") {
|
||||
process.exit(0);
|
||||
}
|
||||
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
record({ kind: "gateway", args, fixtureProfiles: config.fixtureProfiles });
|
||||
record({
|
||||
kind: "gateway",
|
||||
args,
|
||||
authDbPath,
|
||||
dbExists: fs.existsSync(authDbPath),
|
||||
configPath,
|
||||
authProfileIds: Object.keys(config.auth?.profiles ?? {}),
|
||||
fixtureProfiles: config.fixtureProfiles,
|
||||
stateDir,
|
||||
});
|
||||
process.stderr.write("fixture gateway exit");
|
||||
process.exit(17);
|
||||
`,
|
||||
@@ -793,6 +812,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
OPENCLAW_QA_TELEGRAM_GROUP_ID: "-1001234567890",
|
||||
OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN: "driver-token",
|
||||
OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN: "sut-token",
|
||||
"BASH_FUNC_sudo%%": "() { printf imported; }",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -804,8 +824,64 @@ describe("buildQaRuntimeEnv", () => {
|
||||
expect(env.OPENCLAW_QA_TELEGRAM_GROUP_ID).toBeUndefined();
|
||||
expect(env.OPENCLAW_QA_TELEGRAM_DRIVER_BOT_TOKEN).toBeUndefined();
|
||||
expect(env.OPENCLAW_QA_TELEGRAM_SUT_BOT_TOKEN).toBeUndefined();
|
||||
expect(env["BASH_FUNC_sudo%%"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it.runIf(process.platform === "linux")(
|
||||
"scrubs inherited shell startup env before the workflow allowlist runs",
|
||||
async () => {
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-shell-startup-env-");
|
||||
const markerPath = path.join(tempRoot, "bash-env-ran");
|
||||
const functionMarkerPath = path.join(tempRoot, "bash-function-ran");
|
||||
const bashEnvPath = path.join(tempRoot, "malicious-bash-env");
|
||||
const allowlistProbePath = path.join(tempRoot, "allowlist-probe.sh");
|
||||
await writeFile(bashEnvPath, `printf 'ran' > ${JSON.stringify(markerPath)}\n`, "utf8");
|
||||
await writeFile(
|
||||
allowlistProbePath,
|
||||
`
|
||||
set -Eeuo pipefail
|
||||
for key in BASH_ENV BASHOPTS ENV SHELLOPTS; do
|
||||
! compgen -e | grep -Fxq "$key"
|
||||
done
|
||||
declare -A keep_env=([SAFE_VALUE]=1)
|
||||
while IFS= read -r key; do
|
||||
if [[ -z "\${keep_env[$key]+x}" ]]; then
|
||||
unset "$key"
|
||||
fi
|
||||
done < <(compgen -e)
|
||||
printf '%s' "\${SAFE_VALUE:?}"
|
||||
`,
|
||||
"utf8",
|
||||
);
|
||||
const env = buildQaRuntimeEnv({
|
||||
...createParams({ SAFE_VALUE: "base" }),
|
||||
runtimeEnvPatch: {
|
||||
SAFE_VALUE: "allowlist-survived",
|
||||
BASH_ENV: bashEnvPath,
|
||||
BASHOPTS: "checkwinsize",
|
||||
ENV: bashEnvPath,
|
||||
SHELLOPTS: "braceexpand",
|
||||
"BASH_FUNC_compgen%%": `() { printf 'ran' > ${JSON.stringify(functionMarkerPath)}; builtin compgen "$@"; }`,
|
||||
},
|
||||
});
|
||||
|
||||
for (const key of ["BASH_ENV", "BASHOPTS", "ENV", "SHELLOPTS"]) {
|
||||
expect(env[key]).toBeUndefined();
|
||||
}
|
||||
expect(env["BASH_FUNC_compgen%%"]).toBeUndefined();
|
||||
|
||||
const result = spawnSync("/bin/bash", ["--noprofile", "--norc", allowlistProbePath], {
|
||||
encoding: "utf8",
|
||||
env,
|
||||
});
|
||||
|
||||
expect(result.status, result.stderr).toBe(0);
|
||||
expect(result.stdout).toBe("allowlist-survived");
|
||||
await expect(readFile(markerPath, "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
||||
await expect(readFile(functionMarkerPath, "utf8")).rejects.toMatchObject({ code: "ENOENT" });
|
||||
},
|
||||
);
|
||||
|
||||
it("re-scrubs blocked credentials in the spawned gateway child env", async () => {
|
||||
const tempParent = await tempDirs.makeTempDir("qa-gateway-env-scrub-");
|
||||
qaTempPathState.preferredTmpDir = tempParent;
|
||||
@@ -1442,16 +1518,28 @@ describe("buildQaRuntimeEnv", () => {
|
||||
],
|
||||
]);
|
||||
for (const record of authRecords) {
|
||||
expect(record.dbExists).toBe(false);
|
||||
expect(record.stdin).toMatch(/^sk-qa-mock-[a-f0-9]{32}\n$/u);
|
||||
expect(record.env).toMatchObject({
|
||||
OPENCLAW_CLI: "1",
|
||||
});
|
||||
expect(record.configMode).toBe(0o600);
|
||||
expect(record.configRegular).toBe(true);
|
||||
expect(record.configSymlink).toBe(false);
|
||||
}
|
||||
expect(authRecords.map((record) => record.dbExists)).toEqual([false, true]);
|
||||
const authConfigPaths = authRecords.map((record) => String(record.configPath));
|
||||
expect(new Set(authConfigPaths).size).toBe(1);
|
||||
expect(authConfigPaths[0]).toBe(
|
||||
path.join(String(authRecords[0]?.stateDir), "qa-auth-bootstrap", "openclaw.json"),
|
||||
);
|
||||
expect(records.at(-1)).toMatchObject({
|
||||
kind: "gateway",
|
||||
fixtureProfiles: ["openai", "anthropic"],
|
||||
authProfileIds: ["qa-mock-openai", "qa-mock-anthropic"],
|
||||
dbExists: true,
|
||||
});
|
||||
expect(records.at(-1)?.configPath).not.toBe(authConfigPaths[0]);
|
||||
expect(records.at(-1)?.fixtureProfiles).toBeUndefined();
|
||||
expect(new Set(records.map((record) => record.authDbPath)).size).toBe(1);
|
||||
});
|
||||
|
||||
it("blocks packaged gateway spawn when candidate auth bootstrap fails", async () => {
|
||||
@@ -1487,7 +1575,13 @@ describe("buildQaRuntimeEnv", () => {
|
||||
);
|
||||
const records = await readJsonLines(recordPath);
|
||||
expect(records).toHaveLength(1);
|
||||
expect(records[0]).toMatchObject({ kind: "auth", dbExists: false });
|
||||
expect(records[0]).toMatchObject({
|
||||
kind: "auth",
|
||||
dbExists: false,
|
||||
configMode: 0o600,
|
||||
configRegular: true,
|
||||
configSymlink: false,
|
||||
});
|
||||
const submittedKey = String(records[0]?.stdin).trim();
|
||||
expect(submittedKey).toMatch(/^sk-qa-mock-[a-f0-9]{32}$/u);
|
||||
expect(error.message).not.toContain(submittedKey);
|
||||
|
||||
@@ -68,7 +68,11 @@ import {
|
||||
stageQaLiveApiKeyProfiles,
|
||||
stageQaLiveAnthropicSetupToken,
|
||||
} from "./providers/live-frontier/auth.js";
|
||||
import { buildQaMockProfileId, stageQaMockAuthProfiles } from "./providers/shared/mock-auth.js";
|
||||
import {
|
||||
applyQaMockAuthProfileConfig,
|
||||
buildQaMockProfileId,
|
||||
stageQaMockAuthProfiles,
|
||||
} from "./providers/shared/mock-auth.js";
|
||||
import { seedQaAgentWorkspace } from "./qa-agent-workspace.js";
|
||||
import { buildQaGatewayConfig, type QaThinkingLevel } from "./qa-gateway-config.js";
|
||||
import type { QaTransportAdapter } from "./qa-transport.js";
|
||||
@@ -175,6 +179,7 @@ function createQaPackagedMockApiKey(): string {
|
||||
|
||||
async function stageQaPackagedMockAuthProfiles(params: {
|
||||
command: QaGatewayChildCommand;
|
||||
configPath: string;
|
||||
cwd: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
providers: readonly string[];
|
||||
@@ -196,7 +201,7 @@ async function stageQaPackagedMockAuthProfiles(params: {
|
||||
buildQaMockProfileId(provider),
|
||||
],
|
||||
cwd: params.command.cwd ?? params.cwd,
|
||||
env: params.env,
|
||||
env: { ...params.env, OPENCLAW_CONFIG_PATH: params.configPath },
|
||||
stdin: `${createQaPackagedMockApiKey()}\n`,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -270,6 +275,7 @@ export async function startQaGatewayChild(params: {
|
||||
const xdgDataHome = path.join(tempRoot, "xdg-data");
|
||||
const xdgCacheHome = path.join(tempRoot, "xdg-cache");
|
||||
const configPath = path.join(tempRoot, "openclaw.json");
|
||||
const packagedAuthConfigPath = path.join(stateDir, "qa-auth-bootstrap", "openclaw.json");
|
||||
const gatewayToken = `qa-suite-${randomUUID()}`;
|
||||
const transport = params.transport ?? createQaGatewayEmptyTransport();
|
||||
await seedQaAgentWorkspace({
|
||||
@@ -352,7 +358,9 @@ export async function startQaGatewayChild(params: {
|
||||
});
|
||||
const mockAuthProviders = getQaProvider(providerMode).mockAuthProviders;
|
||||
if (mockAuthProviders && mockAuthProviders.length > 0) {
|
||||
if (!usesPackagedCandidate) {
|
||||
if (usesPackagedCandidate) {
|
||||
cfg = applyQaMockAuthProfileConfig({ cfg, providers: mockAuthProviders });
|
||||
} else {
|
||||
cfg = await stageQaMockAuthProfiles({
|
||||
cfg,
|
||||
stateDir,
|
||||
@@ -378,6 +386,7 @@ export async function startQaGatewayChild(params: {
|
||||
let cfg!: OpenClawConfig;
|
||||
let getChildFailure: (() => QaChildFailure | null) | null = null;
|
||||
let env: NodeJS.ProcessEnv | null = null;
|
||||
let packagedMockAuthStaged = false;
|
||||
let migrationConvergenceRestartUsed = false;
|
||||
let reuseStartupLaunchState = false;
|
||||
|
||||
@@ -576,13 +585,29 @@ export async function startQaGatewayChild(params: {
|
||||
mode: 0o600,
|
||||
});
|
||||
const mockAuthProviders = getQaProvider(providerMode).mockAuthProviders;
|
||||
if (usesPackagedCandidate && gatewayCommand && mockAuthProviders?.length) {
|
||||
if (
|
||||
usesPackagedCandidate &&
|
||||
gatewayCommand &&
|
||||
mockAuthProviders?.length &&
|
||||
!packagedMockAuthStaged
|
||||
) {
|
||||
const canonicalConfig = await fs.readFile(configPath);
|
||||
await fs.mkdir(path.dirname(packagedAuthConfigPath), { recursive: true, mode: 0o700 });
|
||||
await fs.writeFile(packagedAuthConfigPath, canonicalConfig, {
|
||||
flag: "wx",
|
||||
mode: 0o600,
|
||||
});
|
||||
await stageQaPackagedMockAuthProfiles({
|
||||
command: gatewayCommand,
|
||||
configPath: packagedAuthConfigPath,
|
||||
cwd: gatewayCwd,
|
||||
env,
|
||||
providers: mockAuthProviders,
|
||||
});
|
||||
if (!canonicalConfig.equals(await fs.readFile(configPath))) {
|
||||
throw new Error("installed package mock auth bootstrap mutated canonical config");
|
||||
}
|
||||
packagedMockAuthStaged = true;
|
||||
}
|
||||
}
|
||||
if (!env) {
|
||||
|
||||
@@ -14,6 +14,22 @@ export function buildQaMockProfileId(provider: string): string {
|
||||
return `qa-mock-${provider}`;
|
||||
}
|
||||
|
||||
export function applyQaMockAuthProfileConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
providers?: readonly string[];
|
||||
}): OpenClawConfig {
|
||||
let next = params.cfg;
|
||||
for (const provider of uniqueStrings(params.providers ?? QA_MOCK_AUTH_PROVIDERS)) {
|
||||
next = applyAuthProfileConfig(next, {
|
||||
profileId: buildQaMockProfileId(provider),
|
||||
provider,
|
||||
mode: "api_key",
|
||||
displayName: `QA mock ${provider} credential`,
|
||||
});
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* In mock provider modes the qa suite runs against an embedded mock server
|
||||
* instead of a real provider API. The mock does not validate credentials, but
|
||||
@@ -41,7 +57,6 @@ export async function stageQaMockAuthProfiles(params: {
|
||||
}): Promise<OpenClawConfig> {
|
||||
const agentIds = uniqueStrings(params.agentIds ?? QA_MOCK_AUTH_AGENT_IDS);
|
||||
const providers = uniqueStrings(params.providers ?? QA_MOCK_AUTH_PROVIDERS);
|
||||
let next = params.cfg;
|
||||
for (const agentId of agentIds) {
|
||||
await writeQaAuthProfiles({
|
||||
agentId,
|
||||
@@ -59,13 +74,5 @@ export async function stageQaMockAuthProfiles(params: {
|
||||
stateDir: params.stateDir,
|
||||
});
|
||||
}
|
||||
for (const provider of providers) {
|
||||
next = applyAuthProfileConfig(next, {
|
||||
profileId: buildQaMockProfileId(provider),
|
||||
provider,
|
||||
mode: "api_key",
|
||||
displayName: `QA mock ${provider} credential`,
|
||||
});
|
||||
}
|
||||
return next;
|
||||
return applyQaMockAuthProfileConfig({ cfg: params.cfg, providers });
|
||||
}
|
||||
|
||||
@@ -925,24 +925,22 @@ describe("release Telegram QA workflow", () => {
|
||||
);
|
||||
expect(createSut).not.toContain('chmod 0711 "$temp_root"');
|
||||
expect(createSut).not.toContain('chmod 1777 "$temp_root"');
|
||||
expect(createSut).toContain('"${temp_root}/state/qa-auth-bootstrap/openclaw.json")');
|
||||
expect(createSut).toContain(
|
||||
'"$(stat -c \'%F:%a:%u:%g\' "$requested_config_path")" == "regular file:600:${SUT_UID}:${SUT_GID}"',
|
||||
);
|
||||
});
|
||||
|
||||
it("adds an empty PS1 only after attested runtime environment verification", () => {
|
||||
it("does not defer Bash startup cleanup to the privileged launcher", () => {
|
||||
const createSut = requireRun(
|
||||
"run_telegram",
|
||||
"Create isolated Telegram SUT identity and launcher",
|
||||
);
|
||||
const launcher = extractHereDocument(createSut, "LAUNCHER");
|
||||
const verification = '[[ "$actual_env_keys_b64" == "$runtime_expected_env_keys_b64" ]]';
|
||||
const ps1Export = "export PS1=";
|
||||
const candidateExec = 'exec "$runtime_node_bin" "${runtime_node_args[@]}"';
|
||||
|
||||
expect(launcher.match(/export PS1=/gu)).toHaveLength(1);
|
||||
expect(launcher.indexOf(verification)).toBeGreaterThan(-1);
|
||||
expect(launcher.indexOf(ps1Export)).toBeGreaterThan(launcher.indexOf(verification));
|
||||
expect(launcher.indexOf(candidateExec)).toBeGreaterThan(launcher.indexOf(ps1Export));
|
||||
expect(launcher.match(/exec "\$runtime_node_bin"/gu)).toHaveLength(1);
|
||||
expect(launcher).toContain('grep -Ev "^(PWD|SHLVL|_)$"');
|
||||
expect(launcher).not.toContain("export PS1=");
|
||||
expect(launcher).not.toContain("export -n BASHOPTS SHELLOPTS");
|
||||
expect(launcher).not.toContain("unset BASH_ENV ENV");
|
||||
});
|
||||
|
||||
it("mounts an isolated SUT-owned tmp without exposing the host tmp tree", () => {
|
||||
|
||||
Reference in New Issue
Block a user