mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 06:00:39 +00:00
fix(redis): block FLUSHDB in MCP instead of running it unconfirmed
This commit is contained in:
@@ -25,7 +25,7 @@ describe("AI Redis console routing", () => {
|
||||
|
||||
it("uses the console safety path and rejects unavailable command input", () => {
|
||||
expect(classifyRedisCommandSafety("CONFIG SET requirepass secret")).toBe("blocked");
|
||||
expect(classifyRedisCommandSafety("FLUSHDB")).toBe("confirm");
|
||||
expect(classifyRedisCommandSafety("FLUSHDB")).toBe("blocked");
|
||||
expect(classifyRedisCommandSafety("SET issue:846 fixed")).toBe("write");
|
||||
expect(classifyRedisCommandSafety("INFO server")).toBe("allowed");
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { isRedisMonitorCommand } from "./redisMonitor";
|
||||
|
||||
export type RedisCommandSafety = "allowed" | "write" | "confirm" | "blocked";
|
||||
|
||||
const BLOCKED_COMMANDS = new Set(["KEYS", "FLUSHALL", "SHUTDOWN", "CONFIG", "SAVE", "BGSAVE", "SLAVEOF", "REPLICAOF", "MIGRATE", "MODULE", "SCRIPT", "EVAL", "EVALSHA"]);
|
||||
const BLOCKED_COMMANDS = new Set(["KEYS", "FLUSHALL", "FLUSHDB", "SHUTDOWN", "CONFIG", "SAVE", "BGSAVE", "SLAVEOF", "REPLICAOF", "MIGRATE", "MODULE", "SCRIPT", "EVAL", "EVALSHA"]);
|
||||
|
||||
// Explicit allowlist matching the backend's read-command classification.
|
||||
// Unknown commands fall through to "blocked" (fail-closed).
|
||||
@@ -199,7 +199,6 @@ const CONFIRM_COMMANDS = new Set([
|
||||
"ZUNIONSTORE",
|
||||
"PFMERGE",
|
||||
"GEOSEARCHSTORE",
|
||||
"FLUSHDB",
|
||||
]);
|
||||
|
||||
const WRITE_COMMANDS = new Set([
|
||||
|
||||
@@ -377,7 +377,7 @@ const RAW_COMMANDS: Record<string, Spec> = {
|
||||
DBSIZE: [1, "server"],
|
||||
FAILOVER: [-1, "server", "confirm"],
|
||||
FLUSHALL: [-1, "server", "blocked"],
|
||||
FLUSHDB: [-1, "server", "confirm"],
|
||||
FLUSHDB: [-1, "server", "blocked"],
|
||||
INFO: [-1, "server"],
|
||||
LASTSAVE: [1, "server"],
|
||||
"MEMORY USAGE": [-3, "server"],
|
||||
|
||||
@@ -2134,7 +2134,7 @@ pub fn classify_command(command: &str) -> RedisCommandSafety {
|
||||
| "ZREM" | "ZPOPMAX" | "ZPOPMIN" | "ZMPOP" | "BZMPOP" | "BZPOPMAX" | "BZPOPMIN" | "ZREMRANGEBYLEX"
|
||||
| "ZREMRANGEBYRANK" | "ZREMRANGEBYSCORE" | "XDEL" | "XTRIM" | "MOVE" | "SORT" | "SDIFFSTORE"
|
||||
| "SINTERSTORE" | "SUNIONSTORE" | "ZDIFFSTORE" | "ZINTERSTORE" | "ZRANGESTORE" | "ZUNIONSTORE" | "PFMERGE"
|
||||
| "GEOSEARCHSTORE" | "FLUSHDB" => RedisCommandSafety::Confirm,
|
||||
| "GEOSEARCHSTORE" => RedisCommandSafety::Confirm,
|
||||
"APPEND" | "BITFIELD" | "BITOP" | "COPY" | "DECR" | "DECRBY" | "GEOADD" | "GEORADIUS" | "GEORADIUSBYMEMBER"
|
||||
| "GETEX" | "GETSET" | "INCR" | "INCRBY" | "INCRBYFLOAT" | "SET" | "SETEX" | "PSETEX" | "SETNX"
|
||||
| "SETRANGE" | "MSET" | "MSETNX" | "PERSIST" | "HSET" | "HMSET" | "HINCRBY" | "HINCRBYFLOAT" | "HSETNX"
|
||||
@@ -7686,7 +7686,7 @@ mod tests {
|
||||
assert_eq!(classify_command("GETEX"), RedisCommandSafety::Write);
|
||||
assert_eq!(classify_command("XREADGROUP"), RedisCommandSafety::Write);
|
||||
assert_eq!(classify_command("del"), RedisCommandSafety::Confirm);
|
||||
assert_eq!(classify_command("flushdb"), RedisCommandSafety::Confirm);
|
||||
assert_eq!(classify_command("flushdb"), RedisCommandSafety::Blocked);
|
||||
assert_eq!(classify_command("JSON.DEL"), RedisCommandSafety::Confirm);
|
||||
assert_eq!(classify_command("JSON.FORGET"), RedisCommandSafety::Confirm);
|
||||
assert_eq!(classify_command("JSON.CLEAR"), RedisCommandSafety::Confirm);
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ test("classifies normal Redis writes separately from destructive commands", () =
|
||||
assert.equal(classifyRedisCommandSafety("HSET hash field value"), "write");
|
||||
assert.equal(classifyRedisCommandSafety("LPUSH queue value"), "write");
|
||||
assert.equal(classifyRedisCommandSafety("DEL session:1"), "confirm");
|
||||
assert.equal(classifyRedisCommandSafety("FLUSHDB"), "confirm");
|
||||
assert.equal(classifyRedisCommandSafety("FLUSHDB"), "blocked");
|
||||
assert.equal(classifyRedisCommandSafety("KEYS *"), "blocked");
|
||||
assert.equal(classifyRedisCommandSafety("GET session:1"), "allowed");
|
||||
});
|
||||
|
||||
+1
-1
@@ -83,5 +83,5 @@ test("normal writes do not require confirmation but destructive commands do", ()
|
||||
assert.equal(resolveRedisCommandSpec(["HSET"])?.safety, "write");
|
||||
assert.equal(resolveRedisCommandSpec(["LPUSH"])?.safety, "write");
|
||||
assert.equal(resolveRedisCommandSpec(["DEL"])?.safety, "confirm");
|
||||
assert.equal(resolveRedisCommandSpec(["FLUSHDB"])?.safety, "confirm");
|
||||
assert.equal(resolveRedisCommandSpec(["FLUSHDB"])?.safety, "blocked");
|
||||
});
|
||||
|
||||
+4
-4
@@ -71,8 +71,8 @@ test("highlights destructive commands as warning", () => {
|
||||
assert.match(diags[0].message, /Dangerous command 'DEL'/);
|
||||
});
|
||||
|
||||
test("highlights flushall as blocked error but flushdb as confirm warning", () => {
|
||||
// Aligned with the execution safety classification: FLUSHALL=blocked, FLUSHDB=confirm.
|
||||
test("highlights flushall and flushdb as blocked errors", () => {
|
||||
// Aligned with the execution safety classification: FLUSHALL/FLUSHDB=blocked.
|
||||
const all = buildRedisSyntaxDiagnostics("FLUSHALL");
|
||||
assert.equal(all.length, 1);
|
||||
assert.equal(all[0].severity, "error");
|
||||
@@ -80,8 +80,8 @@ test("highlights flushall as blocked error but flushdb as confirm warning", () =
|
||||
|
||||
const db = buildRedisSyntaxDiagnostics("FLUSHDB");
|
||||
assert.equal(db.length, 1);
|
||||
assert.equal(db[0].severity, "warning");
|
||||
assert.match(db[0].message, /Dangerous command 'FLUSHDB'/);
|
||||
assert.equal(db[0].severity, "error");
|
||||
assert.match(db[0].message, /Blocked command 'FLUSHDB'/);
|
||||
});
|
||||
|
||||
test("subcommands resolve via MAIN SUB key", () => {
|
||||
|
||||
Reference in New Issue
Block a user