From 8dd6428a2f2e1c0df6b00a0a90c6f865ae38bd12 Mon Sep 17 00:00:00 2001 From: Naitik Date: Wed, 8 Jul 2026 23:24:59 +0530 Subject: [PATCH] fix(plugins): reject declared signatures when signing key is missing Require SECUSCAN_PLUGIN_SIGNATURE_KEY whenever a plugin declares a signature, even if signature enforcement is disabled globally. Co-authored-by: Cursor --- backend/secuscan/plugins.py | 24 +++++++------- testing/backend/unit/test_plugin_validator.py | 32 +++++++++++++++++-- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/backend/secuscan/plugins.py b/backend/secuscan/plugins.py index a8775d2de..623998d84 100644 --- a/backend/secuscan/plugins.py +++ b/backend/secuscan/plugins.py @@ -254,19 +254,17 @@ def _verify_plugin_integrity(self, plugin: PluginMetadata, plugin_dir: Path) -> if has_signature: if not settings.plugin_signature_key: - if settings.enforce_plugin_signatures: - logger.error("SECUSCAN_PLUGIN_SIGNATURE_KEY required for verifying %s", plugin.id) - return False - logger.warning("Skipping signature verification for %s: key not configured", plugin.id) - else: - expected_sig = hmac.new( - settings.plugin_signature_key.encode("utf-8"), - combined_digest.encode("utf-8"), - hashlib.sha256, - ).hexdigest() - if not hmac.compare_digest(expected_sig, plugin.signature): - logger.error("Signature mismatch for plugin %s", plugin.id) - return False + logger.error("SECUSCAN_PLUGIN_SIGNATURE_KEY required for verifying %s", plugin.id) + return False + + expected_sig = hmac.new( + settings.plugin_signature_key.encode("utf-8"), + combined_digest.encode("utf-8"), + hashlib.sha256, + ).hexdigest() + if not hmac.compare_digest(expected_sig, plugin.signature): + logger.error("Signature mismatch for plugin %s", plugin.id) + return False return True diff --git a/testing/backend/unit/test_plugin_validator.py b/testing/backend/unit/test_plugin_validator.py index 4d3db8767..e153d972e 100644 --- a/testing/backend/unit/test_plugin_validator.py +++ b/testing/backend/unit/test_plugin_validator.py @@ -681,8 +681,6 @@ def test_integrity_check_fails_on_checksum_mismatch(self, tmp_path): """PluginManager._verify_plugin_integrity should return False if checksum mismatches.""" from backend.secuscan.plugins import PluginManager from backend.secuscan.models import PluginMetadata - - # 1. Create a valid metadata but with wrong checksum data = _minimal_valid() data["checksum"] = "b" * 64 # wrong checksum data["presets"] = {} @@ -696,6 +694,36 @@ def test_integrity_check_fails_on_checksum_mismatch(self, tmp_path): assert mgr._verify_plugin_integrity(plugin, plugin_dir) is False + def test_declared_signature_requires_key_even_when_enforcement_off(self, tmp_path, monkeypatch): + """A plugin with a signature must not load when the signing key is missing.""" + from backend.secuscan.plugins import PluginManager + from backend.secuscan.config import settings + from backend.secuscan.models import PluginMetadata + + monkeypatch.setattr(settings, "enforce_plugin_signatures", False) + monkeypatch.setattr(settings, "plugin_signature_key", None) + + data = _minimal_valid() + data["presets"] = {} + for field in data["fields"]: + if field["type"] == "number": + field["type"] = "integer" + + plugin_dir = _write_metadata(tmp_path, data) + metadata_file = plugin_dir / "metadata.json" + parser_file = plugin_dir / "parser.py" + parser_file.write_text("def parse(output):\n return {'findings': []}\n", encoding="utf-8") + + digest = PluginManager.compute_plugin_digest(metadata_file, parser_file) + data["checksum"] = digest + data["signature"] = "a" * 64 + metadata_file.write_text(json.dumps(data), encoding="utf-8") + + plugin = PluginMetadata(**data) + mgr = PluginManager(plugins_dir=str(tmp_path)) + + assert mgr._verify_plugin_integrity(plugin, plugin_dir) is False + def test_sandbox_exec_fails_on_exec_statement(self): """Parser sandbox should raise ParserSandboxError when parser execution encounters exec().""" from backend.secuscan.parser_sandbox import run_parser_in_sandbox, ParserSandboxError