Agents: add structured error observation logs#41023
Conversation
🔒 Aisle Security AnalysisWe found 5 potential security issue(s) in this PR:
1. 🟡 Unsalted truncated SHA-256 used as "redaction" enables identifier re-identification and cross-run correlation in logs
Description
Implications:
New call sites include:
Vulnerable core code: return `sha256:${sha256HexPrefix(trimmed, opts?.len ?? 12)}`;This is not reversible, but it is not a secure redaction primitive for identifiers because it is unkeyed and deterministic. RecommendationUse a keyed construction (HMAC) with a deployment secret (pepper) so log readers cannot brute-force identifiers, while preserving stability for correlation within the same deployment. Suggested implementation: import crypto from "node:crypto";
const LOG_REDACTION_KEY = process.env.LOG_REDACTION_KEY; // 32+ bytes recommended
export function redactIdentifier(value: string | undefined, opts?: { len?: number; purpose?: string }): string {
const trimmed = value?.trim();
if (!trimmed) return "-";
const len = Number.isFinite(opts?.len) ? Math.max(8, Math.floor(opts!.len!)) : 24;
const purpose = opts?.purpose ?? "id";
if (!LOG_REDACTION_KEY) {
// safer default than unkeyed hashes: avoid pretending to redact
return "redacted";
}
const digest = crypto
.createHmac("sha256", LOG_REDACTION_KEY)
.update(`${purpose}:`)
.update(trimmed)
.digest("hex")
.slice(0, len);
return `hmac256:${digest}`;
}Additionally:
2. 🟡 CPU DoS risk from unbounded provider error redaction/parsing in observation logging
DescriptionThe new observation sanitization path performs synchronous parsing and multi-pass regex redaction over the entire raw provider error string before truncation, allowing very large error payloads to consume substantial CPU and block the Node.js event loop. Key points:
Impact:
Vulnerable code (full-input parsing + redaction before truncation): const parsed = parseApiErrorInfo(trimmed);
...
const redactedRawPreview = replaceRequestIdPreview(redactObservationText(trimmed), requestId);
...
rawErrorPreview: truncateForObservation(redactedRawPreview, RAW_ERROR_PREVIEW_MAX_CHARS),RecommendationAdd guardrails so preview-generation never processes unbounded strings synchronously. Recommended changes:
const MAX_REDACT_INPUT = 32_768; // or lower
const trimmed = rawError?.trim();
if (!trimmed) return {};
const redactInput = trimmed.length > MAX_REDACT_INPUT ? trimmed.slice(0, MAX_REDACT_INPUT) : trimmed;
// Only parse/redact the bounded prefix used for previews/fingerprints
const parsed = parseApiErrorInfo(redactInput);
const redactedRawPreview = replaceRequestIdPreview(redactObservationText(redactInput), requestId);
3. 🟡 Console log suppression can be triggered by untrusted runId/sessionId prefix "probe-"
Description
This creates a log-evasion/suppression primitive if Relevant code (console suppression): const runLikeId = typeof params.meta?.runId === "string"
? params.meta.runId
: typeof params.meta?.sessionId === "string"
? params.meta.sessionId
: undefined;
if (runLikeId?.startsWith("probe-")) {
return true;
}
return /(sessionId|runId)=probe-/.test(params.message);[src/logging/subsystem.ts:253-283] Trust boundary / attacker control:
Impact:
Why this is security-relevant:
RecommendationDo not use user-controlled identifiers ( Recommended mitigations (pick one):
function shouldSuppressProbeConsoleLine({ level, subsystem, meta }: {...}): boolean {
if (isVerbose() || level === "error" || level === "fatal") return false;
const isProbeSuppressedSubsystem = /* same as today */;
if (!isProbeSuppressedSubsystem) return false;
return meta?.isProbe === true;
}
Also remove the message-regex fallback ( 4. 🔵 Log forging / terminal escape injection via unsanitized runId/provider/model in consoleMessage (failover observation)
Description
Because
Concrete input control:
Vulnerable code: consoleMessage:
`embedded run failover decision: runId=${normalizedBase.runId ?? "-"} ... ` +
`... provider=${normalizedBase.provider}/${normalizedBase.model} ...`A safer precedent exists in this repo: RecommendationSanitize every untrusted value before interpolating into Use the existing import { sanitizeForLog } from "../../../terminal/ansi.js";
const safeRunId = normalizedBase.runId ? sanitizeForLog(normalizedBase.runId) : "-";
const safeProvider = sanitizeForLog(normalizedBase.provider);
const safeModel = sanitizeForLog(normalizedBase.model);
consoleMessage:
`embedded run failover decision: runId=${safeRunId} stage=${normalizedBase.stage} decision=${decision} ` +
`reason=${reasonText} provider=${safeProvider}/${safeModel} profile=${profileText}`Optionally, add a defense-in-depth layer in 5. 🔵 Log forging / terminal escape injection via unsanitized runId/provider in consoleMessage (auth profile failure state logging)
Description
Vulnerable code: consoleMessage:
`auth profile failure state updated: runId=${params.runId ?? "-"} profile=${safeProfileId} provider=${params.provider} ` +
`reason=${params.reason} window=${windowType} reused=${String(windowReused)}`,Although RecommendationSanitize interpolated values in import { sanitizeForLog } from "../../terminal/ansi.js";
const safeRunId = params.runId ? sanitizeForLog(params.runId) : "-";
const safeProvider = sanitizeForLog(params.provider);
consoleMessage:
`auth profile failure state updated: runId=${safeRunId} profile=${safeProfileId} provider=${safeProvider} ` +
`reason=${params.reason} window=${windowType} reused=${String(windowReused)}`Defense-in-depth: consider sanitizing non-JSON console messages centrally in Analyzed PR: #41023 at commit Last updated on: 2026-03-09T18:09:55Z |
Greptile SummaryThis PR adds structured observation logs across the embedded agent error-handling path (lifecycle end, failover decisions, auth-profile state updates, and model-fallback decisions) and replaces always-on raw provider payload logging with sanitized Key findings:
Confidence Score: 3/5
Last reviewed commit: 6fdc781 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d784c8a554
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0bf381634
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 08991dfe60
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
08991df to
c02c8dc
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c02c8dcc71
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@greptileai review |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@greptileai review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6fdc781a9f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5528c2ab96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Split into stacked drafts:
Leaving #41023 as draft for reference while the stack takes over review. |
Summary
runIdcontext through the observation path so the full chain can be tailed withjq, while preserving current runtime behavior.Change Type
Scope
User-visible / Behavior Changes
openclaw logs --follow --jsonnow emits stableevent,tags, andrunIdfields for the new error-handling observation records.runId; this PR does not change cron run identity semantics.Example filters:
Security Impact
Validation
pnpm tsgopnpm test src/agents/pi-embedded-error-observation.test.tspnpm test src/agents/pi-embedded-subscribe.handlers.lifecycle.test.tspnpm test src/cron/isolated-agent/run.owner-auth.test.tspnpm exec vitest run --config vitest.e2e.config.ts src/agents/pi-embedded-runner.run-embedded-pi-agent.auth-profile-rotation.e2e.test.tspnpm exec vitest run --config vitest.e2e.config.ts src/agents/model-fallback.run-embedded.e2e.test.tsHuman Verification
createFailoverDecisionLogger(...),logAuthProfileFailureStateChange(...), andlogModelFallbackDecision(...)and confirmed all three emitted the same sanitized observation fields with the currentrunIdcontext.Compatibility / Migration
Failure Recovery
runIdon observation events or raw provider payload text appearing verbatim in file logs.Risks and Mitigations