mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-29 11:47:01 +00:00
fix(nodes): close capability-gating gaps in node compatibility (#1261)
* fix(nodes): close capability-gating gaps in node compatibility Vulnerability scanning is now gated correctly on whether the active node advertises support for it: - A node without the Trivy binary stops advertising the scanning capability. Previously the capability was toggled only on a state change, so a node that booted without Trivy kept advertising scanning it could not perform. - The control node's own capability list now reflects features disabled at runtime, matching what it advertises to peers. - The scan history surface shows a clear "not available on this node" card, with its header actions hidden, instead of attempting a request that fails. A node's version and capability metadata now refreshes immediately after a connection test or a completed update, rather than waiting out the cache. Capability gates fail closed to the unavailable card when a node's metadata request errors, instead of staying open until the next fetch. Adds a test that fails if the frontend and backend capability lists drift, plus coverage for the metadata error path, the runtime-disabled local meta, the scanning capability sync, and the metadata cache invalidation paths. * fix(nodes): refresh node metadata client-side after a connection test A connection test dropped the server-side metadata cache, but the dashboard kept its own cached copy until the client TTL expired, so version and capability gates could stay stale in the browser. The test now forces a client-side metadata refresh for that node, so the version pill and gates reflect the node's current state immediately. Also strips any URL userinfo before logging the metadata fetch target, and makes the scanning-capability detection test deterministically exercise the no-binary disable path rather than depending on whether the runner has Trivy.
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
/**
|
||||
* Tests for node management API - focusing on api_url validation (SSRF fix C2).
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { setupTestDb, cleanupTestDb, TEST_USERNAME, TEST_JWT_SECRET } from './helpers/setupTestDb';
|
||||
import { disableCapability, enableCapability } from '../services/CapabilityRegistry';
|
||||
import { NodeRegistry } from '../services/NodeRegistry';
|
||||
import { CacheService } from '../services/CacheService';
|
||||
|
||||
let tmpDir: string;
|
||||
let app: import('express').Express;
|
||||
@@ -79,6 +82,44 @@ describe('POST /api/nodes - api_url SSRF validation (C2 fix)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /api/nodes/:id/meta - local meta honors runtime-disabled capabilities', () => {
|
||||
it('omits a capability that has been disabled at runtime', async () => {
|
||||
const list = await request(app).get('/api/nodes').set('Authorization', authHeader);
|
||||
const local = (list.body as Array<{ id: number; type: string }>).find((n) => n.type === 'local');
|
||||
expect(local).toBeTruthy();
|
||||
|
||||
disableCapability('vulnerability-scanning');
|
||||
try {
|
||||
const res = await request(app)
|
||||
.get(`/api/nodes/${local!.id}/meta`)
|
||||
.set('Authorization', authHeader);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.capabilities).toContain('stacks');
|
||||
expect(res.body.capabilities).not.toContain('vulnerability-scanning');
|
||||
} finally {
|
||||
enableCapability('vulnerability-scanning');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/nodes/:id/test - invalidates remote-meta cache', () => {
|
||||
it('drops the cached meta so the next read rebuilds version and capabilities live', async () => {
|
||||
const testSpy = vi
|
||||
.spyOn(NodeRegistry.getInstance(), 'testConnection')
|
||||
.mockResolvedValue({ success: true });
|
||||
const invalidateSpy = vi.spyOn(CacheService.getInstance(), 'invalidate');
|
||||
try {
|
||||
const res = await request(app).post('/api/nodes/7/test').set('Authorization', authHeader);
|
||||
expect(res.status).toBe(200);
|
||||
expect(testSpy).toHaveBeenCalledWith(7);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith('remote-meta:7');
|
||||
} finally {
|
||||
testSpy.mockRestore();
|
||||
invalidateSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Stack name validation on GET routes (H3 fix)', () => {
|
||||
it('rejects path traversal in GET /api/stacks/:stackName', async () => {
|
||||
const res = await request(app)
|
||||
|
||||
Reference in New Issue
Block a user