mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(security): root-cause guards against blank tool guard rule rows
This commit is contained in:
parent
4567bd6019
commit
ae4d432ee2
@ -138,6 +138,17 @@ public class SecurityController {
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "按主键 ID 删除自定义规则(兜底,rule_id 异常时使用)")
|
||||
@DeleteMapping("/guard/rules/by-id/{id}")
|
||||
public R<String> deleteRuleByPk(@PathVariable Long id) {
|
||||
try {
|
||||
ruleService.deleteRuleByPk(id);
|
||||
return R.ok("删除成功");
|
||||
} catch (IllegalArgumentException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Audit ====================
|
||||
|
||||
@Operation(summary = "审计日志")
|
||||
|
||||
@ -64,10 +64,15 @@ public class ToolGuardRuleService {
|
||||
* 新增自定义规则
|
||||
*/
|
||||
public ToolGuardRuleEntity createRule(ToolGuardRuleEntity rule) {
|
||||
if (rule == null || rule.getRuleId() == null || rule.getRuleId().isBlank()) {
|
||||
throw new IllegalArgumentException("Rule ID is required");
|
||||
if (rule == null) {
|
||||
throw new IllegalArgumentException("Rule body is required");
|
||||
}
|
||||
requireNonBlank(rule.getRuleId(), "Rule ID");
|
||||
requireNonBlank(rule.getName(), "Rule name");
|
||||
requireNonBlank(rule.getPattern(), "Rule pattern");
|
||||
rule.setRuleId(rule.getRuleId().trim());
|
||||
rule.setName(rule.getName().trim());
|
||||
rule.setPattern(rule.getPattern().trim());
|
||||
rule.setBuiltin(false);
|
||||
ruleMapper.insert(rule);
|
||||
ruleRegistry.reload();
|
||||
@ -75,7 +80,8 @@ public class ToolGuardRuleService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新规则
|
||||
* 更新规则。仅覆盖请求里显式提供的字段;显式传入的关键字段(name / pattern)
|
||||
* 不允许置为空白,避免回写出无意义的"空名空模式"行。
|
||||
*/
|
||||
public ToolGuardRuleEntity updateRule(String ruleId, ToolGuardRuleEntity update) {
|
||||
ToolGuardRuleEntity existing = getByRuleId(ruleId);
|
||||
@ -83,14 +89,20 @@ public class ToolGuardRuleService {
|
||||
throw new IllegalArgumentException("Rule not found: " + ruleId);
|
||||
}
|
||||
|
||||
if (update.getName() != null) existing.setName(update.getName());
|
||||
if (update.getName() != null) {
|
||||
requireNonBlank(update.getName(), "Rule name");
|
||||
existing.setName(update.getName().trim());
|
||||
}
|
||||
if (update.getDescription() != null) existing.setDescription(update.getDescription());
|
||||
if (update.getToolName() != null) existing.setToolName(update.getToolName());
|
||||
if (update.getParamName() != null) existing.setParamName(update.getParamName());
|
||||
if (update.getCategory() != null) existing.setCategory(update.getCategory());
|
||||
if (update.getSeverity() != null) existing.setSeverity(update.getSeverity());
|
||||
if (update.getDecision() != null) existing.setDecision(update.getDecision());
|
||||
if (update.getPattern() != null) existing.setPattern(update.getPattern());
|
||||
if (update.getPattern() != null) {
|
||||
requireNonBlank(update.getPattern(), "Rule pattern");
|
||||
existing.setPattern(update.getPattern().trim());
|
||||
}
|
||||
if (update.getExcludePattern() != null) existing.setExcludePattern(update.getExcludePattern());
|
||||
if (update.getRemediation() != null) existing.setRemediation(update.getRemediation());
|
||||
if (update.getEnabled() != null) existing.setEnabled(update.getEnabled());
|
||||
@ -101,6 +113,12 @@ public class ToolGuardRuleService {
|
||||
return existing;
|
||||
}
|
||||
|
||||
private static void requireNonBlank(String value, String fieldLabel) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException(fieldLabel + " is required");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用规则
|
||||
*/
|
||||
@ -128,4 +146,24 @@ public class ToolGuardRuleService {
|
||||
ruleMapper.deleteById(existing.getId());
|
||||
ruleRegistry.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键 ID 删除自定义规则。兜底通道:当 rule_id 因历史脏数据为空或无法走
|
||||
* /guard/rules/{ruleId} 路径变量时,UI 仍可通过主键删除。
|
||||
*/
|
||||
public void deleteRuleByPk(Long id) {
|
||||
if (id == null) {
|
||||
throw new IllegalArgumentException("Rule primary key is required");
|
||||
}
|
||||
ToolGuardRuleEntity existing = ruleMapper.selectById(id);
|
||||
if (existing == null) {
|
||||
throw new IllegalArgumentException("Rule not found: id=" + id);
|
||||
}
|
||||
if (Boolean.TRUE.equals(existing.getBuiltin())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot delete builtin rule: " + existing.getRuleId());
|
||||
}
|
||||
ruleMapper.deleteById(id);
|
||||
ruleRegistry.reload();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
-- Earlier releases let the rule-create API persist a custom guard rule
|
||||
-- with a blank rule_id because the service skipped the not-blank check.
|
||||
-- The resulting row was undeletable from the UI: the delete endpoint is
|
||||
-- /guard/rules/{ruleId}, and a blank path variable produces a 404 instead
|
||||
-- of resolving to the row. This migration does two things:
|
||||
--
|
||||
-- 1. Purge any orphan rows already persisted on existing installations
|
||||
-- so users who hit the bug on v1.2.0 can recover without direct DB
|
||||
-- surgery. NULL, empty, and whitespace-only rule_id are all swept;
|
||||
-- built-in rules are excluded defensively because they are seeded
|
||||
-- with stable IDs and should never appear here.
|
||||
--
|
||||
-- 2. Add a CHECK constraint so the database itself rejects blank
|
||||
-- rule_id going forward. The service-layer guard already prevents
|
||||
-- this from the UI, but the DB constraint defends against any
|
||||
-- future code path that bypasses the service (batch import, direct
|
||||
-- SQL, future endpoints) and makes the invariant explicit at the
|
||||
-- schema level.
|
||||
|
||||
DELETE FROM mate_tool_guard_rule
|
||||
WHERE (rule_id IS NULL OR LENGTH(TRIM(rule_id)) = 0)
|
||||
AND (builtin IS NULL OR builtin = FALSE);
|
||||
|
||||
ALTER TABLE mate_tool_guard_rule
|
||||
ADD CONSTRAINT ck_tool_guard_rule_id_nonblank
|
||||
CHECK (rule_id IS NOT NULL AND LENGTH(TRIM(rule_id)) > 0);
|
||||
@ -0,0 +1,14 @@
|
||||
-- See the matching H2 file for context. This migration purges any
|
||||
-- orphan rows that earlier releases persisted with a blank rule_id and
|
||||
-- then installs a CHECK constraint so the schema itself rejects blank
|
||||
-- rule_id, defending against any future code path that bypasses the
|
||||
-- service-layer guard. CHECK constraints are enforced from MySQL 8.0.16
|
||||
-- onward; this project targets MySQL 8.0+ so the constraint is live.
|
||||
|
||||
DELETE FROM mate_tool_guard_rule
|
||||
WHERE (rule_id IS NULL OR LENGTH(TRIM(rule_id)) = 0)
|
||||
AND (builtin IS NULL OR builtin = FALSE);
|
||||
|
||||
ALTER TABLE mate_tool_guard_rule
|
||||
ADD CONSTRAINT ck_tool_guard_rule_id_nonblank
|
||||
CHECK (rule_id IS NOT NULL AND LENGTH(TRIM(rule_id)) > 0);
|
||||
Loading…
Reference in New Issue
Block a user