fix(secrets): store openclaw_gateway credentials as secret refs
The openclaw_gateway adapter ships no getConfigSchema(), so listAdapterSchemaSecretFieldKeys() falls back to FALLBACK_ADAPTER_SCHEMA_SECRET_FIELDS, which only had an entry for hermes_gateway. As a result no adapterConfig field of an openclaw_gateway agent was ever routed through normalizeSchemaSecretFieldForPersistence(), and authToken / token / password / devicePrivateKeyPem were persisted verbatim in agents.adapter_config. Declare those four fields for openclaw_gateway so they are converted to secret refs on write, exactly like hermes_gateway apiKey. Runtime resolution is unchanged: resolveAdapterConfigForRuntime() rehydrates the plaintext before execute(), so the adapter needs no change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4683f26c97
commit
1eba85bc1c
|
|
@ -262,6 +262,77 @@ describeEmbeddedPostgres("agent service secret binding sync", () => {
|
|||
expect(JSON.stringify(persistedConfig)).not.toContain(literalApiKey);
|
||||
});
|
||||
|
||||
it("converts OpenClaw gateway credential strings into persisted secret refs", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const literalAuthToken = `openclaw-token-${randomUUID()}`;
|
||||
const literalPassword = `openclaw-password-${randomUUID()}`;
|
||||
const literalPrivateKeyPem = [
|
||||
"-----BEGIN PRIVATE KEY-----",
|
||||
`openclaw-device-key-${randomUUID()}`,
|
||||
"-----END PRIVATE KEY-----",
|
||||
].join("\n");
|
||||
|
||||
const created = await agentService(db).create(companyId, {
|
||||
name: "OpenClaw Gateway",
|
||||
role: "engineer",
|
||||
status: "idle",
|
||||
adapterType: "openclaw_gateway",
|
||||
adapterConfig: {
|
||||
url: "ws://127.0.0.1:18789",
|
||||
authToken: literalAuthToken,
|
||||
password: literalPassword,
|
||||
devicePrivateKeyPem: literalPrivateKeyPem,
|
||||
},
|
||||
runtimeConfig: {},
|
||||
spentMonthlyCents: 0,
|
||||
lastHeartbeatAt: null,
|
||||
});
|
||||
|
||||
const persistedRows = await db
|
||||
.select()
|
||||
.from(agents)
|
||||
.where(eq(agents.id, created.id));
|
||||
const persistedConfig = persistedRows[0]?.adapterConfig as Record<string, unknown>;
|
||||
const serializedConfig = JSON.stringify(persistedConfig);
|
||||
expect(serializedConfig).not.toContain(literalAuthToken);
|
||||
expect(serializedConfig).not.toContain(literalPassword);
|
||||
expect(serializedConfig).not.toContain(literalPrivateKeyPem);
|
||||
for (const key of ["authToken", "password", "devicePrivateKeyPem"]) {
|
||||
expect(persistedConfig[key]).toMatchObject({
|
||||
type: "secret_ref",
|
||||
version: "latest",
|
||||
});
|
||||
}
|
||||
expect(persistedConfig.url).toBe("ws://127.0.0.1:18789");
|
||||
|
||||
const bindings = await db
|
||||
.select()
|
||||
.from(companySecretBindings)
|
||||
.where(and(
|
||||
eq(companySecretBindings.companyId, companyId),
|
||||
eq(companySecretBindings.targetType, "agent"),
|
||||
eq(companySecretBindings.targetId, created.id),
|
||||
));
|
||||
expect(bindings.map((binding) => binding.configPath).sort()).toEqual([
|
||||
"authToken",
|
||||
"devicePrivateKeyPem",
|
||||
"password",
|
||||
]);
|
||||
|
||||
const resolved = await secretService(db).resolveAdapterConfigForRuntime(
|
||||
companyId,
|
||||
persistedConfig,
|
||||
{
|
||||
consumerType: "agent",
|
||||
consumerId: created.id,
|
||||
},
|
||||
{ adapterType: "openclaw_gateway" },
|
||||
);
|
||||
expect(resolved.config.authToken).toBe(literalAuthToken);
|
||||
expect(resolved.config.password).toBe(literalPassword);
|
||||
expect(resolved.config.devicePrivateKeyPem).toBe(literalPrivateKeyPem);
|
||||
});
|
||||
|
||||
it("replaces agent secret bindings when adapterConfig env changes", async () => {
|
||||
const companyId = await seedCompany();
|
||||
const secrets = secretService(db);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ const COMING_SOON_SECRET_PROVIDERS: ReadonlySet<SecretProvider> = new Set([
|
|||
]);
|
||||
const FALLBACK_ADAPTER_SCHEMA_SECRET_FIELDS: Readonly<Record<string, readonly string[]>> = {
|
||||
hermes_gateway: ["apiKey"],
|
||||
openclaw_gateway: ["authToken", "token", "password", "devicePrivateKeyPem"],
|
||||
};
|
||||
const USER_SECRET_DEFINITION_KEY_UNIQUE_CONSTRAINT = "user_secret_definitions_company_key_uq";
|
||||
const USER_SECRET_VALUE_UNIQUE_CONSTRAINT = "company_secrets_user_definition_owner_uq";
|
||||
|
|
|
|||
Loading…
Reference in New Issue