You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Addresses Codex P2, Greptile P1+P2x2, and Aisle High #1 + Med #2/#3/#4
on PR #73261:
# Greptile P1 / Aisle High #1: asymmetric baseUrl (CWE-918, SSRF)
The previous guard:
if (typeof configuredProvider.baseUrl === 'string' &&
configuredProvider.baseUrl !== diskProvider.baseUrl)
short-circuited the check entirely when config omitted baseUrl
(common for providers with compiled-in defaults). An attacker who
could tamper with on-disk models.json could set baseUrl to an exfil
endpoint and the short-circuit would accept it silently \u2014 exactly
the SSRF/credential-exfil vector this PR was meant to close.
Replaced with symmetric stableEqual() so config-undefined vs
disk-string is a mismatch and falls through to full planning, which
re-applies provider/plugin defaults and rewrites the file.
# Codex P2: env-ref API key comparison
resolveConfiguredApiKeyForCompare resolved env refs to the env-var
VALUE (env[ref.id]). But planOpenClawModelsJson persists env-source
api keys to models.json as the env-var NAME (e.g. 'OPENAI_API_KEY'),
not the value \u2014 that's what resolveApiKeyFromCredential returns for
env source. Comparing value-vs-name always mismatched, so the
short-circuit never fired for the most common config form
('apiKey: "${env.OPENAI_API_KEY}"').
Now the helper returns the env-var NAME for env-source refs, while
still verifying the env is currently populated (so we don't
short-circuit on a misconfigured environment). Plaintext values
still compare directly.
# Greptile P2 (perf): readyCache integration
The short-circuit returned before the readyCache check, so warm
callers paid the disk read + JSON.parse cost on every call.
Reordered:
1. compute fingerprint (cheap)
2. check readyCache \u2014 warm hit returns immediately, no disk I/O
3. if cold, attempt short-circuit on disk
4. if short-circuit succeeds, populate readyCache so subsequent
calls take the warm path
Net effect: warm callers now skip disk entirely; cold callers with
intact disk state still get the short-circuit benefit; full plan
fires only on real drift.
# Aisle Med #2 (CWE-59): symlink-following short-circuit reads
readExistingProviderMatchesConfig used fs.readFile on a possibly-
symlinked target. Now lstat-checks first and refuses symlinks /
non-regular files.
# Aisle Med #3 (CWE-1321): prototype-chain key access
explicitProviders[targetProvider] could fall through to a prototype
key like '__proto__' / 'constructor' / 'prototype'. Now uses
Object.hasOwn at both lookup sites (caller AND
readExistingProviderMatchesConfig) to refuse inherited keys. Also
explicit string check rejects the three known dangerous keys.
# Aisle Med #4 (CWE-400): unbounded models.json read
Added MAX_MODELS_JSON_SHORT_CIRCUIT_BYTES = 1 MiB cap on the disk
read in the short-circuit path. Files above the cap fall through
to full planning rather than being parsed.
# Tests
Updated hit-after-warm-fingerprint test (Greptile P2) to use
fs.readFile spy and assert no disk read occurs on the warm path.
Added short-circuit-populates-cache test that drops the in-memory
cache between runs, fires the short-circuit, then verifies the
third call takes the warm path with no disk read.
14/14 tests pass. Lint: 0 errors.
0 commit comments