mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-02 12:22:22 +00:00
🔒 fix: Address Codex re-review on memory capability (round 5)
- Gate inline memory registration (memoryAvailable) on the memory WRITE permissions (USE+CREATE+UPDATE), so a read-only-memory role no longer has set_memory/delete_memory shown to the model only for the runtime loader to refuse them (api/server/services/Endpoints/agents/initialize.js). - Enforce the per-agent memory opt-in at execution: handleTools now refuses to construct set_memory/delete_memory unless the agent actually declared them (toolDefinitions/tools), blocking hallucinated/undeclared memory tool calls from mutating memory. - Fail closed when getFormattedMemories errors with a configured tokenLimit, instead of writing as if storage were empty and bypassing the cap (api/app/clients/tools/util/handleTools.js).
This commit is contained in:
parent
e52ca9d3d2
commit
74e6744150
2 changed files with 35 additions and 6 deletions
|
|
@ -85,6 +85,23 @@ async function isMemoryToolUsable(req, writePermissions = []) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the agent actually declared the requested inline memory tool. The
|
||||
* event-driven executor loads tools by requested name, so a hallucinated or
|
||||
* undeclared `set_memory`/`delete_memory` call (e.g. on an agent that never
|
||||
* opted into the memory capability) must not be constructed.
|
||||
* @param {{ toolDefinitions?: Array<{ name?: string }>, tools?: Array<unknown> }} [agent]
|
||||
* @param {string} toolName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function agentDeclaredMemoryTool(agent, toolName) {
|
||||
if (!agent) {
|
||||
return false;
|
||||
}
|
||||
const declared = (entry) => (typeof entry === 'string' ? entry : entry?.name) === toolName;
|
||||
return (agent.toolDefinitions ?? []).some(declared) || (agent.tools ?? []).some(declared);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the availability and authentication of tools for a user based on environment variables or user-specific plugin authentication values.
|
||||
* Tools without required authentication or with valid authentication are considered valid.
|
||||
|
|
@ -397,7 +414,10 @@ const loadTools = async ({
|
|||
const validKeys = memoryConfig?.validKeys;
|
||||
const tokenLimit = memoryConfig?.tokenLimit;
|
||||
requestedTools[tool] = async () => {
|
||||
if (!(await isMemoryToolUsable(options.req, [Permissions.CREATE, Permissions.UPDATE]))) {
|
||||
if (
|
||||
!agentDeclaredMemoryTool(agent, SET_MEMORY_TOOL_NAME) ||
|
||||
!(await isMemoryToolUsable(options.req, [Permissions.CREATE, Permissions.UPDATE]))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
let totalTokens = 0;
|
||||
|
|
@ -407,6 +427,9 @@ const loadTools = async ({
|
|||
totalTokens = formatted?.totalTokens ?? 0;
|
||||
} catch (error) {
|
||||
logger.error('[handleTools] Failed to load memory token count for set_memory', error);
|
||||
/** Fail closed: without the current usage total a configured
|
||||
* tokenLimit could be silently bypassed. */
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return createMemoryTool({ userId: user, setMemory, validKeys, tokenLimit, totalTokens });
|
||||
|
|
@ -415,7 +438,10 @@ const loadTools = async ({
|
|||
} else if (tool === DELETE_MEMORY_TOOL_NAME) {
|
||||
const memoryConfig = options.req?.config?.memory;
|
||||
requestedTools[tool] = async () => {
|
||||
if (!(await isMemoryToolUsable(options.req, [Permissions.UPDATE]))) {
|
||||
if (
|
||||
!agentDeclaredMemoryTool(agent, DELETE_MEMORY_TOOL_NAME) ||
|
||||
!(await isMemoryToolUsable(options.req, [Permissions.UPDATE]))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return createDeleteMemoryTool({
|
||||
|
|
|
|||
|
|
@ -153,9 +153,12 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
|
|||
const skillDbMethods = getSkillDbMethods();
|
||||
|
||||
/** Run-level gate for inline memory tools: the `memory` capability must be
|
||||
* enabled, memory must be configured, and the user must not have opted out
|
||||
* or lost the `MEMORIES.USE` permission. Agents (or the ephemeral memory
|
||||
* badge) opt in per-agent via the `memory` marker on `tools`. */
|
||||
* enabled, memory must be configured, and the user must not have opted out.
|
||||
* Requires the memory WRITE permissions (CREATE + UPDATE) — both inline tools
|
||||
* mutate memory — so the tools aren't registered (and shown to the model) for
|
||||
* read-only-memory roles that the runtime loader would then refuse to build.
|
||||
* Agents (or the ephemeral memory badge) opt in per-agent via the `memory`
|
||||
* marker on `tools`. */
|
||||
const memoryAvailable =
|
||||
enabledCapabilities.has(AgentCapabilities.memory) &&
|
||||
isMemoryEnabled(appConfig?.memory) &&
|
||||
|
|
@ -163,7 +166,7 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
|
|||
(await checkAccess({
|
||||
user: req.user,
|
||||
permissionType: PermissionTypes.MEMORIES,
|
||||
permissions: [Permissions.USE],
|
||||
permissions: [Permissions.USE, Permissions.CREATE, Permissions.UPDATE],
|
||||
getRoleByName: db.getRoleByName,
|
||||
}));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue