fix(memory): prevent QMD scope deny bypass

This commit is contained in:
Peter Steinberger
2026-02-15 02:41:30 +00:00
parent 014b42dd45
commit f9bb748a6c
11 changed files with 80 additions and 6 deletions

View File

@@ -33,4 +33,22 @@ describe("qmd scope", () => {
expect(isQmdScopeAllowed(scope, "agent:agent-1:workspace:group:123")).toBe(true);
expect(isQmdScopeAllowed(scope, "agent:agent-1:other:group:123")).toBe(false);
});
it("supports rawKeyPrefix matches for agent-prefixed keys", () => {
const scope: ResolvedQmdConfig["scope"] = {
default: "allow",
rules: [{ action: "deny", match: { rawKeyPrefix: "agent:main:discord:" } }],
};
expect(isQmdScopeAllowed(scope, "agent:main:discord:channel:c123")).toBe(false);
expect(isQmdScopeAllowed(scope, "agent:main:slack:channel:c123")).toBe(true);
});
it("keeps legacy agent-prefixed keyPrefix rules working", () => {
const scope: ResolvedQmdConfig["scope"] = {
default: "allow",
rules: [{ action: "deny", match: { keyPrefix: "agent:main:discord:" } }],
};
expect(isQmdScopeAllowed(scope, "agent:main:discord:channel:c123")).toBe(false);
expect(isQmdScopeAllowed(scope, "agent:main:slack:channel:c123")).toBe(true);
});
});

View File

@@ -15,6 +15,7 @@ export function isQmdScopeAllowed(scope: ResolvedQmdConfig["scope"], sessionKey?
const channel = parsed.channel;
const chatType = parsed.chatType;
const normalizedKey = parsed.normalizedKey ?? "";
const rawKey = sessionKey?.trim().toLowerCase() ?? "";
for (const rule of scope.rules ?? []) {
if (!rule) {
continue;
@@ -26,9 +27,23 @@ export function isQmdScopeAllowed(scope: ResolvedQmdConfig["scope"], sessionKey?
if (match.chatType && match.chatType !== chatType) {
continue;
}
if (match.keyPrefix && !normalizedKey.startsWith(match.keyPrefix)) {
const normalizedPrefix = match.keyPrefix?.trim().toLowerCase() || undefined;
const rawPrefix = match.rawKeyPrefix?.trim().toLowerCase() || undefined;
if (rawPrefix && !rawKey.startsWith(rawPrefix)) {
continue;
}
if (normalizedPrefix) {
// Backward compat: older configs used `keyPrefix: "agent:<id>:..."` to match raw keys.
const isLegacyRaw = normalizedPrefix.startsWith("agent:");
if (isLegacyRaw) {
if (!rawKey.startsWith(normalizedPrefix)) {
continue;
}
} else if (!normalizedKey.startsWith(normalizedPrefix)) {
continue;
}
}
return rule.action === "allow";
}
const fallback = scope.default ?? "allow";