Skip to content

fix(registry): keep project npmrc env refs literal - #856

Merged
jdx merged 3 commits into
mainfrom
codex/restrict-repo-config-env
Jun 11, 2026
Merged

jdx merged 3 commits into
mainfrom
codex/restrict-repo-config-env

Conversation

@jdx

@jdx jdx commented Jun 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Stop expanding ${VAR} references from repository-controlled .npmrc sources before registry/auth config is applied. User/global .npmrc and env-derived config keep trusted substitution, while project .npmrc and project-reachable npmrc-auth-file entries now preserve env references literally.

This prevents cloned repositories from turning the caller's environment into registry destinations or credentials.

Validation

  • cargo test -p aube-registry npmrc -- --nocapture
  • cargo test -p aube-registry
  • cargo clippy -p aube-registry -- -D warnings

This PR was generated by Codex.


Note

High Risk
Changes trusted vs untrusted registry auth parsing and credential application—security-critical—and may break projects that relied on env-expanded auth in committed .npmrc.

Overview
Hardens registry auth so repository-controlled .npmrc cannot turn the caller's environment into registry URLs or credentials.

Project .npmrc and project-reachable npmrc-auth-file content are parsed with parse_npmrc_untrusted, which leaves ${VAR} literals in keys and values. During apply_tagged, auth entries from project-controlled sources that still contain env references are dropped and WARN_AUBE_UNTRUSTED_AUTH_ENV is logged. User/global sources (~/.npmrc, pnpm auth.ini, user-declared auth files) keep full ${…} expansion.

NpmrcSource::NpmrcAuthFile is split into UserNpmrcAuthFile vs ProjectNpmrcAuthFile according to which layer set npmrc-auth-file, with matching trust for subprocess settings and load order. Tests and the error-code catalog document the new warning.

Reviewed by Cursor Bugbot for commit 12a2616. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Security
    • Project-scoped .npmrc and project-referenced auth files are now treated as untrusted: environment-variable placeholders in auth entries are ignored; user-level .npmrc continues to expand env vars.
  • New Features
    • Distinguishes user vs project origins for auth-file entries so trust rules are applied correctly.
  • Warnings
    • New warning emitted when a project-controlled auth entry contains an environment-variable reference and is ignored.
  • Tests
    • Added tests covering trusted vs untrusted .npmrc parsing and auth handling.
  • Documentation
    • Error-code catalog updated with the new warning entry.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c475132-95fd-4300-8ded-bbfb8f033e00

📥 Commits

Reviewing files that changed from the base of the PR and between 4da94a3 and 12a2616.

📒 Files selected for processing (2)
  • crates/aube-registry/src/config/load.rs
  • crates/aube-registry/src/config/tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/aube-registry/src/config/tests.rs
  • crates/aube-registry/src/config/load.rs

📝 Walkthrough

Walkthrough

This PR adds an untrusted .npmrc parser that preserves ${VAR} in project-controlled files, threads auth-file source tags through loaders, enforces a trust gate that ignores project-scoped env-referenced auth entries (with a warning), and adds tests and a new warning code.

Changes

Untrusted .npmrc Parsing

