renovate 43.36.0 → 43.36.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,17 +23,22 @@ var ApiCache = class {
23
23
  updateItem(item) {
24
24
  this.cache.items[item.number] = item;
25
25
  }
26
+ getLastModified() {
27
+ return this.cache.lastModified;
28
+ }
29
+ updateLastModified(timestamp) {
30
+ const current = this.cache.lastModified ? DateTime.fromISO(this.cache.lastModified) : null;
31
+ const incoming = DateTime.fromISO(timestamp);
32
+ if (!current || incoming > current) this.cache.lastModified = timestamp;
33
+ }
26
34
  /**
27
- * Copies items from `page` to `cache`.
28
- * Updates internal cache timestamp.
35
+ * Copies items from `page` to cache and updates the internal timestamp.
29
36
  *
30
- * @param cache Cache object
31
- * @param page List of cacheable items, sorted by `updated_at` field
32
- * starting from the most recently updated.
33
- * @returns `true` when the next page is likely to contain fresh items,
34
- * otherwise `false`.
37
+ * @param page Items sorted by `updated_at` desc (most recent first).
38
+ * @returns `true` when the next page is likely to contain fresh items.
35
39
  */
36
40
  reconcile(page) {
41
+ if (page.length === 0) return false;
37
42
  const { items } = this.cache;
38
43
  let { lastModified } = this.cache;
39
44
  let needNextPage = true;
@@ -1 +1 @@
1
- {"version":3,"file":"api-cache.js","names":[],"sources":["../../../../lib/modules/platform/github/api-cache.ts"],"sourcesContent":["import { dequal } from 'dequal';\nimport { DateTime } from 'luxon';\nimport { logger } from '../../../logger/index.ts';\nimport type { ApiPageCache, ApiPageItem } from './types.ts';\n\nexport class ApiCache<T extends ApiPageItem> {\n private cache: ApiPageCache<T>;\n\n constructor(cache: ApiPageCache<T>) {\n this.cache = cache;\n }\n\n getItems(): T[] {\n const items = Object.values(this.cache.items);\n return items;\n }\n\n getItem(number: number): T | null {\n return this.cache.items[number] ?? null;\n }\n\n /**\n * It intentionally doesn't alter `lastModified` cache field.\n *\n * The point is to allow cache modifications during run, but\n * force fetching and refreshing of modified items next run.\n */\n updateItem(item: T): void {\n this.cache.items[item.number] = item;\n }\n\n /**\n * Copies items from `page` to `cache`.\n * Updates internal cache timestamp.\n *\n * @param cache Cache object\n * @param page List of cacheable items, sorted by `updated_at` field\n * starting from the most recently updated.\n * @returns `true` when the next page is likely to contain fresh items,\n * otherwise `false`.\n */\n reconcile(page: T[]): boolean {\n const { items } = this.cache;\n let { lastModified } = this.cache;\n\n let needNextPage = true;\n\n for (const newItem of page) {\n const number = newItem.number;\n const oldItem = items[number];\n\n const itemNewTime = DateTime.fromISO(newItem.updated_at);\n const itemOldTime = oldItem?.updated_at\n ? DateTime.fromISO(oldItem.updated_at)\n : null;\n\n if (!dequal(oldItem, newItem)) {\n logger.trace(`PR cache: updating item ${number}`);\n items[number] = newItem;\n }\n\n needNextPage = itemOldTime ? itemOldTime < itemNewTime : true;\n\n const cacheOldTime = lastModified ? DateTime.fromISO(lastModified) : null;\n if (!cacheOldTime || itemNewTime > cacheOldTime) {\n lastModified = newItem.updated_at;\n }\n }\n\n this.cache.lastModified = lastModified;\n\n return needNextPage;\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,WAAb,MAA6C;CAC3C,AAAQ;CAER,YAAY,OAAwB;AAClC,OAAK,QAAQ;;CAGf,WAAgB;AAEd,SADc,OAAO,OAAO,KAAK,MAAM,MAAM;;CAI/C,QAAQ,QAA0B;AAChC,SAAO,KAAK,MAAM,MAAM,WAAW;;;;;;;;CASrC,WAAW,MAAe;AACxB,OAAK,MAAM,MAAM,KAAK,UAAU;;;;;;;;;;;;CAalC,UAAU,MAAoB;EAC5B,MAAM,EAAE,UAAU,KAAK;EACvB,IAAI,EAAE,iBAAiB,KAAK;EAE5B,IAAI,eAAe;AAEnB,OAAK,MAAM,WAAW,MAAM;GAC1B,MAAM,SAAS,QAAQ;GACvB,MAAM,UAAU,MAAM;GAEtB,MAAM,cAAc,SAAS,QAAQ,QAAQ,WAAW;GACxD,MAAM,cAAc,SAAS,aACzB,SAAS,QAAQ,QAAQ,WAAW,GACpC;AAEJ,OAAI,CAAC,OAAO,SAAS,QAAQ,EAAE;AAC7B,WAAO,MAAM,2BAA2B,SAAS;AACjD,UAAM,UAAU;;AAGlB,kBAAe,cAAc,cAAc,cAAc;GAEzD,MAAM,eAAe,eAAe,SAAS,QAAQ,aAAa,GAAG;AACrE,OAAI,CAAC,gBAAgB,cAAc,aACjC,gBAAe,QAAQ;;AAI3B,OAAK,MAAM,eAAe;AAE1B,SAAO"}
1
+ {"version":3,"file":"api-cache.js","names":[],"sources":["../../../../lib/modules/platform/github/api-cache.ts"],"sourcesContent":["import { dequal } from 'dequal';\nimport { DateTime } from 'luxon';\nimport { logger } from '../../../logger/index.ts';\nimport type { ApiPageCache, ApiPageItem } from './types.ts';\n\nexport class ApiCache<T extends ApiPageItem> {\n private cache: ApiPageCache<T>;\n\n constructor(cache: ApiPageCache<T>) {\n this.cache = cache;\n }\n\n getItems(): T[] {\n return Object.values(this.cache.items);\n }\n\n getItem(number: number): T | null {\n return this.cache.items[number] ?? null;\n }\n\n /**\n * It intentionally doesn't alter `lastModified` cache field.\n *\n * The point is to allow cache modifications during run, but\n * force fetching and refreshing of modified items next run.\n */\n updateItem(item: T): void {\n this.cache.items[item.number] = item;\n }\n\n getLastModified(): string | undefined {\n return this.cache.lastModified;\n }\n\n updateLastModified(timestamp: string): void {\n const current = this.cache.lastModified\n ? DateTime.fromISO(this.cache.lastModified)\n : null;\n const incoming = DateTime.fromISO(timestamp);\n if (!current || incoming > current) {\n this.cache.lastModified = timestamp;\n }\n }\n\n /**\n * Copies items from `page` to cache and updates the internal timestamp.\n *\n * @param page Items sorted by `updated_at` desc (most recent first).\n * @returns `true` when the next page is likely to contain fresh items.\n */\n reconcile(page: T[]): boolean {\n if (page.length === 0) {\n return false;\n }\n\n const { items } = this.cache;\n let { lastModified } = this.cache;\n\n let needNextPage = true;\n\n for (const newItem of page) {\n const number = newItem.number;\n const oldItem = items[number];\n\n const itemNewTime = DateTime.fromISO(newItem.updated_at);\n const itemOldTime = oldItem?.updated_at\n ? DateTime.fromISO(oldItem.updated_at)\n : null;\n\n if (!dequal(oldItem, newItem)) {\n logger.trace(`PR cache: updating item ${number}`);\n items[number] = newItem;\n }\n\n needNextPage = itemOldTime ? itemOldTime < itemNewTime : true;\n\n const cacheOldTime = lastModified ? DateTime.fromISO(lastModified) : null;\n if (!cacheOldTime || itemNewTime > cacheOldTime) {\n lastModified = newItem.updated_at;\n }\n }\n\n this.cache.lastModified = lastModified;\n\n return needNextPage;\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,WAAb,MAA6C;CAC3C,AAAQ;CAER,YAAY,OAAwB;AAClC,OAAK,QAAQ;;CAGf,WAAgB;AACd,SAAO,OAAO,OAAO,KAAK,MAAM,MAAM;;CAGxC,QAAQ,QAA0B;AAChC,SAAO,KAAK,MAAM,MAAM,WAAW;;;;;;;;CASrC,WAAW,MAAe;AACxB,OAAK,MAAM,MAAM,KAAK,UAAU;;CAGlC,kBAAsC;AACpC,SAAO,KAAK,MAAM;;CAGpB,mBAAmB,WAAyB;EAC1C,MAAM,UAAU,KAAK,MAAM,eACvB,SAAS,QAAQ,KAAK,MAAM,aAAa,GACzC;EACJ,MAAM,WAAW,SAAS,QAAQ,UAAU;AAC5C,MAAI,CAAC,WAAW,WAAW,QACzB,MAAK,MAAM,eAAe;;;;;;;;CAU9B,UAAU,MAAoB;AAC5B,MAAI,KAAK,WAAW,EAClB,QAAO;EAGT,MAAM,EAAE,UAAU,KAAK;EACvB,IAAI,EAAE,iBAAiB,KAAK;EAE5B,IAAI,eAAe;AAEnB,OAAK,MAAM,WAAW,MAAM;GAC1B,MAAM,SAAS,QAAQ;GACvB,MAAM,UAAU,MAAM;GAEtB,MAAM,cAAc,SAAS,QAAQ,QAAQ,WAAW;GACxD,MAAM,cAAc,SAAS,aACzB,SAAS,QAAQ,QAAQ,WAAW,GACpC;AAEJ,OAAI,CAAC,OAAO,SAAS,QAAQ,EAAE;AAC7B,WAAO,MAAM,2BAA2B,SAAS;AACjD,UAAM,UAAU;;AAGlB,kBAAe,cAAc,cAAc,cAAc;GAEzD,MAAM,eAAe,eAAe,SAAS,QAAQ,aAAa,GAAG;AACrE,OAAI,CAAC,gBAAgB,cAAc,aACjC,gBAAe,QAAQ;;AAI3B,OAAK,MAAM,eAAe;AAE1B,SAAO"}
@@ -5,9 +5,11 @@ import { getCache } from "../../../util/cache/repository/index.js";
5
5
  import { repoCacheProvider } from "../../../util/http/cache/repository-http-cache-provider.js";
6
6
  import { coerceRestPr } from "./common.js";
7
7
  import { ApiCache } from "./api-cache.js";
8
- import { isEmptyArray } from "@sindresorhus/is";
8
+ import { isEmptyArray, isNonEmptyArray } from "@sindresorhus/is";
9
+ import { DateTime } from "luxon";
9
10
 
10
11
  //#region lib/modules/platform/github/pr.ts
12
+ const MAX_SYNC_PAGES = 100;
11
13
  function getPrApiCache() {
12
14
  const repoCache = getCache();
13
15
  if (!repoCache?.platform?.github?.pullRequestsCache) {
@@ -39,8 +41,8 @@ function getPrApiCache() {
39
41
  * b. Some of PRs had changed since last run.
40
42
  *
41
43
  * In this case, we sequentially fetch page by page
42
- * until `ApiCache.coerce` function indicates that
43
- * no more fresh items can be found in the next page.
44
+ * until the oldest item on an unfiltered page predates
45
+ * the cache's `lastModified` timestamp.
44
46
  *
45
47
  * We expect to fetch just one page per run in average,
46
48
  * since it's rare to have more than 100 updated PRs.
@@ -48,6 +50,12 @@ function getPrApiCache() {
48
50
  async function getPrCache(http, repo, username) {
49
51
  const prApiCache = getPrApiCache();
50
52
  const isInitial = isEmptyArray(prApiCache.getItems());
53
+ let lastModifiedRaw = prApiCache.getLastModified();
54
+ if (!lastModifiedRaw && !isInitial) {
55
+ const items = prApiCache.getItems();
56
+ for (const item of items) if (!lastModifiedRaw || item.updated_at > lastModifiedRaw) lastModifiedRaw = item.updated_at;
57
+ }
58
+ const cutoffTime = lastModifiedRaw ? DateTime.fromISO(lastModifiedRaw) : null;
51
59
  try {
52
60
  let requestsTotal = 0;
53
61
  let apiQuotaAffected = false;
@@ -77,15 +85,26 @@ async function getPrCache(http, repo, username) {
77
85
  requestsTotal += 1;
78
86
  const { headers: { link: linkHeader } } = res;
79
87
  let { body: page } = res;
88
+ if (!isInitial && cutoffTime && isNonEmptyArray(page)) {
89
+ prApiCache.updateLastModified(page[0].updated_at);
90
+ if (DateTime.fromISO(page[page.length - 1].updated_at) < cutoffTime) needNextPageSync = false;
91
+ }
80
92
  if (username) {
81
93
  const filteredPage = page.filter((ghPr) => ghPr?.user?.login && ghPr.user.login === username);
82
94
  logger.debug(`PR cache: Filtered ${page.length} PRs to ${filteredPage.length} (user=${username})`);
83
95
  page = filteredPage;
84
96
  }
85
97
  const items = page.map(coerceRestPr);
86
- needNextPageSync = prApiCache.reconcile(items);
98
+ if (isNonEmptyArray(items)) prApiCache.reconcile(items);
87
99
  needNextPageFetch = !!parseLinkHeader(linkHeader)?.next;
88
100
  if (pageIdx === 1) needNextPageFetch &&= !opts.paginate;
101
+ if (!isInitial && needNextPageFetch && needNextPageSync && pageIdx >= MAX_SYNC_PAGES) {
102
+ logger.warn({
103
+ repo,
104
+ pages: pageIdx
105
+ }, "PR cache: hit max sync pages, stopping");
106
+ needNextPageSync = false;
107
+ }
89
108
  pageIdx += 1;
90
109
  }
91
110
  logger.debug({
@@ -1 +1 @@
1
- {"version":3,"file":"pr.js","names":[],"sources":["../../../../lib/modules/platform/github/pr.ts"],"sourcesContent":["import { isEmptyArray } from '@sindresorhus/is';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { getCache } from '../../../util/cache/repository/index.ts';\nimport { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider.ts';\nimport type {\n GithubHttp,\n GithubHttpOptions,\n} from '../../../util/http/github.ts';\nimport { parseLinkHeader } from '../../../util/url.ts';\nimport { ApiCache } from './api-cache.ts';\nimport { coerceRestPr } from './common.ts';\nimport type { ApiPageCache, GhPr, GhRestPr } from './types.ts';\n\nfunction getPrApiCache(): ApiCache<GhPr> {\n const repoCache = getCache();\n if (!repoCache?.platform?.github?.pullRequestsCache) {\n logger.debug('PR cache: cached data not found, creating new cache');\n repoCache.platform ??= {};\n repoCache.platform.github ??= {};\n repoCache.platform.github.pullRequestsCache ??= { items: {} };\n }\n\n const prApiCache = new ApiCache<GhPr>(\n repoCache.platform.github.pullRequestsCache as ApiPageCache<GhPr>,\n );\n return prApiCache;\n}\n\n/**\n * Fetch and return Pull Requests from GitHub repository:\n *\n * 1. Synchronize long-term cache.\n *\n * 2. Store items in raw format, i.e. exactly what\n * has been returned by GitHub REST API.\n *\n * 3. Convert items to the Renovate format and return.\n *\n * In order synchronize ApiCache properly, we handle 3 cases:\n *\n * a. We never fetched PR list for this repo before.\n * If cached PR list is empty, we assume it's the case.\n *\n * In this case, we're falling back to quick fetch via\n * `paginate=true` option (see `util/http/github.ts`).\n *\n * b. Some of PRs had changed since last run.\n *\n * In this case, we sequentially fetch page by page\n * until `ApiCache.coerce` function indicates that\n * no more fresh items can be found in the next page.\n *\n * We expect to fetch just one page per run in average,\n * since it's rare to have more than 100 updated PRs.\n */\nexport async function getPrCache(\n http: GithubHttp,\n repo: string,\n username?: string,\n): Promise<Record<number, GhPr>> {\n const prApiCache = getPrApiCache();\n const isInitial = isEmptyArray(prApiCache.getItems());\n\n try {\n let requestsTotal = 0;\n let apiQuotaAffected = false;\n let needNextPageFetch = true;\n let needNextPageSync = true;\n\n let pageIdx = 1;\n while (needNextPageFetch && needNextPageSync) {\n const opts: GithubHttpOptions = { paginate: false, memCache: false };\n if (pageIdx === 1) {\n opts.cacheProvider = repoCacheProvider;\n if (isInitial) {\n // Speed up initial fetch\n opts.paginate = true;\n }\n }\n\n let perPage: number;\n if (isInitial) {\n logger.debug('PR cache: initial fetch');\n perPage = 100;\n } else {\n logger.debug('PR cache: sync fetch');\n perPage = 20;\n }\n\n const urlPath = `repos/${repo}/pulls?per_page=${perPage}&state=all&sort=updated&direction=desc&page=${pageIdx}`;\n\n const res = await http.getJsonUnchecked<GhRestPr[]>(urlPath, opts);\n apiQuotaAffected = true;\n requestsTotal += 1;\n\n const {\n headers: { link: linkHeader },\n } = res;\n\n let { body: page } = res;\n\n if (username) {\n const filteredPage = page.filter(\n (ghPr) => ghPr?.user?.login && ghPr.user.login === username,\n );\n\n logger.debug(\n `PR cache: Filtered ${page.length} PRs to ${filteredPage.length} (user=${username})`,\n );\n\n page = filteredPage;\n }\n\n const items = page.map(coerceRestPr);\n\n needNextPageSync = prApiCache.reconcile(items);\n needNextPageFetch = !!parseLinkHeader(linkHeader)?.next;\n\n if (pageIdx === 1) {\n needNextPageFetch &&= !opts.paginate;\n }\n\n pageIdx += 1;\n }\n\n logger.debug(\n {\n pullsTotal: prApiCache.getItems().length,\n requestsTotal,\n apiQuotaAffected,\n },\n `PR cache: getPrList success`,\n );\n } catch (err) /* v8 ignore next */ {\n logger.debug({ err }, 'PR cache: getPrList err');\n throw new ExternalHostError(err, 'github');\n }\n\n return prApiCache.getItems();\n}\n\nexport function updatePrCache(pr: GhPr): void {\n const cache = getPrApiCache();\n cache.updateItem(pr);\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAS,gBAAgC;CACvC,MAAM,YAAY,UAAU;AAC5B,KAAI,CAAC,WAAW,UAAU,QAAQ,mBAAmB;AACnD,SAAO,MAAM,sDAAsD;AACnE,YAAU,aAAa,EAAE;AACzB,YAAU,SAAS,WAAW,EAAE;AAChC,YAAU,SAAS,OAAO,sBAAsB,EAAE,OAAO,EAAE,EAAE;;AAM/D,QAHmB,IAAI,SACrB,UAAU,SAAS,OAAO,kBAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BH,eAAsB,WACpB,MACA,MACA,UAC+B;CAC/B,MAAM,aAAa,eAAe;CAClC,MAAM,YAAY,aAAa,WAAW,UAAU,CAAC;AAErD,KAAI;EACF,IAAI,gBAAgB;EACpB,IAAI,mBAAmB;EACvB,IAAI,oBAAoB;EACxB,IAAI,mBAAmB;EAEvB,IAAI,UAAU;AACd,SAAO,qBAAqB,kBAAkB;GAC5C,MAAM,OAA0B;IAAE,UAAU;IAAO,UAAU;IAAO;AACpE,OAAI,YAAY,GAAG;AACjB,SAAK,gBAAgB;AACrB,QAAI,UAEF,MAAK,WAAW;;GAIpB,IAAI;AACJ,OAAI,WAAW;AACb,WAAO,MAAM,0BAA0B;AACvC,cAAU;UACL;AACL,WAAO,MAAM,uBAAuB;AACpC,cAAU;;GAGZ,MAAM,UAAU,SAAS,KAAK,kBAAkB,QAAQ,8CAA8C;GAEtG,MAAM,MAAM,MAAM,KAAK,iBAA6B,SAAS,KAAK;AAClE,sBAAmB;AACnB,oBAAiB;GAEjB,MAAM,EACJ,SAAS,EAAE,MAAM,iBACf;GAEJ,IAAI,EAAE,MAAM,SAAS;AAErB,OAAI,UAAU;IACZ,MAAM,eAAe,KAAK,QACvB,SAAS,MAAM,MAAM,SAAS,KAAK,KAAK,UAAU,SACpD;AAED,WAAO,MACL,sBAAsB,KAAK,OAAO,UAAU,aAAa,OAAO,SAAS,SAAS,GACnF;AAED,WAAO;;GAGT,MAAM,QAAQ,KAAK,IAAI,aAAa;AAEpC,sBAAmB,WAAW,UAAU,MAAM;AAC9C,uBAAoB,CAAC,CAAC,gBAAgB,WAAW,EAAE;AAEnD,OAAI,YAAY,EACd,uBAAsB,CAAC,KAAK;AAG9B,cAAW;;AAGb,SAAO,MACL;GACE,YAAY,WAAW,UAAU,CAAC;GAClC;GACA;GACD,EACD,8BACD;UACM,2BAA0B;AACjC,SAAO,MAAM,EAAE,KAAK,EAAE,0BAA0B;AAChD,QAAM,IAAI,kBAAkB,KAAK,SAAS;;AAG5C,QAAO,WAAW,UAAU;;AAG9B,SAAgB,cAAc,IAAgB;AAE5C,CADc,eAAe,CACvB,WAAW,GAAG"}
1
+ {"version":3,"file":"pr.js","names":[],"sources":["../../../../lib/modules/platform/github/pr.ts"],"sourcesContent":["import { isEmptyArray, isNonEmptyArray } from '@sindresorhus/is';\nimport { DateTime } from 'luxon';\nimport { logger } from '../../../logger/index.ts';\nimport { ExternalHostError } from '../../../types/errors/external-host-error.ts';\nimport { getCache } from '../../../util/cache/repository/index.ts';\nimport { repoCacheProvider } from '../../../util/http/cache/repository-http-cache-provider.ts';\nimport type {\n GithubHttp,\n GithubHttpOptions,\n} from '../../../util/http/github.ts';\nimport { parseLinkHeader } from '../../../util/url.ts';\nimport { ApiCache } from './api-cache.ts';\nimport { coerceRestPr } from './common.ts';\nimport type { ApiPageCache, GhPr, GhRestPr } from './types.ts';\n\nconst MAX_SYNC_PAGES = 100;\n\nfunction getPrApiCache(): ApiCache<GhPr> {\n const repoCache = getCache();\n if (!repoCache?.platform?.github?.pullRequestsCache) {\n logger.debug('PR cache: cached data not found, creating new cache');\n repoCache.platform ??= {};\n repoCache.platform.github ??= {};\n repoCache.platform.github.pullRequestsCache ??= { items: {} };\n }\n\n const prApiCache = new ApiCache<GhPr>(\n repoCache.platform.github.pullRequestsCache as ApiPageCache<GhPr>,\n );\n return prApiCache;\n}\n\n/**\n * Fetch and return Pull Requests from GitHub repository:\n *\n * 1. Synchronize long-term cache.\n *\n * 2. Store items in raw format, i.e. exactly what\n * has been returned by GitHub REST API.\n *\n * 3. Convert items to the Renovate format and return.\n *\n * In order synchronize ApiCache properly, we handle 3 cases:\n *\n * a. We never fetched PR list for this repo before.\n * If cached PR list is empty, we assume it's the case.\n *\n * In this case, we're falling back to quick fetch via\n * `paginate=true` option (see `util/http/github.ts`).\n *\n * b. Some of PRs had changed since last run.\n *\n * In this case, we sequentially fetch page by page\n * until the oldest item on an unfiltered page predates\n * the cache's `lastModified` timestamp.\n *\n * We expect to fetch just one page per run in average,\n * since it's rare to have more than 100 updated PRs.\n */\nexport async function getPrCache(\n http: GithubHttp,\n repo: string,\n username?: string,\n): Promise<Record<number, GhPr>> {\n const prApiCache = getPrApiCache();\n const isInitial = isEmptyArray(prApiCache.getItems());\n\n // Snapshot before the loop — reconcile() updates lastModified as it\n // processes items, so reading it inside the loop would create a moving target.\n // If lastModified is missing but items exist (populated via updateItem()),\n // derive cutoff from the newest cached item.\n let lastModifiedRaw = prApiCache.getLastModified();\n if (!lastModifiedRaw && !isInitial) {\n const items = prApiCache.getItems();\n for (const item of items) {\n if (!lastModifiedRaw || item.updated_at > lastModifiedRaw) {\n lastModifiedRaw = item.updated_at;\n }\n }\n }\n const cutoffTime = lastModifiedRaw ? DateTime.fromISO(lastModifiedRaw) : null;\n\n try {\n let requestsTotal = 0;\n let apiQuotaAffected = false;\n let needNextPageFetch = true;\n let needNextPageSync = true;\n\n let pageIdx = 1;\n while (needNextPageFetch && needNextPageSync) {\n const opts: GithubHttpOptions = { paginate: false, memCache: false };\n if (pageIdx === 1) {\n opts.cacheProvider = repoCacheProvider;\n if (isInitial) {\n opts.paginate = true;\n }\n }\n\n let perPage: number;\n if (isInitial) {\n logger.debug('PR cache: initial fetch');\n perPage = 100;\n } else {\n logger.debug('PR cache: sync fetch');\n perPage = 20;\n }\n\n const urlPath = `repos/${repo}/pulls?per_page=${perPage}&state=all&sort=updated&direction=desc&page=${pageIdx}`;\n\n const res = await http.getJsonUnchecked<GhRestPr[]>(urlPath, opts);\n apiQuotaAffected = true;\n requestsTotal += 1;\n\n const {\n headers: { link: linkHeader },\n } = res;\n\n let { body: page } = res;\n\n if (!isInitial && cutoffTime && isNonEmptyArray(page)) {\n // Advance watermark so next run doesn't re-scan these pages,\n // even if no Renovate PRs are found.\n prApiCache.updateLastModified(page[0].updated_at);\n\n const oldestOnPage = DateTime.fromISO(page[page.length - 1].updated_at);\n if (oldestOnPage < cutoffTime) {\n needNextPageSync = false;\n }\n }\n\n if (username) {\n const filteredPage = page.filter(\n (ghPr) => ghPr?.user?.login && ghPr.user.login === username,\n );\n\n logger.debug(\n `PR cache: Filtered ${page.length} PRs to ${filteredPage.length} (user=${username})`,\n );\n\n page = filteredPage;\n }\n\n const items = page.map(coerceRestPr);\n\n if (isNonEmptyArray(items)) {\n prApiCache.reconcile(items);\n }\n\n needNextPageFetch = !!parseLinkHeader(linkHeader)?.next;\n\n if (pageIdx === 1) {\n needNextPageFetch &&= !opts.paginate;\n }\n\n // Safety net: cutoff-based stop should always fire first\n if (\n !isInitial &&\n needNextPageFetch &&\n needNextPageSync &&\n pageIdx >= MAX_SYNC_PAGES\n ) {\n logger.warn(\n { repo, pages: pageIdx },\n 'PR cache: hit max sync pages, stopping',\n );\n needNextPageSync = false;\n }\n\n pageIdx += 1;\n }\n\n logger.debug(\n {\n pullsTotal: prApiCache.getItems().length,\n requestsTotal,\n apiQuotaAffected,\n },\n `PR cache: getPrList success`,\n );\n } catch (err) /* v8 ignore next */ {\n logger.debug({ err }, 'PR cache: getPrList err');\n throw new ExternalHostError(err, 'github');\n }\n\n return prApiCache.getItems();\n}\n\nexport function updatePrCache(pr: GhPr): void {\n const cache = getPrApiCache();\n cache.updateItem(pr);\n}\n"],"mappings":";;;;;;;;;;;AAeA,MAAM,iBAAiB;AAEvB,SAAS,gBAAgC;CACvC,MAAM,YAAY,UAAU;AAC5B,KAAI,CAAC,WAAW,UAAU,QAAQ,mBAAmB;AACnD,SAAO,MAAM,sDAAsD;AACnE,YAAU,aAAa,EAAE;AACzB,YAAU,SAAS,WAAW,EAAE;AAChC,YAAU,SAAS,OAAO,sBAAsB,EAAE,OAAO,EAAE,EAAE;;AAM/D,QAHmB,IAAI,SACrB,UAAU,SAAS,OAAO,kBAC3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BH,eAAsB,WACpB,MACA,MACA,UAC+B;CAC/B,MAAM,aAAa,eAAe;CAClC,MAAM,YAAY,aAAa,WAAW,UAAU,CAAC;CAMrD,IAAI,kBAAkB,WAAW,iBAAiB;AAClD,KAAI,CAAC,mBAAmB,CAAC,WAAW;EAClC,MAAM,QAAQ,WAAW,UAAU;AACnC,OAAK,MAAM,QAAQ,MACjB,KAAI,CAAC,mBAAmB,KAAK,aAAa,gBACxC,mBAAkB,KAAK;;CAI7B,MAAM,aAAa,kBAAkB,SAAS,QAAQ,gBAAgB,GAAG;AAEzE,KAAI;EACF,IAAI,gBAAgB;EACpB,IAAI,mBAAmB;EACvB,IAAI,oBAAoB;EACxB,IAAI,mBAAmB;EAEvB,IAAI,UAAU;AACd,SAAO,qBAAqB,kBAAkB;GAC5C,MAAM,OAA0B;IAAE,UAAU;IAAO,UAAU;IAAO;AACpE,OAAI,YAAY,GAAG;AACjB,SAAK,gBAAgB;AACrB,QAAI,UACF,MAAK,WAAW;;GAIpB,IAAI;AACJ,OAAI,WAAW;AACb,WAAO,MAAM,0BAA0B;AACvC,cAAU;UACL;AACL,WAAO,MAAM,uBAAuB;AACpC,cAAU;;GAGZ,MAAM,UAAU,SAAS,KAAK,kBAAkB,QAAQ,8CAA8C;GAEtG,MAAM,MAAM,MAAM,KAAK,iBAA6B,SAAS,KAAK;AAClE,sBAAmB;AACnB,oBAAiB;GAEjB,MAAM,EACJ,SAAS,EAAE,MAAM,iBACf;GAEJ,IAAI,EAAE,MAAM,SAAS;AAErB,OAAI,CAAC,aAAa,cAAc,gBAAgB,KAAK,EAAE;AAGrD,eAAW,mBAAmB,KAAK,GAAG,WAAW;AAGjD,QADqB,SAAS,QAAQ,KAAK,KAAK,SAAS,GAAG,WAAW,GACpD,WACjB,oBAAmB;;AAIvB,OAAI,UAAU;IACZ,MAAM,eAAe,KAAK,QACvB,SAAS,MAAM,MAAM,SAAS,KAAK,KAAK,UAAU,SACpD;AAED,WAAO,MACL,sBAAsB,KAAK,OAAO,UAAU,aAAa,OAAO,SAAS,SAAS,GACnF;AAED,WAAO;;GAGT,MAAM,QAAQ,KAAK,IAAI,aAAa;AAEpC,OAAI,gBAAgB,MAAM,CACxB,YAAW,UAAU,MAAM;AAG7B,uBAAoB,CAAC,CAAC,gBAAgB,WAAW,EAAE;AAEnD,OAAI,YAAY,EACd,uBAAsB,CAAC,KAAK;AAI9B,OACE,CAAC,aACD,qBACA,oBACA,WAAW,gBACX;AACA,WAAO,KACL;KAAE;KAAM,OAAO;KAAS,EACxB,yCACD;AACD,uBAAmB;;AAGrB,cAAW;;AAGb,SAAO,MACL;GACE,YAAY,WAAW,UAAU,CAAC;GAClC;GACA;GACD,EACD,8BACD;UACM,2BAA0B;AACjC,SAAO,MAAM,EAAE,KAAK,EAAE,0BAA0B;AAChD,QAAM,IAAI,kBAAkB,KAAK,SAAS;;AAG5C,QAAO,WAAW,UAAU;;AAG9B,SAAgB,cAAc,IAAgB;AAE5C,CADc,eAAe,CACvB,WAAW,GAAG"}
@@ -2,8 +2,21 @@ import { hiddenUnicodeCharactersRegex, toUnicodeEscape } from "./regex.js";
2
2
  import { logger } from "../logger/index.js";
3
3
 
4
4
  //#region lib/util/unicode.ts
5
+ function isBinaryContent(content) {
6
+ const sampleSize = Math.min(content.length, 8192);
7
+ for (let i = 0; i < sampleSize; i++) if (content[i] === 0) return true;
8
+ return false;
9
+ }
5
10
  function logWarningIfUnicodeHiddenCharactersInPackageFile(file, content) {
6
- const hiddenCharacters = content.toString("utf8").match(hiddenUnicodeCharactersRegex);
11
+ const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);
12
+ const hiddenCharacters = buffer.toString("utf8").match(hiddenUnicodeCharactersRegex);
13
+ if (isBinaryContent(buffer) && hiddenCharacters) {
14
+ logger.trace({
15
+ file,
16
+ hiddenCharacters: toUnicodeEscape(hiddenCharacters.join(""))
17
+ }, `Hidden Unicode characters discovered in file \`${file}\`, but not logging higher than TRACE as it appears to be a binary file`);
18
+ return;
19
+ }
7
20
  if (hiddenCharacters) if (hiddenCharacters.length === 1 && hiddenCharacters[0] === "") logger.once.trace({
8
21
  file,
9
22
  hiddenCharacters: toUnicodeEscape(hiddenCharacters.join(""))
@@ -1 +1 @@
1
- {"version":3,"file":"unicode.js","names":[],"sources":["../../lib/util/unicode.ts"],"sourcesContent":["import { logger } from '../logger/index.ts';\nimport { hiddenUnicodeCharactersRegex, toUnicodeEscape } from './regex.ts';\n\nexport function logWarningIfUnicodeHiddenCharactersInPackageFile(\n file: string,\n content: string | Buffer,\n): void {\n const hiddenCharacters = content\n .toString('utf8')\n .match(hiddenUnicodeCharactersRegex);\n if (hiddenCharacters) {\n if (hiddenCharacters.length === 1 && hiddenCharacters[0] === '\\uFEFF') {\n logger.once.trace(\n {\n file,\n hiddenCharacters: toUnicodeEscape(hiddenCharacters.join('')),\n },\n `Hidden Byte Order Mark (BOM) Unicode characters has been discovered in the file \\`${file}\\`. This is likely safe, if you are using Microsoft Windows, but please confirm that they are intended to be there, as they could be an attempt to \"smuggle\" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)`,\n );\n } else {\n logger.once.warn(\n {\n file,\n hiddenCharacters: toUnicodeEscape(hiddenCharacters.join('')),\n },\n `Hidden Unicode characters have been discovered in file(s) in your repository. See your Renovate logs for more details. Please confirm that they are intended to be there, as they could be an attempt to \"smuggle\" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)`,\n );\n }\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,iDACd,MACA,SACM;CACN,MAAM,mBAAmB,QACtB,SAAS,OAAO,CAChB,MAAM,6BAA6B;AACtC,KAAI,iBACF,KAAI,iBAAiB,WAAW,KAAK,iBAAiB,OAAO,IAC3D,QAAO,KAAK,MACV;EACE;EACA,kBAAkB,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;EAC7D,EACD,qFAAqF,KAAK,8PAC3F;KAED,QAAO,KAAK,KACV;EACE;EACA,kBAAkB,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;EAC7D,EACD,qTACD"}
1
+ {"version":3,"file":"unicode.js","names":[],"sources":["../../lib/util/unicode.ts"],"sourcesContent":["import { logger } from '../logger/index.ts';\nimport { hiddenUnicodeCharactersRegex, toUnicodeEscape } from './regex.ts';\n\nfunction isBinaryContent(content: Buffer): boolean {\n // Check for null bytes in the first 8KB - a common indicator of binary content\n const sampleSize = Math.min(content.length, 8192);\n for (let i = 0; i < sampleSize; i++) {\n if (content[i] === 0) {\n return true;\n }\n }\n return false;\n}\n\nexport function logWarningIfUnicodeHiddenCharactersInPackageFile(\n file: string,\n content: string | Buffer,\n): void {\n const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content);\n const hiddenCharacters = buffer\n .toString('utf8')\n .match(hiddenUnicodeCharactersRegex);\n\n if (isBinaryContent(buffer) && hiddenCharacters) {\n logger.trace(\n {\n file,\n hiddenCharacters: toUnicodeEscape(hiddenCharacters.join('')),\n },\n\n `Hidden Unicode characters discovered in file \\`${file}\\`, but not logging higher than TRACE as it appears to be a binary file`,\n );\n return;\n }\n\n if (hiddenCharacters) {\n if (hiddenCharacters.length === 1 && hiddenCharacters[0] === '\\uFEFF') {\n logger.once.trace(\n {\n file,\n hiddenCharacters: toUnicodeEscape(hiddenCharacters.join('')),\n },\n `Hidden Byte Order Mark (BOM) Unicode characters has been discovered in the file \\`${file}\\`. This is likely safe, if you are using Microsoft Windows, but please confirm that they are intended to be there, as they could be an attempt to \"smuggle\" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)`,\n );\n } else {\n logger.once.warn(\n {\n file,\n hiddenCharacters: toUnicodeEscape(hiddenCharacters.join('')),\n },\n `Hidden Unicode characters have been discovered in file(s) in your repository. See your Renovate logs for more details. Please confirm that they are intended to be there, as they could be an attempt to \"smuggle\" text into your codebase, or used to confuse tools like Renovate or Large Language Models (LLMs)`,\n );\n }\n }\n}\n"],"mappings":";;;;AAGA,SAAS,gBAAgB,SAA0B;CAEjD,MAAM,aAAa,KAAK,IAAI,QAAQ,QAAQ,KAAK;AACjD,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,IAC9B,KAAI,QAAQ,OAAO,EACjB,QAAO;AAGX,QAAO;;AAGT,SAAgB,iDACd,MACA,SACM;CACN,MAAM,SAAS,OAAO,SAAS,QAAQ,GAAG,UAAU,OAAO,KAAK,QAAQ;CACxE,MAAM,mBAAmB,OACtB,SAAS,OAAO,CAChB,MAAM,6BAA6B;AAEtC,KAAI,gBAAgB,OAAO,IAAI,kBAAkB;AAC/C,SAAO,MACL;GACE;GACA,kBAAkB,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;GAC7D,EAED,kDAAkD,KAAK,yEACxD;AACD;;AAGF,KAAI,iBACF,KAAI,iBAAiB,WAAW,KAAK,iBAAiB,OAAO,IAC3D,QAAO,KAAK,MACV;EACE;EACA,kBAAkB,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;EAC7D,EACD,qFAAqF,KAAK,8PAC3F;KAED,QAAO,KAAK,KACV;EACE;EACA,kBAAkB,gBAAgB,iBAAiB,KAAK,GAAG,CAAC;EAC7D,EACD,qTACD"}
@@ -2,6 +2,7 @@ import { regEx } from "../../../util/regex.js";
2
2
  import { titleCase } from "../../../util/string.js";
3
3
  import { logger } from "../../../logger/index.js";
4
4
  import { all } from "../../../util/promises.js";
5
+ import { instrument } from "../../../instrumentation/index.js";
5
6
  import { get } from "../../../modules/versioning/index.js";
6
7
  import { getDefaultVersioning } from "../../../modules/datasource/common.js";
7
8
  import { mergeChildConfig } from "../../../config/utils.js";
@@ -92,7 +93,10 @@ var Vulnerabilities = class Vulnerabilities {
92
93
  let packageName = dep.packageName ?? dep.depName;
93
94
  if (ecosystem === "PyPI") packageName = packageName.toLowerCase().replace(regEx(/[_.-]+/g), "-");
94
95
  try {
95
- const osvVulnerabilities = await this.osvOffline?.getVulnerabilities(ecosystem, packageName);
96
+ const osvVulnerabilities = await instrument("get OSV vulnerabilities", () => this.osvOffline?.getVulnerabilities(ecosystem, packageName), { attributes: {
97
+ packageName,
98
+ ecosystem
99
+ } });
96
100
  if (isNullOrUndefined(osvVulnerabilities) || isEmptyArray(osvVulnerabilities)) {
97
101
  logger.trace(`No vulnerabilities found in OSV database for ${packageName}`);
98
102
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"vulnerabilities.js","names":["p.all","getVersioning"],"sources":["../../../../lib/workers/repository/process/vulnerabilities.ts"],"sourcesContent":["// TODO #22198\nimport type { Ecosystem, Osv } from '@renovatebot/osv-offline';\nimport { OsvOffline } from '@renovatebot/osv-offline';\nimport {\n isEmptyArray,\n isNonEmptyString,\n isNullOrUndefined,\n isTruthy,\n} from '@sindresorhus/is';\nimport type { CvssVector } from 'ae-cvss-calculator';\nimport * as _aeCvss from 'ae-cvss-calculator';\nimport { z } from 'zod/v3';\nimport { getManagerConfig, mergeChildConfig } from '../../../config/index.ts';\nimport type { PackageRule, RenovateConfig } from '../../../config/types.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { getDefaultVersioning } from '../../../modules/datasource/common.ts';\nimport type {\n PackageDependency,\n PackageFile,\n} from '../../../modules/manager/types.ts';\nimport type { VersioningApi } from '../../../modules/versioning/index.ts';\nimport { get as getVersioning } from '../../../modules/versioning/index.ts';\nimport { sanitizeMarkdown } from '../../../util/markdown.ts';\nimport * as p from '../../../util/promises.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { titleCase } from '../../../util/string.ts';\nimport type {\n DependencyVulnerabilities,\n SeverityDetails,\n Vulnerability,\n} from './types.ts';\n\nconst { fromVector } = _aeCvss as unknown as typeof _aeCvss.default;\n\nexport class Vulnerabilities {\n private osvOffline: OsvOffline | undefined;\n\n private static readonly datasourceEcosystemMap: Record<\n string,\n Ecosystem | undefined\n > = {\n crate: 'crates.io',\n go: 'Go',\n hackage: 'Hackage',\n hex: 'Hex',\n maven: 'Maven',\n npm: 'npm',\n nuget: 'NuGet',\n packagist: 'Packagist',\n pypi: 'PyPI',\n rubygems: 'RubyGems',\n };\n\n private constructor() {\n // private constructor\n }\n\n private async initialize(): Promise<void> {\n this.osvOffline = await OsvOffline.create();\n }\n\n static async create(): Promise<Vulnerabilities> {\n const instance = new Vulnerabilities();\n await instance.initialize();\n return instance;\n }\n\n async appendVulnerabilityPackageRules(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<void> {\n const dependencyVulnerabilities = await this.fetchDependencyVulnerabilities(\n config,\n packageFiles,\n );\n\n config.packageRules ??= [];\n for (const {\n vulnerabilities,\n versioningApi,\n } of dependencyVulnerabilities) {\n const groupPackageRules: PackageRule[] = [];\n for (const vulnerability of vulnerabilities) {\n const rule = this.vulnerabilityToPackageRules(vulnerability);\n if (isNullOrUndefined(rule)) {\n continue;\n }\n groupPackageRules.push(rule);\n }\n this.sortByFixedVersion(groupPackageRules, versioningApi);\n\n config.packageRules.push(...groupPackageRules);\n }\n }\n\n async fetchVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<Vulnerability[]> {\n const groups = await this.fetchDependencyVulnerabilities(\n config,\n packageFiles,\n );\n return groups.flatMap((group) => group.vulnerabilities);\n }\n\n private async fetchDependencyVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<DependencyVulnerabilities[]> {\n const managers = Object.keys(packageFiles);\n const allManagerJobs = managers.map((manager) =>\n this.fetchManagerVulnerabilities(config, packageFiles, manager),\n );\n return (await Promise.all(allManagerJobs)).flat();\n }\n\n private async fetchManagerVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n manager: string,\n ): Promise<DependencyVulnerabilities[]> {\n const managerConfig = getManagerConfig(config, manager);\n const queue = packageFiles[manager].map(\n (pFile) => (): Promise<DependencyVulnerabilities[]> =>\n this.fetchManagerPackageFileVulnerabilities(managerConfig, pFile),\n );\n logger.trace(\n { manager, queueLength: queue.length },\n 'fetchManagerVulnerabilities starting',\n );\n const result = (await p.all(queue)).flat();\n logger.trace({ manager }, 'fetchManagerVulnerabilities finished');\n return result;\n }\n\n private async fetchManagerPackageFileVulnerabilities(\n managerConfig: RenovateConfig,\n pFile: PackageFile,\n ): Promise<DependencyVulnerabilities[]> {\n const { packageFile } = pFile;\n const packageFileConfig = mergeChildConfig(managerConfig, pFile);\n const { manager } = packageFileConfig;\n const queue = pFile.deps.map(\n (dep) => (): Promise<DependencyVulnerabilities | null> =>\n this.fetchDependencyVulnerability(packageFileConfig, dep),\n );\n logger.trace(\n { manager, packageFile, queueLength: queue.length },\n 'fetchManagerPackageFileVulnerabilities starting with concurrency',\n );\n\n const result = await p.all(queue);\n logger.trace(\n { packageFile },\n 'fetchManagerPackageFileVulnerabilities finished',\n );\n\n return result.filter(isTruthy);\n }\n\n private async fetchDependencyVulnerability(\n packageFileConfig: RenovateConfig & PackageFile,\n dep: PackageDependency,\n ): Promise<DependencyVulnerabilities | null> {\n const ecosystem = Vulnerabilities.datasourceEcosystemMap[dep.datasource!];\n if (!ecosystem) {\n logger.trace(`Cannot map datasource ${dep.datasource!} to OSV ecosystem`);\n return null;\n }\n\n let packageName = dep.packageName ?? dep.depName!;\n if (ecosystem === 'PyPI') {\n // https://peps.python.org/pep-0503/#normalized-names\n packageName = packageName.toLowerCase().replace(regEx(/[_.-]+/g), '-');\n }\n\n try {\n const osvVulnerabilities = await this.osvOffline?.getVulnerabilities(\n ecosystem,\n packageName,\n );\n if (\n isNullOrUndefined(osvVulnerabilities) ||\n isEmptyArray(osvVulnerabilities)\n ) {\n logger.trace(\n `No vulnerabilities found in OSV database for ${packageName}`,\n );\n return null;\n }\n\n const depVersion =\n dep.lockedVersion ?? dep.currentVersion ?? dep.currentValue!;\n\n const versioning = dep.versioning ?? getDefaultVersioning(dep.datasource);\n const versioningApi = getVersioning(versioning);\n\n if (!versioningApi.isVersion(depVersion)) {\n logger.debug(\n `Skipping vulnerability lookup for package ${packageName} due to unsupported version ${depVersion}`,\n );\n return null;\n }\n\n const vulnerabilities: Vulnerability[] = [];\n for (const osvVulnerability of osvVulnerabilities) {\n if (osvVulnerability.withdrawn) {\n logger.trace(\n `Skipping withdrawn vulnerability ${osvVulnerability.id}`,\n );\n continue;\n }\n\n for (const affected of osvVulnerability.affected ?? []) {\n const isVulnerable = this.isPackageVulnerable(\n ecosystem,\n packageName,\n depVersion,\n affected,\n versioningApi,\n );\n if (!isVulnerable) {\n continue;\n }\n\n logger.debug(\n `Vulnerability ${osvVulnerability.id} affects ${packageName} ${depVersion}`,\n );\n const fixedVersion = this.getFixedVersion(\n ecosystem,\n depVersion,\n affected,\n versioningApi,\n );\n\n vulnerabilities.push({\n packageName,\n vulnerability: osvVulnerability,\n affected,\n depVersion,\n fixedVersion,\n datasource: dep.datasource!,\n packageFileConfig,\n });\n }\n }\n\n return { vulnerabilities, versioningApi };\n } catch (err) {\n logger.warn(\n { err, packageName },\n 'Error fetching vulnerability information for package',\n );\n return null;\n }\n }\n\n private sortByFixedVersion(\n packageRules: PackageRule[],\n versioningApi: VersioningApi,\n ): void {\n const versionsCleaned: Record<string, string> = {};\n for (const rule of packageRules) {\n const version = rule.allowedVersions!;\n versionsCleaned[version] = version.replace(regEx(/[(),=> ]+/g), '');\n }\n packageRules.sort((a, b) =>\n versioningApi.sortVersions(\n versionsCleaned[a.allowedVersions!],\n versionsCleaned[b.allowedVersions!],\n ),\n );\n }\n\n // https://ossf.github.io/osv-schema/#affectedrangesevents-fields\n private sortEvents(\n events: Osv.Event[],\n versioningApi: VersioningApi,\n ): Osv.Event[] {\n const sortedCopy: Osv.Event[] = [];\n let zeroEvent: Osv.Event | null = null;\n\n for (const event of events) {\n if (event.introduced === '0') {\n zeroEvent = event;\n } else if (versioningApi.isVersion(Object.values(event)[0])) {\n sortedCopy.push(event);\n } else {\n logger.debug({ event }, 'Skipping OSV event with invalid version');\n }\n }\n\n sortedCopy.sort((a, b) =>\n // no pre-processing, as there are only very few values to sort\n versioningApi.sortVersions(Object.values(a)[0], Object.values(b)[0]),\n );\n\n if (zeroEvent) {\n sortedCopy.unshift(zeroEvent);\n }\n\n return sortedCopy;\n }\n\n private isPackageAffected(\n ecosystem: Ecosystem,\n packageName: string,\n affected: Osv.Affected,\n ): boolean {\n return (\n affected.package?.name === packageName &&\n affected.package?.ecosystem === ecosystem\n );\n }\n\n private includedInVersions(\n depVersion: string,\n affected: Osv.Affected,\n ): boolean {\n return !!affected.versions?.includes(depVersion);\n }\n\n private includedInRanges(\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): boolean {\n for (const range of affected.ranges ?? []) {\n if (range.type === 'GIT') {\n continue;\n }\n\n let vulnerable = false;\n for (const event of this.sortEvents(range.events, versioningApi)) {\n if (\n isNonEmptyString(event.introduced) &&\n (event.introduced === '0' ||\n this.isVersionGtOrEq(depVersion, event.introduced, versioningApi))\n ) {\n vulnerable = true;\n } else if (\n isNonEmptyString(event.fixed) &&\n this.isVersionGtOrEq(depVersion, event.fixed, versioningApi)\n ) {\n vulnerable = false;\n } else if (\n isNonEmptyString(event.last_affected) &&\n this.isVersionGt(depVersion, event.last_affected, versioningApi)\n ) {\n vulnerable = false;\n }\n }\n\n if (vulnerable) {\n return true;\n }\n }\n\n return false;\n }\n\n // https://ossf.github.io/osv-schema/#evaluation\n private isPackageVulnerable(\n ecosystem: Ecosystem,\n packageName: string,\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n this.isPackageAffected(ecosystem, packageName, affected) &&\n (this.includedInVersions(depVersion, affected) ||\n this.includedInRanges(depVersion, affected, versioningApi))\n );\n }\n\n private getFixedVersion(\n ecosystem: Ecosystem,\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): string | null {\n const fixedVersions: string[] = [];\n const lastAffectedVersions: string[] = [];\n\n for (const range of affected.ranges ?? []) {\n if (range.type === 'GIT') {\n continue;\n }\n\n for (const event of range.events) {\n if (\n isNonEmptyString(event.fixed) &&\n versioningApi.isVersion(event.fixed)\n ) {\n fixedVersions.push(event.fixed);\n } else if (\n isNonEmptyString(event.last_affected) &&\n versioningApi.isVersion(event.last_affected)\n ) {\n lastAffectedVersions.push(event.last_affected);\n }\n }\n }\n\n fixedVersions.sort((a, b) => versioningApi.sortVersions(a, b));\n const fixedVersion = fixedVersions.find((version) =>\n this.isVersionGt(version, depVersion, versioningApi),\n );\n if (fixedVersion) {\n return this.getFixedVersionByEcosystem(fixedVersion, ecosystem);\n }\n\n lastAffectedVersions.sort((a, b) => versioningApi.sortVersions(a, b));\n const lastAffected = lastAffectedVersions.find((version) =>\n this.isVersionGtOrEq(version, depVersion, versioningApi),\n );\n if (lastAffected) {\n return this.getLastAffectedByEcosystem(lastAffected, ecosystem);\n }\n\n return null;\n }\n\n private getFixedVersionByEcosystem(\n fixedVersion: string,\n ecosystem: Ecosystem,\n ): string {\n if (ecosystem === 'Maven' || ecosystem === 'NuGet') {\n return `[${fixedVersion},)`;\n }\n\n // crates.io, Go, Hex, npm, RubyGems, PyPI\n return `>= ${fixedVersion}`;\n }\n\n private getLastAffectedByEcosystem(\n lastAffected: string,\n ecosystem: Ecosystem,\n ): string {\n if (ecosystem === 'Maven') {\n return `(${lastAffected},)`;\n }\n\n // crates.io, Go, Hex, npm, RubyGems, PyPI\n return `> ${lastAffected}`;\n }\n\n private isVersionGt(\n version: string,\n other: string,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n versioningApi.isVersion(version) &&\n versioningApi.isVersion(other) &&\n versioningApi.isGreaterThan(version, other)\n );\n }\n\n private isVersionGtOrEq(\n version: string,\n other: string,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n versioningApi.isVersion(version) &&\n versioningApi.isVersion(other) &&\n (versioningApi.equals(version, other) ||\n versioningApi.isGreaterThan(version, other))\n );\n }\n\n private vulnerabilityToPackageRules(vul: Vulnerability): PackageRule | null {\n const {\n vulnerability,\n affected,\n packageName,\n depVersion,\n fixedVersion,\n datasource,\n packageFileConfig,\n } = vul;\n if (isNullOrUndefined(fixedVersion)) {\n logger.debug(\n `No fixed version available for vulnerability ${vulnerability.id} in ${packageName} ${depVersion}`,\n );\n return null;\n }\n\n logger.debug(\n `Setting allowed version ${fixedVersion} to fix vulnerability ${vulnerability.id} in ${packageName} ${depVersion}`,\n );\n\n const severityDetails = this.extractSeverityDetails(\n vulnerability,\n affected,\n );\n\n return {\n matchDatasources: [datasource],\n matchPackageNames: [packageName],\n matchCurrentVersion: depVersion,\n allowedVersions: fixedVersion,\n isVulnerabilityAlert: true,\n vulnerabilitySeverity: severityDetails.severityLevel,\n prBodyNotes: this.generatePrBodyNotes(vulnerability, affected),\n force: {\n ...packageFileConfig.vulnerabilityAlerts,\n },\n };\n }\n\n static evaluateCvssVector(vector: string): [string, string] {\n const CvssJsonSchema = z.object({\n baseScore: z.number().default(0.0),\n baseSeverity: z.string().toUpperCase().default('UNKNOWN'),\n });\n\n try {\n const parsedCvssScore: CvssVector<any> | null = fromVector(vector);\n const res = CvssJsonSchema.parse(parsedCvssScore?.createJsonSchema());\n\n return [res.baseScore.toFixed(1), res.baseSeverity];\n } catch {\n logger.debug(`Error processing CVSS vector ${vector}`);\n }\n\n return ['', ''];\n }\n\n private generatePrBodyNotes(\n vulnerability: Osv.Vulnerability,\n affected: Osv.Affected,\n ): string[] {\n let aliases = [vulnerability.id].concat(vulnerability.aliases ?? []).sort();\n aliases = aliases.map((id) => {\n if (id.startsWith('CVE-')) {\n return `[${id}](https://nvd.nist.gov/vuln/detail/${id})`;\n } else if (id.startsWith('GHSA-')) {\n return `[${id}](https://github.com/advisories/${id})`;\n } else if (id.startsWith('GO-')) {\n return `[${id}](https://pkg.go.dev/vuln/${id})`;\n } else if (id.startsWith('RUSTSEC-')) {\n return `[${id}](https://rustsec.org/advisories/${id}.html)`;\n }\n\n return id;\n });\n\n let content = '\\n\\n---\\n\\n### ';\n content += vulnerability.summary ? `${vulnerability.summary}\\n` : '';\n content += `${aliases.join(' / ')}\\n`;\n content += `\\n<details>\\n<summary>More information</summary>\\n`;\n\n const details = vulnerability.details?.replace(\n regEx(/^#{1,4} /gm),\n '##### ',\n );\n content += `#### Details\\n${details ?? 'No details.'}\\n`;\n\n content += '#### Severity\\n';\n const severityDetails = this.extractSeverityDetails(\n vulnerability,\n affected,\n );\n\n if (severityDetails.cvssVector) {\n content += `- CVSS Score: ${severityDetails.score}\\n`;\n content += `- Vector String: \\`${severityDetails.cvssVector}\\`\\n`;\n } else {\n content += `${titleCase(severityDetails.severityLevel)}\\n`;\n }\n\n content += `\\n#### References\\n${\n vulnerability.references\n ?.map((ref) => {\n return `- [${ref.url}](${ref.url})`;\n })\n .join('\\n') ?? 'No references.'\n }`;\n\n let attribution = '';\n if (vulnerability.id.startsWith('GHSA-')) {\n attribution = ` and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md))`;\n } else if (vulnerability.id.startsWith('GO-')) {\n attribution = ` and the [Go Vulnerability Database](https://github.com/golang/vulndb) ([CC-BY 4.0](https://github.com/golang/vulndb#license))`;\n } else if (vulnerability.id.startsWith('PYSEC-')) {\n attribution = ` and the [PyPI Advisory Database](https://github.com/pypa/advisory-database) ([CC-BY 4.0](https://github.com/pypa/advisory-database/blob/main/LICENSE))`;\n } else if (vulnerability.id.startsWith('RUSTSEC-')) {\n attribution = ` and the [Rust Advisory Database](https://github.com/RustSec/advisory-db) ([CC0 1.0](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt))`;\n }\n content += `\\n\\nThis data is provided by [OSV](https://osv.dev/vulnerability/${vulnerability.id})${attribution}.\\n`;\n content += `</details>`;\n\n return [sanitizeMarkdown(content)];\n }\n\n private extractSeverityDetails(\n vulnerability: Osv.Vulnerability,\n affected: Osv.Affected,\n ): SeverityDetails {\n let severityLevel = 'UNKNOWN';\n let score = 'Unknown';\n\n const cvssVector =\n vulnerability.severity?.find((e) => e.type === 'CVSS_V4')?.score ??\n vulnerability.severity?.find((e) => e.type === 'CVSS_V3')?.score ??\n (affected.database_specific?.cvss as string); // RUSTSEC\n\n if (cvssVector) {\n const [baseScore, severity] =\n Vulnerabilities.evaluateCvssVector(cvssVector);\n severityLevel = severity ? severity.toUpperCase() : 'UNKNOWN';\n score = baseScore\n ? `${baseScore} / 10 (${titleCase(severityLevel)})`\n : 'Unknown';\n } else if (\n vulnerability.id.startsWith('GHSA-') &&\n vulnerability.database_specific?.severity\n ) {\n const severity = vulnerability.database_specific.severity as string;\n severityLevel = severity.toUpperCase();\n }\n\n return {\n cvssVector,\n score,\n severityLevel,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgCA,MAAM,EAAE,eAAe;AAEvB,IAAa,kBAAb,MAAa,gBAAgB;CAC3B,AAAQ;CAER,OAAwB,yBAGpB;EACF,OAAO;EACP,IAAI;EACJ,SAAS;EACT,KAAK;EACL,OAAO;EACP,KAAK;EACL,OAAO;EACP,WAAW;EACX,MAAM;EACN,UAAU;EACX;CAED,AAAQ,cAAc;CAItB,MAAc,aAA4B;AACxC,OAAK,aAAa,MAAM,WAAW,QAAQ;;CAG7C,aAAa,SAAmC;EAC9C,MAAM,WAAW,IAAI,iBAAiB;AACtC,QAAM,SAAS,YAAY;AAC3B,SAAO;;CAGT,MAAM,gCACJ,QACA,cACe;EACf,MAAM,4BAA4B,MAAM,KAAK,+BAC3C,QACA,aACD;AAED,SAAO,iBAAiB,EAAE;AAC1B,OAAK,MAAM,EACT,iBACA,mBACG,2BAA2B;GAC9B,MAAM,oBAAmC,EAAE;AAC3C,QAAK,MAAM,iBAAiB,iBAAiB;IAC3C,MAAM,OAAO,KAAK,4BAA4B,cAAc;AAC5D,QAAI,kBAAkB,KAAK,CACzB;AAEF,sBAAkB,KAAK,KAAK;;AAE9B,QAAK,mBAAmB,mBAAmB,cAAc;AAEzD,UAAO,aAAa,KAAK,GAAG,kBAAkB;;;CAIlD,MAAM,qBACJ,QACA,cAC0B;AAK1B,UAJe,MAAM,KAAK,+BACxB,QACA,aACD,EACa,SAAS,UAAU,MAAM,gBAAgB;;CAGzD,MAAc,+BACZ,QACA,cACsC;EAEtC,MAAM,iBADW,OAAO,KAAK,aAAa,CACV,KAAK,YACnC,KAAK,4BAA4B,QAAQ,cAAc,QAAQ,CAChE;AACD,UAAQ,MAAM,QAAQ,IAAI,eAAe,EAAE,MAAM;;CAGnD,MAAc,4BACZ,QACA,cACA,SACsC;EACtC,MAAM,gBAAgB,iBAAiB,QAAQ,QAAQ;EACvD,MAAM,QAAQ,aAAa,SAAS,KACjC,gBACC,KAAK,uCAAuC,eAAe,MAAM,CACpE;AACD,SAAO,MACL;GAAE;GAAS,aAAa,MAAM;GAAQ,EACtC,uCACD;EACD,MAAM,UAAU,MAAMA,IAAM,MAAM,EAAE,MAAM;AAC1C,SAAO,MAAM,EAAE,SAAS,EAAE,uCAAuC;AACjE,SAAO;;CAGT,MAAc,uCACZ,eACA,OACsC;EACtC,MAAM,EAAE,gBAAgB;EACxB,MAAM,oBAAoB,iBAAiB,eAAe,MAAM;EAChE,MAAM,EAAE,YAAY;EACpB,MAAM,QAAQ,MAAM,KAAK,KACtB,cACC,KAAK,6BAA6B,mBAAmB,IAAI,CAC5D;AACD,SAAO,MACL;GAAE;GAAS;GAAa,aAAa,MAAM;GAAQ,EACnD,mEACD;EAED,MAAM,SAAS,MAAMA,IAAM,MAAM;AACjC,SAAO,MACL,EAAE,aAAa,EACf,kDACD;AAED,SAAO,OAAO,OAAO,SAAS;;CAGhC,MAAc,6BACZ,mBACA,KAC2C;EAC3C,MAAM,YAAY,gBAAgB,uBAAuB,IAAI;AAC7D,MAAI,CAAC,WAAW;AACd,UAAO,MAAM,yBAAyB,IAAI,WAAY,mBAAmB;AACzE,UAAO;;EAGT,IAAI,cAAc,IAAI,eAAe,IAAI;AACzC,MAAI,cAAc,OAEhB,eAAc,YAAY,aAAa,CAAC,QAAQ,MAAM,UAAU,EAAE,IAAI;AAGxE,MAAI;GACF,MAAM,qBAAqB,MAAM,KAAK,YAAY,mBAChD,WACA,YACD;AACD,OACE,kBAAkB,mBAAmB,IACrC,aAAa,mBAAmB,EAChC;AACA,WAAO,MACL,gDAAgD,cACjD;AACD,WAAO;;GAGT,MAAM,aACJ,IAAI,iBAAiB,IAAI,kBAAkB,IAAI;GAGjD,MAAM,gBAAgBC,IADH,IAAI,cAAc,qBAAqB,IAAI,WAAW,CAC1B;AAE/C,OAAI,CAAC,cAAc,UAAU,WAAW,EAAE;AACxC,WAAO,MACL,6CAA6C,YAAY,8BAA8B,aACxF;AACD,WAAO;;GAGT,MAAM,kBAAmC,EAAE;AAC3C,QAAK,MAAM,oBAAoB,oBAAoB;AACjD,QAAI,iBAAiB,WAAW;AAC9B,YAAO,MACL,oCAAoC,iBAAiB,KACtD;AACD;;AAGF,SAAK,MAAM,YAAY,iBAAiB,YAAY,EAAE,EAAE;AAQtD,SAAI,CAPiB,KAAK,oBACxB,WACA,aACA,YACA,UACA,cACD,CAEC;AAGF,YAAO,MACL,iBAAiB,iBAAiB,GAAG,WAAW,YAAY,GAAG,aAChE;KACD,MAAM,eAAe,KAAK,gBACxB,WACA,YACA,UACA,cACD;AAED,qBAAgB,KAAK;MACnB;MACA,eAAe;MACf;MACA;MACA;MACA,YAAY,IAAI;MAChB;MACD,CAAC;;;AAIN,UAAO;IAAE;IAAiB;IAAe;WAClC,KAAK;AACZ,UAAO,KACL;IAAE;IAAK;IAAa,EACpB,uDACD;AACD,UAAO;;;CAIX,AAAQ,mBACN,cACA,eACM;EACN,MAAM,kBAA0C,EAAE;AAClD,OAAK,MAAM,QAAQ,cAAc;GAC/B,MAAM,UAAU,KAAK;AACrB,mBAAgB,WAAW,QAAQ,QAAQ,MAAM,aAAa,EAAE,GAAG;;AAErE,eAAa,MAAM,GAAG,MACpB,cAAc,aACZ,gBAAgB,EAAE,kBAClB,gBAAgB,EAAE,iBACnB,CACF;;CAIH,AAAQ,WACN,QACA,eACa;EACb,MAAM,aAA0B,EAAE;EAClC,IAAI,YAA8B;AAElC,OAAK,MAAM,SAAS,OAClB,KAAI,MAAM,eAAe,IACvB,aAAY;WACH,cAAc,UAAU,OAAO,OAAO,MAAM,CAAC,GAAG,CACzD,YAAW,KAAK,MAAM;MAEtB,QAAO,MAAM,EAAE,OAAO,EAAE,0CAA0C;AAItE,aAAW,MAAM,GAAG,MAElB,cAAc,aAAa,OAAO,OAAO,EAAE,CAAC,IAAI,OAAO,OAAO,EAAE,CAAC,GAAG,CACrE;AAED,MAAI,UACF,YAAW,QAAQ,UAAU;AAG/B,SAAO;;CAGT,AAAQ,kBACN,WACA,aACA,UACS;AACT,SACE,SAAS,SAAS,SAAS,eAC3B,SAAS,SAAS,cAAc;;CAIpC,AAAQ,mBACN,YACA,UACS;AACT,SAAO,CAAC,CAAC,SAAS,UAAU,SAAS,WAAW;;CAGlD,AAAQ,iBACN,YACA,UACA,eACS;AACT,OAAK,MAAM,SAAS,SAAS,UAAU,EAAE,EAAE;AACzC,OAAI,MAAM,SAAS,MACjB;GAGF,IAAI,aAAa;AACjB,QAAK,MAAM,SAAS,KAAK,WAAW,MAAM,QAAQ,cAAc,CAC9D,KACE,iBAAiB,MAAM,WAAW,KACjC,MAAM,eAAe,OACpB,KAAK,gBAAgB,YAAY,MAAM,YAAY,cAAc,EAEnE,cAAa;YAEb,iBAAiB,MAAM,MAAM,IAC7B,KAAK,gBAAgB,YAAY,MAAM,OAAO,cAAc,CAE5D,cAAa;YAEb,iBAAiB,MAAM,cAAc,IACrC,KAAK,YAAY,YAAY,MAAM,eAAe,cAAc,CAEhE,cAAa;AAIjB,OAAI,WACF,QAAO;;AAIX,SAAO;;CAIT,AAAQ,oBACN,WACA,aACA,YACA,UACA,eACS;AACT,SACE,KAAK,kBAAkB,WAAW,aAAa,SAAS,KACvD,KAAK,mBAAmB,YAAY,SAAS,IAC5C,KAAK,iBAAiB,YAAY,UAAU,cAAc;;CAIhE,AAAQ,gBACN,WACA,YACA,UACA,eACe;EACf,MAAM,gBAA0B,EAAE;EAClC,MAAM,uBAAiC,EAAE;AAEzC,OAAK,MAAM,SAAS,SAAS,UAAU,EAAE,EAAE;AACzC,OAAI,MAAM,SAAS,MACjB;AAGF,QAAK,MAAM,SAAS,MAAM,OACxB,KACE,iBAAiB,MAAM,MAAM,IAC7B,cAAc,UAAU,MAAM,MAAM,CAEpC,eAAc,KAAK,MAAM,MAAM;YAE/B,iBAAiB,MAAM,cAAc,IACrC,cAAc,UAAU,MAAM,cAAc,CAE5C,sBAAqB,KAAK,MAAM,cAAc;;AAKpD,gBAAc,MAAM,GAAG,MAAM,cAAc,aAAa,GAAG,EAAE,CAAC;EAC9D,MAAM,eAAe,cAAc,MAAM,YACvC,KAAK,YAAY,SAAS,YAAY,cAAc,CACrD;AACD,MAAI,aACF,QAAO,KAAK,2BAA2B,cAAc,UAAU;AAGjE,uBAAqB,MAAM,GAAG,MAAM,cAAc,aAAa,GAAG,EAAE,CAAC;EACrE,MAAM,eAAe,qBAAqB,MAAM,YAC9C,KAAK,gBAAgB,SAAS,YAAY,cAAc,CACzD;AACD,MAAI,aACF,QAAO,KAAK,2BAA2B,cAAc,UAAU;AAGjE,SAAO;;CAGT,AAAQ,2BACN,cACA,WACQ;AACR,MAAI,cAAc,WAAW,cAAc,QACzC,QAAO,IAAI,aAAa;AAI1B,SAAO,MAAM;;CAGf,AAAQ,2BACN,cACA,WACQ;AACR,MAAI,cAAc,QAChB,QAAO,IAAI,aAAa;AAI1B,SAAO,KAAK;;CAGd,AAAQ,YACN,SACA,OACA,eACS;AACT,SACE,cAAc,UAAU,QAAQ,IAChC,cAAc,UAAU,MAAM,IAC9B,cAAc,cAAc,SAAS,MAAM;;CAI/C,AAAQ,gBACN,SACA,OACA,eACS;AACT,SACE,cAAc,UAAU,QAAQ,IAChC,cAAc,UAAU,MAAM,KAC7B,cAAc,OAAO,SAAS,MAAM,IACnC,cAAc,cAAc,SAAS,MAAM;;CAIjD,AAAQ,4BAA4B,KAAwC;EAC1E,MAAM,EACJ,eACA,UACA,aACA,YACA,cACA,YACA,sBACE;AACJ,MAAI,kBAAkB,aAAa,EAAE;AACnC,UAAO,MACL,gDAAgD,cAAc,GAAG,MAAM,YAAY,GAAG,aACvF;AACD,UAAO;;AAGT,SAAO,MACL,2BAA2B,aAAa,wBAAwB,cAAc,GAAG,MAAM,YAAY,GAAG,aACvG;EAED,MAAM,kBAAkB,KAAK,uBAC3B,eACA,SACD;AAED,SAAO;GACL,kBAAkB,CAAC,WAAW;GAC9B,mBAAmB,CAAC,YAAY;GAChC,qBAAqB;GACrB,iBAAiB;GACjB,sBAAsB;GACtB,uBAAuB,gBAAgB;GACvC,aAAa,KAAK,oBAAoB,eAAe,SAAS;GAC9D,OAAO,EACL,GAAG,kBAAkB,qBACtB;GACF;;CAGH,OAAO,mBAAmB,QAAkC;EAC1D,MAAM,iBAAiB,EAAE,OAAO;GAC9B,WAAW,EAAE,QAAQ,CAAC,QAAQ,EAAI;GAClC,cAAc,EAAE,QAAQ,CAAC,aAAa,CAAC,QAAQ,UAAU;GAC1D,CAAC;AAEF,MAAI;GACF,MAAM,kBAA0C,WAAW,OAAO;GAClE,MAAM,MAAM,eAAe,MAAM,iBAAiB,kBAAkB,CAAC;AAErE,UAAO,CAAC,IAAI,UAAU,QAAQ,EAAE,EAAE,IAAI,aAAa;UAC7C;AACN,UAAO,MAAM,gCAAgC,SAAS;;AAGxD,SAAO,CAAC,IAAI,GAAG;;CAGjB,AAAQ,oBACN,eACA,UACU;EACV,IAAI,UAAU,CAAC,cAAc,GAAG,CAAC,OAAO,cAAc,WAAW,EAAE,CAAC,CAAC,MAAM;AAC3E,YAAU,QAAQ,KAAK,OAAO;AAC5B,OAAI,GAAG,WAAW,OAAO,CACvB,QAAO,IAAI,GAAG,qCAAqC,GAAG;YAC7C,GAAG,WAAW,QAAQ,CAC/B,QAAO,IAAI,GAAG,kCAAkC,GAAG;YAC1C,GAAG,WAAW,MAAM,CAC7B,QAAO,IAAI,GAAG,4BAA4B,GAAG;YACpC,GAAG,WAAW,WAAW,CAClC,QAAO,IAAI,GAAG,mCAAmC,GAAG;AAGtD,UAAO;IACP;EAEF,IAAI,UAAU;AACd,aAAW,cAAc,UAAU,GAAG,cAAc,QAAQ,MAAM;AAClE,aAAW,GAAG,QAAQ,KAAK,MAAM,CAAC;AAClC,aAAW;EAEX,MAAM,UAAU,cAAc,SAAS,QACrC,MAAM,aAAa,EACnB,SACD;AACD,aAAW,iBAAiB,WAAW,cAAc;AAErD,aAAW;EACX,MAAM,kBAAkB,KAAK,uBAC3B,eACA,SACD;AAED,MAAI,gBAAgB,YAAY;AAC9B,cAAW,iBAAiB,gBAAgB,MAAM;AAClD,cAAW,sBAAsB,gBAAgB,WAAW;QAE5D,YAAW,GAAG,UAAU,gBAAgB,cAAc,CAAC;AAGzD,aAAW,sBACT,cAAc,YACV,KAAK,QAAQ;AACb,UAAO,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI;IACjC,CACD,KAAK,KAAK,IAAI;EAGnB,IAAI,cAAc;AAClB,MAAI,cAAc,GAAG,WAAW,QAAQ,CACtC,eAAc;WACL,cAAc,GAAG,WAAW,MAAM,CAC3C,eAAc;WACL,cAAc,GAAG,WAAW,SAAS,CAC9C,eAAc;WACL,cAAc,GAAG,WAAW,WAAW,CAChD,eAAc;AAEhB,aAAW,oEAAoE,cAAc,GAAG,GAAG,YAAY;AAC/G,aAAW;AAEX,SAAO,CAAC,iBAAiB,QAAQ,CAAC;;CAGpC,AAAQ,uBACN,eACA,UACiB;EACjB,IAAI,gBAAgB;EACpB,IAAI,QAAQ;EAEZ,MAAM,aACJ,cAAc,UAAU,MAAM,MAAM,EAAE,SAAS,UAAU,EAAE,SAC3D,cAAc,UAAU,MAAM,MAAM,EAAE,SAAS,UAAU,EAAE,SAC1D,SAAS,mBAAmB;AAE/B,MAAI,YAAY;GACd,MAAM,CAAC,WAAW,YAChB,gBAAgB,mBAAmB,WAAW;AAChD,mBAAgB,WAAW,SAAS,aAAa,GAAG;AACpD,WAAQ,YACJ,GAAG,UAAU,SAAS,UAAU,cAAc,CAAC,KAC/C;aAEJ,cAAc,GAAG,WAAW,QAAQ,IACpC,cAAc,mBAAmB,SAGjC,iBADiB,cAAc,kBAAkB,SACxB,aAAa;AAGxC,SAAO;GACL;GACA;GACA;GACD"}
1
+ {"version":3,"file":"vulnerabilities.js","names":["p.all","getVersioning"],"sources":["../../../../lib/workers/repository/process/vulnerabilities.ts"],"sourcesContent":["// TODO #22198\nimport type { Ecosystem, Osv } from '@renovatebot/osv-offline';\nimport { OsvOffline } from '@renovatebot/osv-offline';\nimport {\n isEmptyArray,\n isNonEmptyString,\n isNullOrUndefined,\n isTruthy,\n} from '@sindresorhus/is';\nimport type { CvssVector } from 'ae-cvss-calculator';\nimport * as _aeCvss from 'ae-cvss-calculator';\nimport { z } from 'zod/v3';\nimport { getManagerConfig, mergeChildConfig } from '../../../config/index.ts';\nimport type { PackageRule, RenovateConfig } from '../../../config/types.ts';\nimport { instrument } from '../../../instrumentation/index.ts';\nimport { logger } from '../../../logger/index.ts';\nimport { getDefaultVersioning } from '../../../modules/datasource/common.ts';\nimport type {\n PackageDependency,\n PackageFile,\n} from '../../../modules/manager/types.ts';\nimport type { VersioningApi } from '../../../modules/versioning/index.ts';\nimport { get as getVersioning } from '../../../modules/versioning/index.ts';\nimport { sanitizeMarkdown } from '../../../util/markdown.ts';\nimport * as p from '../../../util/promises.ts';\nimport { regEx } from '../../../util/regex.ts';\nimport { titleCase } from '../../../util/string.ts';\nimport type {\n DependencyVulnerabilities,\n SeverityDetails,\n Vulnerability,\n} from './types.ts';\n\nconst { fromVector } = _aeCvss as unknown as typeof _aeCvss.default;\n\nexport class Vulnerabilities {\n private osvOffline: OsvOffline | undefined;\n\n private static readonly datasourceEcosystemMap: Record<\n string,\n Ecosystem | undefined\n > = {\n crate: 'crates.io',\n go: 'Go',\n hackage: 'Hackage',\n hex: 'Hex',\n maven: 'Maven',\n npm: 'npm',\n nuget: 'NuGet',\n packagist: 'Packagist',\n pypi: 'PyPI',\n rubygems: 'RubyGems',\n };\n\n private constructor() {\n // private constructor\n }\n\n private async initialize(): Promise<void> {\n this.osvOffline = await OsvOffline.create();\n }\n\n static async create(): Promise<Vulnerabilities> {\n const instance = new Vulnerabilities();\n await instance.initialize();\n return instance;\n }\n\n async appendVulnerabilityPackageRules(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<void> {\n const dependencyVulnerabilities = await this.fetchDependencyVulnerabilities(\n config,\n packageFiles,\n );\n\n config.packageRules ??= [];\n for (const {\n vulnerabilities,\n versioningApi,\n } of dependencyVulnerabilities) {\n const groupPackageRules: PackageRule[] = [];\n for (const vulnerability of vulnerabilities) {\n const rule = this.vulnerabilityToPackageRules(vulnerability);\n if (isNullOrUndefined(rule)) {\n continue;\n }\n groupPackageRules.push(rule);\n }\n this.sortByFixedVersion(groupPackageRules, versioningApi);\n\n config.packageRules.push(...groupPackageRules);\n }\n }\n\n async fetchVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<Vulnerability[]> {\n const groups = await this.fetchDependencyVulnerabilities(\n config,\n packageFiles,\n );\n return groups.flatMap((group) => group.vulnerabilities);\n }\n\n private async fetchDependencyVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n ): Promise<DependencyVulnerabilities[]> {\n const managers = Object.keys(packageFiles);\n const allManagerJobs = managers.map((manager) =>\n this.fetchManagerVulnerabilities(config, packageFiles, manager),\n );\n return (await Promise.all(allManagerJobs)).flat();\n }\n\n private async fetchManagerVulnerabilities(\n config: RenovateConfig,\n packageFiles: Record<string, PackageFile[]>,\n manager: string,\n ): Promise<DependencyVulnerabilities[]> {\n const managerConfig = getManagerConfig(config, manager);\n const queue = packageFiles[manager].map(\n (pFile) => (): Promise<DependencyVulnerabilities[]> =>\n this.fetchManagerPackageFileVulnerabilities(managerConfig, pFile),\n );\n logger.trace(\n { manager, queueLength: queue.length },\n 'fetchManagerVulnerabilities starting',\n );\n const result = (await p.all(queue)).flat();\n logger.trace({ manager }, 'fetchManagerVulnerabilities finished');\n return result;\n }\n\n private async fetchManagerPackageFileVulnerabilities(\n managerConfig: RenovateConfig,\n pFile: PackageFile,\n ): Promise<DependencyVulnerabilities[]> {\n const { packageFile } = pFile;\n const packageFileConfig = mergeChildConfig(managerConfig, pFile);\n const { manager } = packageFileConfig;\n const queue = pFile.deps.map(\n (dep) => (): Promise<DependencyVulnerabilities | null> =>\n this.fetchDependencyVulnerability(packageFileConfig, dep),\n );\n logger.trace(\n { manager, packageFile, queueLength: queue.length },\n 'fetchManagerPackageFileVulnerabilities starting with concurrency',\n );\n\n const result = await p.all(queue);\n logger.trace(\n { packageFile },\n 'fetchManagerPackageFileVulnerabilities finished',\n );\n\n return result.filter(isTruthy);\n }\n\n private async fetchDependencyVulnerability(\n packageFileConfig: RenovateConfig & PackageFile,\n dep: PackageDependency,\n ): Promise<DependencyVulnerabilities | null> {\n const ecosystem = Vulnerabilities.datasourceEcosystemMap[dep.datasource!];\n if (!ecosystem) {\n logger.trace(`Cannot map datasource ${dep.datasource!} to OSV ecosystem`);\n return null;\n }\n\n let packageName = dep.packageName ?? dep.depName!;\n if (ecosystem === 'PyPI') {\n // https://peps.python.org/pep-0503/#normalized-names\n packageName = packageName.toLowerCase().replace(regEx(/[_.-]+/g), '-');\n }\n\n try {\n const osvVulnerabilities = await instrument(\n 'get OSV vulnerabilities',\n () => this.osvOffline?.getVulnerabilities(ecosystem, packageName),\n {\n attributes: {\n packageName,\n ecosystem,\n },\n },\n );\n if (\n isNullOrUndefined(osvVulnerabilities) ||\n isEmptyArray(osvVulnerabilities)\n ) {\n logger.trace(\n `No vulnerabilities found in OSV database for ${packageName}`,\n );\n return null;\n }\n\n const depVersion =\n dep.lockedVersion ?? dep.currentVersion ?? dep.currentValue!;\n\n const versioning = dep.versioning ?? getDefaultVersioning(dep.datasource);\n const versioningApi = getVersioning(versioning);\n\n if (!versioningApi.isVersion(depVersion)) {\n logger.debug(\n `Skipping vulnerability lookup for package ${packageName} due to unsupported version ${depVersion}`,\n );\n return null;\n }\n\n const vulnerabilities: Vulnerability[] = [];\n for (const osvVulnerability of osvVulnerabilities) {\n if (osvVulnerability.withdrawn) {\n logger.trace(\n `Skipping withdrawn vulnerability ${osvVulnerability.id}`,\n );\n continue;\n }\n\n for (const affected of osvVulnerability.affected ?? []) {\n const isVulnerable = this.isPackageVulnerable(\n ecosystem,\n packageName,\n depVersion,\n affected,\n versioningApi,\n );\n if (!isVulnerable) {\n continue;\n }\n\n logger.debug(\n `Vulnerability ${osvVulnerability.id} affects ${packageName} ${depVersion}`,\n );\n const fixedVersion = this.getFixedVersion(\n ecosystem,\n depVersion,\n affected,\n versioningApi,\n );\n\n vulnerabilities.push({\n packageName,\n vulnerability: osvVulnerability,\n affected,\n depVersion,\n fixedVersion,\n datasource: dep.datasource!,\n packageFileConfig,\n });\n }\n }\n\n return { vulnerabilities, versioningApi };\n } catch (err) {\n logger.warn(\n { err, packageName },\n 'Error fetching vulnerability information for package',\n );\n return null;\n }\n }\n\n private sortByFixedVersion(\n packageRules: PackageRule[],\n versioningApi: VersioningApi,\n ): void {\n const versionsCleaned: Record<string, string> = {};\n for (const rule of packageRules) {\n const version = rule.allowedVersions!;\n versionsCleaned[version] = version.replace(regEx(/[(),=> ]+/g), '');\n }\n packageRules.sort((a, b) =>\n versioningApi.sortVersions(\n versionsCleaned[a.allowedVersions!],\n versionsCleaned[b.allowedVersions!],\n ),\n );\n }\n\n // https://ossf.github.io/osv-schema/#affectedrangesevents-fields\n private sortEvents(\n events: Osv.Event[],\n versioningApi: VersioningApi,\n ): Osv.Event[] {\n const sortedCopy: Osv.Event[] = [];\n let zeroEvent: Osv.Event | null = null;\n\n for (const event of events) {\n if (event.introduced === '0') {\n zeroEvent = event;\n } else if (versioningApi.isVersion(Object.values(event)[0])) {\n sortedCopy.push(event);\n } else {\n logger.debug({ event }, 'Skipping OSV event with invalid version');\n }\n }\n\n sortedCopy.sort((a, b) =>\n // no pre-processing, as there are only very few values to sort\n versioningApi.sortVersions(Object.values(a)[0], Object.values(b)[0]),\n );\n\n if (zeroEvent) {\n sortedCopy.unshift(zeroEvent);\n }\n\n return sortedCopy;\n }\n\n private isPackageAffected(\n ecosystem: Ecosystem,\n packageName: string,\n affected: Osv.Affected,\n ): boolean {\n return (\n affected.package?.name === packageName &&\n affected.package?.ecosystem === ecosystem\n );\n }\n\n private includedInVersions(\n depVersion: string,\n affected: Osv.Affected,\n ): boolean {\n return !!affected.versions?.includes(depVersion);\n }\n\n private includedInRanges(\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): boolean {\n for (const range of affected.ranges ?? []) {\n if (range.type === 'GIT') {\n continue;\n }\n\n let vulnerable = false;\n for (const event of this.sortEvents(range.events, versioningApi)) {\n if (\n isNonEmptyString(event.introduced) &&\n (event.introduced === '0' ||\n this.isVersionGtOrEq(depVersion, event.introduced, versioningApi))\n ) {\n vulnerable = true;\n } else if (\n isNonEmptyString(event.fixed) &&\n this.isVersionGtOrEq(depVersion, event.fixed, versioningApi)\n ) {\n vulnerable = false;\n } else if (\n isNonEmptyString(event.last_affected) &&\n this.isVersionGt(depVersion, event.last_affected, versioningApi)\n ) {\n vulnerable = false;\n }\n }\n\n if (vulnerable) {\n return true;\n }\n }\n\n return false;\n }\n\n // https://ossf.github.io/osv-schema/#evaluation\n private isPackageVulnerable(\n ecosystem: Ecosystem,\n packageName: string,\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n this.isPackageAffected(ecosystem, packageName, affected) &&\n (this.includedInVersions(depVersion, affected) ||\n this.includedInRanges(depVersion, affected, versioningApi))\n );\n }\n\n private getFixedVersion(\n ecosystem: Ecosystem,\n depVersion: string,\n affected: Osv.Affected,\n versioningApi: VersioningApi,\n ): string | null {\n const fixedVersions: string[] = [];\n const lastAffectedVersions: string[] = [];\n\n for (const range of affected.ranges ?? []) {\n if (range.type === 'GIT') {\n continue;\n }\n\n for (const event of range.events) {\n if (\n isNonEmptyString(event.fixed) &&\n versioningApi.isVersion(event.fixed)\n ) {\n fixedVersions.push(event.fixed);\n } else if (\n isNonEmptyString(event.last_affected) &&\n versioningApi.isVersion(event.last_affected)\n ) {\n lastAffectedVersions.push(event.last_affected);\n }\n }\n }\n\n fixedVersions.sort((a, b) => versioningApi.sortVersions(a, b));\n const fixedVersion = fixedVersions.find((version) =>\n this.isVersionGt(version, depVersion, versioningApi),\n );\n if (fixedVersion) {\n return this.getFixedVersionByEcosystem(fixedVersion, ecosystem);\n }\n\n lastAffectedVersions.sort((a, b) => versioningApi.sortVersions(a, b));\n const lastAffected = lastAffectedVersions.find((version) =>\n this.isVersionGtOrEq(version, depVersion, versioningApi),\n );\n if (lastAffected) {\n return this.getLastAffectedByEcosystem(lastAffected, ecosystem);\n }\n\n return null;\n }\n\n private getFixedVersionByEcosystem(\n fixedVersion: string,\n ecosystem: Ecosystem,\n ): string {\n if (ecosystem === 'Maven' || ecosystem === 'NuGet') {\n return `[${fixedVersion},)`;\n }\n\n // crates.io, Go, Hex, npm, RubyGems, PyPI\n return `>= ${fixedVersion}`;\n }\n\n private getLastAffectedByEcosystem(\n lastAffected: string,\n ecosystem: Ecosystem,\n ): string {\n if (ecosystem === 'Maven') {\n return `(${lastAffected},)`;\n }\n\n // crates.io, Go, Hex, npm, RubyGems, PyPI\n return `> ${lastAffected}`;\n }\n\n private isVersionGt(\n version: string,\n other: string,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n versioningApi.isVersion(version) &&\n versioningApi.isVersion(other) &&\n versioningApi.isGreaterThan(version, other)\n );\n }\n\n private isVersionGtOrEq(\n version: string,\n other: string,\n versioningApi: VersioningApi,\n ): boolean {\n return (\n versioningApi.isVersion(version) &&\n versioningApi.isVersion(other) &&\n (versioningApi.equals(version, other) ||\n versioningApi.isGreaterThan(version, other))\n );\n }\n\n private vulnerabilityToPackageRules(vul: Vulnerability): PackageRule | null {\n const {\n vulnerability,\n affected,\n packageName,\n depVersion,\n fixedVersion,\n datasource,\n packageFileConfig,\n } = vul;\n if (isNullOrUndefined(fixedVersion)) {\n logger.debug(\n `No fixed version available for vulnerability ${vulnerability.id} in ${packageName} ${depVersion}`,\n );\n return null;\n }\n\n logger.debug(\n `Setting allowed version ${fixedVersion} to fix vulnerability ${vulnerability.id} in ${packageName} ${depVersion}`,\n );\n\n const severityDetails = this.extractSeverityDetails(\n vulnerability,\n affected,\n );\n\n return {\n matchDatasources: [datasource],\n matchPackageNames: [packageName],\n matchCurrentVersion: depVersion,\n allowedVersions: fixedVersion,\n isVulnerabilityAlert: true,\n vulnerabilitySeverity: severityDetails.severityLevel,\n prBodyNotes: this.generatePrBodyNotes(vulnerability, affected),\n force: {\n ...packageFileConfig.vulnerabilityAlerts,\n },\n };\n }\n\n static evaluateCvssVector(vector: string): [string, string] {\n const CvssJsonSchema = z.object({\n baseScore: z.number().default(0.0),\n baseSeverity: z.string().toUpperCase().default('UNKNOWN'),\n });\n\n try {\n const parsedCvssScore: CvssVector<any> | null = fromVector(vector);\n const res = CvssJsonSchema.parse(parsedCvssScore?.createJsonSchema());\n\n return [res.baseScore.toFixed(1), res.baseSeverity];\n } catch {\n logger.debug(`Error processing CVSS vector ${vector}`);\n }\n\n return ['', ''];\n }\n\n private generatePrBodyNotes(\n vulnerability: Osv.Vulnerability,\n affected: Osv.Affected,\n ): string[] {\n let aliases = [vulnerability.id].concat(vulnerability.aliases ?? []).sort();\n aliases = aliases.map((id) => {\n if (id.startsWith('CVE-')) {\n return `[${id}](https://nvd.nist.gov/vuln/detail/${id})`;\n } else if (id.startsWith('GHSA-')) {\n return `[${id}](https://github.com/advisories/${id})`;\n } else if (id.startsWith('GO-')) {\n return `[${id}](https://pkg.go.dev/vuln/${id})`;\n } else if (id.startsWith('RUSTSEC-')) {\n return `[${id}](https://rustsec.org/advisories/${id}.html)`;\n }\n\n return id;\n });\n\n let content = '\\n\\n---\\n\\n### ';\n content += vulnerability.summary ? `${vulnerability.summary}\\n` : '';\n content += `${aliases.join(' / ')}\\n`;\n content += `\\n<details>\\n<summary>More information</summary>\\n`;\n\n const details = vulnerability.details?.replace(\n regEx(/^#{1,4} /gm),\n '##### ',\n );\n content += `#### Details\\n${details ?? 'No details.'}\\n`;\n\n content += '#### Severity\\n';\n const severityDetails = this.extractSeverityDetails(\n vulnerability,\n affected,\n );\n\n if (severityDetails.cvssVector) {\n content += `- CVSS Score: ${severityDetails.score}\\n`;\n content += `- Vector String: \\`${severityDetails.cvssVector}\\`\\n`;\n } else {\n content += `${titleCase(severityDetails.severityLevel)}\\n`;\n }\n\n content += `\\n#### References\\n${\n vulnerability.references\n ?.map((ref) => {\n return `- [${ref.url}](${ref.url})`;\n })\n .join('\\n') ?? 'No references.'\n }`;\n\n let attribution = '';\n if (vulnerability.id.startsWith('GHSA-')) {\n attribution = ` and the [GitHub Advisory Database](https://github.com/github/advisory-database) ([CC-BY 4.0](https://github.com/github/advisory-database/blob/main/LICENSE.md))`;\n } else if (vulnerability.id.startsWith('GO-')) {\n attribution = ` and the [Go Vulnerability Database](https://github.com/golang/vulndb) ([CC-BY 4.0](https://github.com/golang/vulndb#license))`;\n } else if (vulnerability.id.startsWith('PYSEC-')) {\n attribution = ` and the [PyPI Advisory Database](https://github.com/pypa/advisory-database) ([CC-BY 4.0](https://github.com/pypa/advisory-database/blob/main/LICENSE))`;\n } else if (vulnerability.id.startsWith('RUSTSEC-')) {\n attribution = ` and the [Rust Advisory Database](https://github.com/RustSec/advisory-db) ([CC0 1.0](https://github.com/rustsec/advisory-db/blob/main/LICENSE.txt))`;\n }\n content += `\\n\\nThis data is provided by [OSV](https://osv.dev/vulnerability/${vulnerability.id})${attribution}.\\n`;\n content += `</details>`;\n\n return [sanitizeMarkdown(content)];\n }\n\n private extractSeverityDetails(\n vulnerability: Osv.Vulnerability,\n affected: Osv.Affected,\n ): SeverityDetails {\n let severityLevel = 'UNKNOWN';\n let score = 'Unknown';\n\n const cvssVector =\n vulnerability.severity?.find((e) => e.type === 'CVSS_V4')?.score ??\n vulnerability.severity?.find((e) => e.type === 'CVSS_V3')?.score ??\n (affected.database_specific?.cvss as string); // RUSTSEC\n\n if (cvssVector) {\n const [baseScore, severity] =\n Vulnerabilities.evaluateCvssVector(cvssVector);\n severityLevel = severity ? severity.toUpperCase() : 'UNKNOWN';\n score = baseScore\n ? `${baseScore} / 10 (${titleCase(severityLevel)})`\n : 'Unknown';\n } else if (\n vulnerability.id.startsWith('GHSA-') &&\n vulnerability.database_specific?.severity\n ) {\n const severity = vulnerability.database_specific.severity as string;\n severityLevel = severity.toUpperCase();\n }\n\n return {\n cvssVector,\n score,\n severityLevel,\n };\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "renovate",
3
3
  "description": "Automated dependency updates. Flexible so you don't need to be.",
4
- "version": "43.36.0",
4
+ "version": "43.36.2",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "renovate": "dist/renovate.js",
@@ -1,7 +1,7 @@
1
1
  {
2
- "title": "JSON schema for Renovate 43.36.0 config files (https://renovatebot.com/)",
2
+ "title": "JSON schema for Renovate 43.36.2 config files (https://renovatebot.com/)",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
- "x-renovate-version": "43.36.0",
4
+ "x-renovate-version": "43.36.2",
5
5
  "allowComments": true,
6
6
  "type": "object",
7
7
  "properties": {