-
Notifications
You must be signed in to change notification settings - Fork 153
Dlp #1909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dlp #1909
Changes from 34 commits
bcc7dcb
497a43c
f0119d3
5f84eb6
e2536fe
6ebb9c4
9d2e722
699b226
7ffee70
559a62b
de3ed95
4c3ff8f
8cfb161
b17b14a
099ec70
02d75ec
2be464b
450204b
75b56d9
8adedb9
e7cc895
0f673bc
6a0bed1
469256e
53bc2f8
7d04071
ff15443
ea1e179
dce7806
e2eaa48
13711b1
7aa7e95
60a13df
0bf1268
21c3489
20ed318
c20c70d
528cc14
8d75f7c
c3ec071
e9a6286
8b1a278
77022dd
5008ee7
e58e11d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCAttribute; | ||
|
|
||
| public abstract class Action implements DLPAction { | ||
|
|
||
| private String field; | ||
|
|
||
| public String getField() { | ||
| return field; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setField(String field) { | ||
| this.field = field; | ||
| } | ||
|
|
||
| @Override | ||
| public abstract String apply(DLPContext context); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.InputStreamReader; | ||
| import java.util.*; | ||
|
|
||
| /** | ||
| * Loads field risk mappings from a CSV file with format: | ||
| * field_name,description,risk_level | ||
| * where risk_level must be one of: high, medium, low, unclassified | ||
| */ | ||
| public class CsvFieldConfiguration implements FieldConfiguration { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(CsvFieldConfiguration.class); | ||
|
|
||
| @Override | ||
| public Map<String, String> getFields(String fileName) { | ||
| Map<String, String> riskDict = new HashMap<>(); | ||
|
|
||
| try (InputStream inputStream = getResourceAsStream(fileName)) { | ||
| BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); | ||
| String line; | ||
| boolean isHeader = true; | ||
|
|
||
| while ((line = reader.readLine()) != null) { | ||
| if (isHeader) { | ||
| isHeader = false; | ||
| continue; | ||
| } | ||
|
|
||
| line = line.trim(); | ||
| if (line.isEmpty() || line.startsWith("#")) continue; | ||
|
|
||
| String[] parts = line.split(",", -1); | ||
| if (parts.length < 3) { | ||
| log.warn("Skipping invalid line (less than 3 columns): {}", line); | ||
| continue; | ||
| } | ||
|
|
||
| String field = parts[0].trim().toLowerCase(Locale.ROOT); | ||
| String riskLevel = parts[2].trim().toLowerCase(Locale.ROOT); | ||
|
|
||
| if (!isValidRiskLevel(riskLevel)) { | ||
| log.warn("Invalid risk level '{}' for field '{}'. Defaulting to 'unclassified'", riskLevel, field); | ||
| riskLevel = "unclassified"; | ||
| } | ||
|
|
||
| riskDict.put(field, riskLevel); | ||
| } | ||
|
|
||
| } catch (IOException e) { | ||
| throw new RuntimeException("Error reading CSV field configuration: " + fileName, e); | ||
| } | ||
| return riskDict; | ||
| } | ||
|
|
||
| private InputStream getResourceAsStream(String fileName) { | ||
| InputStream is = CsvFieldConfiguration.class.getClassLoader().getResourceAsStream(fileName); | ||
| if (is == null) { | ||
| String msg = "Could not find CSV config file: " + fileName; | ||
| log.error(msg); | ||
| throw new IllegalArgumentException(msg); | ||
| } | ||
| return is; | ||
| } | ||
|
|
||
| private boolean isValidRiskLevel(String level) { | ||
| return switch (level) { | ||
| case "high", "medium", "low", "unclassified" -> true; | ||
| default -> false; | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| public interface DLPAction { | ||
| String apply(DLPContext context); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,78 @@ | ||||||||||||||||||||
| package com.predic8.membrane.core.interceptor.dlp; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import com.fasterxml.jackson.core.JsonFactory; | ||||||||||||||||||||
| import com.fasterxml.jackson.core.JsonFactoryBuilder; | ||||||||||||||||||||
| import com.fasterxml.jackson.core.StreamReadConstraints; | ||||||||||||||||||||
| import com.fasterxml.jackson.core.json.JsonReadFeature; | ||||||||||||||||||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||||||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||||||||||||||||||
| import com.predic8.membrane.core.http.Message; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import java.io.InputStream; | ||||||||||||||||||||
| import java.util.*; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public class DLPAnalyzer { | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static final JsonFactory JSON_FACTORY = new JsonFactoryBuilder() | ||||||||||||||||||||
| .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true) | ||||||||||||||||||||
| .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, false) | ||||||||||||||||||||
| .streamReadConstraints(StreamReadConstraints.builder() | ||||||||||||||||||||
| .maxNestingDepth(64) | ||||||||||||||||||||
| .maxStringLength(16 * 1024) | ||||||||||||||||||||
| .build()) | ||||||||||||||||||||
| .build(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private static final ObjectMapper MAPPER = new ObjectMapper(JSON_FACTORY); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| private final Map<String, RiskReport.Category> riskDict; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| public DLPAnalyzer(Map<String, String> rawRiskMap) { | ||||||||||||||||||||
| this.riskDict = mapToEnumRiskLevels(rawRiskMap); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
||||||||||||||||||||
| public DLPAnalyzer(Map<String, String> rawRiskMap) { | |
| this.riskDict = mapToEnumRiskLevels(rawRiskMap); | |
| } | |
| public DLPAnalyzer(Map<String, String> rawRiskMap) { | |
| if (rawRiskMap == null) { | |
| throw new IllegalArgumentException("Risk map cannot be null"); | |
| } | |
| this.riskDict = mapToEnumRiskLevels(rawRiskMap); | |
| } |
🤖 Prompt for AI Agents
In core/src/main/java/com/predic8/membrane/core/interceptor/dlp/DLPAnalyzer.java
around lines 29 to 31, the constructor accepts a rawRiskMap parameter but does
not validate it for null, risking a NullPointerException. Add a null check at
the start of the constructor to verify rawRiskMap is not null, and if it is,
throw an IllegalArgumentException or handle it appropriately to prevent
downstream errors.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| package com.predic8.membrane.core.interceptor.dlp; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public class DLPContext { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private final String body; | ||||||||||||||||||||||||||||||||||||||||||||||
| private final RiskReport riskReport; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public DLPContext(String body, RiskReport riskReport) { | ||||||||||||||||||||||||||||||||||||||||||||||
| this.riskReport = riskReport; | ||||||||||||||||||||||||||||||||||||||||||||||
| this.body = body; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| public DLPContext(String body, RiskReport riskReport) { | |
| this.riskReport = riskReport; | |
| this.body = body; | |
| } | |
| public DLPContext(String body, RiskReport riskReport) { | |
| if (body == null) { | |
| throw new IllegalArgumentException("body cannot be null"); | |
| } | |
| this.body = body; | |
| this.riskReport = riskReport; | |
| } |
🤖 Prompt for AI Agents
In core/src/main/java/com/predic8/membrane/core/interceptor/dlp/DLPContext.java
around lines 8 to 11, the constructor parameters are in a different order than
the field declarations and lack input validation. To fix this, reorder the
constructor parameters to match the field declaration order, placing the body
parameter first followed by riskReport. Additionally, add validation checks to
ensure that neither parameter is null or invalid before assigning them to the
fields.
🛠️ Refactor suggestion
Consider adding constructor validation for required parameters.
The constructor should validate that the body parameter is not null, as it's likely a required field for DLP processing.
public DLPContext(String body, RiskReport riskReport) {
+ if (body == null) {
+ throw new IllegalArgumentException("body cannot be null");
+ }
this.riskReport = riskReport;
this.body = body;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public DLPContext(String body, RiskReport riskReport) { | |
| this.riskReport = riskReport; | |
| this.body = body; | |
| } | |
| public DLPContext(String body, RiskReport riskReport) { | |
| if (body == null) { | |
| throw new IllegalArgumentException("body cannot be null"); | |
| } | |
| this.riskReport = riskReport; | |
| this.body = body; | |
| } |
🤖 Prompt for AI Agents
In core/src/main/java/com/predic8/membrane/core/interceptor/dlp/DLPContext.java
around lines 8 to 11, the constructor does not validate the body parameter which
is likely required. Add a null check for the body parameter at the start of the
constructor and throw an IllegalArgumentException if it is null to ensure the
object is always created with valid data.
🛠️ Refactor suggestion
Add null validation in constructor.
The constructor should validate parameters to prevent null pointer exceptions in downstream code.
public DLPContext(String body, RiskReport riskReport) {
+ this.body = Objects.requireNonNull(body, "body cannot be null");
- this.riskReport = riskReport;
- this.body = body;
+ this.riskReport = riskReport; // riskReport can be null as indicated by hasRiskReport()
}🤖 Prompt for AI Agents
In core/src/main/java/com/predic8/membrane/core/interceptor/dlp/DLPContext.java
around lines 8 to 11, the constructor lacks null checks for its parameters. Add
validation to ensure that neither the body nor the riskReport parameters are
null. If either is null, throw an IllegalArgumentException with a clear message
indicating which parameter is missing. This prevents null pointer exceptions
later in the code.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCAttribute; | ||
| import com.predic8.membrane.annot.MCChildElement; | ||
| import com.predic8.membrane.annot.MCElement; | ||
| import com.predic8.membrane.core.exchange.Exchange; | ||
| import com.predic8.membrane.core.http.Message; | ||
| import com.predic8.membrane.core.interceptor.AbstractInterceptor; | ||
| import com.predic8.membrane.core.interceptor.Outcome; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| import static com.predic8.membrane.core.interceptor.Outcome.CONTINUE; | ||
|
|
||
| @MCElement(name = "dlp") | ||
| public class DLPInterceptor extends AbstractInterceptor { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(DLPInterceptor.class); | ||
|
|
||
| private DLPAnalyzer dlpAnalyzer; | ||
| private String fieldsConfig; | ||
|
|
||
| private List<Mask> masks = new ArrayList<>(); | ||
| private List<Filter> filters = new ArrayList<>(); | ||
| private List<Report> reports = new ArrayList<>(); | ||
|
|
||
| private final List<DLPAction> actions = new ArrayList<>(); | ||
|
|
||
| @Override | ||
| public void init() { | ||
| Map<String, String> config = fieldsConfig != null ? | ||
| new CsvFieldConfiguration().getFields(fieldsConfig) : | ||
| Map.of(); | ||
| actions.addAll(masks); | ||
| actions.addAll(filters); | ||
| actions.addAll(reports); | ||
| this.dlpAnalyzer = new DLPAnalyzer(config); | ||
| super.init(); | ||
| } | ||
|
|
||
| @Override | ||
| public Outcome handleRequest(Exchange exc) { | ||
| return handleInternal(exc.getRequest()); | ||
| } | ||
|
|
||
| private Outcome handleInternal(Message msg) { | ||
| try { | ||
| String body = msg.getBodyAsStringDecoded(); | ||
| RiskReport report = dlpAnalyzer.analyze(msg); | ||
| log.info("DLP Risk Analysis: {}", report.getStructuredReport()); | ||
|
|
||
| for (DLPAction action : actions) { | ||
| body = action.apply(new DLPContext(body, report)); | ||
| } | ||
|
|
||
| msg.setBodyContent(body.getBytes(StandardCharsets.UTF_8)); | ||
| return CONTINUE; | ||
|
|
||
| } catch (Exception e) { | ||
| log.error("Exception in DLPInterceptor.handleInternal: ", e); | ||
| return Outcome.ABORT; | ||
| } | ||
| } | ||
|
|
||
| public String getFieldsConfig() { | ||
| return fieldsConfig; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setFieldsConfig(String fieldsConfig) { | ||
| this.fieldsConfig = fieldsConfig; | ||
| } | ||
|
|
||
| public List<Mask> getMasks() { | ||
| return masks; | ||
| } | ||
|
|
||
| @MCChildElement | ||
| public DLPInterceptor setMasks(List<Mask> masks) { | ||
| this.masks = masks; | ||
| return this; | ||
| } | ||
|
|
||
| public List<Filter> getFilters() { | ||
| return filters; | ||
| } | ||
|
|
||
| @MCChildElement(order = 1) | ||
| public DLPInterceptor setFilters(List<Filter> filters) { | ||
| this.filters = filters; | ||
| return this; | ||
| } | ||
|
|
||
| public List<Report> getReports() { | ||
| return reports; | ||
| } | ||
|
|
||
| @MCChildElement(order = 2) | ||
| public void setReports(List<Report> reports) { | ||
| this.reports = reports; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCAttribute; | ||
| import com.predic8.membrane.annot.MCElement; | ||
|
|
||
| @MCElement(name = "field") | ||
| public class Field { | ||
|
|
||
| private String jsonpath; | ||
|
|
||
| public String getJsonpath() { | ||
| return jsonpath; | ||
| } | ||
|
|
||
| @MCAttribute | ||
| public void setJsonpath(String jsonpath) { | ||
| this.jsonpath = jsonpath; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| public interface FieldConfiguration { | ||
| Map<String, String> getFields(String fileName); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.predic8.membrane.core.interceptor.dlp; | ||
|
|
||
| import com.predic8.membrane.annot.MCChildElement; | ||
| import com.predic8.membrane.annot.MCElement; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @MCElement(name = "fields") | ||
| public class Fields { | ||
|
|
||
| private List<Field> fields = new ArrayList<>(); | ||
|
|
||
| @MCChildElement | ||
| public Fields setFields(List<Field> fields) { | ||
| this.fields = fields; | ||
| return this; | ||
| } | ||
|
|
||
| public List<Field> getFields() { | ||
| return fields; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for the field parameter.
The setter should validate that the field is not empty and is a valid JSONPath expression to prevent runtime errors.
@MCAttribute public void setField(String field) { + if (field != null && field.trim().isEmpty()) { + throw new IllegalArgumentException("field cannot be empty"); + } this.field = field; }🤖 Prompt for AI Agents