mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 21:04:31 +00:00
fix: exclude maxTokens from config redaction + honor deleteAfterRun on skipped cron jobs (#13342)
* fix: exclude maxTokens and token-count fields from config redaction The /token/i regex in SENSITIVE_KEY_PATTERNS falsely matched fields like maxTokens, maxOutputTokens, maxCompletionTokens etc. These are numeric config fields for token counts, not sensitive credentials. Added a whitelist (SENSITIVE_KEY_WHITELIST) that explicitly excludes known token-count field names from redaction. This prevents config corruption when maxTokens gets replaced with __OPENCLAW_REDACTED__ during config round-trips. Fixes #13236 * fix: honor deleteAfterRun for one-shot 'at' jobs with 'skipped' status Previously, deleteAfterRun only triggered when result.status was 'ok'. For one-shot 'at' jobs, a 'skipped' status (e.g. empty heartbeat file) would leave the job in state but disabled, never getting cleaned up. Now deleteAfterRun also triggers on 'skipped' status for 'at' jobs, since a skipped one-shot job has no meaningful retry path. Fixes #13249 * Cron: format timer.ts --------- Co-authored-by: nice03 <niceyslee@gmail.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -111,6 +111,7 @@ describe("redactConfigSnapshot", () => {
|
|||||||
|
|
||||||
it("does not redact maxTokens-style fields", () => {
|
it("does not redact maxTokens-style fields", () => {
|
||||||
const snapshot = makeSnapshot({
|
const snapshot = makeSnapshot({
|
||||||
|
maxTokens: 16384,
|
||||||
models: {
|
models: {
|
||||||
providers: {
|
providers: {
|
||||||
openai: {
|
openai: {
|
||||||
@@ -124,12 +125,21 @@ describe("redactConfigSnapshot", () => {
|
|||||||
],
|
],
|
||||||
apiKey: "sk-proj-abcdef1234567890ghij",
|
apiKey: "sk-proj-abcdef1234567890ghij",
|
||||||
accessToken: "access-token-value-1234567890",
|
accessToken: "access-token-value-1234567890",
|
||||||
|
maxTokens: 8192,
|
||||||
|
maxOutputTokens: 4096,
|
||||||
|
maxCompletionTokens: 2048,
|
||||||
|
contextTokens: 128000,
|
||||||
|
tokenCount: 500,
|
||||||
|
tokenLimit: 100000,
|
||||||
|
tokenBudget: 50000,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
gateway: { auth: { token: "secret-gateway-token-value" } },
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = redactConfigSnapshot(snapshot);
|
const result = redactConfigSnapshot(snapshot);
|
||||||
|
expect((result.config as Record<string, unknown>).maxTokens).toBe(16384);
|
||||||
const models = result.config.models as Record<string, unknown>;
|
const models = result.config.models as Record<string, unknown>;
|
||||||
const providerList = ((
|
const providerList = ((
|
||||||
(models.providers as Record<string, unknown>).openai as Record<string, unknown>
|
(models.providers as Record<string, unknown>).openai as Record<string, unknown>
|
||||||
@@ -138,9 +148,19 @@ describe("redactConfigSnapshot", () => {
|
|||||||
expect(providerList[0]?.contextTokens).toBe(200000);
|
expect(providerList[0]?.contextTokens).toBe(200000);
|
||||||
expect(providerList[0]?.maxTokensField).toBe("max_completion_tokens");
|
expect(providerList[0]?.maxTokensField).toBe("max_completion_tokens");
|
||||||
|
|
||||||
const providers = (models.providers as Record<string, Record<string, string>>) ?? {};
|
const providers = (models.providers as Record<string, Record<string, unknown>>) ?? {};
|
||||||
expect(providers.openai.apiKey).toBe(REDACTED_SENTINEL);
|
expect(providers.openai.apiKey).toBe(REDACTED_SENTINEL);
|
||||||
expect(providers.openai.accessToken).toBe(REDACTED_SENTINEL);
|
expect(providers.openai.accessToken).toBe(REDACTED_SENTINEL);
|
||||||
|
expect(providers.openai.maxTokens).toBe(8192);
|
||||||
|
expect(providers.openai.maxOutputTokens).toBe(4096);
|
||||||
|
expect(providers.openai.maxCompletionTokens).toBe(2048);
|
||||||
|
expect(providers.openai.contextTokens).toBe(128000);
|
||||||
|
expect(providers.openai.tokenCount).toBe(500);
|
||||||
|
expect(providers.openai.tokenLimit).toBe(100000);
|
||||||
|
expect(providers.openai.tokenBudget).toBe(50000);
|
||||||
|
|
||||||
|
const gw = result.config.gateway as Record<string, Record<string, string>>;
|
||||||
|
expect(gw.auth.token).toBe(REDACTED_SENTINEL);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves hash unchanged", () => {
|
it("preserves hash unchanged", () => {
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ import type { ConfigFileSnapshot } from "./types.openclaw.js";
|
|||||||
*/
|
*/
|
||||||
export const REDACTED_SENTINEL = "__OPENCLAW_REDACTED__";
|
export const REDACTED_SENTINEL = "__OPENCLAW_REDACTED__";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-sensitive field names that happen to match sensitive patterns.
|
||||||
|
* These are explicitly excluded from redaction.
|
||||||
|
*/
|
||||||
|
const SENSITIVE_KEY_WHITELIST = new Set([
|
||||||
|
"maxtokens",
|
||||||
|
"maxoutputtokens",
|
||||||
|
"maxinputtokens",
|
||||||
|
"maxcompletiontokens",
|
||||||
|
"contexttokens",
|
||||||
|
"totaltokens",
|
||||||
|
"tokencount",
|
||||||
|
"tokenlimit",
|
||||||
|
"tokenbudget",
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patterns that identify sensitive config field names.
|
* Patterns that identify sensitive config field names.
|
||||||
* Aligned with the UI-hint logic in schema.ts.
|
* Aligned with the UI-hint logic in schema.ts.
|
||||||
@@ -15,6 +31,9 @@ export const REDACTED_SENTINEL = "__OPENCLAW_REDACTED__";
|
|||||||
const SENSITIVE_KEY_PATTERNS = [/token$/i, /password/i, /secret/i, /api.?key/i];
|
const SENSITIVE_KEY_PATTERNS = [/token$/i, /password/i, /secret/i, /api.?key/i];
|
||||||
|
|
||||||
function isSensitiveKey(key: string): boolean {
|
function isSensitiveKey(key: string): boolean {
|
||||||
|
if (SENSITIVE_KEY_WHITELIST.has(key.toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ function applyJobResult(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shouldDelete =
|
const shouldDelete =
|
||||||
job.schedule.kind === "at" && result.status === "ok" && job.deleteAfterRun === true;
|
job.schedule.kind === "at" &&
|
||||||
|
job.deleteAfterRun === true &&
|
||||||
|
(result.status === "ok" || result.status === "skipped");
|
||||||
|
|
||||||
if (!shouldDelete) {
|
if (!shouldDelete) {
|
||||||
if (job.schedule.kind === "at") {
|
if (job.schedule.kind === "at") {
|
||||||
|
|||||||
Reference in New Issue
Block a user