Layer / File(s) Summary
Untrusted parsing API and implementation
crates/aube-registry/src/config/npmrc.rs
Added parse_npmrc_untrusted and refactored internal parser to accept an expand_env flag; introduced maybe_substitute_env to conditionally expand ${VAR} after trimming and quote stripping.
Loaders and auth-file resolution
crates/aube-registry/src/config/load.rs
Loaders now import parse_npmrc_untrusted, classify new auth-file NpmrcSource variants into user/project slices, use untrusted parsing for project .npmrc and for project-sourced npmrc-auth-file, and add resolve_npmrc_auth_file_tagged / path helper.
NpmrcSource variants and apply trust gate
crates/aube-registry/src/config/types.rs, crates/aube-registry/src/config/apply.rs
Added UserNpmrcAuthFile and ProjectNpmrcAuthFile variants; updated trust logic so only user/global auth-file is trusted for subprocess settings; apply_tagged warns and ignores auth-related entries containing ${...} when source is project-controlled.
Tests, warnings, and docs
crates/aube-registry/src/config/tests.rs, crates/aube-codes/src/warnings.rs, docs/error-codes.data.json
Added ScopedEnvVars RAII guard, expanded tests verifying trusted vs untrusted env expansion (including npmrc-auth-file behavior), added WARN_AUBE_UNTRUSTED_AUTH_ENV code and JSON catalog entry.
Module import update
crates/aube-registry/src/config.rs
Imported parse_npmrc_untrusted alongside existing parsing helpers for test/setup usage.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble keys and skip the snare,
Project files keep variables bare;
User configs bloom with env's light,
Repo auths stay locked up tight.
Hoppity hop — parsing done right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: preventing environment variable expansion in project-controlled npmrc files, which is the core security fix addressed across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens registry auth handling by stopping ${VAR} expansion in project-controlled .npmrc files. It splits NpmrcAuthFile into UserNpmrcAuthFile / ProjectNpmrcAuthFile, introduces parse_npmrc_untrusted that preserves env refs literally, and adds an auth_entry_contains_env_ref guard in apply_tagged that drops literal-ref auth entries from project sources with a new WARN_AUBE_UNTRUSTED_AUTH_ENV diagnostic.

  • load_npmrc_entries_tagged_with_home now makes two separate calls to resolve_npmrc_auth_file_tagged: one before loading the project .npmrc (loads user-declared auth files with trusted parsing), and one after (loads project-declared auth files with untrusted parsing). This correctly enforces per-source trust.
  • auth_entry_contains_env_ref runs at the top of the apply_tagged loop, before any key-specific branch, ensuring scoped and unscoped auth entries from project sources are both protected.
  • Six new integration tests cover the full trust matrix (user vs project vs auth-file, env-expanding vs literal).

Confidence Score: 3/5

The env-expansion security fix is correctly implemented, but the loader change silently reverses a documented pnpm invariant — user-declared auth files now lose to the project .npmrc instead of winning over it.

The core security goal (no env expansion in project-controlled sources) is sound and well-tested. However, the two-call pattern in load_npmrc_entries_tagged_with_home changes when UserNpmrcAuthFile entries are appended relative to Project entries. The old code loaded the auth file last so it always had the highest precedence; the new code inserts user auth file entries before project entries, inverting that invariant. A CI pipeline that relied on npmrc-auth-file in ~/.npmrc to supply override credentials will now silently use the project token instead. The test user_declared_npmrc_auth_file_loses_to_project_npmrc locks in the new behavior, but neither the PR description nor any new comment documents that this is an intentional break from the previously stated pnpm-compatible semantics.

crates/aube-registry/src/config/load.rs — specifically the ordering of UserNpmrcAuthFile entries relative to Project entries and whether the precedence reversal is intentional and safe for existing setups.

Important Files Changed

Filename Overview
crates/aube-registry/src/config/load.rs Refactors the tagged loader to make two separate calls to resolve_npmrc_auth_file_tagged — once for user-sourced auth files (trusted, before project entries) and once for project-sourced auth files (untrusted, after project entries). Correctly splits parsing trust, but silently reverses the old precedence rule where a user-declared auth file always beat the project .npmrc.
crates/aube-registry/src/config/apply.rs Adds auth_entry_contains_env_ref guard that drops any auth key/value from a project-controlled source that still contains a ${...} reference. Logic correctly fires before all key-specific branches. Minor: auth_suffix list and inner matches! are redundantly duplicated.
crates/aube-registry/src/config/npmrc.rs Splits parse_npmrc into trusted/untrusted variants via a private parse_npmrc_inner(expand_env: bool); untrusted path stores ${VAR} literals verbatim. Clean refactor with correct doc comments.
crates/aube-registry/src/config/types.rs Replaces NpmrcAuthFile with UserNpmrcAuthFile and ProjectNpmrcAuthFile; adds is_project_controlled() and updates is_trusted_for_subprocess_settings() to include UserNpmrcAuthFile. Model is clean and well-documented.
crates/aube-registry/src/config/tests.rs Adds ScopedEnvVars RAII guard (deduplicating the old inline struct) and six new tests covering: untrusted literal preservation, project auth env-ref dropping, user auth expansion, project/user auth-file trust, and user-auth-file precedence vs project.
crates/aube-codes/src/warnings.rs Adds WARN_AUBE_UNTRUSTED_AUTH_ENV warning constant and CodeMeta entry for the new diagnostic when project-source auth entries with env refs are dropped.
docs/error-codes.data.json Adds the WARN_AUBE_UNTRUSTED_AUTH_ENV entry to the error-code catalog, matching the new CodeMeta in warnings.rs.
crates/aube-registry/src/config.rs Adds parse_npmrc_untrusted to the #[cfg(test)] import block to expose it for the new test assertions.

