fix(providers): reserve credential placeholder revisions#2049
Conversation
Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
|
🌿 Preview your docs: https://nvidia-preview-pr-2049.docs.buildwithfern.com/openshell |
|
Label |
elezar
left a comment
There was a problem hiding this comment.
Review: reserve credential placeholder revisions
Solid change overall — the reservation closes a real ambiguity, and the graceful fallback (stale revisioned placeholder → current credential only when the key still exists) is well-tested. A few things to address before merge, plus notes.
1. The two reserved-namespace checks are duplicated across crates (gate on this)
secrets.rs::uses_reserved_revision_namespace (openshell-core) and profiles.rs::uses_reserved_placeholder_revision_namespace (openshell-providers) are semantically identical — same v<digits>_<key> grammar, same digit check, same emptiness checks. The only textual difference is that secrets.rs names the byte predicate (is_env_key_char) while profiles.rs inlines |b| b.is_ascii_alphanumeric() || b == b'_' — and is_env_key_char (secrets.rs:18) is exactly that. So they're byte-for-byte equivalent behavior.
This matters because they're the two enforcement halves of one invariant:
profiles.rsis the load-time gate — rejects a profile whosecredentials.env_varsname lands in the reserved namespace (profiles.rs:1138).secrets.rsis the runtime enforcement — skips such keys when building the resolver (secrets.rs:180).
If these ever drift, you get a real gap: a key the validator accepts but the runtime strips (credential silently missing at runtime), or — worse — a runtime check narrower than the validator, letting a crafted key slip past stripping and collide with a generated placeholder, which is exactly the ambiguity the reservation exists to prevent.
The reserved namespace is defined by the placeholder format in secrets.rs (placeholder_for_env_key_for_revision → {PREFIX}v{revision}_{key}), and openshell-providers already depends on openshell-core. Recommendation: keep one canonical check in openshell-core::secrets, make it pub, have profiles.rs call it, and delete the providers-side copy. Drift becomes structurally impossible.
Bonus: there's a third copy of the same grammar — revisioned_placeholder_env_key (secrets.rs:489) parses {PREFIX}v<digits>_<KEY> with the identical tail. All three want one private parser, e.g. fn split_revisioned_key(s: &str) -> Option<(&str, &str)>, with uses_reserved_revision_namespace/revisioned_placeholder_env_key as thin wrappers and a pub export for providers. That also collapses the two hand-maintained edge-case test suites into one.
2. Scope — three unrelated changes bundled
The PR is fix(providers): reserve credential placeholder revisions, but also carries (a) the OCSF NET:FAIL message-formatting change and (b) the codex files.openai.com allowlist entry. Neither relates to the reservation. I understand these were carved out of #1826, but they're independently reviewable/revertable — please split (b) at minimum (it arguably warrants its own issue), and justify (a) on its own.
3. Fallback ignores the revision value entirely
In resolve_placeholder, once the direct lookup misses, revisioned_placeholder_env_key extracts only the key and resolves via the current canonical alias — the numeric revision is discarded. So v999999_GITHUB_TOKEN (a revision that never existed) resolves to the current token identically to a genuinely-aged-out v10_, and any retained v<N>_ is honored indefinitely while the key exists. Placeholders are sandbox-internal, so this is likely acceptable — but the revision component is non-authoritative on the fallback path, and stale vs. bogus revisions are indistinguishable. Please confirm that's intended and worth a comment.
Notes (non-blocking)
- OCSF
activity == "FAIL"is stringly-typed (shorthand.rs). Behaviorally correct (Fail is a valid NetworkActivityactivity_id), but special-casing the uppercased display name is brittle — if the label is ever reworded the message silently stops appearing. Prefer matching the typedActivityId::Fail. - Broadened OCSF message emission — confirm no secrets. NET:FAIL now surfaces
messageeven when dst/rule/reason context is present. AGENTS.md is explicit that OCSF messages must never contain secrets and the JSONL may ship externally. Please confirm failure-path messages are builder-controlled and sanitized. - codex
files.openai.comgrantedread-write. Consistent with sibling endpoints, but a file CDN host is worth a deliberateread-writevsreaddecision; no test covers the profile change. - Double-parse / double-warn per install.
from_provider_env_for_current_revisionclones the env and parses it twice (falsethentrue), so a reserved-namespace key emits the samewarn!twice per install. The current-aliases pass could be derived from the first rather than re-parsed. by_placeholder.is_empty() → None. A non-empty provider_env consisting entirely of reserved keys now yieldsNoneinstead ofSome(empty)— confirm no caller treatsSomeas "provider env was present." (Low risk;mergealready collapses empty→None.)- Reserved keys are dropped from
child_env, not just the resolver. Safe given the production caller (provider_credentials.rs) feeds profile-validated keys that the new load-time gate already rejects — butv<digits>_is a broad reservation; a one-line comment noting that assumption would help.
Checked and not issues (for the record)
- Merge order does not corrupt the "current" fallback. Per-revision resolvers in
generationsare built withinclude_current_aliases=false(revisioned placeholders only, no collisions); the canonical alias is contributed solely bycurrent_resolver, replaced on everyinstall_environmentand chained last inmerge_resolvers. Fallback always points to the newest revision. - Retained in-window revisions resolve to their own values (direct
by_placeholderhit), not collapsed to current — fallback only triggers after a revision ages out ofMAX_RETAINED_CREDENTIAL_GENERATIONS.
Items 1–3 are what I'd like addressed/answered before merge.
Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
|
/ok to test 42fbc04 |
Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
|
Addressed in
The OCSF/Codex notes are no longer part of this PR and can be handled separately if needed. |
elezar
left a comment
There was a problem hiding this comment.
Thanks @johntmyers. Definitely a cleaner diff now.
Summary
Split the non-agent provider/runtime changes out of #1826. Reserve OpenShell's revision-scoped credential placeholder namespace so provider env vars cannot collide with generated placeholders, and allow stale revisioned placeholders to fall back to current credentials only when the key still exists.
Related Issue
Related to #1826.
Changes
v<digits>_placeholder namespace.files.openai.comin the provider profile.Testing
git diff --checkcargo test -p openshell-core --libcargo test -p openshell-providers profiles --libcargo test -p openshell-ocsf format::shorthand --libmise run pre-commitpassesNote: first pre-commit attempt in the temp worktree failed because the fresh mise venv had not installed
grpc_toolsbefore the concurrentpython:prototask ran. Rerunning after dependency installation passed.Checklist