fix(config): harden SecretRef round-trip handling in Control UI and RPC writes (#58044)

* Config: harden SecretRef round-trip handling

* Gateway: test SecretRef preflight on config writes

* Agents: align skill loader with upstream Skill type

* Docs: align SecretRef write semantics with Control UI and RPC behavior

* Config: add UI and gateway regression evidence for SecretRef hardening

* Config: add token SecretRef restore regression and skill sourceInfo compat

* UI: scope structured-value lockout to SecretRef fields

* Agents: remove out-of-scope skill loader compat edits

* UI: reduce app-render churn to rawAvailable-only changes

* Gateway: scope SecretRef preflight to submitted config

* Docs: clarify config write SecretRef preflight scope

* changelog

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>

---------

Signed-off-by: joshavant <830519+joshavant@users.noreply.github.com>
This commit is contained in:
Josh Avant
2026-03-30 23:55:03 -05:00
committed by GitHub
parent f7ced438f7
commit 81b777c768
20 changed files with 1115 additions and 273 deletions

View File

@@ -348,6 +348,39 @@ describe("secrets runtime snapshot", () => {
expect(snapshot.config.channels?.matrix?.accessToken).toBe("default-matrix-token");
});
it("can skip auth-profile SecretRef resolution when includeAuthStoreRefs is false", async () => {
const missingEnvVar = `OPENCLAW_MISSING_AUTH_PROFILE_SECRET_${Date.now()}`;
delete process.env[missingEnvVar];
const loadAuthStore = () =>
loadAuthStoreWithProfiles({
"custom:token": {
type: "token",
provider: "custom",
tokenRef: { source: "env", provider: "default", id: missingEnvVar },
},
});
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore,
}),
).rejects.toThrow(`Environment variable "${missingEnvVar}" is missing or empty.`);
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({}),
env: {},
includeAuthStoreRefs: false,
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore,
});
expect(snapshot.authStores).toEqual([]);
});
it("ignores Matrix password refs that are shadowed by scoped env access tokens", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({