Skip to content

Commit e83567a

Browse files
committed
fix: security issue
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 262bec8 commit e83567a

5 files changed

Lines changed: 37 additions & 27 deletions

File tree

services/apps/packages_worker/src/maven/scripts/fetchMavenMaintainers.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*
1616
* Usage:
1717
* LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy \
18-
* tsx src/scripts/fetchMavenMaintainers.ts /path/to/packages_top500.csv [output.csv]
18+
* tsx src/maven/scripts/fetchMavenMaintainers.ts /path/to/packages_top500.csv [output.csv]
1919
*/
2020
import axios, { AxiosInstance } from 'axios'
2121
import * as fs from 'fs'
@@ -94,7 +94,7 @@ async function safeGet<T>(client: AxiosInstance, url: string): Promise<T | null>
9494
await sleep(wait)
9595
return safeGet<T>(client, url)
9696
}
97-
console.warn(` HTTP ${status} for ${url}`)
97+
console.warn(` HTTP ${status} for ${url.replace(/([?&]api_key=)[^&]+/, '$1[redacted]')}`)
9898
}
9999
return null
100100
}
@@ -358,10 +358,11 @@ function parseCODEOWNERS(content: string): string[] {
358358
for (const line of content.split('\n')) {
359359
const trimmed = line.trim()
360360
if (!trimmed || trimmed.startsWith('#')) continue
361-
// Match @login — skip @org/team (contain a slash after @)
361+
// Match @login — skip @org/team entries (slash immediately after the handle)
362362
let match: RegExpExecArray | null
363363
const re = /@([a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)/g
364364
while ((match = re.exec(trimmed)) !== null) {
365+
if (trimmed[match.index + match[0].length] === '/') continue
365366
const login = match[1].toLowerCase()
366367
if (!seen.has(login)) {
367368
seen.add(login)
@@ -447,8 +448,9 @@ async function fetchTopContributors(
447448
): Promise<GithubContributor[]> {
448449
const results: GithubContributor[] = []
449450
let page = 1
451+
let hasMore = true
450452

451-
while (true) {
453+
while (hasMore) {
452454
const data = await safeGet<GithubContributor[]>(
453455
github,
454456
`/repos/${owner}/${repo}/contributors?per_page=100&page=${page}&anon=false`,
@@ -458,13 +460,11 @@ async function fetchTopContributors(
458460
const users = data.filter((c) => c.type === 'User')
459461
results.push(...users)
460462

461-
// Stop if we've hit the requested limit (0 = unlimited)
462-
if (limit > 0 && results.length >= limit) break
463-
// Stop if GitHub returned fewer than a full page — no more pages
464-
if (data.length < 100) break
465-
466-
page++
467-
await sleep(GITHUB_DELAY_MS)
463+
hasMore = data.length === 100 && (limit === 0 || results.length < limit)
464+
if (hasMore) {
465+
page++
466+
await sleep(GITHUB_DELAY_MS)
467+
}
468468
}
469469

470470
return limit > 0 ? results.slice(0, limit) : results
@@ -658,7 +658,7 @@ async function main() {
658658
const inputPath = process.argv[2]
659659
if (!inputPath) {
660660
console.error(
661-
'Usage: LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy tsx src/scripts/fetchMavenMaintainers.ts <input.csv> [output.csv]',
661+
'Usage: LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy tsx src/maven/scripts/fetchMavenMaintainers.ts <input.csv> [output.csv]',
662662
)
663663
process.exit(1)
664664
}
@@ -676,16 +676,17 @@ async function main() {
676676
const CSV_HEADER =
677677
'package_purl,package_namespace,package_name,github_repo,repo_source,maintainer_github_login,maintainer_display_name,maintainer_email,maintainer_url,role,source,contributions,notes'
678678

679-
const lines: string[] = [CSV_HEADER]
679+
const resultsByIndex: MaintainerRow[][] = new Array(rows.length)
680680
const stats = { codeowners: 0, maintainers_file: 0, contributors: 0, none: 0 }
681+
let completed = 0
681682

682683
// Concurrency 3 — Libraries.io rate limit is the bottleneck
683684
await withConcurrency(rows, 3, async (row, i) => {
684685
const pkg = `${row.namespace}/${row.name}`
685686
process.stdout.write(`[${i + 1}/${rows.length}] ${pkg} ... `)
686687

687688
const results = await enrichPackage(row)
688-
for (const r of results) lines.push(toCsvLine(r))
689+
resultsByIndex[i] = results
689690

690691
const source = results[0]?.source ?? ''
691692
if (source === 'codeowners') stats.codeowners++
@@ -695,15 +696,19 @@ async function main() {
695696

696697
console.log(`${results.length} maintainer(s) [${source || 'none'}]`)
697698

698-
// Checkpoint every 50 packages — safe to re-run
699-
if ((i + 1) % 50 === 0) {
700-
fs.writeFileSync(outputPath, lines.join('\n'))
701-
console.log(` ✓ checkpoint saved (${i + 1} done)`)
699+
// Checkpoint every 50 packages — write rows in original input order
700+
completed++
701+
if (completed % 50 === 0) {
702+
const checkpointLines = [CSV_HEADER, ...resultsByIndex.flat().map(toCsvLine)]
703+
fs.writeFileSync(outputPath, checkpointLines.join('\n'))
704+
console.log(` ✓ checkpoint saved (${completed} done)`)
702705
}
703706
})
704707

708+
const lines = [CSV_HEADER, ...resultsByIndex.flat().map(toCsvLine)]
705709
fs.writeFileSync(outputPath, lines.join('\n'))
706710
console.log(`\nDone. ${lines.length - 1} rows written to: ${outputPath}`)
711+
707712
console.log(
708713
` CODEOWNERS: ${stats.codeowners} | MAINTAINERS file: ${stats.maintainers_file} | contributors fallback: ${stats.contributors} | not found: ${stats.none}`,
709714
)

services/apps/packages_worker/src/maven/scripts/importMaintainersFromCsv.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* --dry-run Print what would be inserted without touching the DB
1313
*
1414
* Usage:
15-
* tsx src/scripts/importMaintainersFromCsv.ts <input.csv> [--dry-run]
15+
* tsx src/maven/scripts/importMaintainersFromCsv.ts <input.csv> [--dry-run]
1616
*/
1717
import * as fs from 'fs'
1818
import * as path from 'path'
@@ -87,9 +87,10 @@ function parseCsv(content: string): Record<string, string>[] {
8787

8888
// ─── Role normalisation ───────────────────────────────────────────────────────
8989

90-
function normaliseRole(raw: string): 'author' | 'maintainer' | null {
90+
function normaliseRole(raw: string): 'author' | 'maintainer' | 'contributor' | null {
9191
if (raw === 'author') return 'author'
9292
if (raw === 'maintainer') return 'maintainer'
93+
if (raw === 'contributor') return 'contributor'
9394
return null
9495
}
9596

@@ -101,7 +102,9 @@ async function main() {
101102
const inputPath = args.find((a) => !a.startsWith('--'))
102103

103104
if (!inputPath) {
104-
console.error('Usage: tsx src/scripts/importMaintainersFromCsv.ts <input.csv> [--dry-run]')
105+
console.error(
106+
'Usage: tsx src/maven/scripts/importMaintainersFromCsv.ts <input.csv> [--dry-run]',
107+
)
105108
process.exit(1)
106109
}
107110

@@ -225,8 +228,9 @@ async function main() {
225228
`-- Deletes only the package_maintainers rows inserted by this import.`,
226229
`-- Maintainer profiles in the maintainers table are left intact.`,
227230
``,
228-
`DELETE FROM package_maintainers`,
229-
`WHERE id IN (${insertedPmIds.join(', ')});`,
231+
insertedPmIds.length > 0
232+
? `DELETE FROM package_maintainers WHERE id IN (${insertedPmIds.join(', ')});`
233+
: `-- Nothing was inserted — no rows to roll back.`,
230234
``,
231235
`-- Rows deleted: ${insertedPmIds.length}`,
232236
].join('\n')
@@ -240,7 +244,7 @@ async function main() {
240244
console.log(` package_maintainers inserted: ${insertedLinks}`)
241245
console.log(` package_maintainers skipped (already existed): ${skippedLinks}`)
242246
console.log(`\n Rollback file: ${rollbackPath}`)
243-
console.log(` To revert: psql \$OSSPCKGS_DB_URL -f ${rollbackPath}`)
247+
console.log(` To revert: psql $OSSPCKGS_DB_URL -f ${rollbackPath}`)
244248
}
245249

246250
main().catch((err) => {

services/libs/data-access-layer/src/osspckgs/maintainers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export async function insertPackageMaintainerLink(
5050
qx: QueryExecutor,
5151
packageId: number,
5252
maintainerId: number,
53-
role: 'author' | 'maintainer' | null,
53+
role: 'author' | 'maintainer' | 'contributor' | null,
5454
ingestionSource?: string | null,
5555
): Promise<number | null> {
5656
const row = await qx.selectOneOrNone(

services/libs/data-access-layer/src/osspckgs/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export type IDbMaintainerUpsert = {
5151
export type IDbPackageMaintainerUpsert = {
5252
packageId: number
5353
maintainerId: number
54-
role: 'author' | 'maintainer' | null
54+
role: 'author' | 'maintainer' | 'contributor' | null
5555
ingestionSource?: string | null
5656
}
5757

services/libs/tinybird/datasources/packageMaintainers.datasource

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ DESCRIPTION >
55
- `id` is the internal primary key.
66
- `packageId` links to the parent packages row.
77
- `maintainerId` links to the parent maintainers row.
8-
- `role` is the maintainer's role on the package: 'author', 'maintainer', or empty string if not specified.
8+
- `role` is the maintainer's role on the package: 'author', 'maintainer', 'contributor', or empty string if not specified.
9+
- `ingestionSource` identifies where this link came from (e.g. 'manual_csv'); empty string for auto-enriched rows.
910
- `createdAt` and `updatedAt` are row-level audit timestamps for Tinybird watermark-based sync.
1011

1112
SCHEMA >

0 commit comments

Comments
 (0)