From e0d8779803e54390368cb3ed4810a8cebda5c7c9 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Wed, 11 Mar 2026 01:49:13 +0900 Subject: [PATCH 1/5] fix: filter out security holding packages from algoria result --- app/pages/search.vue | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/pages/search.vue b/app/pages/search.vue index ce503b995b..47042bebf2 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -81,6 +81,13 @@ const visibleResults = computed(() => { let objects = raw.objects + // Filter out "Security holding package" package takendown by npm registory + objects = objects.filter( + r => + r.package.version !== '0.0.1-security' || + r.package.description !== 'security holding package', + ) + // Filter out platform-specific packages if setting is enabled if (settings.value.hidePlatformPackages) { objects = objects.filter(r => !isPlatformSpecificPackage(r.package.name)) From 78c7afbbabfbfe5dec8ef0a2389f8752596a78e7 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Wed, 11 Mar 2026 10:22:22 +0900 Subject: [PATCH 2/5] chore: update code comment --- app/pages/search.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/search.vue b/app/pages/search.vue index 47042bebf2..1add1d83af 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -73,7 +73,7 @@ const { settings } = useSettings() /** * Reorder results to put exact package name match at the top, - * and optionally filter out platform-specific packages. + * and optionally filter out platform-specific packages or security holding packages. */ const visibleResults = computed(() => { const raw = rawVisibleResults.value From 1d87786436c539c185ad639926e511d7e4872317 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 14 Mar 2026 03:17:02 +0900 Subject: [PATCH 3/5] chore: add `NpmSearchRepository` type --- shared/types/npm-registry.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts index 250a9218e6..e44b4ac762 100644 --- a/shared/types/npm-registry.ts +++ b/shared/types/npm-registry.ts @@ -187,6 +187,7 @@ export interface NpmSearchPackage { publisher?: NpmSearchPublisher maintainers?: NpmPerson[] license?: string + repository?: NpmSearchRepository } export interface NpmSearchScore { @@ -312,6 +313,20 @@ export interface NpmTrustedPublisher { ciConfigPath?: string } +/** + * Repository types + * Note: Not covered by @npm/types + */ +export interface NpmSearchRepository { + type: 'git' + url: string + project: string + user: string + host: string + path: string + branch: string +} + /** * jsDelivr API Types * Used for package file browsing From 42555f9c49508f0f275d8c34a5fa0890160eff0e Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 14 Mar 2026 03:19:12 +0900 Subject: [PATCH 4/5] fix: compare repository to `npm/security-holder` instead --- app/pages/search.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/pages/search.vue b/app/pages/search.vue index 1add1d83af..0928f8d34a 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -82,11 +82,7 @@ const visibleResults = computed(() => { let objects = raw.objects // Filter out "Security holding package" package takendown by npm registory - objects = objects.filter( - r => - r.package.version !== '0.0.1-security' || - r.package.description !== 'security holding package', - ) + objects = objects.filter(r => r.package.repository?.url !== 'npm/security-holder') // Filter out platform-specific packages if setting is enabled if (settings.value.hidePlatformPackages) { From b70fad2cb4e1eca8c01a30d700f05359e672f72e Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 18 Apr 2026 12:23:11 +0900 Subject: [PATCH 5/5] feat: use `isSecurityHeld` included in algolia api response --- app/composables/npm/useAlgoliaSearch.ts | 3 + app/pages/search.vue | 4 +- shared/types/npm-registry.ts | 17 +---- .../algolia/search/security-holder.json | 69 +++++++++++++++++++ .../composables/use-algolia-search.spec.ts | 27 ++++++++ 5 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 test/fixtures/algolia/search/security-holder.json create mode 100644 test/nuxt/composables/use-algolia-search.spec.ts diff --git a/app/composables/npm/useAlgoliaSearch.ts b/app/composables/npm/useAlgoliaSearch.ts index 02ce461517..b678d7e6ab 100644 --- a/app/composables/npm/useAlgoliaSearch.ts +++ b/app/composables/npm/useAlgoliaSearch.ts @@ -50,6 +50,7 @@ interface AlgoliaHit { deprecated: boolean | string isDeprecated: boolean license: string | null + isSecurityHeld: boolean } const ATTRIBUTES_TO_RETRIEVE = [ @@ -67,6 +68,7 @@ const ATTRIBUTES_TO_RETRIEVE = [ 'deprecated', 'isDeprecated', 'license', + 'isSecurityHeld', ] const EXISTENCE_CHECK_ATTRS = ['name'] @@ -90,6 +92,7 @@ function hitToSearchResult(hit: AlgoliaHit): NpmSearchResult { email: owner.email, })) : [], + isSecurityHeld: hit.isSecurityHeld, }, searchScore: 0, downloads: { diff --git a/app/pages/search.vue b/app/pages/search.vue index eec4cc41b4..58805476c6 100644 --- a/app/pages/search.vue +++ b/app/pages/search.vue @@ -94,8 +94,8 @@ const visibleResults = computed(() => { let objects = raw.objects - // Filter out "Security holding package" package takendown by npm registory - objects = objects.filter(r => r.package.repository?.url !== 'npm/security-holder') + // Filter out "Security holding package" packages taken down by npm registry + objects = objects.filter(r => !r.package.isSecurityHeld) // Filter out platform-specific packages if setting is enabled if (settings.value.hidePlatformPackages) { diff --git a/shared/types/npm-registry.ts b/shared/types/npm-registry.ts index 9b088b1d2f..6ffe94aded 100644 --- a/shared/types/npm-registry.ts +++ b/shared/types/npm-registry.ts @@ -189,7 +189,8 @@ export interface NpmSearchPackage { publisher?: NpmSearchPublisher maintainers?: NpmPerson[] license?: string - repository?: NpmSearchRepository + /** Algolia-only: package is an npm-owned security-holder takedown */ + isSecurityHeld?: boolean } /** @@ -306,20 +307,6 @@ export interface NpmTrustedPublisher { ciConfigPath?: string } -/** - * Repository types - * Note: Not covered by @npm/types - */ -export interface NpmSearchRepository { - type: 'git' - url: string - project: string - user: string - host: string - path: string - branch: string -} - /** * jsDelivr API Types * Used for package file browsing diff --git a/test/fixtures/algolia/search/security-holder.json b/test/fixtures/algolia/search/security-holder.json new file mode 100644 index 0000000000..3c0bf8911a --- /dev/null +++ b/test/fixtures/algolia/search/security-holder.json @@ -0,0 +1,69 @@ +[ + { + "name": "vuln-npm", + "downloadsLast30Days": 0, + "downloadsRatio": 0, + "popular": false, + "version": "0.0.1-security", + "description": "security holding package", + "repository": { + "type": "git", + "url": "npm/security-holder", + "project": "security-holder", + "user": "npm", + "host": "github.com", + "path": "", + "branch": "master" + }, + "deprecated": false, + "isDeprecated": false, + "isSecurityHeld": true, + "homepage": null, + "license": null, + "keywords": [], + "modified": 1692592458394, + "owners": [ + { + "email": "npm@npmjs.com", + "name": "npm", + "avatar": "https://gravatar.com/avatar/46d8d00e190be647053f7d97fd0478e4", + "link": "https://www.npmjs.com/~npm" + } + ], + "objectID": "vuln-npm" + }, + { + "name": "npmx-connector", + "downloadsLast30Days": 1047, + "downloadsRatio": 0, + "popular": false, + "version": "0.9.0", + "description": "Local connector for npmx.dev - enables authenticated npm operations from the web UI", + "repository": { + "type": "git", + "url": "https://github.com/npmx-dev/npmx.dev", + "project": "npmx.dev", + "user": "npmx-dev", + "host": "github.com", + "path": "", + "head": "1f574e52f494032683c1aec7f64f6de72d4413a0", + "branch": "1f574e52f494032683c1aec7f64f6de72d4413a0" + }, + "deprecated": false, + "isDeprecated": false, + "isSecurityHeld": false, + "homepage": "https://npmx.dev", + "license": "MIT", + "keywords": [], + "modified": 1776194979856, + "owners": [ + { + "name": "danielroe", + "email": "daniel@roe.dev", + "avatar": "https://gravatar.com/avatar/f366ee6556b7307a1a2a253c8fb842ca", + "link": "https://www.npmjs.com/~danielroe" + } + ], + "objectID": "npmx-connector" + } +] diff --git a/test/nuxt/composables/use-algolia-search.spec.ts b/test/nuxt/composables/use-algolia-search.spec.ts new file mode 100644 index 0000000000..1e0113691e --- /dev/null +++ b/test/nuxt/composables/use-algolia-search.spec.ts @@ -0,0 +1,27 @@ +import { describe, expect, it, vi } from 'vitest' +import fixture from '~~/test/fixtures/algolia/search/security-holder.json' + +const mockSearch = vi.fn() +vi.mock('algoliasearch/lite', () => ({ + liteClient: () => ({ search: mockSearch }), +})) + +describe('useAlgoliaSearch', () => { + it('maps isSecurityHeld through to NpmSearchResult.package', async () => { + mockSearch.mockResolvedValue({ + results: [{ hits: fixture, nbHits: fixture.length }], + }) + + const { search } = useAlgoliaSearch() + const { objects } = await search('') + + const bad = objects.find(o => o.package.name === 'vuln-npm') + const good = objects.find(o => o.package.name === 'npmx-connector') + + expect(bad?.package.isSecurityHeld).toBe(true) + expect(good?.package.isSecurityHeld).toBe(false) + + const filtered = objects.filter(o => !o.package.isSecurityHeld).map(o => o.package.name) + expect(filtered).toEqual(['npmx-connector']) + }) +})