Fix All in Claude Code

Reviews (3): Last reviewed commit: "fix(registry): preserve user auth file p..." | Re-trigger Greptile

Comment on lines +14 to +17
/// Parse a repository-controlled .npmrc file without `${VAR}` expansion.
pub(super) fn parse_npmrc_untrusted(path: &Path) -> Result<Vec<(String, String)>, std::io::Error> {
parse_npmrc_inner(path, false)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Silent auth failure for common CI/CD credential-injection pattern

The widely-used pattern of placing ${NPM_TOKEN} (or ${NEXUS_URL}:_auth=${NEXUS_TOKEN}) in a project .npmrc to receive CI-injected credentials is now broken without any diagnostic. Before this change, aube explicitly regression-fixed this (#519) because "pnpm/npm both substitute ${VAR} in keys as well as values." With parse_npmrc_untrusted the literal string ${NPM_TOKEN} is stored as the auth token; every registry request will receive that literal string and get a 401, with no indication that env expansion was suppressed. A warning log when a value matching \$\{[^}]+\} is stored from an untrusted source would help users diagnose this failure.

Fix in Claude Code

@jdx
jdx marked this pull request as ready for review June 10, 2026 21:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/aube-registry/src/config/tests.rs (1)

149-156: 💤 Low value

Consider consolidating duplicate RAII guard.

The new ScopedEnvVars helper (lines 7-15) provides the same functionality as this local EnvVars struct. Consider replacing this with ScopedEnvVars for consistency.

♻️ Proposed refactor
-    struct EnvVars(&'static [&'static str]);
-    impl Drop for EnvVars {
-        fn drop(&mut self) {
-            for name in self.0 {
-                unsafe { std::env::remove_var(name) };
-            }
-        }
-    }
-    let _vars = EnvVars(&["AUBE_TEST_NEXUS_HOST_CFG", "AUBE_TEST_NEXUS_TOKEN_CFG"]);
+    let _vars = ScopedEnvVars(&["AUBE_TEST_NEXUS_HOST_CFG", "AUBE_TEST_NEXUS_TOKEN_CFG"]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aube-registry/src/config/tests.rs` around lines 149 - 156, The local
RAII guard EnvVars (struct EnvVars and its Drop impl) duplicates ScopedEnvVars;
remove EnvVars and replace its usage in the tests with ScopedEnvVars so the same
cleanup behavior is reused. Find the EnvVars definition and any places creating
EnvVars instances, delete the struct and Drop impl, and construct a
ScopedEnvVars instead (import or fully qualify the module where ScopedEnvVars
lives) so environment variables are removed on drop exactly as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/aube-registry/src/config/tests.rs`:
- Around line 149-156: The local RAII guard EnvVars (struct EnvVars and its Drop
impl) duplicates ScopedEnvVars; remove EnvVars and replace its usage in the
tests with ScopedEnvVars so the same cleanup behavior is reused. Find the
EnvVars definition and any places creating EnvVars instances, delete the struct
and Drop impl, and construct a ScopedEnvVars instead (import or fully qualify
the module where ScopedEnvVars lives) so environment variables are removed on
drop exactly as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b4cd8093-b140-456f-89dc-1d77c21fab84

📥 Commits

Reviewing files that changed from the base of the PR and between 8533c5d and f489079.

📒 Files selected for processing (4)
  • crates/aube-registry/src/config.rs
  • crates/aube-registry/src/config/load.rs
  • crates/aube-registry/src/config/npmrc.rs
  • crates/aube-registry/src/config/tests.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4da94a3. Configure here.

Comment thread crates/aube-registry/src/config/load.rs Resolved
@jdx
jdx merged commit 60ba8b5 into main Jun 11, 2026
29 of 31 checks passed
@jdx
jdx deleted the codex/restrict-repo-config-env branch June 11, 2026 02:58
@cursor cursor Bot mentioned this pull request Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant