diff --git a/src/main/java/org/jenkinsci/plugins/credentialsbinding/masking/SecretPatterns.java b/src/main/java/org/jenkinsci/plugins/credentialsbinding/masking/SecretPatterns.java index f1f90f25..829d4620 100644 --- a/src/main/java/org/jenkinsci/plugins/credentialsbinding/masking/SecretPatterns.java +++ b/src/main/java/org/jenkinsci/plugins/credentialsbinding/masking/SecretPatterns.java @@ -44,6 +44,12 @@ public class SecretPatterns { private static final Comparator BY_LENGTH_DESCENDING = Comparator.comparingInt(String::length).reversed().thenComparing(String::compareTo); + /** + * Masking (encoded) “secrets” shorter than this would just be ridiculous. + * Particularly relevant with {@link Base64SecretPatternFactory}. + */ + private static final int MINIMUM_ENCODED_LENGTH = 3; + /** * Constructs a regular expression to match against all known forms that the given collection of input strings may * appear. This pattern is optimized such that longer masks are checked before shorter masks. By doing so, this @@ -61,6 +67,7 @@ public class SecretPatterns { .flatMap(input -> secretPatternFactories.stream().flatMap(factory -> factory.getEncodedForms(input).stream())) + .filter(encoded -> encoded.length() >= MINIMUM_ENCODED_LENGTH) .sorted(BY_LENGTH_DESCENDING) .distinct() .map(Pattern::quote) diff --git a/src/test/java/org/jenkinsci/plugins/credentialsbinding/impl/BindingStepTest.java b/src/test/java/org/jenkinsci/plugins/credentialsbinding/impl/BindingStepTest.java index c246ce67..7c7ad732 100644 --- a/src/test/java/org/jenkinsci/plugins/credentialsbinding/impl/BindingStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/credentialsbinding/impl/BindingStepTest.java @@ -321,6 +321,25 @@ public void widerRequiredContext() throws Throwable { }); } + @Issue("JENKINS-72412") + @Test public void maskingOfOneCharSecretShouldNotMangleOutput() throws Throwable { + rr.then(r -> { + String credentialsId = "creds"; + String secret = "a"; + CredentialsProvider.lookupStores(r.jenkins).iterator().next().addCredentials(Domain.global(), new StringCredentialsImpl(CredentialsScope.GLOBAL, credentialsId, "sample", Secret.fromString(secret))); + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("" + + "node {\n" + + " withCredentials([string(credentialsId: '" + credentialsId + "', variable: 'SECRET')]) {\n" + // forgot set +x, ran /usr/bin/env, etc. + + " if (isUnix()) {sh 'echo $SECRET > oops'} else {bat 'echo %SECRET% > oops'}\n" + + " }\n" + + "}", true)); + WorkflowRun b = r.assertBuildStatusSuccess(p.scheduleBuild2(0).get()); + r.assertLogContains("echo", b); + }); + } + @Issue("JENKINS-30326") @Test public void testGlobalBindingWithAuthorization() throws Throwable {