Skip to content

feat(vscode): select org explicitly - #83

Open
John-David Dalton (jdalton) wants to merge 1 commit into
mainfrom
feat/package-intelligence-vscode-org-selection-delivery
Open

John-David Dalton (jdalton) wants to merge 1 commit into
mainfrom
feat/package-intelligence-vscode-org-selection-delivery

Conversation

@jdalton

@jdalton John-David Dalton (jdalton) commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

LLM Description written by Codex: GPT-6

Prepares the VS Code extension for organization-scoped package intelligence: users can select an accessible organization, and credential changes clear stale results. Existing token-only sessions keep working.

This prerequisite does not switch package requests to the new API or apply organization policy to the current package view. Package requests still use the existing API. A separate integration will use the selected organization after the new API is deployed and SDK support is released.

The extension exposes socket-security.orgSlug, validates the selected slug against accessible organizations, and invalidates stale authentication and manager results when credentials or organization context changes.

Pending local gate fixes: the signed commit in this PR's remote head does not yet contain the following corrections. They are committed locally as a9c3ca85662487ca9aea924f03013df5abc3dfdc but could not be pushed because mandatory pre-push validation rejected the unrelated base workflow lint issue described below:

  • Lockfile metadata for six hydrated hook importers.
  • Six repo-owned imports changed to the canonical fail-soft runner path in test-extension, fuzz, and install-claude-plugins scripts.
Validation and coverage

Remote head 1555d380413a535fe9022222872d54bf0472bd90 had 62 focused tests pass across 5 files, source type check pass, and git diff --check pass. Its remote CI setup failed on all operating systems because the hydrated claude-md-rule-add-guard importer lacked @socketsecurity/lib-stable@catalog: in the lockfile. The local pending lockfile correction passes frozen install with pinned pnpm 12.10.1.

The local pending imports pass script --describe and --help checks, repo source type check, and git diff --check. Full pnpm run check:types on the original remote head was blocked by missing generated ignored scripts/fleet/process/run-main.mts.

The push hook also reports an unrelated lint failure: .github/workflows/publish-npm.yml: run name must use the validated publish task name contract. Fix the canonical label and sync its copies. This issue is in the base and remains unresolved.

pnpm test could not run because another shared heavy job occupied the host. No real VS Code extension-host test ran; SecretStorage behavior was not verified in the host.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Org selection unused by package fetches
    • Package fetches now send the organization resolved by getAuthenticatedContext to the org-scoped purl endpoint when socket-security.orgSlug selects an accessible org.

Create PR

Or push these changes by commenting:

@cursor push 260a33cf1b
Preview (260a33cf1b)
diff --git a/src/api.mts b/src/api.mts
--- a/src/api.mts
+++ b/src/api.mts
@@ -23,6 +23,7 @@
 }
 
 export type PackageRequestOptions = {
+  orgSlug?: string | undefined
   signal?: AbortSignal | undefined
   timeout?: number | undefined
 }
