Skip to content

Commit feeeaac

Browse files
committed
Fix secrets detection redaction gating
Signed-off-by: lucarlig <luca.carlig@ibm.com>
1 parent 58d37e9 commit feeeaac

4 files changed

Lines changed: 47 additions & 14 deletions

File tree

plugins/rust/python-package/secrets_detection/cpex_secrets_detection/secrets_detection_rust/__init__.pyi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import builtins
55
import typing
66
__all__ = [
7-
"SecretsDetectionPluginCore",
87
"SecretsDetectionPluginCore",
98
"py_scan_container",
109
]

plugins/rust/python-package/secrets_detection/src/bin/stub_gen.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,8 @@ use secrets_detection_rust::stub_info;
1010

1111
fn curate_extension_stub() {
1212
let stub_path = Path::new("cpex_secrets_detection/secrets_detection_rust/__init__.pyi");
13-
let mut content = fs::read_to_string(stub_path).expect("Failed to read generated stub file");
14-
content = content.replace(
15-
"\"py_scan_container\",\n]",
16-
"\"SecretsDetectionPluginCore\",\n \"py_scan_container\",\n]",
17-
);
18-
if !content.contains("class SecretsDetectionPluginCore:") {
19-
content.push_str(
20-
"\n\n@typing.final\nclass SecretsDetectionPluginCore:\n def __new__(cls, config: dict) -> SecretsDetectionPluginCore: ...\n def prompt_pre_fetch(self, payload: typing.Any, context: typing.Any) -> typing.Any: ...\n def tool_post_invoke(self, payload: typing.Any, context: typing.Any) -> typing.Any: ...\n def resource_post_fetch(self, payload: typing.Any, context: typing.Any) -> typing.Any: ...\n",
21-
);
22-
}
13+
let content = fs::read_to_string(stub_path).expect("Failed to read generated stub file");
14+
let content = content.trim_end().to_string() + "\n";
2315
fs::write(stub_path, content).expect("Failed to write curated stub file");
2416
}
2517

plugins/rust/python-package/secrets_detection/src/plugin.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl SecretsDetectionPluginCore {
4848
);
4949
}
5050

51-
if self.config.redact && !redacted.is(&source) {
51+
if self.config.redact && count > 0 {
5252
payload.setattr("args", &redacted)?;
5353
return build_framework_object(
5454
py,
@@ -97,7 +97,7 @@ impl SecretsDetectionPluginCore {
9797
);
9898
}
9999

100-
if self.config.redact && !redacted.is(&value) {
100+
if self.config.redact && count > 0 {
101101
payload.setattr("result", &redacted)?;
102102
return build_framework_object(
103103
py,
@@ -149,7 +149,7 @@ impl SecretsDetectionPluginCore {
149149
);
150150
}
151151

152-
if self.config.redact && !redacted.is(&text) {
152+
if self.config.redact && count > 0 {
153153
content.setattr("text", &redacted)?;
154154
return build_framework_object(
155155
py,

plugins/rust/python-package/secrets_detection/tests/test_plugin.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ async def test_prompt_pre_fetch_redacts_without_blocking(self, plugin):
9292
assert result.modified_payload.args["input"] == "AWS_ACCESS_KEY_ID=[REDACTED]"
9393
assert result.metadata == {"secrets_redacted": True, "count": 1}
9494

95+
async def test_prompt_pre_fetch_leaves_clean_payload_unmodified(self, plugin):
96+
payload = PromptPrehookPayload(
97+
prompt_id="prompt-1",
98+
args={"input": "hello world"},
99+
)
100+
101+
result = await plugin.prompt_pre_fetch(payload, _make_context())
102+
103+
assert result.continue_processing is True
104+
assert result.violation is None
105+
assert result.modified_payload is None
106+
assert result.metadata == {}
107+
95108
async def test_prompt_pre_fetch_blocks_without_redaction(self):
96109
plugin = SecretsDetectionPlugin(_make_config(block_on_detection=True, redact=False))
97110
payload = PromptPrehookPayload(
@@ -131,6 +144,22 @@ async def test_tool_post_invoke_redacts_mcp_content_payload(self, plugin):
131144
assert result.modified_payload.result["isError"] is False
132145
assert result.metadata == {"secrets_redacted": True, "count": 1}
133146

147+
async def test_tool_post_invoke_leaves_clean_payload_unmodified(self, plugin):
148+
payload = ToolPostInvokePayload(
149+
name="writer",
150+
result={
151+
"content": [{"type": "text", "text": "plain text"}],
152+
"isError": False,
153+
},
154+
)
155+
156+
result = await plugin.tool_post_invoke(payload, _make_context())
157+
158+
assert result.continue_processing is True
159+
assert result.violation is None
160+
assert result.modified_payload is None
161+
assert result.metadata == {}
162+
134163
async def test_resource_post_fetch_redacts_text_content(self, plugin):
135164
payload = ResourcePostFetchPayload(
136165
uri="file:///tmp/secret.txt",
@@ -144,6 +173,19 @@ async def test_resource_post_fetch_redacts_text_content(self, plugin):
144173
assert result.modified_payload.content.text == "SLACK_TOKEN=[REDACTED]"
145174
assert result.metadata == {"secrets_redacted": True, "count": 1}
146175

176+
async def test_resource_post_fetch_leaves_clean_payload_unmodified(self, plugin):
177+
payload = ResourcePostFetchPayload(
178+
uri="file:///tmp/secret.txt",
179+
content=ResourceContent(text="plain text"),
180+
)
181+
182+
result = await plugin.resource_post_fetch(payload, _make_context())
183+
184+
assert result.continue_processing is True
185+
assert result.violation is None
186+
assert result.modified_payload is None
187+
assert result.metadata == {}
188+
147189
async def test_resource_post_fetch_blocks_when_threshold_met(self):
148190
plugin = SecretsDetectionPlugin(
149191
_make_config(block_on_detection=True, redact=False, min_findings_to_block=1)

0 commit comments

Comments
 (0)