Skip to content

Commit

Permalink
v1.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
mbalabash committed Nov 21, 2023
1 parent 1d92d66 commit 3d55177
Show file tree
Hide file tree
Showing 15 changed files with 1,458 additions and 812 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This project adheres to [Semantic Versioning](http://semver.org/).

## 1.0.4

- Upgraded dependencies

## 1.0.3

- Upgraded dependencies
Expand Down
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,6 @@ npm diff [email protected] [email protected]

6. **Remote execution**: An attacker may target a package by compromising the third-party services used by that package.

## Roadmap

| Status | Name | Description |
| ------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| To do | **Expired Maintainer Domain** | An attacker can hijack a package if a maintainer’s domain is expired ([info](https://therecord.media/thousands-of-npm-accounts-use-email-addresses-with-expired-domains/)) |

## Install

```js
Expand Down
28 changes: 14 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { from, cwd } from '@nodesecure/scanner'
import { cwd, from } from '@nodesecure/scanner'

import { getMetrics, gatherLockFileSafetyMetric } from './src/metrics.js'
import { loadConfig, loadIgnoreFile, findLockFilePath, resolveMode } from './src/utils.js'
import { gatherLockFileSafetyMetric, getMetrics } from './src/metrics.js'
import { createReport } from './src/report.js'
import { findLockFilePath, loadConfig, loadIgnoreFile, resolveMode } from './src/utils.js'

/**
* @typedef {import('./index').check} check
* @type check
*/
export async function check({
rootDir = '',
packageName = '',
version = '',
config,
ignoredPackages
ignoredPackages,
packageName = '',
rootDir = '',
version = ''
}) {
try {
if (!rootDir && !packageName) {
Expand All @@ -29,25 +29,25 @@ export async function check({
let nssOutput =
mode === 'internal'
? await cwd(rootDir, {
vulnerabilityStrategy: 'npm',
forceRootAnalysis: true
forceRootAnalysis: true,
vulnerabilityStrategy: 'npm'
})
: await from(version ? `${packageName}@${version}` : packageName, {
vulnerabilityStrategy: 'npm',
forceRootAnalysis: true
forceRootAnalysis: true,
vulnerabilityStrategy: 'npm'
})

return createReport({
config,
findings: await getMetrics(nssOutput, config, rootDir),
lockFileIsNotSafe: await gatherLockFileSafetyMetric(lockfilePath),
ignoredPackages,
config
lockFileIsNotSafe: await gatherLockFileSafetyMetric(lockfilePath)
})
} catch (error) {
/* eslint-disable no-console */
console.error(error)
console.error('ERROR: Could not perform sdc-check audit')
/* eslint-enable no-console */
return { type: 'none', errors: [], warnings: [] }
return { errors: [], type: 'none', warnings: [] }
}
}
30 changes: 16 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sdc-check",
"version": "1.0.3",
"version": "1.0.4",
"description": "Small tool to inform you about potential risks in your project dependencies list",
"keywords": [
"supply-chain-security",
Expand All @@ -25,27 +25,29 @@
"test": "yarn unit && eslint . --ignore-pattern test/ && yarn sdc-check"
},
"dependencies": {
"@nodesecure/scanner": "^3.8.2",
"commander": "^10.0.0",
"lockfile-lint-api": "^5.5.1",
"@nodesecure/scanner": "^5.0.1",
"commander": "^11.1.0",
"lockfile-lint-api": "^5.8.0",
"nanospinner": "^1.1.0",
"pacote": "^15.1.1",
"pacote": "^17.0.4",
"picocolors": "^1.0.0"
},
"devDependencies": {
"@logux/eslint-config": "^49.0.0",
"@types/lockfile-lint-api": "^5.1.1",
"@types/pacote": "^11.1.5",
"clean-publish": "^4.1.1",
"eslint": "^8.36.0",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"@logux/eslint-config": "^52.0.2",
"@types/lockfile-lint-api": "^5.1.5",
"@types/pacote": "^11.1.8",
"clean-publish": "^4.2.0",
"eslint": "^8.54.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^16.3.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-node-import": "^1.0.4",
"eslint-plugin-perfectionist": "^2.4.0",
"eslint-plugin-prefer-let": "^3.0.1",
"eslint-plugin-promise": "^6.1.1",
"tsm": "^2.3.0",
"typescript": "^5.0.2",
"typescript": "^5.3.2",
"uvu": "^0.5.6"
},
"bin": {
Expand Down
26 changes: 13 additions & 13 deletions src/cli.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/usr/bin/env node

/* eslint-disable no-console */
import pc from 'picocolors'
import { exit } from 'process'
import { resolve } from 'path'
import { Command } from 'commander'
import { createSpinner } from 'nanospinner'
import { resolve } from 'node:path'
import { exit } from 'node:process'
/* eslint-disable no-console */
import pc from 'picocolors'

import { check } from '../index.js'

Expand Down Expand Up @@ -34,8 +34,8 @@ program.parse(process.argv)
;(async () => {
let cliOptions = program.opts()
let sdcOptions = {
rootDir: typeof cliOptions.d === 'string' ? resolve(cliOptions.d) : undefined,
packageName: cliOptions.p,
rootDir: typeof cliOptions.d === 'string' ? resolve(cliOptions.d) : undefined,
version: cliOptions.v
}
let spinner = createSpinner('Running sdc-check')
Expand All @@ -54,14 +54,14 @@ program.parse(process.argv)
printWarningsInfo(report.warnings, cliOptions)

if (report.type === 'error') {
spinner.error({ text: pc.red('sdc-check has found errors'), mark: '\n🚨' })
spinner.error({ mark: '\n🚨', text: pc.red('sdc-check has found errors') })
exit(1)
} else {
spinner.success({ text: pc.green('sdc-check completed without any errors'), mark: '\n✅' })
spinner.success({ mark: '\n✅', text: pc.green('sdc-check completed without any errors') })
}
} catch (error) {
console.error(error)
spinner.error({ text: pc.red('sdc-check exited with error'), mark: '\n🚫' })
spinner.error({ mark: '\n🚫', text: pc.red('sdc-check exited with error') })
}
})()

Expand All @@ -72,15 +72,15 @@ program.parse(process.argv)
function getReportStatsInfo(reportedItems) {
let stats = {
'dangerous-shell-commands': 0,
'package-is-too-new': 0,
'has-os-scripts': 0,
'install-scripts': 0,
'lockfile-is-not-safe': 0,
'no-source-code': 0,
'obfuscated-code': 0,
'package-is-too-new': 0,
'released-after-long-period-of-inactivity': 0,
'too-many-decision-makers': 0,
'unmaintained-package': 0,
'install-scripts': 0,
'no-source-code': 0,
'has-os-scripts': 0
'unmaintained-package': 0
}

reportedItems.forEach(({ metric }) => {
Expand Down
31 changes: 15 additions & 16 deletions src/metrics.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ParseLockfile, ValidateHost } from 'lockfile-lint-api'
import { readFile } from 'node:fs/promises'
import { extname, join } from 'node:path'
import pacote from 'pacote'
import { extname, join } from 'path'
import { readFile } from 'fs/promises'
import { ValidateHost, ParseLockfile } from 'lockfile-lint-api'

import { omit, pick } from './utils.js'

Expand Down Expand Up @@ -37,8 +37,6 @@ export async function getMetrics(nssOutput, config, rootDir) {
})

findings.push({
package: name,
version,
metrics: {
...gatherMetricsFromNodeSecScanner(
dependencyData,
Expand All @@ -50,7 +48,9 @@ export async function getMetrics(nssOutput, config, rootDir) {
...gatherDangerousShellCommandsMetric(packageManifestObject),
...(await gatherReleaseActivityMetrics(packument, version, config)),
...gatherOsScriptsMetric(versionData)
}
},
package: name,
version
})
}
}
Expand Down Expand Up @@ -79,14 +79,6 @@ export function gatherMetricsFromNodeSecScanner(
let obfuscatedCodeWarns = versionData.warnings.filter(warn => warn.kind === 'obfuscated-code')

return {
hasTooManyDecisionMakers: {
result: decisionMakers > config.limitOfDecisionMakers,
value: decisionMakers
},
isPackageUnmaintained: {
result: dependencyData.metadata.hasReceivedUpdateInOneYear === false,
value: new Date(dependencyData.metadata.lastUpdateAt).toISOString().slice(0, 10)
},
hasInstallScripts: {
result: versionData.flags.some(flag => flag === 'hasScript'),
value: JSON.stringify(pick(INSTALL_HOOKS, packageManifestObject.scripts || {}))
Expand All @@ -97,6 +89,14 @@ export function gatherMetricsFromNodeSecScanner(
// @ts-expect-error Bad type inference
.map(warn => `${warn.value}-${warn.file}`)
.join(', ')
},
hasTooManyDecisionMakers: {
result: decisionMakers > config.limitOfDecisionMakers,
value: decisionMakers
},
isPackageUnmaintained: {
result: dependencyData.metadata.hasReceivedUpdateInOneYear === false,
value: new Date(dependencyData.metadata.lastUpdateAt).toISOString().slice(0, 10)
}
}
}
Expand Down Expand Up @@ -164,7 +164,6 @@ export function gatherReleaseActivityMetrics(packument, version, config) {
}

let releaseDates = Object.values(omit(['modified', 'created'], packument.time))
// @ts-expect-error TS ignored if-check above and thinks that packument.time could be an undefined
let previousVersionIndex = releaseDates.findIndex(date => date === packument.time[version]) - 1

let versionReleaseDate = new Date(packument.time[version].slice(0, 10))
Expand Down Expand Up @@ -216,7 +215,7 @@ export function gatherReleaseActivityMetrics(packument, version, config) {
*/
export async function gatherLockFileSafetyMetric(lockfilePath) {
if (!lockfilePath) {
return { type: 'success', object: {} }
return { object: {}, type: 'success' }
}

try {
Expand Down
22 changes: 11 additions & 11 deletions src/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import { omit } from './utils.js'
* @type METRICS_ALIASES
*/
export const SDC_CHECK_METRICS_ALIASES = {
lockFileIsNotSafe: 'lockfile-is-not-safe',
hasInstallScripts: 'install-scripts',
hasObfuscatedCode: 'obfuscated-code',
hasOsScripts: 'has-os-scripts',
hasTooManyDecisionMakers: 'too-many-decision-makers',
isPackageUnmaintained: 'unmaintained-package',
hasInstallScripts: 'install-scripts',
lockFileIsNotSafe: 'lockfile-is-not-safe',
noSourceCodeRepository: 'no-source-code',
scriptsHaveDangerousShellCommands: 'dangerous-shell-commands',
packageReleasedAfterLongPeriodOfInactivity: 'released-after-long-period-of-inactivity',
packageVersionIsTooNew: 'package-is-too-new',
hasOsScripts: 'has-os-scripts',
hasObfuscatedCode: 'obfuscated-code'
scriptsHaveDangerousShellCommands: 'dangerous-shell-commands'
}

/**
Expand All @@ -31,7 +31,7 @@ export function createReport(options) {
throw new Error('There are no metrics data to create report')
}

let { findings, lockFileIsNotSafe, config, ignoredPackages } = options
let { config, findings, ignoredPackages, lockFileIsNotSafe } = options
let keys = getReportMetricKeys(SDC_CHECK_METRICS_ALIASES)
let report = initReport()

Expand All @@ -40,15 +40,15 @@ export function createReport(options) {
let level = getReportLevel(config, SDC_CHECK_METRICS_ALIASES.lockFileIsNotSafe)
lockFileIsNotSafe.errors.forEach(error => {
report[level].push({
metric: SDC_CHECK_METRICS_ALIASES.lockFileIsNotSafe,
message: error.message,
metric: SDC_CHECK_METRICS_ALIASES.lockFileIsNotSafe,
package: error.package
})
})
}

// Add report data for other metrics:
for (let { package: _package, version, metrics } of findings) {
for (let { metrics, package: _package, version } of findings) {
let packageSpec = `${_package}@${version}`
if (ignoredPackages[packageSpec] === true || ignoredPackages[_package] === true) {
continue
Expand All @@ -59,9 +59,9 @@ export function createReport(options) {
if (metrics[key].result === true && !isIgnoredMetric(metric, packageSpec, ignoredPackages)) {
let level = getReportLevel(config, metric)
report[level].push({
message: getMetricMessageForReport(metric, metrics[key].value),
metric,
package: packageSpec,
message: getMetricMessageForReport(metric, metrics[key].value)
package: packageSpec
})
}
})
Expand All @@ -75,7 +75,7 @@ export function createReport(options) {
* @type initReport
*/
function initReport() {
return { type: 'none', errors: [], warnings: [] }
return { errors: [], type: 'none', warnings: [] }
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { access, readFile } from 'fs/promises'
import { join } from 'path'
import { constants } from 'fs'
import { constants } from 'node:fs'
import { access, readFile } from 'node:fs/promises'
import { join } from 'node:path'

const PACKAGE_JSON_CONFIG_KEY = 'sdc-check'
const IGNORE_FILE_NAME = '.sdccheckignore'

const defaultOptionsConfig = {
limitOfDecisionMakers: 7,
daysBeforeUpgradeToNewVersion: 5,
limitOfDecisionMakers: 7,
monthsOfInactivityAllowed: 10
}

Expand Down
Loading

0 comments on commit 3d55177

Please sign in to comment.