@@ -236,17 +237,25 @@
   options?: PackageRequestOptions | undefined,
 ): AsyncGenerator {
   const sdk = createSocketSdk(apiKey, options)
+  const { orgSlug } = {
+    __proto__: null,
+    ...options,
+  } as PackageRequestOptions
   const batchSize = 1024
+  const query = { alerts: true, compact: false }
   for (let index = 0, { length } = purls; index < length; index += batchSize) {
     const components = purls
       .slice(index, index + batchSize)
       .map(purl => ({ __proto__: null, purl }))
-    const result = await sdk.batchPackageFetch(
-      { components },
-      { alerts: true, compact: false },
-    )
+    const result = orgSlug
+      ? await sdk.batchOrgPackageFetch(orgSlug, { components }, query)
+      : await sdk.batchPackageFetch({ components }, query)
     if (!result.success) {
-      throw createPackageRequestError(result)
+      throw createPackageRequestError({
+        error: '',
+        status: result.status,
+        success: false,
+      })
     }
     yield* result.data
   }

diff --git a/src/ui/purl-alerts-and-scores/manager.mts b/src/ui/purl-alerts-and-scores/manager.mts
--- a/src/ui/purl-alerts-and-scores/manager.mts
+++ b/src/ui/purl-alerts-and-scores/manager.mts
@@ -3,12 +3,15 @@
 import { logger } from '../../infra/log.mts'
 import path from 'node:path'
 import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
-import { getAPIKey } from '../../auth.mts'
+import { getAPIKey, getAuthenticatedContext } from '../../auth.mts'
 import {
   isPackageDisplayDataRenderable,
   streamPackageScores,
 } from '../../api.mts'
-import type { PackageScoreAndAlerts } from '../../api.mts'
+import type {
+  PackageRequestOptions,
+  PackageScoreAndAlerts,
+} from '../../api.mts'
 import { safeDeleteSync } from '@socketsecurity/lib/fs/safe'
 import { isObject } from '@socketsecurity/lib/objects/predicates'
 import { worstArtifactsByPurl } from './select-artifacts.mts'
@@ -168,6 +171,33 @@
   #generation = 0
   #contextReady = true
   private constructor() {}
+  async #resolvePackageFetchRequest(
+    signal: AbortSignal,
+    timeout: number,
+  ): Promise<{
+    apiKey: string | undefined
+    options: PackageRequestOptions
+  }> {
+    const transport = { signal, timeout }
+    const configuredSlug = vscode.workspace
+      .getConfiguration()
+      .get('socket-security.orgSlug')
+    if (typeof configuredSlug !== 'string' || configuredSlug.length === 0) {
+      return { apiKey: await getAPIKey(), options: transport }
+    }
+    const authenticated = await getAuthenticatedContext()
+    if (!authenticated) {
+      return { apiKey: await getAPIKey(), options: transport }
+    }
+    return {
+      apiKey: authenticated.accessToken,
+      options: {
+        orgSlug: authenticated.organization.slug,
+        signal,
+        timeout,
+      },
+    }
+  }
   clear(requeueActive = true) {
     this.#generation += 1
     if (!requeueActive) {
@@ -280,17 +310,21 @@
         )
       })
       try {
-        const apiKey = await getAPIKey()
+        const request = await this.#resolvePackageFetchRequest(
+          controller.signal,
+          this.timeout,
+        )
         if (controller.signal.aborted || generation !== this.#generation) {
           return
         }
         // logger.info(`Requesting Socket API for PURLs: ${[...thesePendingUpdates].join(', ')}`)
         // Bound the SDK request with the same ceiling the AbortController timer
         // uses so a hung connection can't leave entries pending forever.
-        const scores = streamPackageScores(apiKey, [...thesePendingUpdates], {
-          signal: controller.signal,
-          timeout: this.timeout,
-        })
+        const scores = streamPackageScores(
+          request.apiKey,
+          [...thesePendingUpdates],
+          request.options,
+        )
         // The /v0/purl endpoint can stream MULTIPLE artifacts for the same
         // input PURL (e.g. a PyPI sdist and wheel of one version) with
         // different scores and alerts. Buffer them all, then collapse to the

diff --git a/test/repo/unit/api.test.mts b/test/repo/unit/api.test.mts
--- a/test/repo/unit/api.test.mts
+++ b/test/repo/unit/api.test.mts
@@ -404,6 +404,22 @@
     expect(seen.map(s => s.score.overall)).toEqual([42, 99])
   })
 
