Skip to content

Commit 39d17ea

Browse files
committed
fix status printing of variable substitution when the variable name contains any of the strings
"password" or "secret" or "confidential" Signed-off-by: ceki <[email protected]>
1 parent 75509a9 commit 39d17ea

File tree

6 files changed

+128
-14
lines changed

6 files changed

+128
-14
lines changed

logback-classic/src/main/java/ch/qos/logback/classic/joran/PropertiesConfigurator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,8 @@ public String subst(String ref) {
187187

188188
String substituted = variableSubstitutionsHelper.subst(ref);
189189
if (ref != null && !ref.equals(substituted)) {
190-
addInfo("value \"" + substituted + "\" substituted for \"" + ref + "\"");
190+
String sanitized = variableSubstitutionsHelper.sanitizeIfConfidential(ref, substituted);
191+
addInfo("value \"" + sanitized + "\" substituted for \"" + ref + "\"");
191192
}
192193
return substituted;
193194
}

logback-core/src/main/java/ch/qos/logback/core/joran/ParamModelHandler.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,19 @@ protected Class<ParamModel> getSupportedModelClass() {
2828
}
2929

3030
@Override
31-
public void handle(ModelInterpretationContext intercon, Model model) throws ModelHandlerException {
31+
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
3232

3333
ParamModel paramModel = (ParamModel) model;
3434

35-
String valueStr = intercon.subst(paramModel.getValue());
35+
String valueStr = mic.subst(paramModel.getValue());
3636

37-
Object o = intercon.peekObject();
37+
Object o = mic.peekObject();
3838

3939
PropertySetter propSetter = new PropertySetter(beanDescriptionCache, o);
4040
propSetter.setContext(context);
4141

4242
// allow for variable substitution for name as well
43-
String finalName = intercon.subst(paramModel.getName());
43+
String finalName = mic.subst(paramModel.getName());
4444
propSetter.setProperty(finalName, valueStr);
4545
}
4646

logback-core/src/main/java/ch/qos/logback/core/model/processor/ModelInterpretationContext.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@
1818
import java.util.HashMap;
1919
import java.util.List;
2020
import java.util.Map;
21-
import java.util.Properties;
2221
import java.util.Stack;
2322
import java.util.function.Supplier;
2423

2524
import ch.qos.logback.core.Appender;
2625
import ch.qos.logback.core.Context;
2726
import ch.qos.logback.core.joran.GenericXMLConfigurator;
28-
import ch.qos.logback.core.joran.JoranConfiguratorBase;
2927
import ch.qos.logback.core.joran.JoranConstants;
3028
import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry;
3129
import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache;
@@ -34,7 +32,6 @@
3432
import ch.qos.logback.core.spi.AppenderAttachable;
3533
import ch.qos.logback.core.spi.ContextAwareBase;
3634
import ch.qos.logback.core.spi.ContextAwarePropertyContainer;
37-
import ch.qos.logback.core.spi.PropertyContainer;
3835

