Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion services/apps/packages_worker/src/rubygems/workflows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { continueAsNew, log, proxyActivities } from '@temporalio/workflow'
import type * as activities from './activities'

const acts = proxyActivities<typeof activities>({
startToCloseTimeout: '30 minutes',
startToCloseTimeout: '60 minutes',
Comment thread
mbani01 marked this conversation as resolved.
retry: { initialInterval: '30 seconds', backoffCoefficient: 2, maximumAttempts: 5 },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ExtractorResult, ProvenanceEntry, RawContact } from '../../types'
import { extractEmails, fetchJson, registryHeaders } from '../http'
import { extractEmails, fetchJson, isEmail, registryHeaders } from '../http'

import { ParsedPurl } from './purl'

const SOURCE = 'rubygems'

/* eslint-disable @typescript-eslint/no-explicit-any */

// RubyGems hides author emails; we can only surface emails when an author string embeds one.
// bug_tracker_uri is an issue tracker, not a security contact, so it is intentionally skipped.
export function mapRubygems(doc: unknown, sourceUrl: string, fetchedAt: string): RawContact[] {
if (!doc || typeof doc !== 'object') return []
Expand All @@ -34,13 +33,61 @@ export function mapRubygems(doc: unknown, sourceUrl: string, fetchedAt: string):
return contacts
}

export function mapRubygemsOwners(
owners: unknown,
sourceUrl: string,
fetchedAt: string,
): RawContact[] {
if (!Array.isArray(owners)) return []

const prov = (): ProvenanceEntry[] => [
{ source: SOURCE, sourceTier: 'B', path: sourceUrl, fetchedAt },
]
const contacts: RawContact[] = []
const seen = new Set<string>()
for (const owner of owners) {
const o = (owner ?? {}) as Record<string, unknown>
const email = o.email
if (typeof email !== 'string' || !isEmail(email)) continue
const key = email.toLowerCase()
if (seen.has(key)) continue
seen.add(key)
const handle = typeof o.handle === 'string' ? o.handle : undefined
contacts.push({
channel: 'email',
value: email,
handle,
Comment thread
mbani01 marked this conversation as resolved.
Outdated
role: 'maintainer',
tier: 'B',
provenance: prov(),
})
}
return contacts
}

export async function fetchRubygems(
parsed: ParsedPurl,
timeoutMs: number,
userAgent: string,
): Promise<ExtractorResult> {
const url = `https://rubygems.org/api/v1/gems/${encodeURIComponent(parsed.name)}.json`
const { json } = await fetchJson(url, timeoutMs, registryHeaders(userAgent))
if (!json) return { contacts: [], policies: {} }
return { contacts: mapRubygems(json, url, new Date().toISOString()), policies: {} }
const gemUrl = `https://rubygems.org/api/v1/gems/${encodeURIComponent(parsed.name)}.json`
const ownersUrl = `https://rubygems.org/api/v1/gems/${encodeURIComponent(parsed.name)}/owners.json`
const fetchedAt = new Date().toISOString()

const [gemResult, ownersResult] = await Promise.all([
fetchJson(gemUrl, timeoutMs, registryHeaders(userAgent)),
fetchJson(ownersUrl, timeoutMs, registryHeaders(userAgent)).catch(() => ({
status: 0,
json: null,
})),
Comment thread
mbani01 marked this conversation as resolved.
Comment on lines +81 to +84
Comment on lines +79 to +84
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gem failure drops owner contacts

Medium Severity

fetchRubygems loads gem metadata and owners in parallel via Promise.all, but only the owners fetchJson call is wrapped in .catch. When the gem request errors (timeout, rate limit after retries, or non-absent HTTP status), the whole Promise.all rejects and owner emails from a successful owners response are never merged.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 87c24a3. Configure here.


if (!gemResult.json) return { contacts: [], policies: {} }

const contacts = [
...mapRubygems(gemResult.json, gemUrl, fetchedAt),
...mapRubygemsOwners(ownersResult.json, ownersUrl, fetchedAt),
]

return { contacts, policies: {} }
}
Loading