mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 14:33:19 +00:00
fix(security): signal when scan comparison is truncated (#658)
Compare endpoint loads up to 1000 findings per scan. When a scan exceeds this cap, the response now includes truncated=true and row_limit, and the comparison sheet surfaces a banner so users understand the diff may be incomplete. Also exposes total_vulnerabilities on scanA/scanB for UI use and logs a warning when truncation occurs.
This commit is contained in:
@@ -266,6 +266,35 @@ describe('GET /api/security/compare', () => {
|
||||
expect(added0102.suppressed).toBe(false);
|
||||
});
|
||||
|
||||
it('flags truncated=true when either scan exceeds the 1000-row cap', async () => {
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const a = seedScan({ scannedAt: 1000, totalVulnerabilities: 1500 });
|
||||
const b = seedScan({ scannedAt: 2000, totalVulnerabilities: 10 });
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/api/security/compare?scanId1=${a}&scanId2=${b}`)
|
||||
.set('Authorization', `Bearer ${adminToken()}`);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.truncated).toBe(true);
|
||||
expect(res.body.row_limit).toBe(1000);
|
||||
expect(res.body.scanA.total_vulnerabilities).toBe(1500);
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('returns truncated=false when both scans fit within the row cap', async () => {
|
||||
const a = seedScan({ scannedAt: 1000, totalVulnerabilities: 5 });
|
||||
const b = seedScan({ scannedAt: 2000, totalVulnerabilities: 10 });
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/api/security/compare?scanId1=${a}&scanId2=${b}`)
|
||||
.set('Authorization', `Bearer ${adminToken()}`);
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.truncated).toBe(false);
|
||||
});
|
||||
|
||||
it('allows cross-image comparison on the same node and preserves distinct image refs', async () => {
|
||||
const a = seedScan({ imageRef: 'alpine:3.18', scannedAt: 1000 });
|
||||
const b = seedScan({ imageRef: 'alpine:3.19', scannedAt: 2000 });
|
||||
|
||||
+24
-4
@@ -7976,8 +7976,16 @@ app.get('/api/security/compare', authMiddleware, (req: Request, res: Response):
|
||||
if (!a || !b || a.node_id !== req.nodeId || b.node_id !== req.nodeId) {
|
||||
res.status(404).json({ error: 'One or both scans not found' }); return;
|
||||
}
|
||||
const aVulns = db.getVulnerabilityDetails(scanId1, { limit: 1000 }).items;
|
||||
const bVulns = db.getVulnerabilityDetails(scanId2, { limit: 1000 }).items;
|
||||
const COMPARE_ROW_LIMIT = 1000;
|
||||
const aVulns = db.getVulnerabilityDetails(scanId1, { limit: COMPARE_ROW_LIMIT }).items;
|
||||
const bVulns = db.getVulnerabilityDetails(scanId2, { limit: COMPARE_ROW_LIMIT }).items;
|
||||
const truncated =
|
||||
a.total_vulnerabilities > COMPARE_ROW_LIMIT || b.total_vulnerabilities > COMPARE_ROW_LIMIT;
|
||||
if (truncated) {
|
||||
console.warn(
|
||||
`[Compare] scan(s) exceed ${COMPARE_ROW_LIMIT}-row cap: scanA=${a.id}(${a.total_vulnerabilities}) scanB=${b.id}(${b.total_vulnerabilities})`,
|
||||
);
|
||||
}
|
||||
const keyOf = (v: { vulnerability_id: string; pkg_name: string }) =>
|
||||
`${v.vulnerability_id}::${v.pkg_name}`;
|
||||
const aMap = new Map(aVulns.map((v) => [keyOf(v), v]));
|
||||
@@ -7990,11 +7998,23 @@ app.get('/api/security/compare', authMiddleware, (req: Request, res: Response):
|
||||
const removed = applySuppressions(removedRaw, a.image_ref, suppressions);
|
||||
const unchanged = applySuppressions(unchangedRaw, b.image_ref, suppressions);
|
||||
res.json({
|
||||
scanA: { id: a.id, scanned_at: a.scanned_at, image_ref: a.image_ref },
|
||||
scanB: { id: b.id, scanned_at: b.scanned_at, image_ref: b.image_ref },
|
||||
scanA: {
|
||||
id: a.id,
|
||||
scanned_at: a.scanned_at,
|
||||
image_ref: a.image_ref,
|
||||
total_vulnerabilities: a.total_vulnerabilities,
|
||||
},
|
||||
scanB: {
|
||||
id: b.id,
|
||||
scanned_at: b.scanned_at,
|
||||
image_ref: b.image_ref,
|
||||
total_vulnerabilities: b.total_vulnerabilities,
|
||||
},
|
||||
added,
|
||||
removed,
|
||||
unchanged,
|
||||
truncated,
|
||||
row_limit: COMPARE_ROW_LIMIT,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user