3936
public class ModelInterpretationContext extends ContextAwareBase implements ContextAwarePropertyContainer {
4037

@@ -165,13 +162,13 @@ public BeanDescriptionCache getBeanDescriptionCache() {
165162
public String subst(String ref) {
166163

167164
String substituted = variableSubstitutionsHelper.subst(ref);
168-
if(ref != null && !ref.equals(substituted)) {
169-
addInfo("value \""+substituted+"\" substituted for \""+ref+"\"");
165+
if(ref != null && !ref.equals(substituted) ) {
166+
String sanitized = variableSubstitutionsHelper.sanitizeIfConfidential(ref, substituted);
167+
addInfo("value \""+sanitized+"\" substituted for \""+ref+"\"");
170168
}
171169
return substituted;
172170
}
173171

174-
175172
public DefaultNestedComponentRegistry getDefaultNestedComponentRegistry() {
176173
return defaultNestedComponentRegistry;
177174
}

logback-core/src/main/java/ch/qos/logback/core/model/util/VariableSubstitutionsHelper.java

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,55 @@
2626
/**
2727
* Helper methods to deal with properties.
2828
*
29+
* <p>This class acts as a small container for substitution properties and
30+
* delegates actual variable substitution to {@link OptionHelper#substVars}.
31+
* It also offers a convenience method to mask confidential property values
32+
* (for example passwords) by returning a blurred placeholder.</p>
33+
*
2934
* @since 1.5.1
3035
*/
3136
public class VariableSubstitutionsHelper extends ContextAwareBase implements ContextAwarePropertyContainer {
3237

38+
static final String PASSWORD = "password";
39+
static final String SECRET = "secret";
40+
static final String CONFIDENTIAL = "confidential";
41+
42+
static final String BLURRED_STR = "******";
43+
3344
protected Map<String, String> propertiesMap;
3445

46+
/**
47+
* Create a helper backed by an empty property map.
48+
*
49+
* @param context the logback context to associate with this helper; may be null
50+
*/
3551
public VariableSubstitutionsHelper(Context context) {
3652
this.setContext(context);
3753
this.propertiesMap = new HashMap<>();
3854
}
3955

56+
/**
57+
* Create a helper pre-populated with the contents of {@code otherMap}.
58+
* The provided map is copied and further modifications do not affect the
59+
* original map.
60+
*
61+
* @param context the logback context to associate with this helper; may be null
62+
* @param otherMap initial properties to copy; if null an empty map is created
63+
*/
4064
public VariableSubstitutionsHelper(Context context, Map<String, String> otherMap) {
4165
this.setContext(context);
4266
this.propertiesMap = new HashMap<>(otherMap);
4367
}
4468

69+
/**
70+
* Perform variable substitution on the provided reference string.
71+
*
72+
* <p>Returns {@code null} if {@code ref} is {@code null}. On parse errors
73+
* the original input string is returned and an error is logged.</p>
74+
*
75+
* @param ref the string possibly containing variables to substitute
76+
* @return the string with substitutions applied, or {@code null} if {@code ref} was {@code null}
77+
*/
4578
@Override
4679
public String subst(String ref) {
4780
if (ref == null) {
@@ -54,12 +87,42 @@ public String subst(String ref) {
5487
addError("Problem while parsing [" + ref + "]", e);
5588
return ref;
5689
}
90+
}
5791

92+
/**
93+
* Return a blurred placeholder for confidential properties.
94+
*
95+
* <p>If the property name {@code ref} contains any of the case-insensitive
96+
* substrings {@code "password"}, {@code "secret"} or {@code "confidential"}
97+
* this method returns a fixed blurred string ("******"). Otherwise, the
98+
* supplied {@code substituted} value is returned unchanged.</p>
99+
*
100+
* @param ref the property name to inspect; must not be {@code null}
101+
* @param substituted the substituted value to return when the property is not confidential
102+
* @return a blurred placeholder when the property appears confidential, otherwise {@code substituted}
103+
* @throws IllegalArgumentException when {@code ref} is {@code null}
104+
*/
105+
public String sanitizeIfConfidential(String ref, String substituted) {
106+
if(ref == null) {
107+
throw new IllegalArgumentException("ref cannot be null");
108+
}
109+
110+
String lowerCaseRef = ref.toLowerCase();
111+
112+
if(lowerCaseRef.contains(PASSWORD) || lowerCaseRef.contains(SECRET) || lowerCaseRef.contains(CONFIDENTIAL)) {
113+
return BLURRED_STR;
114+
} else
115+
return substituted;
58116
}
59117

60118
/**
61-
* Add a property to the properties of this execution context. If the property
62-
* exists already, it is overwritten.
119+
* Add or overwrite a substitution property.
120+
*
121+
* <p>Null keys or values are ignored. Values are trimmed before storing
122+
* to avoid surprises caused by leading or trailing whitespace.</p>
123+
*
124+
* @param key the property name; ignored if {@code null}
125+
* @param value the property value; ignored if {@code null}
63126
*/
64127
@Override
65128
public void addSubstitutionProperty(String key, String value) {
@@ -71,13 +134,27 @@ public void addSubstitutionProperty(String key, String value) {
71134
propertiesMap.put(key, value);
72135
}
73136

137+
/**
138+
* Retrieve a property value by name.
139+
*
140+
* @param key the property name
141+
* @return the property value or {@code null} if not present
142+
*/
74143
@Override
75144
public String getProperty(String key) {
76145
return propertiesMap.get(key);
77146
}
78147

148+
/**
149+
* Return a shallow copy of the internal property map.
150+
*
151+
* <p>The returned map is a copy and modifications to it do not affect the
152+
* internal state of this helper.</p>
153+
*
154+
* @return a copy of the property map
155+
*/
79156
@Override
80157
public Map<String, String> getCopyOfPropertyMap() {
81-
return new HashMap<String, String>(propertiesMap);
158+
return new HashMap<>(propertiesMap);
82159
}
83160
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!--
2+
~ Logback: the reliable, generic, fast and flexible logging framework.
3+
~ Copyright (C) 1999-2025, QOS.ch. All rights reserved.
4+
~
5+
~ This program and the accompanying materials are dual-licensed under
6+
~ either the terms of the Eclipse Public License v1.0 as published by
7+
~ the Eclipse Foundation
8+
~
9+
~ or (per the licensee's choosing)
10+
~
11+
~ under the terms of the GNU Lesser General Public License version 2.1
12+
~ as published by the Free Software Foundation.
13+
-->
14+
15+
<context>
16+
<fruit class="ch.qos.logback.core.joran.implicitAction.Fruit">
17+
<name>${A_PASSWORD}</name>
18+
<text>hello</text>
19+
<text>world</text>
20+
</fruit>
21+
</context>

logback-core/src/test/java/ch/qos/logback/core/joran/implicitAction/ImplicitActionTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ public void nestedComplex() throws Exception {
9191
}
9292
}
9393

94+
@Test
95+
public void nestedComplexWithPassword() throws Exception {
96+
try {
97+
fruitContext.addSubstitutionProperty("A_PASSWORD", "blue");
98+
99+
simpleConfigurator.doConfigure(IMPLCIT_DIR + "nestedComplexWithPassword.xml");
100+
StatusPrinter.print(fruitContext);
101+
StatusChecker checker = new StatusChecker(fruitContext);
102+
103+
checker.assertContainsMatch("value \"\\*{6}\" substituted for \"\\$\\{A_PASSWORD\\}\"");
104+
verifyFruit();
105+
106+
} catch (Exception je) {
107+
StatusPrinter.print(fruitContext);
108+
throw je;
109+
}
110+
}
111+
94112
@Test
95113
public void nestedComplexWithoutClassAtrribute() throws Exception {
96114
try {

0 commit comments

Comments
 (0)