+  test('requests the organization purl endpoint for a selected org', async () => {
+    const purl = 'pkg:npm/example-org-policy@1.0.0' as SimPURL
+    const scope = nock(API_ORIGIN)
+      .post('/v0/orgs/example-org/purl', { components: [{ purl }] })
+      .query({ alerts: 'true', compact: 'false' })
+      .reply(200, `${artifactLine(purl, 0.5)}\n`)
+
+    const seen = await collectPackageScores(
+      streamPackageScores(TOKEN, [purl], { orgSlug: 'example-org' }),
+    )
+
+    expect(seen).toHaveLength(1)
+    expect(seen[0]?.score.overall).toBe(0.5)
+    expect(scope.isDone()).toBe(true)
+  })
+
   test('requests /v0/purl with the pending purls in the body', async () => {
     const purl = 'pkg:npm/express@4.0.0' as SimPURL
     let sentBody: unknown

diff --git a/test/repo/unit/ui/purl-alerts-and-scores/manager-cache-requests.test.mts b/test/repo/unit/ui/purl-alerts-and-scores/manager-cache-requests.test.mts
--- a/test/repo/unit/ui/purl-alerts-and-scores/manager-cache-requests.test.mts
+++ b/test/repo/unit/ui/purl-alerts-and-scores/manager-cache-requests.test.mts
@@ -4,11 +4,15 @@
 import type { logger as realLogger } from '../../../../../src/infra/log.mts'
 import type { SimPURL } from '../../../../../src/ui/externals/parse-externals.mts'
 import { PURLDataCache } from '../../../../../src/ui/purl-alerts-and-scores/manager.mts'
+import { setStubWorkspaceState } from '../../../../stubs/vscode.mts'
 
-const { getAPIKey, streamPackageScores } = vi.hoisted(() => ({
-  getAPIKey: vi.fn(),
-  streamPackageScores: vi.fn(),
-}))
+const { getAPIKey, getAuthenticatedContext, streamPackageScores } = vi.hoisted(
+  () => ({
+    getAPIKey: vi.fn(),
+    getAuthenticatedContext: vi.fn(),
+    streamPackageScores: vi.fn(),
+  }),
+)
 
 vi.mock(import('node:fs'), async importOriginal => ({
   ...(await importOriginal()),
@@ -20,7 +24,10 @@
   writeFileSync: vi.fn(),
 }))
 vi.mock(import('../../../../../src/api.mts'), () => ({ streamPackageScores }))
-vi.mock(import('../../../../../src/auth.mts'), () => ({ getAPIKey }))
+vi.mock(import('../../../../../src/auth.mts'), () => ({
+  getAPIKey,
+  getAuthenticatedContext,
+}))
 vi.mock(import('../../../../../src/infra/log.mts'), () => ({
   logger: {
     debug: vi.fn(),
@@ -60,6 +67,8 @@
     vi.useFakeTimers()
     vi.setSystemTime(new Date('2026-01-01T00:00:00Z'))
     getAPIKey.mockReset().mockResolvedValue('test-placeholder-token')
+    getAuthenticatedContext.mockReset().mockResolvedValue(undefined)
+    setStubWorkspaceState({})
     streamPackageScores.mockReset().mockImplementation(async function* (
       ...request: [string, SimPURL[]]
     ) {
@@ -141,6 +150,37 @@
     expect(entry.pkgData).toEqual(packageScore(purl))
   })
 
+  test('sends the selected organization slug with the package request', async () => {
+    setStubWorkspaceState({
+      configuration: { 'socket-security.orgSlug': 'example-org' },
+    })
+    getAuthenticatedContext.mockResolvedValue({
+      accessToken: 'test-placeholder-token',
+      organization: {
+        id: 'example-org',
+        image: undefined,
+        name: 'Example Organization',
+        plan: 'team',
+        slug: 'example-org',
+      },
+    })
+    const purl = 'pkg:npm/example-org-policy@1.0.0'
+    const entry = cache.watch(purl)
+    await flushRequests()
+
+    expect(getAPIKey).not.toHaveBeenCalled()
+    expect(streamPackageScores).toHaveBeenCalledWith(
+      'test-placeholder-token',
+      [purl],
+      {
+        orgSlug: 'example-org',
+        signal: expect.any(AbortSignal),
+        timeout: cache.timeout,
+      },
+    )
+    expect(entry.pkgData).toEqual(packageScore(purl))
+  })
+
   test('uses anonymous lookup without a token and clears the timer', async () => {
     getAPIKey.mockResolvedValueOnce(undefined)
     const purl = 'pkg:npm/example-no-token@1.0.0'

You can send follow-ups to the cloud agent here.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1555d38. Configure here.

Comment thread src/ui/purl-alerts-and-scores/manager.mts
Comment thread src/ui/purl-alerts-and-scores/manager.mts
@jdalton
John-David Dalton (jdalton) force-pushed the feat/package-intelligence-vscode-org-selection-delivery branch 2 times, most recently from 9c17495 to f91670d Compare October 10, 2026 17:48
@socket-security

socket-security Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​socketsecurity/​lib@​7.0.3 ⏵ 7.1.093 +210098 +197100

View full report

@socket-security-staging

socket-security-staging Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​socketsecurity/​lib@​7.0.3 ⏵ 7.1.093 +210098 +197100

View full report

@jdalton
John-David Dalton (jdalton) force-pushed the feat/package-intelligence-vscode-org-selection-delivery branch from f91670d to 98f85dc Compare October 10, 2026 17:57
Allow users to choose an accessible organization for package intelligence while keeping existing token-only sessions available.

Assisted-by: Codex:GPT-6
@jdalton
John-David Dalton (jdalton) force-pushed the feat/package-intelligence-vscode-org-selection-delivery branch from 98f85dc to ac26ac7 Compare October 10, 2026 18:02
Comment thread scripts/fleet/npm/scan/staged.mts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant