Skip to content

Commit eb5d4f6

Browse files
committed
refactor: drop legacy auth json secret audit
1 parent ae1d3d9 commit eb5d4f6

4 files changed

Lines changed: 2 additions & 156 deletions

File tree

docs/refactor/database-first.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ The branch already has a real shared SQLite base:
192192
- Secret target metadata now talks about stores instead of pretending every
193193
credential target is a config file. `openclaw.json` remains the config store;
194194
auth-profile targets use the SQLite auth profile store.
195+
- Secret audit no longer scans retired per-agent `auth.json` files. Doctor owns
196+
warning about, importing, and removing that legacy file.
195197
- Legacy auth profile path helpers now live in doctor legacy code. Core auth
196198
profile path helpers expose SQLite KV store identity and display locations,
197199
not `auth-profiles.json` or `auth-state.json` runtime paths.

src/secrets/audit.test.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ type AuditFixture = {
1212
stateDir: string;
1313
configPath: string;
1414
legacyAuthProfilePath: string;
15-
authJsonPath: string;
1615
agentDir: string;
1716
modelCatalogSource: string;
1817
envPath: string;
@@ -129,10 +128,6 @@ function expectFindingCode(report: Awaited<ReturnType<typeof runSecretsAudit>>,
129128
expect(hasFinding(report, (entry) => entry.code === code)).toBe(true);
130129
}
131130

132-
function expectFindingFile(report: Awaited<ReturnType<typeof runSecretsAudit>>, filePath: string) {
133-
expect(hasFinding(report, (entry) => entry.file === filePath)).toBe(true);
134-
}
135-
136131
async function expectPathMissing(filePath: string): Promise<void> {
137132
try {
138133
await fs.stat(filePath);
@@ -148,7 +143,6 @@ async function createAuditFixture(): Promise<AuditFixture> {
148143
const configPath = path.join(stateDir, "openclaw.json");
149144
const agentDir = path.join(stateDir, "agents", "main", "agent");
150145
const legacyAuthProfilePath = path.join(agentDir, "auth-profiles.json");
151-
const authJsonPath = path.join(stateDir, "agents", "main", "agent", "auth.json");
152146
const modelCatalogSource = `stored model catalog: ${agentDir}`;
153147
const envPath = path.join(stateDir, ".env");
154148

@@ -160,7 +154,6 @@ async function createAuditFixture(): Promise<AuditFixture> {
160154
stateDir,
161155
configPath,
162156
legacyAuthProfilePath,
163-
authJsonPath,
164157
agentDir,
165158
modelCatalogSource,
166159
envPath,
@@ -276,30 +269,6 @@ describe("secrets audit", () => {
276269
expectFindingCode(report, "PLAINTEXT_FOUND");
277270
});
278271

279-
it("does not mutate legacy auth.json during audit", async () => {
280-
await fs.rm(fixture.legacyAuthProfilePath, { force: true });
281-
await writeJsonFile(fixture.authJsonPath, {
282-
openai: {
283-
type: "api_key",
284-
key: "sk-legacy-auth-json",
285-
},
286-
});
287-
288-
const report = await runSecretsAudit({ env: fixture.env });
289-
expectFindingCode(report, "LEGACY_RESIDUE");
290-
const authJsonStat = await fs.stat(fixture.authJsonPath);
291-
expect(authJsonStat.isFile()).toBe(true);
292-
await expectPathMissing(fixture.legacyAuthProfilePath);
293-
});
294-
295-
it("reports malformed sidecar JSON as findings instead of crashing", async () => {
296-
await fs.writeFile(fixture.authJsonPath, "{invalid-json", "utf8");
297-
298-
const report = await runSecretsAudit({ env: fixture.env });
299-
expectFindingFile(report, fixture.authJsonPath);
300-
expectFindingCode(report, "REF_UNRESOLVED");
301-
});
302-
303272
it("skips exec ref resolution during audit unless explicitly allowed", async () => {
304273
if (process.platform === "win32") {
305274
return;

src/secrets/audit.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,7 @@ import { isNonEmptyString, isRecord } from "./shared.js";
3535
import {
3636
listAgentModelCatalogDirs,
3737
listAuthProfileStoreAgentDirs,
38-
listLegacyAuthJsonPaths,
3938
parseEnvAssignmentValue,
40-
readJsonObjectIfExists,
4139
} from "./storage-scan.js";
4240
import { discoverConfigSecretTargets } from "./target-registry.js";
4341

@@ -324,42 +322,6 @@ function collectAuthStoreSecrets(params: {
324322
}
325323
}
326324

327-
function collectAuthJsonResidue(params: { stateDir: string; collector: AuditCollector }): void {
328-
for (const authJsonPath of listLegacyAuthJsonPaths(params.stateDir)) {
329-
params.collector.filesScanned.add(authJsonPath);
330-
const parsedResult = readJsonObjectIfExists(authJsonPath);
331-
if (parsedResult.error) {
332-
addFinding(params.collector, {
333-
code: "REF_UNRESOLVED",
334-
severity: "error",
335-
file: authJsonPath,
336-
jsonPath: "<root>",
337-
message: `Invalid JSON in legacy auth.json: ${parsedResult.error}`,
338-
});
339-
continue;
340-
}
341-
const parsed = parsedResult.value;
342-
if (!parsed) {
343-
continue;
344-
}
345-
for (const [providerId, value] of Object.entries(parsed)) {
346-
if (!isRecord(value)) {
347-
continue;
348-
}
349-
if (value.type === "api_key" && isNonEmptyString(value.key)) {
350-
addFinding(params.collector, {
351-
code: "LEGACY_RESIDUE",
352-
severity: "warn",
353-
file: authJsonPath,
354-
jsonPath: providerId,
355-
message: "Legacy auth.json contains static api_key credentials.",
356-
provider: providerId,
357-
});
358-
}
359-
}
360-
}
361-
}
362-
363325
function collectStoredModelCatalogSecrets(params: {
364326
agentDir: string;
365327
env: NodeJS.ProcessEnv;
@@ -721,11 +683,6 @@ export async function runSecretsAudit(
721683
envPath,
722684
collector,
723685
});
724-
collectAuthJsonResidue({
725-
stateDir,
726-
collector,
727-
});
728-
729686
const summary = summarizeFindings(collector.findings);
730687
const status: SecretsAuditStatus =
731688
summary.unresolvedRefCount > 0

src/secrets/storage-scan.ts

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ import fs from "node:fs";
22
import path from "node:path";
33
import { listAgentIds, resolveAgentDir } from "../agents/agent-scope.js";
44
import type { OpenClawConfig } from "../config/types.openclaw.js";
5-
import { formatErrorMessage } from "../infra/errors.js";
65
import { resolveUserPath } from "../utils.js";
76
import { listAuthProfileStoreAgentDirs as listAuthProfileStoreAgentDirsFromAuthStorePaths } from "./auth-store-paths.js";
87
import { parseEnvValue } from "./shared.js";
98

10-
function isJsonObject(value: unknown): value is Record<string, unknown> {
11-
return typeof value === "object" && value !== null && !Array.isArray(value);
12-
}
13-
149
export function parseEnvAssignmentValue(raw: string): string {
1510
return parseEnvValue(raw);
1611
}
@@ -19,24 +14,6 @@ export function listAuthProfileStoreAgentDirs(config: OpenClawConfig, stateDir:
1914
return listAuthProfileStoreAgentDirsFromAuthStorePaths(config, stateDir);
2015
}
2116

22-
export function listLegacyAuthJsonPaths(stateDir: string): string[] {
23-
const out: string[] = [];
24-
const agentsRoot = path.join(resolveUserPath(stateDir), "agents");
25-
if (!fs.existsSync(agentsRoot)) {
26-
return out;
27-
}
28-
for (const entry of fs.readdirSync(agentsRoot, { withFileTypes: true })) {
29-
if (!entry.isDirectory()) {
30-
continue;
31-
}
32-
const candidate = path.join(agentsRoot, entry.name, "agent", "auth.json");
33-
if (fs.existsSync(candidate)) {
34-
out.push(candidate);
35-
}
36-
}
37-
return out;
38-
}
39-
4017
function resolveActiveAgentDir(stateDir: string, env: NodeJS.ProcessEnv = process.env): string {
4118
const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim();
4219
if (override) {
@@ -76,62 +53,3 @@ export function listAgentModelCatalogDirs(
7653

7754
return [...dirs];
7855
}
79-
80-
export type ReadJsonObjectOptions = {
81-
maxBytes?: number;
82-
requireRegularFile?: boolean;
83-
};
84-
85-
export function readJsonObjectIfExists(filePath: string): {
86-
value: Record<string, unknown> | null;
87-
error?: string;
88-
};
89-
export function readJsonObjectIfExists(
90-
filePath: string,
91-
options: ReadJsonObjectOptions,
92-
): {
93-
value: Record<string, unknown> | null;
94-
error?: string;
95-
};
96-
export function readJsonObjectIfExists(
97-
filePath: string,
98-
options: ReadJsonObjectOptions = {},
99-
): {
100-
value: Record<string, unknown> | null;
101-
error?: string;
102-
} {
103-
if (!fs.existsSync(filePath)) {
104-
return { value: null };
105-
}
106-
try {
107-
const stats = fs.statSync(filePath);
108-
if (options.requireRegularFile && !stats.isFile()) {
109-
return {
110-
value: null,
111-
error: `Refusing to read non-regular file: ${filePath}`,
112-
};
113-
}
114-
if (
115-
typeof options.maxBytes === "number" &&
116-
Number.isFinite(options.maxBytes) &&
117-
options.maxBytes >= 0 &&
118-
stats.size > options.maxBytes
119-
) {
120-
return {
121-
value: null,
122-
error: `Refusing to read oversized JSON (${stats.size} bytes): ${filePath}`,
123-
};
124-
}
125-
const raw = fs.readFileSync(filePath, "utf8");
126-
const parsed: unknown = JSON.parse(raw);
127-
if (!isJsonObject(parsed)) {
128-
return { value: null };
129-
}
130-
return { value: parsed };
131-
} catch (err) {
132-
return {
133-
value: null,
134-
error: formatErrorMessage(err),
135-
};
136-
}
137-
}

0 commit comments

Comments
 (0)