fix: address all 11 review items for group-based access control

Bugs fixed:
- knowledgeBase.js: logAudit calls converted from positional args to object signature
- archerTickets.js: targetType/targetId renamed to entityType/entityId
- server.js: single CVE delete now has cascade/compliance check for Standard_User

Unprotected endpoints secured:
- ivantiTodoQueue.js: POST/PUT/DELETE now require Admin or Standard_User
- ivantiFindings.js: PUT note and POST sync now require Admin or Standard_User
- compliance.js: POST notes now requires Admin or Standard_User
- ivantiWorkflows.js: POST sync now requires Admin or Standard_User
- auth.js: cleanup-sessions now requires Admin via requireAuth + requireGroup

Additional fixes:
- ExportsPage.js: canExport() guard blocks Read_Only users
- knowledgeBase.js: Standard_User delete checks created_by ownership
- Migration: added INSERT/UPDATE triggers to enforce valid user_group values
This commit is contained in:
jramos
2026-04-07 09:52:26 -06:00
parent d910af847e
commit e9e2c0961d
10 changed files with 154 additions and 64 deletions

View File

@@ -846,6 +846,59 @@ app.delete('/api/cves/:id', requireAuth(db), requireGroup('Admin', 'Standard_Use
return res.status(403).json({ error: 'You can only delete resources you created' });
}
// Cascade/compliance check for Standard_User
if (req.user.group === 'Standard_User') {
return db.all('SELECT id, exc_number FROM archer_tickets WHERE cve_id = ? AND vendor = ?', [cve.cve_id, cve.vendor], (archerErr, archerTickets) => {
if (archerErr) { console.error(archerErr); return res.status(500).json({ error: 'Internal server error.' }); }
db.all('SELECT id, ticket_key FROM jira_tickets WHERE cve_id = ? AND vendor = ?', [cve.cve_id, cve.vendor], (jiraErr, jiraTickets) => {
if (jiraErr && jiraErr.message && jiraErr.message.includes('no such table')) { jiraTickets = []; }
else if (jiraErr) { console.error(jiraErr); return res.status(500).json({ error: 'Internal server error.' }); }
const allTickets = [
...(archerTickets || []).map(t => ({ ...t, source: 'archer', key: t.exc_number })),
...(jiraTickets || []).map(t => ({ ...t, source: 'jira', key: t.ticket_key }))
];
if (allTickets.length === 0) {
return doSingleCveDelete(req, res, id, cve);
}
const likeConditions = allTickets.map(() => 'ci.extra_json LIKE ?');
const likeParams = allTickets.map(t => `%${t.key}%`);
db.all(
`SELECT ci.id, ci.extra_json FROM compliance_items ci
JOIN compliance_uploads cu ON ci.upload_id = cu.id
WHERE ci.status = 'active' AND (${likeConditions.join(' OR ')})`,
likeParams,
(compErr, compLinks) => {
if (compErr && compErr.message && compErr.message.includes('no such table')) { compLinks = []; }
else if (compErr) { console.error(compErr); return res.status(500).json({ error: 'Internal server error.' }); }
const hasLink = (compLinks || []).some(cl => {
const json = cl.extra_json || '';
return allTickets.some(t => json.includes(t.key));
});
if (hasLink) {
return res.status(403).json({
error: 'CVE deletion blocked: associated ticket linked to compliance report',
cascade_impact: { blocked: true, blocked_reason: 'Associated ticket is linked to a compliance report' }
});
}
return doSingleCveDelete(req, res, id, cve);
}
);
});
});
}
doSingleCveDelete(req, res, id, cve);
});
function doSingleCveDelete(req, res, id, cve) {
// Delete associated documents from DB
db.all('SELECT id, file_path FROM documents WHERE cve_id = ? AND vendor = ?', [cve.cve_id, cve.vendor], (docErr, docs) => {
if (docErr) console.error('Error fetching documents:', docErr);
@@ -892,7 +945,7 @@ app.delete('/api/cves/:id', requireAuth(db), requireGroup('Admin', 'Standard_Use
});
});
});
});
}
});
// ========== DOCUMENT ENDPOINTS ==========