Skip to content

feat: apply netrc/env credentials to Maven repository authentication#2583

Open
maxandersen wants to merge 2 commits into
jbangdev:mainfrom
maxandersen:netrc-maven-auth
Open

feat: apply netrc/env credentials to Maven repository authentication#2583
maxandersen wants to merge 2 commits into
jbangdev:mainfrom
maxandersen:netrc-maven-auth

Conversation

@maxandersen

@maxandersen maxandersen commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

jbang --repos 'name=https://maven.pkg.github.com/...' artifact:id:version fails to resolve because Maven's Aether resolver does not honor .netrc files. JBang already had netrc support for HTTP downloads, but the Maven artifact resolver created RemoteRepository objects without any authentication.

Continuation of #2565.

Solution

Credential lookup chain (most-specific first)

The auth chain has been reordered so explicit per-host config wins over generic env vars:

Priority Source Scope
1 URL userinfo (https://user:pass@host/...) HTTP downloads only
2 .netrc exact host match HTTP + Maven
3 GITHUB_TOKEN / GITLAB_TOKEN env vars Known hosts only
4 .netrc default entry Catch-all
5 JBANG_AUTH_BASIC_* env vars Global fallback

For Maven repositories, ~/.m2/settings.xml <server> entries take precedence over all of the above when the server <id> matches the repository ID.

Shared credential lookup

Extracted NetUtil.getCredentialsForHost(String host) — returns String[] {username, password} or null. Both the HTTP download path and Maven resolver share this method, eliminating duplicated lookup logic.

Maven repository auth

ArtifactResolver.toRemoteRepo() calls getCredentialsForHost() for each repository URL's hostname and attaches Aether Authentication when credentials are found.

Git credential helper support (closes #2570)

Adds a JBang-specific jbang key to .netrc entries:

machine gitlab.mycompany.com
jbang git-credential

When getCredentialsForHost() encounters jbang git-credential, it runs git credential fill to obtain credentials from whatever backend the user has configured (macOS Keychain, Windows Credential Manager, credential-store, etc.).

  • Opt-in per host — only entries with the jbang key trigger the subprocess
  • Cached per-host for the process lifetime
  • Falls back gracefully to login/password in the same entry if git credential fails
  • Ignored by other tools — curl, wget, git skip unknown netrc keys

Tests

WireMock integration tests verify actual Authorization headers on HTTP requests:

  • .netrc credentials sent after 401 challenge-response
  • settings.xml <server> credentials sent to matching repo
  • settings.xml takes precedence over .netrc for same repo ID
  • .netrc used as fallback when settings.xml server ID doesn't match
  • No auth sent when neither source has credentials
  • jbang git-credential delegates to git credential fill
  • Fallback from failed git-credential to login/password

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ai-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d23be13-913b-41f2-947f-13566119c8c3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: Maven repository auth now uses netrc and environment credentials.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@src/test/java/dev/jbang/dependencies/TestNetrcMavenAuth.java`:
- Around line 127-135: The `testGetCredentialsForHostReturnsCredentials` test is
flaky because `NetUtil.getCredentialsForHost(...)` will prefer `GITHUB_TOKEN`
for hosts recognized by `isAGithubHost(...)`, including `maven.pkg.github.com`.
Add the same environment guard used by the other tests (or switch the test to a
non-GitHub host) so the `.netrc` path is exercised consistently and the
`assertThat(creds[0]...)` / `assertThat(creds[1]...)` checks remain stable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6df2b24-d97c-47c9-82ea-cfd7d11041dd

📥 Commits

Reviewing files that changed from the base of the PR and between 915cf65 and f195ba0.

📒 Files selected for processing (4)
  • src/main/java/dev/jbang/dependencies/ArtifactResolver.java
  • src/main/java/dev/jbang/util/NetUtil.java
  • src/test/java/dev/jbang/dependencies/TestNetrcMavenAuth.java
  • src/test/java/dev/jbang/util/TestNetrcAuth.java

Comment on lines +127 to +135
@Test
void testGetCredentialsForHostReturnsCredentials() throws IOException {
setNetrc("machine maven.pkg.github.com\nlogin __token__\npassword ghp_testtoken123\n");

String[] creds = NetUtil.getCredentialsForHost("maven.pkg.github.com");
assertThat(creds, notNullValue());
assertThat(creds[0], equalTo("__token__"));
assertThat(creds[1], equalTo("ghp_testtoken123"));
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against GITHUB_TOKEN to avoid a flaky test.

The host maven.pkg.github.com ends with .github.com, so isAGithubHost(...) is true. When GITHUB_TOKEN is present in the environment (common in CI), getCredentialsForHost short-circuits at step 1 and returns ["oauth2", <token>] before reaching the .netrc entry, so this assertion fails. Add an assumption guard like the other tests, or use a non-GitHub host.

💚 Proposed guard
 	`@Test`
 	void testGetCredentialsForHostReturnsCredentials() throws IOException {
 		setNetrc("machine maven.pkg.github.com\nlogin __token__\npassword ghp_testtoken123\n");
+
+		org.junit.jupiter.api.Assumptions.assumeTrue(
+				System.getenv("GITHUB_TOKEN") == null,
+				"Skipping: GITHUB_TOKEN env var is set");
 
 		String[] creds = NetUtil.getCredentialsForHost("maven.pkg.github.com");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
void testGetCredentialsForHostReturnsCredentials() throws IOException {
setNetrc("machine maven.pkg.github.com\nlogin __token__\npassword ghp_testtoken123\n");
String[] creds = NetUtil.getCredentialsForHost("maven.pkg.github.com");
assertThat(creds, notNullValue());
assertThat(creds[0], equalTo("__token__"));
assertThat(creds[1], equalTo("ghp_testtoken123"));
}
`@Test`
void testGetCredentialsForHostReturnsCredentials() throws IOException {
setNetrc("machine maven.pkg.github.com\nlogin __token__\npassword ghp_testtoken123\n");
org.junit.jupiter.api.Assumptions.assumeTrue(
System.getenv("GITHUB_TOKEN") == null,
"Skipping: GITHUB_TOKEN env var is set");
String[] creds = NetUtil.getCredentialsForHost("maven.pkg.github.com");
assertThat(creds, notNullValue());
assertThat(creds[0], equalTo("__token__"));
assertThat(creds[1], equalTo("ghp_testtoken123"));
}
🤖 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 `@src/test/java/dev/jbang/dependencies/TestNetrcMavenAuth.java` around lines
127 - 135, The `testGetCredentialsForHostReturnsCredentials` test is flaky
because `NetUtil.getCredentialsForHost(...)` will prefer `GITHUB_TOKEN` for
hosts recognized by `isAGithubHost(...)`, including `maven.pkg.github.com`. Add
the same environment guard used by the other tests (or switch the test to a
non-GitHub host) so the `.netrc` path is exercised consistently and the
`assertThat(creds[0]...)` / `assertThat(creds[1]...)` checks remain stable.

@maxandersen maxandersen requested a review from quintesse July 5, 2026 01:01
@maxandersen

Copy link
Copy Markdown
Collaborator Author

@quintesse i think this works but good with extra set of eyes before release (would like it in as it makes netrc / gh-creds feature complete)

@maxandersen maxandersen force-pushed the netrc-maven-auth branch 8 times, most recently from 0d79d44 to daba49b Compare July 5, 2026 09:18
Maven's Aether resolver does not honor .netrc files, so repositories
like GitHub Packages that require authentication failed to resolve.

Adds NetUtil.getCredentialsForHost() — a shared credential lookup
used by both HTTP downloads and Maven resolution:

  1. .netrc exact host match (most specific)
  2. GITHUB_TOKEN / GITLAB_TOKEN env vars
  3. .netrc default entry
  4. JBANG_AUTH_BASIC_* env vars

For Maven repos, settings.xml <server> entries take precedence over
all of the above when the server <id> matches the repository ID.

Adds jbang-auth key for .netrc credential helper delegation:

  machine maven.pkg.github.com
  login myuser
  jbang-auth gh-auth,git-credential
  jbang-auth-host github.com

- gh-auth: runs 'gh auth token --hostname <host>'
- git-credential: runs 'git credential fill' (with GIT_TERMINAL_PROMPT=0)
- Comma-separated methods tried in order, falls back to login/password
- jbang-auth-host redirects lookups to a different host (e.g. github.com
  for maven.pkg.github.com)
- Results cached per-host for the process lifetime

Also adds Util.runCommand(stdin, env, cmd...) overload for commands
that need stdin or extra environment variables.

Closes jbangdev#2570
@quintesse

Copy link
Copy Markdown
Contributor

It all looks fine. But can you give me an actual example of how to use the GH credentials that I can run?

@maxandersen

Copy link
Copy Markdown
Collaborator Author

It all looks fine. But can you give me an actual example of how to use the GH credentials that I can run?

is the example in the docs sufficient or need something more?

https://github.com/jbangdev/jbang/pull/2583/files#diff-86c239705f82056e8ff17f17bb986a64d1d5ebdfb61a9b52f7a5c6f7aa3dea01R167

machine maven.pkg.github.com
login maxandersen
jbang-auth gh-auth
jbang-auth-host github.com

@maxandersen

Copy link
Copy Markdown
Collaborator Author

note, you probably don't even need the login key here as gh-auth is pure token/pwd based.

@quintesse

Copy link
Copy Markdown
Contributor

is the example in the docs sufficient or need something more?

Well, the thing is that example isn't immediately usable. I can't copy & paste it to see if it works.
And I don't have a GH Maven repository ready to test with.

So I wondered if there was perhaps a more "public" example that anyone could use.

@maxandersen

Copy link
Copy Markdown
Collaborator Author

Try

jbang --java 21 --fresh --repos 'quarkus-github-action=https://maven.pkg.github.com/quarkusio/conversational-release-action/' --repos 'mavencentral' io.quarkus.bot:conversational-release-action:999-SNAPSHOT

@quintesse

quintesse commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Doesn't seem to work?

> ./build/install/jbang/bin/jbang --java 21 --fresh --repos quarkus-github-action=https://maven.pkg.github.com/quarkusio/conversational-release-action/ --repos mavencentral io.quarkus.bot:conversational-release-action:999-SNAPSHOT
[jbang] Resolving dependencies...
[jbang]    io.quarkus.bot:conversational-release-action:999-SNAPSHOT
[jbang] [ERROR] Could not read artifact descriptor for io.quarkus.bot:conversational-release-action:jar:999-SNAPSHOT
[jbang] Run with --verbose or -x for more details. The --verbose or -x must be placed before the jbang command. I.e. jbang --verbose run [...]

With the final cause:

Caused by: org.apache.http.client.HttpResponseException: status code: 401, reason phrase: Unauthorized (401)

@maxandersen

Copy link
Copy Markdown
Collaborator Author

Did you create a .netrc / _netrc file in your home directory?

What does --verbose tell you?

@quintesse

Copy link
Copy Markdown
Contributor

The verbose tells me the last line in my previous message.

And I don't know what to put in that netrc file, you didn't mention that when you said "Try ..." :-)

@maxandersen

Copy link
Copy Markdown
Collaborator Author

machine maven.pkg.github.com
jbang-auth gh-auth
jbang-auth-host github.com

@quintesse

Copy link
Copy Markdown
Contributor

Same result and I don't see anything special in the verbose output (same unauthorized exception)

@maxandersen

Copy link
Copy Markdown
Collaborator Author

are you sure you are running with the build ? :) it should print info about looking for .netrc etc?

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.

feat: support git credential helpers for HTTP authentication

2 participants