Narrow authorize-target to pending approvals

This commit is contained in:
Codex committed 2026-07-12 09:50:43 -04:00
1 parent 3d6dc62957
commit 6fef62aa8e
2 files changed
+47 -25

No files matched your search

@@ -50,4 +50,17 @@ describe('local API authorization hardening invariants', () => {
expect(serverSource).toMatch(/Wildcard host approval requires explicit opt-in/);
expect(serverSource).toMatch(/body\.allowWildcard !== true/);
});
it('authorize-target only approves existing pending receipts and cannot mint broad active-action approvals', () => {
const route = routeBlock("app.post('/api/approvals/authorize-target'", "app.post('/api/approvals/:id/reject'");
expect(route).toMatch(/approvalId/);
expect(route).toMatch(/approvalIds/);
expect(route).toMatch(/approvalRequests\.get\(id\)/);
expect(route).toMatch(/approval\.status !== 'pending'/);
expect(route).toMatch(/Math\.min\(30/);
expect(route).not.toMatch(/createApprovalRequest/);
expect(route).not.toMatch(/allowedActions/);
expect(route).not.toMatch(/command_execution['"],\s*['"]autonomous_execution/);
});
});
+34 -25
View File
@@ -4874,8 +4874,15 @@ app.post('/api/approvals/:id/approve', (req: Request, res: Response) => {
app.post('/api/approvals/authorize-target', (req: Request, res: Response) => {
const body = req.body as Record<string, unknown>;
const target = normalizeTargetValue(body.target);
if (!target) {
res.status(400).json({ error: 'target required' });
const approvalIds = [
typeof body.approvalId === 'string' ? body.approvalId : '',
...(Array.isArray(body.approvalIds) ? body.approvalIds.filter((id): id is string => typeof id === 'string') : []),
].filter(Boolean);
if (!approvalIds.length) {
res.status(400).json({
error: 'approvalId or approvalIds required',
next: 'Request a pending receipt first, then approve that exact receipt id.',
});
return;
}
if (target === '*') {
@@ -4893,35 +4900,37 @@ app.post('/api/approvals/authorize-target', (req: Request, res: Response) => {
return;
}
const requestedActions = Array.isArray(body.actions) ? body.actions.map(String) : [];
const allowedActions: GuardAction[] = ['mission_execution', 'network_request', 'command_execution', 'autonomous_execution'];
const actions = (requestedActions.length ? requestedActions : allowedActions)
.filter((action): action is GuardAction => allowedActions.includes(action as GuardAction));
if (!actions.length) {
res.status(400).json({ error: 'No supported approval actions requested' });
return;
}
const ttlMinutes = Number(body.ttlMinutes || process.env.T3MP3ST_APPROVAL_TTL_MINUTES || 240);
const expiresAt = new Date(Date.now() + Math.max(1, ttlMinutes) * 60_000).toISOString();
const approvals = actions.map((action) => {
const approval = createApprovalRequest(
action,
target,
typeof body.reason === 'string' && body.reason.trim()
? body.reason.trim()
: `Authorize ${action} against ${target}`,
body
);
const ttlMinutes = Math.max(1, Math.min(30, Number(body.ttlMinutes || 30)));
const expiresAt = new Date(Date.now() + ttlMinutes * 60_000).toISOString();
const approvals: ApprovalRequest[] = [];
for (const id of [...new Set(approvalIds)]) {
const approval = approvalRequests.get(id);
if (!approval) {
res.status(404).json({ error: 'Approval request not found', approvalId: id });
return;
}
if (approval.status !== 'pending') {
res.status(409).json({ error: 'Approval request is not pending', approvalId: id, status: approval.status });
return;
}
if (target && !approvalMatches(approval, approval.action, target)) {
res.status(400).json({
error: 'Approval target mismatch',
approvalId: id,
approvalTarget: approval.target,
target,
});
return;
}
approval.status = 'approved';
approval.approvedBy = typeof body.approvedBy === 'string' ? body.approvedBy : 'local-operator';
approval.expiresAt = expiresAt;
approval.updatedAt = nowIso();
emitContractEvent('approval.approved', { approvalId: approval.id, action: approval.action, target: approval.target });
return approval;
});
approvals.push(approval);
}
res.status(201).json({ target, expiresAt, approvals });
res.json({ target: target || null, expiresAt, approvals });
});
app.post('/api/approvals/:id/reject', (req: Request, res: Response) => {