diff --git a/fleetd/src/main/java/dev/ltms/fleet/session/GitWorktrees.java b/fleetd/src/main/java/dev/ltms/fleet/session/GitWorktrees.java index bcf7c6d..571dda0 100644 --- a/fleetd/src/main/java/dev/ltms/fleet/session/GitWorktrees.java +++ b/fleetd/src/main/java/dev/ltms/fleet/session/GitWorktrees.java @@ -211,6 +211,17 @@ public final class GitWorktrees implements Worktrees { * guessing at it risks rewriting to the wrong place. A repo provisioned from that form keeps * today's (broken, if the policy blocks the agent) SSH-only behaviour rather than a wrong rewrite. */ + /** + * Blank the user-info of a remote URL before it reaches a log. A remote URL is not obviously a + * credential channel, which is exactly why one has leaked here three times ({@code git remote -v} + * printing a token inline, and fleetd #157 / #182). An {@code ssh://} authority normally carries + * only {@code git@}, so this usually changes nothing — it is here so that the one origin that + * does carry a secret cannot print it. Matches every {@code ://…@} pair, not just the first. + */ + private static String redactUserInfo(String url) { + return url == null ? null : url.replaceAll("://[^@/]*@", "://@"); + } + private void configureHttpsUrlRewriteForSshOrigin(String repoRoot, String worktreePath) { if (exitCode("git", "-C", repoRoot, "config", "--get", "remote.origin.url") != 0) { return; @@ -220,20 +231,20 @@ public final class GitWorktrees implements Worktrees { try { uri = new URI(origin); } catch (URISyntaxException e) { - log.warn("origin URL {} is not a valid URI; skipping worktree HTTPS rewrite", origin); + log.warn("origin URL {} is not a valid URI; skipping worktree HTTPS rewrite", redactUserInfo(origin)); return; } String scheme = uri.getScheme(); if (!"ssh".equalsIgnoreCase(scheme)) { // Already https:// (the credential helper covers it), or a scheme-less/scp-like origin // left alone on purpose — see the javadoc above. - log.debug("origin scheme is not ssh ({}) — no worktree HTTPS rewrite needed", origin); + log.debug("origin scheme is not ssh ({}) — no worktree HTTPS rewrite needed", redactUserInfo(origin)); return; } String host = uri.getHost(); String authority = uri.getRawAuthority(); if (host == null || host.isBlank() || authority == null || authority.isBlank()) { - log.warn("ssh origin {} has no resolvable host; skipping worktree HTTPS rewrite", origin); + log.warn("ssh origin {} has no resolvable host; skipping worktree HTTPS rewrite", redactUserInfo(origin)); return; } String sshBase = "ssh://" + authority + "/"; @@ -241,7 +252,7 @@ public final class GitWorktrees implements Worktrees { exec("git", "-C", worktreePath, "config", "--worktree", "--replace-all", "url." + httpsBase + ".insteadOf", sshBase); log.info("worktree {} rewrites {} to {} (worktree-scoped; parent checkout untouched)", - worktreePath, sshBase, httpsBase); + worktreePath, redactUserInfo(sshBase), httpsBase); } /**