🧱 refactor: Require Broad Config Management for Base Field Mutations (#14775)

This commit is contained in:
Danny Avila 2026-08-12 22:32:38 -04:00 committed by GitHub
parent e696b07619
commit 8f1f961212
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 2 deletions

View file

@ -1728,7 +1728,7 @@ describe('createAdminConfigHandlers', () => {
});
});
describe('scope-lifecycle: __base__ short-circuit', () => {
describe('invariant: __base__ requires broad manage:configs', () => {
it('upsert against __base__ returns 403 for assign-only caller', async () => {
const { handlers, deps } = createHandlers({
hasConfigCapability: jest.fn().mockResolvedValue(false),
@ -1794,6 +1794,70 @@ describe('createAdminConfigHandlers', () => {
expect(res.statusCode).toBe(201);
expect(deps.upsertConfig).toHaveBeenCalled();
});
it('patch against __base__ returns 403 for a section-scoped manager', async () => {
const { handlers, deps } = createHandlers({
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
});
const req = mockReq({
params: { principalType: 'role', principalId: '__base__' },
body: { entries: [{ fieldPath: 'memory.context', value: 'updated' }] },
});
const res = mockRes();
await handlers.patchConfigField(req, res);
expect(res.statusCode).toBe(403);
expect(deps.patchConfigFields).not.toHaveBeenCalled();
});
it('tombstone against __base__ returns 403 for a section-scoped manager', async () => {
const { handlers, deps } = createHandlers({
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
});
const req = mockReq({
params: { principalType: 'role', principalId: '__base__' },
body: { fieldPath: 'memory.context' },
});
const res = mockRes();
await handlers.tombstoneConfigField(req, res);
expect(res.statusCode).toBe(403);
expect(deps.tombstoneConfigField).not.toHaveBeenCalled();
});
it('field delete against __base__ returns 403 for a section-scoped manager', async () => {
const { handlers, deps } = createHandlers({
hasConfigCapability: jest.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(true),
});
const req = mockReq({
params: { principalType: 'role', principalId: '__base__' },
query: { fieldPath: 'memory.context' },
});
const res = mockRes();
await handlers.deleteConfigField(req, res);
expect(res.statusCode).toBe(403);
expect(deps.unsetConfigField).not.toHaveBeenCalled();
});
it('patch against __base__ succeeds for a broad-manage caller', async () => {
const { handlers, deps } = createHandlers({
hasConfigCapability: jest.fn().mockResolvedValue(true),
});
const req = mockReq({
params: { principalType: 'role', principalId: '__base__' },
body: { entries: [{ fieldPath: 'memory.context', value: 'updated' }] },
});
const res = mockRes();
await handlers.patchConfigField(req, res);
expect(res.statusCode).toBe(200);
expect(deps.patchConfigFields).toHaveBeenCalled();
});
});
describe('scope-lifecycle: atomic empty-state guard for assign-only callers', () => {

View file

@ -747,6 +747,10 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
if (validEntries.length === 0) {
if (!hasBroadManage) {
return res.status(403).json({ error: 'Insufficient permissions' });
@ -858,6 +862,11 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
const section = getTopLevelSection(fieldPath);
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
if (
!hasBroadManage &&
!(await hasConfigCapability(user, section as ConfigSection, 'manage'))
@ -950,7 +959,16 @@ export function createAdminConfigHandlers(deps: AdminConfigDeps): {
const section = getTopLevelSection(fieldPath);
if (!(await hasConfigCapability(user, section as ConfigSection, 'manage'))) {
const hasBroadManage = await hasConfigCapability(user, null, 'manage');
if (principalId === BASE_CONFIG_PRINCIPAL_ID && !hasBroadManage) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
if (
!hasBroadManage &&
!(await hasConfigCapability(user, section as ConfigSection, 'manage'))
) {
return res.status(403).json({
error: `Insufficient permissions for config section: ${section}`,
});