Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3d06a86
Adding Redacted Keys
clr182 Jul 1, 2024
bce2bc4
Fixed check tests
clr182 Jul 1, 2024
6ec55a3
Fixed conflicting filters and redacted keys tests
clr182 Jul 1, 2024
9ac6180
update AutoRedactedKeysScenario.java
clr182 Jul 1, 2024
fc657ac
altered default filters string array in the configuration
clr182 Jul 4, 2024
6f774b4
update CHANGELOG
clr182 Jul 4, 2024
42ec37b
Changes to RedactedKeysMap
clr182 Jul 5, 2024
0fd5542
remove duplicate filter config
clr182 Jul 12, 2024
088aa21
fix fixed reference errors
clr182 Jul 12, 2024
1c840e1
config option broken
clr182 Jul 12, 2024
16760f5
Added tests to check regex matching
clr182 Jul 12, 2024
415f03e
scenario syntax fix
clr182 Jul 12, 2024
9b2e42d
Changes to add back public filters
clr182 Jul 17, 2024
4dab99e
updated variable names
clr182 Jul 18, 2024
9e9e84b
Update bugsnag/src/test/resources/logback.xml
clr182 Jul 18, 2024
c17fb7c
Merge branch 'next' into PLAT-4543-AddRedactedKeys
clr182 Jul 18, 2024
2fc8d1c
Converting to Patterns
clr182 Jul 23, 2024
a536540
update javadoc reference
clr182 Jul 23, 2024
a3e5fd0
setRedactedKey to string parameter rather than pattern for logback
clr182 Jul 24, 2024
cae37b6
adding in scenarios
clr182 Jul 24, 2024
0fc2727
Adding Pattern import to scenario
clr182 Jul 24, 2024
089e3a8
filters are appended to setRedactedKeys
clr182 Jul 25, 2024
d1dbada
Update bugsnag/src/main/java/com/bugsnag/Bugsnag.java
clr182 Jul 26, 2024
51203d1
Added concat list
clr182 Aug 8, 2024
b663e58
fix(redacted keys): changed RedactedKeysMap to retain logic for `filt…
lemnik Jun 30, 2025
7b2b506
Merge branch 'next' into PLAT-4543-AddRedactedKeys
lemnik Jun 30, 2025
17d7fda
chore(changelog): fixed the changelog after next sync
lemnik Jun 30, 2025
1b7b28c
chore(checkstyle): fixed checkstyle errors
lemnik Jun 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## TBD

### Changed

* Renamed the configuration option `filters` to `redactedKeys`. `filters` is now marked as deprecated and will be removed in the next major release. [#217](https://github.com/bugsnag/bugsnag-java/pull/217)

## 3.7.2 (2024-08-28)

### Changed
Expand Down
21 changes: 20 additions & 1 deletion bugsnag/src/main/java/com/bugsnag/Bugsnag.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.Set;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
Expand All @@ -23,6 +24,8 @@
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import java.util.regex.Pattern;

public class Bugsnag implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(Bugsnag.class);
private static final int SHUTDOWN_TIMEOUT_MS = 5000;
Expand Down Expand Up @@ -232,14 +235,30 @@ public void setEndpoint(String endpoint) {
* Use this when you want to ensure sensitive information, such as passwords
* or credit card information is stripped from metaData you send to Bugsnag.
* Any keys in metaData which contain these strings will be marked as
* [FILTERED] when send to Bugsnag.
* [REDACTED] when send to Bugsnag.
*
* @param filters a list of String keys to filter from metaData
* @deprecated to be removed and replaced with setRedactedKeys
*/
@Deprecated
public void setFilters(String... filters) {
config.filters = filters;
}

/**
* Sets which values should be removed from any metadata before sending them
* to Bugsnag.
* <p>
* Use this if you want to ensure you don't transmit sensitive data such as
* passwords and credit card numbers. Any property whose key matches a
* redacted key will be filtered and replaced with [REDACTED].
*
* @param redactedKeys a list of Patterns to match the key of properties to be redacted from metadata
*/
public void setRedactedKeys(Pattern... redactedKeys) {
config.redactedKeys = redactedKeys;
}

/**
* Set which exception classes should be ignored (not sent) by Bugsnag.
*
Expand Down
66 changes: 56 additions & 10 deletions bugsnag/src/main/java/com/bugsnag/BugsnagAppender.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -48,8 +49,8 @@ public class BugsnagAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
/** Bugsnag error server endpoint. */
private String endpoint;

/** Property names that should be filtered out before sending to Bugsnag servers. */
private Set<String> filteredProperties = new HashSet<String>();
/** Property names that should be redacted before sending to Bugsnag servers. */
private Set<Pattern> redactedKeys = new HashSet<Pattern>();

/** Exception classes to be ignored. */
private Set<String> ignoredClasses = new HashSet<String>();
Expand Down Expand Up @@ -254,13 +255,13 @@ private Bugsnag createBugsnag() {
bugsnag.setTimeout(timeout);
}

if (filteredProperties.size() > 0) {
bugsnag.setFilters(filteredProperties.toArray(new String[0]));
if (!redactedKeys.isEmpty()) {
bugsnag.setRedactedKeys(redactedKeys.toArray(new Pattern[0]));
}

bugsnag.setIgnoreClasses(ignoredClasses.toArray(new String[0]));

if (notifyReleaseStages.size() > 0) {
if (!notifyReleaseStages.isEmpty()) {
bugsnag.setNotifyReleaseStages(notifyReleaseStages.toArray(new String[0]));
}

Expand Down Expand Up @@ -375,23 +376,54 @@ public void setEndpoint(String endpoint) {

/**
* @see Bugsnag#setFilters(String...)
* @deprecated use #setRedactedKey(String) instead
*/
@Deprecated
public void setFilteredProperty(String filter) {
this.filteredProperties.add(filter);
this.redactedKeys.add(Pattern.compile(filter));

if (bugsnag != null) {
bugsnag.setFilters(this.filteredProperties.toArray(new String[0]));
bugsnag.setRedactedKeys(this.redactedKeys.toArray(new Pattern[0]));
}
}

/**
* @see Bugsnag#setFilters(String...)
* @deprecated use #setRedactedKeys(String) instead
*/
public void setFilteredProperties(String filters) {
this.filteredProperties.addAll(split(filters));
@Deprecated
public void setFilteredProperties(Collection<String> filters) {
this.redactedKeys.addAll(convertStringsToPatterns(filters));

if (bugsnag != null) {
bugsnag.setFilters(this.filteredProperties.toArray(new String[0]));
bugsnag.setRedactedKeys(this.redactedKeys.toArray(new Pattern[0]));
}
}

/**
* @see Bugsnag#setRedactedKeys(Pattern...)
*/
public void setRedactedKey(String redactedKey) {
Pattern pattern = Pattern.compile(redactedKey);
this.redactedKeys.add(pattern);

if (bugsnag != null) {
bugsnag.setRedactedKeys(this.redactedKeys.toArray(new Pattern[0]));
}

}

/**
* @see Bugsnag#setRedactedKeys(Pattern...)
*/
public void setRedactedKeys(Collection<String> redactedKeys) {
for (String key : redactedKeys) {
Pattern pattern = Pattern.compile(key);
this.redactedKeys.add(pattern);
}

if (bugsnag != null) {
bugsnag.setRedactedKeys(this.redactedKeys.toArray(new Pattern[0]));
}
}

Expand Down Expand Up @@ -559,6 +591,20 @@ public Bugsnag getClient() {
return bugsnag;
}

/**
* Converts a collection of strings to a collection of patterns.
*
* @param strings the collection of strings to convert
* @return a collection of patterns
*/
private Collection<Pattern> convertStringsToPatterns(Collection<String> strings) {
Collection<Pattern> patterns = new HashSet<>();
for (String str : strings) {
patterns.add(Pattern.compile(str));
}
return patterns;
}

/**
* Whether or not a logger is excluded from generating Bugsnag reports
* @param loggerName The name of the logger
Expand Down
14 changes: 10 additions & 4 deletions bugsnag/src/main/java/com/bugsnag/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

@SuppressWarnings("visibilitymodifier")
public class Configuration {
Expand All @@ -39,6 +40,12 @@ public class Configuration {
public Delivery delivery = new AsyncHttpDelivery(SyncHttpDelivery.DEFAULT_NOTIFY_ENDPOINT);
public Delivery sessionDelivery =
new AsyncHttpDelivery(SyncHttpDelivery.DEFAULT_SESSION_ENDPOINT);
public Pattern[] redactedKeys = new Pattern[]{
Pattern.compile("password", Pattern.CASE_INSENSITIVE),
Pattern.compile("secret", Pattern.CASE_INSENSITIVE),
Pattern.compile("authorization", Pattern.CASE_INSENSITIVE),
Pattern.compile("cookie", Pattern.CASE_INSENSITIVE)
};
public String[] filters = new String[]{"password", "secret", "Authorization", "Cookie"};
public String[] ignoreClasses;
public String[] notifyReleaseStages = null;
Expand Down Expand Up @@ -123,17 +130,16 @@ public boolean shouldSendUncaughtExceptions() {
* Set the endpoints to send data to. By default we'll send error reports to
* https://notify.bugsnag.com, and sessions to https://sessions.bugsnag.com, but you can
* override this if you are using Bugsnag Enterprise to point to your own Bugsnag endpoint.
*
* <p>
* Please note that it is recommended that you set both endpoints. If the notify endpoint is
* missing, an exception will be thrown. If the session endpoint is missing, a warning will be
* logged and sessions will not be sent automatically.
*
* <p>
* Note that if you are setting a custom {@link Delivery}, this method should be called after
* the custom implementation has been set.
*
* @param notify the notify endpoint
* @param notify the notify endpoint
* @param sessions the sessions endpoint
*
* @throws IllegalArgumentException if the notify endpoint is empty or null
*/
public void setEndpoints(String notify, String sessions) throws IllegalArgumentException {
Expand Down
154 changes: 154 additions & 0 deletions bugsnag/src/main/java/com/bugsnag/RedactedKeysMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.bugsnag;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
* Decorates a map by replacing values of redacted keys.
*/
class RedactedKeysMap implements Map<String, Object> {

private static final String REDACTED_PLACEHOLDER = "[REDACTED]";

private final Map<String, Object> redactedKeyCopy;
private final Pattern[] redactedKeys;
private final String[] filters;

/**
* Constructs a new RedactedKeysMap by copying the provided map and applying
* redaction rules to the specified keys.
*
* @param map the original map to be wrapped and redacted
* @param configuration the configuration
*/
RedactedKeysMap(Map<String, Object> map, Configuration configuration) {
this(map, configuration.redactedKeys, configuration.filters);
}

RedactedKeysMap(Map<String, Object> map, Pattern[] redactedKeys, String[] filters) {
this.filters = filters;
this.redactedKeys = redactedKeys;
this.redactedKeyCopy = createCopy(map);
}

/**
* Creates a copy of the given map, applying redaction rules to the specified keys.
*
* @param map the original map to be copied and redacted
* @return a new map with the same entries as the original, but with redacted values where applicable
*/
private Map<String, Object> createCopy(Map<? extends String, ?> map) {
Map<String, Object> copy = new HashMap<>();
for (Entry<? extends String, ?> entry : map.entrySet()) {
if (entry.getValue() == null) {
copy.put(entry.getKey(), null);
} else {
Object transformedValue = transformEntry(entry.getKey(), entry.getValue());
copy.put(entry.getKey(), transformedValue);
}
}
return copy;
}

@Override
public int size() {
return redactedKeyCopy.size();
}

@Override
public boolean isEmpty() {
return redactedKeyCopy.isEmpty();
}

@Override
public boolean containsKey(Object key) {
return redactedKeyCopy.containsKey(key);
}

@Override
public boolean containsValue(Object value) {
return redactedKeyCopy.containsValue(value);
}

@Override
public Object get(Object key) {
return redactedKeyCopy.get(key);
}

@Override
public Object put(String key, Object value) {
if (value == null) {
return redactedKeyCopy.put(key, null);
}
Object transformedValue = transformEntry(key, value);
return redactedKeyCopy.put(key, transformedValue);
}

@Override
public Object remove(Object key) {
return redactedKeyCopy.remove(key);
}

@Override
public void putAll(Map<? extends String, ?> mapValues) {
Map<String, Object> copy = createCopy(mapValues);
redactedKeyCopy.putAll(copy);
}

@Override
public void clear() {
redactedKeyCopy.clear();
}

@Override
public Set<String> keySet() {
return redactedKeyCopy.keySet();
}

@Override
public Collection<Object> values() {
return redactedKeyCopy.values();
}

@Override
public Set<Entry<String, Object>> entrySet() {
return redactedKeyCopy.entrySet();
}

@SuppressWarnings("unchecked")
private Object transformEntry(Object key, Object value) {
if (value instanceof Map) {
return new RedactedKeysMap((Map<String, Object>) value, redactedKeys, filters);
}
return shouldRedactKey((String) key) ? REDACTED_PLACEHOLDER : value;
}

private boolean shouldRedactKey(String key) {
if (key == null) {
return false;
}

Pattern[] redactedKeys = this.redactedKeys;
if (redactedKeys != null) {
for (Pattern pattern : redactedKeys) {
if (pattern.matcher(key).matches()) {
return true;
}
}
}

String[] filters = this.filters;
if (filters != null) {
for (String filter : filters) {
if (filter != null && key.contains(filter)) {
return true;
}
}
}

return false;
}
}
5 changes: 1 addition & 4 deletions bugsnag/src/main/java/com/bugsnag/Report.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import com.bugsnag.serialization.Expose;

import com.bugsnag.util.FilteredMap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -126,7 +123,7 @@ public Map<String, String> getUser() {

@Expose
public Map<String, Object> getMetaData() {
return new FilteredMap(diagnostics.metaData, Arrays.asList(config.filters));
return new RedactedKeysMap(diagnostics.metaData, config);
}

@Expose
Expand Down
Loading