From ed0c2eafc04e320c29b994d9645e30c0766261bc Mon Sep 17 00:00:00 2001 From: Philippe Serhal Date: Sat, 10 Oct 2026 23:20:48 -0400 Subject: [PATCH] perf: only resolve trust downgrade status for resolved version When rendering the package page, the server was resolving and sending the full list of every version (trust info and publish date for each), just so that the browser could render whether *the viewed version* has a trust downgrade. For packages with many versions, this list was the predominant part of the payload size. For example, `next` has 14k+ versions and that list alone was 601 kB of a 700 kB payload. Of course, it's also lookup time, parse time, transfer time, and our hosting costs for all these plus ISR cache reads and writes. It adds up. --- app/composables/npm/usePackage.ts | 30 ++++---- app/pages/package/[[org]]/[name].vue | 17 +---- app/utils/publish-security.ts | 13 +--- shared/types/npm-registry.ts | 22 +++++- .../composables/use-package-transform.spec.ts | 70 +++++++++---------- test/unit/app/utils/publish-security.spec.ts | 40 +++++------ 6 files changed, 88 insertions(+), 104 deletions(-) diff --git a/app/composables/npm/usePackage.ts b/app/composables/npm/usePackage.ts index baae299150..7f16200f87 100644 --- a/app/composables/npm/usePackage.ts +++ b/app/composables/npm/usePackage.ts @@ -1,5 +1,6 @@ -import { getTrustLevel, getTrustStatus } from 'packumeta' +import { getTrustStatus } from 'packumeta' import { normalizeLicense } from '#shared/utils/npm' +import { detectSecurityDowngradeForVersion } from '~/utils/publish-security' /** Number of recent versions to include in initial payload */ const RECENT_VERSIONS_COUNT = 5 @@ -37,21 +38,16 @@ export function transformPackument( includedVersions.add(requestedVersion) } - // Build security metadata for all versions, but only include in payload - // when the package has mixed trust levels (i.e. a downgrade could exist) - const securityVersionEntries = Object.entries(pkg.versions).map(([version, metadata]) => { - const trustStatus = getTrustStatus(metadata) - return { - version, - time: pkg.time[version], - trustStatus, - deprecated: metadata.deprecated, - } - }) - - const trustLevels = new Set(securityVersionEntries.map(v => getTrustLevel(v.trustStatus))) - const hasMixedTrust = trustLevels.size > 1 - const securityVersions = hasMixedTrust ? securityVersionEntries : undefined + // Computed from the full version history so only the small result is serialized. + const securityVersionEntries = Object.entries(pkg.versions).map(([version, metadata]) => ({ + version, + time: pkg.time[version], + trustStatus: getTrustStatus(metadata), + deprecated: metadata.deprecated, + })) + const publishSecurityDowngrade = requestedVersion + ? detectSecurityDowngradeForVersion(securityVersionEntries, requestedVersion) + : null // Build filtered versions object with install scripts info per version const filteredVersions: Record = {} @@ -122,7 +118,7 @@ export function transformPackument( ...(storybook && { storybook }), 'requestedVersion': versionData, 'versions': filteredVersions, - 'securityVersions': securityVersions, + 'publishSecurityDowngrade': publishSecurityDowngrade, } } diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue index 1cb7073cb8..a4d3dc49db 100644 --- a/app/pages/package/[[org]]/[name].vue +++ b/app/pages/package/[[org]]/[name].vue @@ -246,17 +246,6 @@ if (isSpaFallback.value || isHydratingWithServerContent.value) { } const displayVersion = computed(() => pkg.value?.requestedVersion ?? null) -const versionSecurityMetadata = computed(() => { - if (!pkg.value) return [] - if (pkg.value.securityVersions?.length) return pkg.value.securityVersions - - return Object.entries(pkg.value.versions).map(([version, metadata]) => ({ - version, - time: pkg.value?.time?.[version], - trustStatus: metadata.trustStatus, - deprecated: metadata.deprecated, - })) -}) // Process package description const pkgDescription = useMarkdown(() => ({ @@ -329,11 +318,7 @@ const deprecationNoticeMessage = useMarkdown(() => ({ text: deprecationNotice.value?.message ?? '', })) -const publishSecurityDowngrade = computed(() => { - const currentVersion = displayVersion.value?.version - if (!currentVersion) return null - return detectPublishSecurityDowngradeForVersion(versionSecurityMetadata.value, currentVersion) -}) +const publishSecurityDowngrade = computed(() => pkg.value?.publishSecurityDowngrade ?? null) const installVersionOverride = computed( () => publishSecurityDowngrade.value?.trustedVersion ?? null, diff --git a/app/utils/publish-security.ts b/app/utils/publish-security.ts index cf3e0c95f1..137465b0b5 100644 --- a/app/utils/publish-security.ts +++ b/app/utils/publish-security.ts @@ -1,15 +1,8 @@ import { getTrustLevel, getTrustLevelName, type TrustLevelName } from 'packumeta' import { compare, getMajor } from 'verkit' +import type { PublishSecurityDowngrade } from '#shared/types/npm-registry' -export interface PublishSecurityDowngrade { - downgradedVersion: string - downgradedPublishedAt?: string - downgradedTrustLevel: TrustLevelName - /** Recommended trusted version within the same major, if one exists */ - trustedVersion?: string - trustedPublishedAt?: string - trustedTrustLevel: TrustLevelName -} +export type { PublishSecurityDowngrade } type VersionWithIndex = PackageVersionInfo & { index: number @@ -48,7 +41,7 @@ function sortByRecency(a: VersionWithIndex, b: VersionWithIndex): number { * A version is considered downgraded when it has no provenance and * there exists an older trusted release. */ -export function detectPublishSecurityDowngradeForVersion( +export function detectSecurityDowngradeForVersion( versions: PackageVersionInfo[], viewedVersion: string, ): PublishSecurityDowngrade | null { diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts index 46ec7caee1..d59f95e135 100644 --- a/shared/types/npm-registry.ts +++ b/shared/types/npm-registry.ts @@ -12,7 +12,7 @@ import type { Contact, } from '@npm/types' import type { ReadmeResponse } from './readme' -import type { TrustStatus } from 'packumeta' +import type { TrustLevelName, TrustStatus } from 'packumeta' // Re-export official npm types for packument/manifest export type { Manifest, ManifestVersion, PackageJSON } from '@npm/types' @@ -91,8 +91,24 @@ export interface SlimPackument { 'requestedVersion': SlimPackumentVersion | null /** Only includes dist-tag versions (with installScripts info added per version) */ 'versions': Record - /** Lightweight security metadata for all versions */ - 'securityVersions'?: PackageVersionInfo[] + /** + * Publish security downgrade for the requested version, or `null`. + * Computed server-side so the payload only carries the small result, not the full version history. + */ + 'publishSecurityDowngrade'?: PublishSecurityDowngrade | null +} + +/** + * A publish-security downgrade detected for a specific viewed version. + */ +export interface PublishSecurityDowngrade { + downgradedVersion: string + downgradedPublishedAt?: string + downgradedTrustLevel: TrustLevelName + /** Recommended trusted version within the same major, if one exists */ + trustedVersion?: string + trustedPublishedAt?: string + trustedTrustLevel: TrustLevelName } /** diff --git a/test/nuxt/composables/use-package-transform.spec.ts b/test/nuxt/composables/use-package-transform.spec.ts index a97e1bc0f8..20e3c13ffd 100644 --- a/test/nuxt/composables/use-package-transform.spec.ts +++ b/test/nuxt/composables/use-package-transform.spec.ts @@ -67,18 +67,6 @@ function createPackument( } } -function toVersionInfos(packument: ReturnType): PackageVersionInfo[] { - return ( - packument.securityVersions ?? - Object.entries(packument.versions).map(([version, metadata]) => ({ - version, - time: packument.time[version], - trustStatus: metadata.trustStatus, - deprecated: metadata.deprecated, - })) - ) -} - describe('transformPackument', () => { it('includes requested old version and preserves provenance on it', () => { const packument = createPackument( @@ -116,10 +104,11 @@ describe('transformPackument', () => { }) expect(transformed.versions['1.0.1']).toBeUndefined() expect(transformed.versions['1.0.2']).toBeUndefined() - expect(transformed.securityVersions).toHaveLength(8) + // 1.0.0 is the oldest and trusted, so the viewed version is not a downgrade + expect(transformed.publishSecurityDowngrade).toBeNull() }) - it('omits securityVersions when all versions have the same trust level', () => { + it('computes no downgrade when all versions have the same trust level', () => { const packument = createPackument( { '1.0.0': createVersion('1.0.0'), @@ -138,11 +127,11 @@ describe('transformPackument', () => { const transformed = transformPackument(packument, '1.0.2') - // All versions have trustLevel 'none', so no mixed trust — omit the array - expect(transformed.securityVersions).toBeUndefined() + // All versions have trustLevel 'none', so nothing to detect + expect(transformed.publishSecurityDowngrade).toBeNull() }) - it('includes securityVersions when package has mixed trust levels', () => { + it('detects a downgrade when the package has mixed trust levels', () => { const packument = createPackument( { '1.0.0': createVersion('1.0.0', true), @@ -159,10 +148,17 @@ describe('transformPackument', () => { const transformed = transformPackument(packument, '1.0.1') - expect(transformed.securityVersions).toHaveLength(2) + expect(transformed.publishSecurityDowngrade).toEqual({ + downgradedVersion: '1.0.1', + downgradedPublishedAt: '2026-01-02T00:00:00.000Z', + downgradedTrustLevel: 'none', + trustedVersion: '1.0.0', + trustedPublishedAt: '2026-01-01T00:00:00.000Z', + trustedTrustLevel: 'provenance', + }) }) - it('works with downgrade detection for viewed version', () => { + it('computes the downgrade for the requested version only', () => { const packument = createPackument( { '1.0.0': createVersion('1.0.0', true), @@ -179,11 +175,8 @@ describe('transformPackument', () => { '1.0.2', ) - const transformed = transformPackument(packument, '1.0.1') - const infos = toVersionInfos(transformed) - - expect(detectPublishSecurityDowngradeForVersion(infos, '1.0.2')).toBeNull() - expect(detectPublishSecurityDowngradeForVersion(infos, '1.0.1')).toEqual({ + expect(transformPackument(packument, '1.0.2').publishSecurityDowngrade).toBeNull() + expect(transformPackument(packument, '1.0.1').publishSecurityDowngrade).toEqual({ downgradedVersion: '1.0.1', downgradedPublishedAt: '2026-01-02T00:00:00.000Z', downgradedTrustLevel: 'none', @@ -211,14 +204,13 @@ describe('transformPackument', () => { ) const transformed = transformPackument(packument, '1.0.1') - const infos = toVersionInfos(transformed) - expect(infos.find(v => v.version === '1.0.0')?.trustStatus).toEqual({ + expect(transformed.versions['1.0.0']?.trustStatus).toEqual({ provenance: true, trustedPublisher: true, stagedPublish: false, }) - expect(detectPublishSecurityDowngradeForVersion(infos, '1.0.1')?.trustedVersion).toBe('1.0.0') + expect(transformed.publishSecurityDowngrade?.trustedVersion).toBe('1.0.0') }) it('prefers trustedPublisher trust level when both trustedPublisher and attestations exist', () => { @@ -264,20 +256,19 @@ describe('transformPackument', () => { ) const transformed = transformPackument(packument, '7.0.1') - const infos = toVersionInfos(transformed) // Both versions should be trustedPublisher — no downgrade - expect(infos.find(v => v.version === '7.0.0')?.trustStatus).toEqual({ + expect(transformed.versions['7.0.0']?.trustStatus).toEqual({ provenance: true, trustedPublisher: true, stagedPublish: false, }) - expect(infos.find(v => v.version === '7.0.1')?.trustStatus).toEqual({ + expect(transformed.versions['7.0.1']?.trustStatus).toEqual({ provenance: true, trustedPublisher: true, stagedPublish: false, }) - expect(detectPublishSecurityDowngradeForVersion(infos, '7.0.1')).toBeNull() + expect(transformed.publishSecurityDowngrade).toBeNull() }) it('flags non-direct downgrade chain until trust is restored', () => { @@ -301,12 +292,15 @@ describe('transformPackument', () => { '2.4.0', ) - const transformed = transformPackument(packument, '2.3.0') - const infos = toVersionInfos(transformed) - - expect(detectPublishSecurityDowngradeForVersion(infos, '2.1.1')?.trustedVersion).toBe('2.1.0') - expect(detectPublishSecurityDowngradeForVersion(infos, '2.2.0')?.trustedVersion).toBe('2.1.0') - expect(detectPublishSecurityDowngradeForVersion(infos, '2.3.0')?.trustedVersion).toBe('2.1.0') - expect(detectPublishSecurityDowngradeForVersion(infos, '2.4.0')).toBeNull() + expect(transformPackument(packument, '2.1.1').publishSecurityDowngrade?.trustedVersion).toBe( + '2.1.0', + ) + expect(transformPackument(packument, '2.2.0').publishSecurityDowngrade?.trustedVersion).toBe( + '2.1.0', + ) + expect(transformPackument(packument, '2.3.0').publishSecurityDowngrade?.trustedVersion).toBe( + '2.1.0', + ) + expect(transformPackument(packument, '2.4.0').publishSecurityDowngrade).toBeNull() }) }) diff --git a/test/unit/app/utils/publish-security.spec.ts b/test/unit/app/utils/publish-security.spec.ts index 8c30c6f08e..20934c024b 100644 --- a/test/unit/app/utils/publish-security.spec.ts +++ b/test/unit/app/utils/publish-security.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest' -import { detectPublishSecurityDowngradeForVersion } from '~/utils/publish-security' +import { detectSecurityDowngradeForVersion } from '~/utils/publish-security' -describe('detectPublishSecurityDowngradeForVersion', () => { +describe('detectSecurityDowngradeForVersion', () => { const versions = [ { version: '1.0.0', @@ -21,12 +21,12 @@ describe('detectPublishSecurityDowngradeForVersion', () => { ] it('does not flag trusted viewed version (1.0.2)', () => { - const result = detectPublishSecurityDowngradeForVersion(versions, '1.0.2') + const result = detectSecurityDowngradeForVersion(versions, '1.0.2') expect(result).toBeNull() }) it('flags downgraded viewed version (1.0.1)', () => { - const result = detectPublishSecurityDowngradeForVersion(versions, '1.0.1') + const result = detectSecurityDowngradeForVersion(versions, '1.0.1') expect(result).toEqual({ downgradedVersion: '1.0.1', downgradedPublishedAt: '2026-01-02T00:00:00.000Z', @@ -38,7 +38,7 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }) it('flags trust downgrade from trustedPublisher to provenance', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0', @@ -65,7 +65,7 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }) it('does not flag upgrade from provenance to trustedPublisher', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0', @@ -113,20 +113,20 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }, ] - expect( - detectPublishSecurityDowngradeForVersion(downgradedVersions, '2.1.1')?.trustedVersion, - ).toBe('2.1.0') - expect( - detectPublishSecurityDowngradeForVersion(downgradedVersions, '2.2.0')?.trustedVersion, - ).toBe('2.1.0') - expect( - detectPublishSecurityDowngradeForVersion(downgradedVersions, '2.3.0')?.trustedVersion, - ).toBe('2.1.0') - expect(detectPublishSecurityDowngradeForVersion(downgradedVersions, '2.4.0')).toBeNull() + expect(detectSecurityDowngradeForVersion(downgradedVersions, '2.1.1')?.trustedVersion).toBe( + '2.1.0', + ) + expect(detectSecurityDowngradeForVersion(downgradedVersions, '2.2.0')?.trustedVersion).toBe( + '2.1.0', + ) + expect(detectSecurityDowngradeForVersion(downgradedVersions, '2.3.0')?.trustedVersion).toBe( + '2.1.0', + ) + expect(detectSecurityDowngradeForVersion(downgradedVersions, '2.4.0')).toBeNull() }) it('skips deprecated versions when selecting trustedVersion', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0', @@ -153,7 +153,7 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }) it('returns null when all older trusted versions are deprecated', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0', @@ -174,7 +174,7 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }) it('detects cross-major downgrade but does not recommend a version', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0', @@ -198,7 +198,7 @@ describe('detectPublishSecurityDowngradeForVersion', () => { }) it('recommends same-major trusted version when cross-major exists', () => { - const result = detectPublishSecurityDowngradeForVersion( + const result = detectSecurityDowngradeForVersion( [ { version: '1.0.0',