Merge pull request #261 from mvanhorn/fix/234-secret-env-redaction

fix(server): redact secret-sourced env vars in logs by provenance
This commit is contained in:
Dotta
2026-03-07 21:22:00 -06:00
committed by GitHub
3 changed files with 20 additions and 11 deletions

View File

@@ -245,7 +245,7 @@ export function agentRoutes(db: Db) {
adapterConfig: Record<string, unknown>, adapterConfig: Record<string, unknown>,
) { ) {
if (adapterType !== "opencode_local") return; if (adapterType !== "opencode_local") return;
const runtimeConfig = await secretsSvc.resolveAdapterConfigForRuntime(companyId, adapterConfig); const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(companyId, adapterConfig);
const runtimeEnv = asRecord(runtimeConfig.env) ?? {}; const runtimeEnv = asRecord(runtimeConfig.env) ?? {};
try { try {
await ensureOpenCodeModelConfiguredAndAvailable({ await ensureOpenCodeModelConfiguredAndAvailable({
@@ -420,7 +420,7 @@ export function agentRoutes(db: Db) {
inputAdapterConfig, inputAdapterConfig,
{ strictMode: strictSecretsMode }, { strictMode: strictSecretsMode },
); );
const runtimeAdapterConfig = await secretsSvc.resolveAdapterConfigForRuntime( const { config: runtimeAdapterConfig } = await secretsSvc.resolveAdapterConfigForRuntime(
companyId, companyId,
normalizedAdapterConfig, normalizedAdapterConfig,
); );
@@ -1264,7 +1264,7 @@ export function agentRoutes(db: Db) {
} }
const config = asRecord(agent.adapterConfig) ?? {}; const config = asRecord(agent.adapterConfig) ?? {};
const runtimeConfig = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, config); const { config: runtimeConfig } = await secretsSvc.resolveAdapterConfigForRuntime(agent.companyId, config);
const result = await runClaudeLogin({ const result = await runClaudeLogin({
runId: `claude-login-${randomUUID()}`, runId: `claude-login-${randomUUID()}`,
agent: { agent: {

View File

@@ -1240,11 +1240,16 @@ export function heartbeatService(db: Db) {
const mergedConfig = issueAssigneeOverrides?.adapterConfig const mergedConfig = issueAssigneeOverrides?.adapterConfig
? { ...config, ...issueAssigneeOverrides.adapterConfig } ? { ...config, ...issueAssigneeOverrides.adapterConfig }
: config; : config;
const resolvedConfig = await secretsSvc.resolveAdapterConfigForRuntime( const { config: resolvedConfig, secretKeys } = await secretsSvc.resolveAdapterConfigForRuntime(
agent.companyId, agent.companyId,
mergedConfig, mergedConfig,
); );
const onAdapterMeta = async (meta: AdapterInvocationMeta) => { const onAdapterMeta = async (meta: AdapterInvocationMeta) => {
if (meta.env && secretKeys.size > 0) {
for (const key of secretKeys) {
if (key in meta.env) meta.env[key] = "***REDACTED***";
}
}
await appendRunEvent(currentRun, seq++, { await appendRunEvent(currentRun, seq++, {
eventType: "adapter.invoke", eventType: "adapter.invoke",
stream: "system", stream: "system",

View File

@@ -308,10 +308,11 @@ export function secretService(db: Db) {
return normalized; return normalized;
}, },
resolveEnvBindings: async (companyId: string, envValue: unknown) => { resolveEnvBindings: async (companyId: string, envValue: unknown): Promise<{ env: Record<string, string>; secretKeys: Set<string> }> => {
const record = asRecord(envValue); const record = asRecord(envValue);
if (!record) return {} as Record<string, string>; if (!record) return { env: {} as Record<string, string>, secretKeys: new Set<string>() };
const resolved: Record<string, string> = {}; const resolved: Record<string, string> = {};
const secretKeys = new Set<string>();
for (const [key, rawBinding] of Object.entries(record)) { for (const [key, rawBinding] of Object.entries(record)) {
if (!ENV_KEY_RE.test(key)) { if (!ENV_KEY_RE.test(key)) {
@@ -326,20 +327,22 @@ export function secretService(db: Db) {
resolved[key] = binding.value; resolved[key] = binding.value;
} else { } else {
resolved[key] = await resolveSecretValue(companyId, binding.secretId, binding.version); resolved[key] = await resolveSecretValue(companyId, binding.secretId, binding.version);
secretKeys.add(key);
} }
} }
return resolved; return { env: resolved, secretKeys };
}, },
resolveAdapterConfigForRuntime: async (companyId: string, adapterConfig: Record<string, unknown>) => { resolveAdapterConfigForRuntime: async (companyId: string, adapterConfig: Record<string, unknown>): Promise<{ config: Record<string, unknown>; secretKeys: Set<string> }> => {
const resolved = { ...adapterConfig }; const resolved = { ...adapterConfig };
const secretKeys = new Set<string>();
if (!Object.prototype.hasOwnProperty.call(adapterConfig, "env")) { if (!Object.prototype.hasOwnProperty.call(adapterConfig, "env")) {
return resolved; return { config: resolved, secretKeys };
} }
const record = asRecord(adapterConfig.env); const record = asRecord(adapterConfig.env);
if (!record) { if (!record) {
resolved.env = {}; resolved.env = {};
return resolved; return { config: resolved, secretKeys };
} }
const env: Record<string, string> = {}; const env: Record<string, string> = {};
for (const [key, rawBinding] of Object.entries(record)) { for (const [key, rawBinding] of Object.entries(record)) {
@@ -355,10 +358,11 @@ export function secretService(db: Db) {
env[key] = binding.value; env[key] = binding.value;
} else { } else {
env[key] = await resolveSecretValue(companyId, binding.secretId, binding.version); env[key] = await resolveSecretValue(companyId, binding.secretId, binding.version);
secretKeys.add(key);
} }
} }
resolved.env = env; resolved.env = env;
return resolved; return { config: resolved, secretKeys };
}, },
}; };
} }