Censor domain authcode by default in tools (#3170)

When inspecting domain entities with tools like GetDomainCommand, displaying
plaintext authentication codes is unnecessary and presents a security risk
for credential exposure in logs and bug ticket comments (b/537293980).

This commit modifies GetDomainCommand to redact sensitive authcodes by
default using an in-memory entity builder snapshot while preserving database
records. An optional --show_authcode flag with arity 1 is introduced to
explicitly display authcodes when required for transfer authorization.

BUG= http://b/537293980
This commit is contained in:
Ben McIlwain
2026-07-24 14:26:38 +00:00
committed by GitHub
parent 7347dbf762
commit 9c4a558d7b
2 changed files with 48 additions and 2 deletions
@@ -20,6 +20,8 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.persistence.transaction.QueryComposer.Comparator;
import google.registry.util.DomainNameUtils;
import java.util.List;
@@ -32,6 +34,12 @@ final class GetDomainCommand extends GetEppResourceCommand {
@Parameter(names = "--show_deleted", description = "Include deleted domains in the print out")
private boolean showDeleted = false;
@Parameter(
names = "--show_authcode",
description = "Include domain authentication code in output",
arity = 1)
private boolean showAuthcode = false;
@Parameter(
description = "Fully qualified domain name(s)",
required = true)
@@ -51,14 +59,26 @@ final class GetDomainCommand extends GetEppResourceCommand {
.stream()
.forEach(
d -> {
printResource("Domain", canonicalDomain, Optional.of(d));
printResource(
"Domain", canonicalDomain, Optional.of(censorAuthcode(d)));
}));
} else {
printResource(
"Domain",
canonicalDomain,
ForeignKeyUtils.loadResource(Domain.class, canonicalDomain, readTimestamp));
ForeignKeyUtils.loadResource(Domain.class, canonicalDomain, readTimestamp)
.map(this::censorAuthcode));
}
}
}
private Domain censorAuthcode(Domain domain) {
if (showAuthcode || domain.getAuthInfo() == null) {
return domain;
}
return domain
.asBuilder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("[REDACTED]")))
.build();
}
}
@@ -24,6 +24,8 @@ import static google.registry.util.DateTimeUtils.plusMonths;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.beust.jcommander.ParameterException;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.testing.DatabaseHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -136,4 +138,28 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
// Deleted
assertInStdout("Websafe key: kind:Domain@sql:rO0ABXQABTMtVExE");
}
@Test
void testSuccess_censorsAuthcodeByDefault() throws Exception {
persistResource(
DatabaseHelper.newDomain("example.tld")
.asBuilder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("secret123")))
.build());
runCommand("example.tld");
assertInStdout("value=[REDACTED]");
assertNotInStdout("secret123");
}
@Test
void testSuccess_showAuthcode() throws Exception {
persistResource(
DatabaseHelper.newDomain("example.tld")
.asBuilder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("secret123")))
.build());
runCommand("example.tld", "--show_authcode=true");
assertInStdout("value=secret123");
assertNotInStdout("[REDACTED]");
}
}