UNPKG

@nodesecure/scanner

Version:

A package API to run a static analysis of your module's dependencies.

149 lines 6.29 kB
// Import Node.js Dependencies import path from "node:path"; // Import Third-party Dependencies import semver from "semver"; import * as npmRegistrySDK from "@nodesecure/npm-registry-sdk"; import { packageJSONIntegrityHash } from "@nodesecure/mama"; import { getNpmRegistryURL } from "@nodesecure/npm-registry-sdk"; import * as i18n from "@nodesecure/i18n"; import { isHTTPError } from "@openally/httpie"; // Import Internal Dependencies import { PackumentExtractor } from "./PackumentExtractor.js"; import { fetchNpmAvatars } from "./fetchNpmAvatars.js"; import { Logger } from "../class/logger.class.js"; import { getLinks } from "../utils/getLinks.js"; import { getDirNameFromUrl } from "../utils/dirname.js"; // CONSTANTS const kNotFoundStatusCode = 404; await i18n.extendFromSystemPath(path.join(getDirNameFromUrl(import.meta.url), "..", "i18n")); export class NpmRegistryProvider { #date; #npmApiClient; #registry; #tokenStore; name; version; constructor(name, version, options = {}) { const { dateProvider = undefined, npmApiClient = npmRegistrySDK, registry = npmRegistrySDK.getLocalRegistryURL(), tokenStore = undefined } = options; this.name = name; this.version = version; this.#date = dateProvider; this.#npmApiClient = npmApiClient; this.#registry = registry; this.#tokenStore = tokenStore; } async collectPackageVersionData() { const packumentVersion = await this.#npmApiClient.packumentVersion(this.name, this.version, { registry: this.#registry, token: this.#tokenStore?.get(this.#registry) }); const { integrity } = packageJSONIntegrityHash(packumentVersion, { isFromRemoteRegistry: true }); return { links: getLinks(packumentVersion), integrity, deprecated: packumentVersion.deprecated, signatures: packumentVersion.dist.signatures, attestations: packumentVersion.dist.attestations }; } async collectPackageData() { const packument = await this.#npmApiClient.packument(this.name, { registry: this.#registry, token: this.#tokenStore?.get(this.#registry) }); const packumentVersion = packument.versions[this.version]; const metadata = new PackumentExtractor(packument, { dateProvider: this.#date }).getMetadata(this.version); const flags = { isOutdated: semver.neq(this.version, metadata.lastVersion), isDeprecated: packumentVersion.deprecated }; return { metadata, flags: Object.keys(flags).filter((key) => flags[key]), version: { links: getLinks(packumentVersion), deprecated: packumentVersion.deprecated } }; } async enrichDependency(logger, dependency) { try { const { metadata, flags, version } = await this.collectPackageData(); await fetchNpmAvatars(metadata); const dependencyVersion = dependency.versions[this.version]; dependency.metadata = metadata; dependencyVersion.flags = [...dependencyVersion.flags, ...flags]; Object.assign(dependencyVersion, version); } catch { // ignored } finally { logger.tick("registry"); } } async enrichDependencyVersion(dependency, warnings, org) { try { const { integrity, deprecated, links, signatures, attestations } = await this.collectPackageVersionData(); Object.assign(dependency.versions[this.version], { links, deprecated, attestations }); dependency.metadata.integrity[this.version] = integrity; if (this.#registry === getNpmRegistryURL()) { return; } try { const packumentVersionFromPublicRegistry = await this.#npmApiClient.packumentVersion(this.name, this.version, { registry: getNpmRegistryURL(), token: this.#tokenStore?.get(getNpmRegistryURL()) }); if (!this.#hasSameSignatures(signatures, packumentVersionFromPublicRegistry.dist.signatures)) { this.#addDependencyConfusionWarning(warnings, await i18n.getToken("scanner.dependency_confusion")); } } catch (err) { const isScoped = Boolean(org); if (isHTTPError(err) && err.statusCode === kNotFoundStatusCode && !isScoped) { this.#addDependencyConfusionWarning(warnings, await i18n.getToken("scanner.dependency_confusion_missing")); } } } catch { // ignore } } #hasSameSignatures(signatures, signaturesFromPublicRegistry) { if (!signatures || !signaturesFromPublicRegistry) { return false; } const sortedSignaturesFromPublic = signaturesFromPublicRegistry.sort((a, b) => a.keyid.localeCompare(b.keyid)); const sortedSignaturesFromPrivate = signatures.sort((a, b) => a.keyid.localeCompare(b.keyid)); return sortedSignaturesFromPrivate.length === signaturesFromPublicRegistry.length && sortedSignaturesFromPrivate?.every((signature, index) => signature.keyid === sortedSignaturesFromPublic[index].keyid && signature.sig === sortedSignaturesFromPublic[index].sig); } async enrichScopedDependencyConfusionWarnings(warnings, org) { try { await this.#npmApiClient.org(this.name); } catch (err) { if (isHTTPError(err) && err.statusCode === kNotFoundStatusCode) { await this.#addDependencyConfusionWarning(warnings, await i18n.getToken("scanner.dependency_confusion_missing_org", org)); } } } async #addDependencyConfusionWarning(warnings, message) { warnings.push({ type: "dependency-confusion", message, metadata: { name: this.name } }); } } //# sourceMappingURL=NpmRegistryProvider.js.map