mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-07 00:15:23 +02:00
* fix: set explicit permission defaults for USER role in roleDefaults
Previously several permission types for the USER role had empty
objects in roleDefaults, causing the getPermissionValue fallback to
resolve SHARE/CREATE via the zod schema defaults on fresh installs.
This silently granted users MCP server creation ability and left
share permissions ambiguous.
Sets explicit defaults for all multi-field permission types:
- PROMPTS/AGENTS: USE and CREATE true, SHARE false
- MCP_SERVERS: USE true, CREATE/SHARE false
- REMOTE_AGENTS: all false
Adds regression tests covering the exact reported scenarios (fresh
install with `agents: { use: true }`, restart preserving admin-panel
overrides) and structural guards against future permission schema
expansions missing explicit USER defaults.
Closes #12306.
* fix: guard MCP_SERVERS.CREATE against configDefaults fallback + add migration
The roleDefaults fix alone was insufficient: loadDefaultInterface propagates
configDefaults.mcpServers.create=true as tier-1 in getPermissionValue, overriding
the roleDefault of false. This commit:
- Adds conditional guards for MCP_SERVERS.CREATE and REMOTE_AGENTS.CREATE matching
the existing AGENTS/PROMPTS pattern (only include CREATE when explicitly configured
in yaml OR on fresh install)
- Uses raw interfaceConfig for MCP_SERVERS.CREATE tier-1 instead of loadedInterface
(which includes configDefaults fallback)
- Adds one-time migration backfill: corrects existing MCP_SERVERS.CREATE=true for
USER role in DB when no explicit yaml config is present
- Adds restart-scenario and migration regression tests for MCP_SERVERS
- Cleans up roles.spec.ts: for..of loops, Permissions[] typing, Set for lookups,
removes unnecessary aliases, improves JSDoc for exclusion list
- Fixes misleading test name for agents regression test
- Removes redundant not.toHaveProperty assertions after strict toEqual
* fix: use raw interfaceConfig for REMOTE_AGENTS.CREATE tier-1 (consistency)
Aligns REMOTE_AGENTS.CREATE with the MCP_SERVERS.CREATE fix — reads from
raw interfaceConfig instead of loadedInterface to prevent a future
configDefaults fallback from silently overriding the roleDefault.
132 lines
4 KiB
TypeScript
132 lines
4 KiB
TypeScript
import { Permissions, PermissionTypes, permissionsSchema } from './permissions';
|
|
import { SystemRoles, roleDefaults } from './roles';
|
|
|
|
const RESOURCE_MANAGEMENT_FIELDS: Permissions[] = [
|
|
Permissions.CREATE,
|
|
Permissions.SHARE,
|
|
Permissions.SHARE_PUBLIC,
|
|
];
|
|
|
|
/**
|
|
* Permission types where CREATE/SHARE/SHARE_PUBLIC must default to false for USER.
|
|
* MEMORIES is excluded: its CREATE/READ/UPDATE apply to the user's own private data.
|
|
* AGENTS/PROMPTS are excluded: CREATE=true is intentional (users own their agents/prompts).
|
|
* Add new types here if they gate shared/multi-user resources.
|
|
*/
|
|
const RESOURCE_PERMISSION_TYPES: PermissionTypes[] = [
|
|
PermissionTypes.MCP_SERVERS,
|
|
PermissionTypes.REMOTE_AGENTS,
|
|
];
|
|
|
|
describe('roleDefaults', () => {
|
|
describe('USER role', () => {
|
|
const userPerms = roleDefaults[SystemRoles.USER].permissions;
|
|
|
|
it('should have explicit values for every field in every multi-field permission type', () => {
|
|
const schemaShape = permissionsSchema.shape;
|
|
|
|
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
|
const fieldNames = Object.keys(subSchema.shape);
|
|
if (fieldNames.length <= 1) {
|
|
continue;
|
|
}
|
|
|
|
const userValues =
|
|
userPerms[permType as PermissionTypes] as Record<string, boolean>;
|
|
|
|
for (const field of fieldNames) {
|
|
expect({
|
|
permType,
|
|
field,
|
|
value: userValues[field],
|
|
}).toEqual(
|
|
expect.objectContaining({
|
|
permType,
|
|
field,
|
|
value: expect.any(Boolean),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should never grant CREATE, SHARE, or SHARE_PUBLIC by default for resource-management types', () => {
|
|
for (const permType of RESOURCE_PERMISSION_TYPES) {
|
|
const permissions = userPerms[permType] as Record<string, boolean>;
|
|
for (const field of RESOURCE_MANAGEMENT_FIELDS) {
|
|
if (permissions[field] === undefined) {
|
|
continue;
|
|
}
|
|
expect({
|
|
permType,
|
|
field,
|
|
value: permissions[field],
|
|
}).toEqual(
|
|
expect.objectContaining({
|
|
permType,
|
|
field,
|
|
value: false,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should cover every permission type that has CREATE, SHARE, or SHARE_PUBLIC fields', () => {
|
|
const schemaShape = permissionsSchema.shape;
|
|
const restrictedSet = new Set<string>(RESOURCE_PERMISSION_TYPES);
|
|
|
|
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
|
const fieldNames = Object.keys(subSchema.shape);
|
|
const hasResourceFields = fieldNames.some((f) => RESOURCE_MANAGEMENT_FIELDS.includes(f as Permissions));
|
|
if (!hasResourceFields) {
|
|
continue;
|
|
}
|
|
|
|
const isTracked =
|
|
restrictedSet.has(permType) ||
|
|
permType === PermissionTypes.MEMORIES ||
|
|
permType === PermissionTypes.PROMPTS ||
|
|
permType === PermissionTypes.AGENTS;
|
|
|
|
expect({
|
|
permType,
|
|
tracked: isTracked,
|
|
}).toEqual(
|
|
expect.objectContaining({
|
|
permType,
|
|
tracked: true,
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('ADMIN role', () => {
|
|
const adminPerms = roleDefaults[SystemRoles.ADMIN].permissions;
|
|
|
|
it('should have explicit values for every field in every permission type', () => {
|
|
const schemaShape = permissionsSchema.shape;
|
|
|
|
for (const [permType, subSchema] of Object.entries(schemaShape)) {
|
|
const fieldNames = Object.keys(subSchema.shape);
|
|
const adminValues =
|
|
adminPerms[permType as PermissionTypes] as Record<string, boolean>;
|
|
|
|
for (const field of fieldNames) {
|
|
expect({
|
|
permType,
|
|
field,
|
|
value: adminValues[field],
|
|
}).toEqual(
|
|
expect.objectContaining({
|
|
permType,
|
|
field,
|
|
value: expect.any(Boolean),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|