mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-09 18:15:50 +00:00
fix(security): upgrade frontend test tooling past new advisories
Use patched Vitest/coverage 4.1.11 and js-yaml 4.3.2. Adapt constructor mocks, callable mock types and explicit call-history cleanup for Vitest 4 without weakening assertions or audit gates.
Change-source: pulse-maintainer
(cherry picked from commit 15000f0a67)
This commit is contained in:
@@ -3230,6 +3230,18 @@ stages pin the Docker Official Images Linux amd64 manifest
|
||||
the checked-in toolchain files and release-script guards must reject an older
|
||||
compiler so local, exact-candidate, provider control-plane, and container builds
|
||||
cannot silently reintroduce the vulnerable runtime.
|
||||
The frontend development-tool dependency boundary must retain patched Vitest
|
||||
and mocker versions at or above `4.1.11`, with the V8 coverage provider matched
|
||||
to the runner, and a `js-yaml` override at or above `4.3.2`. These floors prevent
|
||||
reintroducing redirect-mock file reads (GHSA-82fw-gwwq-j7x9) and empty-merge CPU
|
||||
exhaustion (GHSA-2883-xcg3-v3hh) through the locked development graph. The
|
||||
`frontend-modern/src/security/__tests__/dependencySecurity.test.ts` proof must
|
||||
check manifest floors and all installed lockfile copies, including nested
|
||||
copies; a successful production build alone does not establish this property.
|
||||
Vitest migration must preserve test assertions while using constructable
|
||||
constructor mocks and explicit standalone mock-history cleanup. Dependency
|
||||
updates must retain the required full-graph audit rather than suppressing it.
|
||||
|
||||
That same dev-runtime dependency-manifest boundary now also owns the maintained
|
||||
Docker engine module floor. `go.mod`, `go.sum`, and
|
||||
`internal/cloudcp/docker/manager.go` must route hosted runtime orchestration
|
||||
|
||||
Generated
+155
-649
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@
|
||||
"seroval": "^1.4.1",
|
||||
"seroval-plugins": "^1.4.1",
|
||||
"@babel/core": "^7.29.6",
|
||||
"js-yaml": "^4.2.0"
|
||||
"js-yaml": "^4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
@@ -76,7 +76,7 @@
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"@typescript-eslint/eslint-plugin": "^8.24.0",
|
||||
"@typescript-eslint/parser": "^8.24.0",
|
||||
"@vitest/coverage-v8": "^3.2.6",
|
||||
"@vitest/coverage-v8": "^4.1.11",
|
||||
"autoprefixer": "^10.4.0",
|
||||
"eslint": "^9.20.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
@@ -92,6 +92,6 @@
|
||||
"vite": "^6.4.3",
|
||||
"vite-plugin-solid": "^2.8.0",
|
||||
"vite-plugin-sri-gen": "^1.3.2",
|
||||
"vitest": "^3.2.6"
|
||||
"vitest": "^4.1.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ const mockProviders = [gmailProvider, sendgridProvider, outlookProvider];
|
||||
|
||||
// --- Tests ---
|
||||
describe('EmailProviderSelect', () => {
|
||||
let onChangeMock: ReturnType<typeof vi.fn>;
|
||||
let onTestMock: ReturnType<typeof vi.fn>;
|
||||
let onChangeMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let onTestMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
|
||||
beforeEach(() => {
|
||||
onChangeMock = vi.fn();
|
||||
|
||||
@@ -168,17 +168,17 @@ interface DefaultProps {
|
||||
title: string;
|
||||
resources: Resource[];
|
||||
columns: string[];
|
||||
onEdit: ReturnType<typeof vi.fn>;
|
||||
onSaveEdit: ReturnType<typeof vi.fn>;
|
||||
onCancelEdit: ReturnType<typeof vi.fn>;
|
||||
onRemoveOverride: ReturnType<typeof vi.fn>;
|
||||
onEdit: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
onSaveEdit: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
onCancelEdit: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
onRemoveOverride: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
editingId: () => string | null;
|
||||
editingThresholds: () => Record<string, number | undefined>;
|
||||
setEditingThresholds: ReturnType<typeof vi.fn>;
|
||||
formatMetricValue: ReturnType<typeof vi.fn>;
|
||||
hasActiveAlert: ReturnType<typeof vi.fn>;
|
||||
setEditingThresholds: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
formatMetricValue: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
hasActiveAlert: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
editingNote: () => string;
|
||||
setEditingNote: ReturnType<typeof vi.fn>;
|
||||
setEditingNote: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
}
|
||||
|
||||
function makeProps(
|
||||
|
||||
@@ -102,10 +102,10 @@ const mockTemplates: WebhookTemplate[] = [
|
||||
|
||||
// --- Tests ---
|
||||
describe('WebhookConfig', () => {
|
||||
let onAddMock: ReturnType<typeof vi.fn>;
|
||||
let onUpdateMock: ReturnType<typeof vi.fn>;
|
||||
let onDeleteMock: ReturnType<typeof vi.fn>;
|
||||
let onTestMock: ReturnType<typeof vi.fn>;
|
||||
let onAddMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let onUpdateMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let onDeleteMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let onTestMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
|
||||
beforeEach(() => {
|
||||
onAddMock = vi.fn();
|
||||
|
||||
@@ -65,12 +65,12 @@ const currentExportDate = (): string => new Date().toISOString().split('T')[0];
|
||||
|
||||
describe('useDiagnosticsPanelState', () => {
|
||||
let useDiagnosticsPanelState: UseDiagnosticsPanelStateModule['useDiagnosticsPanelState'];
|
||||
let apiFetchJSONMock: ReturnType<typeof vi.fn>;
|
||||
let showErrorMock: ReturnType<typeof vi.fn>;
|
||||
let showSuccessMock: ReturnType<typeof vi.fn>;
|
||||
let createObjectURLMock: ReturnType<typeof vi.fn>;
|
||||
let revokeObjectURLMock: ReturnType<typeof vi.fn>;
|
||||
let anchorClickMock: ReturnType<typeof vi.fn>;
|
||||
let apiFetchJSONMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let showErrorMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let showSuccessMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let createObjectURLMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let revokeObjectURLMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let anchorClickMock: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
let createdAnchor: HTMLAnchorElement | null;
|
||||
let originalCreateObjectURL: typeof URL.createObjectURL | undefined;
|
||||
let originalRevokeObjectURL: typeof URL.revokeObjectURL | undefined;
|
||||
|
||||
@@ -84,6 +84,8 @@ function getTagDotRing(dot: Element) {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.restoreAllMocks();
|
||||
// Vitest 4 restores spies without clearing standalone vi.fn call history.
|
||||
vi.clearAllMocks();
|
||||
// Reset dark mode to default (false) for isolation
|
||||
darkModeMock.mockReturnValue(false);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
interface PackageManifest {
|
||||
dependencies: Record<string, string>;
|
||||
devDependencies: Record<string, string>;
|
||||
overrides: Record<string, string>;
|
||||
}
|
||||
|
||||
interface PackageLock {
|
||||
@@ -58,6 +60,34 @@ const nanoidIsPatched = (version: string): boolean => {
|
||||
};
|
||||
|
||||
describe('frontend dependency security floors', () => {
|
||||
it('keeps Vitest and its mocker above the redirect-mock file-read floor', () => {
|
||||
// GHSA-82fw-gwwq-j7x9: the maintained 4.x fix starts at 4.1.11.
|
||||
expect(manifest.devDependencies.vitest).toBe('^4.1.11');
|
||||
expect(manifest.devDependencies['@vitest/coverage-v8']).toBe('^4.1.11');
|
||||
const runner = lockedVersions('vitest');
|
||||
expect(runner).toHaveLength(1);
|
||||
for (const name of ['vitest', '@vitest/mocker', '@vitest/coverage-v8']) {
|
||||
const versions = lockedVersions(name);
|
||||
expect(versions).not.toHaveLength(0);
|
||||
for (const version of versions) {
|
||||
expect(version).not.toContain('-');
|
||||
expect(atLeast(version, [4, 1, 11]), `${name} ${version} is vulnerable`).toBe(true);
|
||||
expect(version, `${name} must match the runner`).toBe(runner[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps every js-yaml copy above the empty-merge CPU exhaustion floor', () => {
|
||||
// GHSA-2883-xcg3-v3hh: our override keeps every transitive copy on patched 4.x.
|
||||
expect(manifest.overrides['js-yaml']).toBe('^4.3.2');
|
||||
const versions = lockedVersions('js-yaml');
|
||||
expect(versions).not.toHaveLength(0);
|
||||
for (const version of versions) {
|
||||
expect(version).not.toContain('-');
|
||||
expect(atLeast(version, [4, 3, 2]), `js-yaml ${version} is vulnerable`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps DOMPurify above the hook-detachment XSS floor', () => {
|
||||
expect(manifest.dependencies.dompurify).toBe('^3.4.13');
|
||||
const versions = lockedVersions('dompurify');
|
||||
|
||||
@@ -26,7 +26,7 @@ interface MockWebSocketInstance {
|
||||
|
||||
let mockWsInstance: MockWebSocketInstance | null = null;
|
||||
|
||||
const MockWebSocket = vi.fn().mockImplementation((url: string): MockWebSocketInstance => {
|
||||
const MockWebSocket = vi.fn().mockImplementation(function (url: string): MockWebSocketInstance {
|
||||
const instance: MockWebSocketInstance = {
|
||||
url,
|
||||
readyState: 1,
|
||||
|
||||
@@ -25,7 +25,7 @@ import type { ResourceMetadataChangedDetail } from '@/utils/resourceMetadataEven
|
||||
const win: Window & typeof globalThis = window;
|
||||
|
||||
describe('dispatchResourceMetadataChanged', () => {
|
||||
let listener: ReturnType<typeof vi.fn>;
|
||||
let listener: ReturnType<typeof vi.fn<(...args: any[]) => any>>;
|
||||
|
||||
beforeEach(() => {
|
||||
listener = vi.fn();
|
||||
|
||||
Reference in New Issue
Block a user