mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 19:01:44 +00:00
Secrets: gate exec dry-run and preflight resolution behind --allow-exec (#49417)
* Secrets: gate exec dry-run resolution behind --allow-exec * Secrets: fix dry-run completeness and skipped exec audit semantics * Secrets: require --allow-exec for exec-containing apply writes * Docs: align secrets exec consent behavior * Changelog: note secrets exec consent gating
This commit is contained in:
@@ -15,6 +15,7 @@ type SecretsReloadOptions = GatewayRpcOpts & { json?: boolean };
|
||||
type SecretsAuditOptions = {
|
||||
check?: boolean;
|
||||
json?: boolean;
|
||||
allowExec?: boolean;
|
||||
};
|
||||
type SecretsConfigureOptions = {
|
||||
apply?: boolean;
|
||||
@@ -23,11 +24,13 @@ type SecretsConfigureOptions = {
|
||||
providersOnly?: boolean;
|
||||
skipProviderSetup?: boolean;
|
||||
agent?: string;
|
||||
allowExec?: boolean;
|
||||
json?: boolean;
|
||||
};
|
||||
type SecretsApplyOptions = {
|
||||
from: string;
|
||||
dryRun?: boolean;
|
||||
allowExec?: boolean;
|
||||
json?: boolean;
|
||||
};
|
||||
|
||||
@@ -82,10 +85,17 @@ export function registerSecretsCli(program: Command) {
|
||||
.command("audit")
|
||||
.description("Audit plaintext secrets, unresolved refs, and precedence drift")
|
||||
.option("--check", "Exit non-zero when findings are present", false)
|
||||
.option(
|
||||
"--allow-exec",
|
||||
"Allow exec SecretRef resolution during audit (may execute provider commands)",
|
||||
false,
|
||||
)
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts: SecretsAuditOptions) => {
|
||||
try {
|
||||
const report = await runSecretsAudit();
|
||||
const report = await runSecretsAudit({
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(report, null, 2));
|
||||
} else {
|
||||
@@ -102,6 +112,11 @@ export function registerSecretsCli(program: Command) {
|
||||
defaultRuntime.log(`... ${report.findings.length - 20} more finding(s).`);
|
||||
}
|
||||
}
|
||||
if (report.resolution.skippedExecRefs > 0) {
|
||||
defaultRuntime.log(
|
||||
`Audit note: skipped ${report.resolution.skippedExecRefs} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during audit.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const exitCode = resolveSecretsAuditExitCode(report, Boolean(opts.check));
|
||||
if (exitCode !== 0) {
|
||||
@@ -128,6 +143,11 @@ export function registerSecretsCli(program: Command) {
|
||||
"--agent <id>",
|
||||
"Agent id for auth-profiles targets (default: configured default agent)",
|
||||
)
|
||||
.option(
|
||||
"--allow-exec",
|
||||
"Allow exec SecretRef preflight checks (may execute provider commands)",
|
||||
false,
|
||||
)
|
||||
.option("--plan-out <path>", "Write generated plan JSON to a file")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts: SecretsConfigureOptions) => {
|
||||
@@ -136,6 +156,7 @@ export function registerSecretsCli(program: Command) {
|
||||
providersOnly: Boolean(opts.providersOnly),
|
||||
skipProviderSetup: Boolean(opts.skipProviderSetup),
|
||||
agentId: typeof opts.agent === "string" ? opts.agent : undefined,
|
||||
allowExecInPreflight: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.planOut) {
|
||||
fs.writeFileSync(opts.planOut, `${JSON.stringify(configured.plan, null, 2)}\n`, "utf8");
|
||||
@@ -160,6 +181,14 @@ export function registerSecretsCli(program: Command) {
|
||||
defaultRuntime.log(`- warning: ${warning}`);
|
||||
}
|
||||
}
|
||||
if (
|
||||
!configured.preflight.checks.resolvabilityComplete &&
|
||||
configured.preflight.skippedExecRefs > 0
|
||||
) {
|
||||
defaultRuntime.log(
|
||||
`Preflight note: skipped ${configured.preflight.skippedExecRefs} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during preflight.`,
|
||||
);
|
||||
}
|
||||
const providerUpserts = Object.keys(configured.plan.providerUpserts ?? {}).length;
|
||||
const providerDeletes = configured.plan.providerDeletes?.length ?? 0;
|
||||
defaultRuntime.log(
|
||||
@@ -196,6 +225,7 @@ export function registerSecretsCli(program: Command) {
|
||||
const result = await runSecretsApply({
|
||||
plan: configured.plan,
|
||||
write: true,
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
@@ -218,6 +248,7 @@ export function registerSecretsCli(program: Command) {
|
||||
.description("Apply a previously generated secrets plan")
|
||||
.requiredOption("--from <path>", "Path to plan JSON")
|
||||
.option("--dry-run", "Validate/preflight only", false)
|
||||
.option("--allow-exec", "Allow exec SecretRef checks (may execute provider commands)", false)
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts: SecretsApplyOptions) => {
|
||||
try {
|
||||
@@ -225,6 +256,7 @@ export function registerSecretsCli(program: Command) {
|
||||
const result = await runSecretsApply({
|
||||
plan,
|
||||
write: !opts.dryRun,
|
||||
allowExec: Boolean(opts.allowExec),
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
@@ -236,6 +268,11 @@ export function registerSecretsCli(program: Command) {
|
||||
? `Secrets apply dry run: ${result.changedFiles.length} file(s) would change.`
|
||||
: "Secrets apply dry run: no changes.",
|
||||
);
|
||||
if (!result.checks.resolvabilityComplete && result.skippedExecRefs > 0) {
|
||||
defaultRuntime.log(
|
||||
`Secrets apply dry-run note: skipped ${result.skippedExecRefs} exec SecretRef resolvability check(s). Re-run with --allow-exec to execute exec providers during dry-run.`,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(
|
||||
|
||||
Reference in New Issue
Block a user