mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ci:force] - Fixed complexity of the scripts
This commit is contained in:
@@ -46,12 +46,18 @@ jobs:
|
|||||||
- name: Ensure SHA pinned actions
|
- name: Ensure SHA pinned actions
|
||||||
uses: hyland/github-actions-ensure-sha-pinned-actions@7d494b6b53e71f75194d74b4dd890a2266d060b5 # v2.0.0
|
uses: hyland/github-actions-ensure-sha-pinned-actions@7d494b6b53e71f75194d74b4dd890a2266d060b5 # v2.0.0
|
||||||
|
|
||||||
- name: Check package-lock.json version
|
- name: Check pnpm-lock.yaml version
|
||||||
run: |
|
run: |
|
||||||
if [[ $(jq '.lockfileVersion == 3' package-lock.json) == "true" ]] ; then
|
if [[ -f "pnpm-lock.yaml" ]]; then
|
||||||
echo "package-lock.json has a correct version"
|
LOCKFILE_VERSION=$(grep "^lockfileVersion:" pnpm-lock.yaml | cut -d"'" -f2)
|
||||||
|
if [[ "$LOCKFILE_VERSION" == "9.0" ]]; then
|
||||||
|
echo "pnpm-lock.yaml has correct version: $LOCKFILE_VERSION"
|
||||||
else
|
else
|
||||||
echo "package-lock must be version 3"
|
echo "pnpm-lock.yaml must be version 9.0, found: $LOCKFILE_VERSION"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "pnpm-lock.yaml is missing"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ on:
|
|||||||
types: [opened, reopened, synchronize]
|
types: [opened, reopened, synchronize]
|
||||||
paths:
|
paths:
|
||||||
- "package.json"
|
- "package.json"
|
||||||
- "package-lock.json"
|
- "pnpm-lock.yaml"
|
||||||
- "**/package.json"
|
- "**/package.json"
|
||||||
- "**/package-lock.json"
|
|
||||||
- "pom.xml"
|
- "pom.xml"
|
||||||
- "**/pom.xml"
|
- "**/pom.xml"
|
||||||
|
|
||||||
|
|||||||
+16
-145
@@ -18,166 +18,37 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-commit security check for new packages.
|
* Pre-commit security check.
|
||||||
*
|
*
|
||||||
* Checks any new/changed packages in pnpm-lock.yaml against OSV and GitHub
|
* Runs via husky pre-commit hook.
|
||||||
* Advisory databases to prevent committing known malicious packages.
|
* Blocks commits containing known malicious packages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync } from 'node:child_process';
|
import { readChangedPackagesFromGitDiff } from './security/lockfile-parser.mjs';
|
||||||
|
import { checkPackagesForMalware } from './security/malware-checker.mjs';
|
||||||
const MALWARE_KEYWORDS = ['malware', 'malicious', 'compromised', 'supply chain', 'backdoor'];
|
import {
|
||||||
|
printCheckingMessage,
|
||||||
function getChangedPackages() {
|
printCommitBlockedWarning,
|
||||||
try {
|
printAllPackagesClean
|
||||||
const diff = execSync('git diff --cached pnpm-lock.yaml', { encoding: 'utf-8' });
|
} from './security/report-printer.mjs';
|
||||||
|
|
||||||
const addedPackages = [];
|
|
||||||
const packageRegex = /^\+\s+'?([^:'\s]+)'?:\s*$/gm;
|
|
||||||
const versionRegex = /^\+\s+version:\s+'?([^'\s]+)'?/gm;
|
|
||||||
|
|
||||||
let match;
|
|
||||||
const lines = diff.split('\n');
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
||||||
const pkgMatch = line.match(/^\+\s+'?\/([^']+)'?:/);
|
|
||||||
if (pkgMatch) {
|
|
||||||
const fullPath = pkgMatch[1];
|
|
||||||
const name = fullPath.includes('@') && !fullPath.startsWith('@')
|
|
||||||
? fullPath.split('@')[0]
|
|
||||||
: fullPath.replace(/@[^/]+$/, '');
|
|
||||||
const version = fullPath.match(/@(\d+\.\d+\.\d+[^/]*)/)?.[1];
|
|
||||||
if (name && version) {
|
|
||||||
addedPackages.push({ name, version });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addedPackages;
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkWithOSV(packages) {
|
|
||||||
if (!packages.length) return [];
|
|
||||||
|
|
||||||
const findings = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://api.osv.dev/v1/querybatch', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
queries: packages.map(pkg => ({
|
|
||||||
package: { name: pkg.name, ecosystem: 'npm' },
|
|
||||||
version: pkg.version
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
signal: AbortSignal.timeout(30000)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) return findings;
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
for (let i = 0; i < (data.results || []).length; i++) {
|
|
||||||
const result = data.results[i];
|
|
||||||
for (const vuln of result.vulns || []) {
|
|
||||||
const summary = [vuln.summary || '', vuln.details || ''].join(' ').toLowerCase();
|
|
||||||
if (MALWARE_KEYWORDS.some(kw => summary.includes(kw))) {
|
|
||||||
findings.push({
|
|
||||||
package: packages[i].name,
|
|
||||||
version: packages[i].version,
|
|
||||||
reason: vuln.summary || 'Malware detected',
|
|
||||||
source: 'OSV'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Network errors are non-fatal
|
|
||||||
}
|
|
||||||
|
|
||||||
return findings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkWithGitHub(packages) {
|
|
||||||
if (!packages.length) return [];
|
|
||||||
|
|
||||||
const findings = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
'https://api.github.com/advisories?ecosystem=npm&type=malware&per_page=100',
|
|
||||||
{
|
|
||||||
headers: { 'Accept': 'application/vnd.github+json' },
|
|
||||||
signal: AbortSignal.timeout(10000)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) return findings;
|
|
||||||
|
|
||||||
const advisories = await response.json();
|
|
||||||
|
|
||||||
for (const pkg of packages) {
|
|
||||||
for (const advisory of advisories) {
|
|
||||||
for (const vuln of advisory.vulnerabilities || []) {
|
|
||||||
if (vuln.package?.name === pkg.name) {
|
|
||||||
findings.push({
|
|
||||||
package: pkg.name,
|
|
||||||
version: pkg.version,
|
|
||||||
reason: advisory.summary || 'Malware advisory',
|
|
||||||
source: 'GitHub Advisory'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Network errors are non-fatal
|
|
||||||
}
|
|
||||||
|
|
||||||
return findings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const changedPackages = getChangedPackages();
|
const changedPackages = readChangedPackagesFromGitDiff();
|
||||||
|
|
||||||
if (!changedPackages.length) {
|
if (!changedPackages.length) {
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n🔍 Checking ${changedPackages.length} new/changed packages against security databases...\n`);
|
printCheckingMessage(changedPackages.length);
|
||||||
|
|
||||||
const [osvFindings, ghFindings] = await Promise.all([
|
const malwareFindings = await checkPackagesForMalware(changedPackages);
|
||||||
checkWithOSV(changedPackages),
|
|
||||||
checkWithGitHub(changedPackages)
|
|
||||||
]);
|
|
||||||
|
|
||||||
const allFindings = [...osvFindings, ...ghFindings];
|
|
||||||
|
|
||||||
if (allFindings.length > 0) {
|
|
||||||
console.error('='.repeat(70));
|
|
||||||
console.error('🚨 MALICIOUS PACKAGES DETECTED - COMMIT BLOCKED');
|
|
||||||
console.error('='.repeat(70) + '\n');
|
|
||||||
|
|
||||||
for (const finding of allFindings) {
|
|
||||||
console.error(` ❌ ${finding.package}@${finding.version}`);
|
|
||||||
console.error(` ${finding.reason} (${finding.source})\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('Remove these packages before committing:');
|
|
||||||
console.error(' pnpm remove <package-name>\n');
|
|
||||||
console.error('='.repeat(70) + '\n');
|
|
||||||
|
|
||||||
|
if (malwareFindings.length > 0) {
|
||||||
|
printCommitBlockedWarning(malwareFindings);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ All new packages passed security checks\n');
|
printAllPackagesClean();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
+25
-137
@@ -3,161 +3,49 @@
|
|||||||
/*!
|
/*!
|
||||||
* @license
|
* @license
|
||||||
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post-install security check - warns if any installed packages are known malware.
|
* Post-install security check.
|
||||||
|
*
|
||||||
* Runs after pnpm install via the prepare hook.
|
* Runs after pnpm install via the prepare hook.
|
||||||
|
* Warns if any installed packages are known malware.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync } from 'node:fs';
|
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
import { readAllPackagesFromLockfile } from './security/lockfile-parser.mjs';
|
||||||
|
import { checkPackagesForMalware } from './security/malware-checker.mjs';
|
||||||
|
import { printMalwareWarning } from './security/report-printer.mjs';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const ROOT_DIR = join(__dirname, '..');
|
const ROOT_DIR = join(__dirname, '..');
|
||||||
const MALWARE_KEYWORDS = ['malware', 'malicious', 'compromised', 'supply chain', 'backdoor'];
|
const LOCKFILE_PATH = join(ROOT_DIR, 'pnpm-lock.yaml');
|
||||||
|
|
||||||
function getInstalledPackages() {
|
|
||||||
try {
|
|
||||||
const lockfile = readFileSync(join(ROOT_DIR, 'pnpm-lock.yaml'), 'utf-8');
|
|
||||||
const packages = [];
|
|
||||||
const regex = /^\s+'?\/([^'(]+)'/gm;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = regex.exec(lockfile)) !== null) {
|
|
||||||
const fullPath = match[1];
|
|
||||||
const lastAt = fullPath.lastIndexOf('@');
|
|
||||||
if (lastAt > 0) {
|
|
||||||
const name = fullPath.substring(0, lastAt);
|
|
||||||
const version = fullPath.substring(lastAt + 1);
|
|
||||||
packages.push({ name, version });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return packages;
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkWithOSV(packages) {
|
|
||||||
const findings = [];
|
|
||||||
const batches = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < packages.length; i += 1000) {
|
|
||||||
batches.push(packages.slice(i, i + 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const batch of batches) {
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://api.osv.dev/v1/querybatch', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
queries: batch.map(pkg => ({
|
|
||||||
package: { name: pkg.name, ecosystem: 'npm' },
|
|
||||||
version: pkg.version
|
|
||||||
}))
|
|
||||||
}),
|
|
||||||
signal: AbortSignal.timeout(30000)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) continue;
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
for (let i = 0; i < (data.results || []).length; i++) {
|
|
||||||
for (const vuln of data.results[i].vulns || []) {
|
|
||||||
const summary = [vuln.summary || '', vuln.details || ''].join(' ').toLowerCase();
|
|
||||||
if (MALWARE_KEYWORDS.some(kw => summary.includes(kw))) {
|
|
||||||
findings.push({
|
|
||||||
name: batch[i].name,
|
|
||||||
version: batch[i].version,
|
|
||||||
reason: vuln.summary || 'Malware detected',
|
|
||||||
source: 'OSV'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return findings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkWithGitHub(packages) {
|
|
||||||
const findings = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
'https://api.github.com/advisories?ecosystem=npm&type=malware&per_page=100',
|
|
||||||
{
|
|
||||||
headers: { 'Accept': 'application/vnd.github+json' },
|
|
||||||
signal: AbortSignal.timeout(10000)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) return findings;
|
|
||||||
|
|
||||||
const advisories = await response.json();
|
|
||||||
const malwareNames = new Set();
|
|
||||||
|
|
||||||
for (const advisory of advisories) {
|
|
||||||
for (const vuln of advisory.vulnerabilities || []) {
|
|
||||||
if (vuln.package?.name) {
|
|
||||||
malwareNames.add(vuln.package.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const pkg of packages) {
|
|
||||||
if (malwareNames.has(pkg.name)) {
|
|
||||||
findings.push({
|
|
||||||
name: pkg.name,
|
|
||||||
version: pkg.version,
|
|
||||||
reason: 'Known malware package',
|
|
||||||
source: 'GitHub Advisory'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
return findings;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const packages = getInstalledPackages();
|
const installedPackages = readAllPackagesFromLockfile(LOCKFILE_PATH);
|
||||||
|
|
||||||
if (!packages.length) {
|
if (!installedPackages.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [osvFindings, ghFindings] = await Promise.all([
|
const malwareFindings = await checkPackagesForMalware(installedPackages);
|
||||||
checkWithOSV(packages),
|
|
||||||
checkWithGitHub(packages)
|
|
||||||
]);
|
|
||||||
|
|
||||||
const allFindings = [...osvFindings, ...ghFindings];
|
|
||||||
|
|
||||||
// Dedupe
|
|
||||||
const unique = allFindings.filter((f, i, arr) =>
|
|
||||||
arr.findIndex(x => x.name === f.name && x.version === f.version) === i
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unique.length > 0) {
|
|
||||||
console.error('\n' + '!'.repeat(70));
|
|
||||||
console.error('🚨 WARNING: MALICIOUS PACKAGES DETECTED');
|
|
||||||
console.error('!'.repeat(70) + '\n');
|
|
||||||
|
|
||||||
for (const finding of unique) {
|
|
||||||
console.error(` ❌ ${finding.name}@${finding.version}`);
|
|
||||||
console.error(` ${finding.reason} (${finding.source})\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error('Remove these packages immediately:');
|
|
||||||
console.error(' pnpm remove <package-name>\n');
|
|
||||||
console.error('!'.repeat(70) + '\n');
|
|
||||||
|
|
||||||
|
if (malwareFindings.length > 0) {
|
||||||
|
printMalwareWarning(malwareFindings);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-86
@@ -18,124 +18,137 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safe add - checks packages against security databases before installing.
|
* Safe add package.
|
||||||
*
|
*
|
||||||
|
* Checks packages against security databases before installing.
|
||||||
* Usage: pnpm run add <package>[@version] [-- -D]
|
* Usage: pnpm run add <package>[@version] [-- -D]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
const MALWARE_KEYWORDS = ['malware', 'malicious', 'compromised', 'supply chain', 'backdoor'];
|
import { checkSinglePackageForMalware } from './security/malware-checker.mjs';
|
||||||
|
import {
|
||||||
|
printSecurityCheckHeader,
|
||||||
|
printPackageClean,
|
||||||
|
printInstallBlockedWarning
|
||||||
|
} from './security/report-printer.mjs';
|
||||||
|
|
||||||
function parsePackageArg(arg) {
|
const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
||||||
const match = arg.match(/^(@?[^@]+)(?:@(.+))?$/);
|
const REQUEST_TIMEOUT_MS = 5000;
|
||||||
return match ? { name: match[1], version: match[2] || 'latest' } : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveVersion(name, version) {
|
// ============================================================================
|
||||||
if (version !== 'latest') return version;
|
// ARGUMENT PARSING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
try {
|
function parsePackageArgument(argument) {
|
||||||
const response = await fetch(`https://registry.npmjs.org/${name}/latest`, {
|
const packageMatch = argument.match(/^(@?[^@]+)(?:@(.+))?$/);
|
||||||
signal: AbortSignal.timeout(5000)
|
|
||||||
});
|
if (!packageMatch) {
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
return data.version;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkOSV(name, version) {
|
return {
|
||||||
|
name: packageMatch[1],
|
||||||
|
version: packageMatch[2] || 'latest'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function separatePackagesAndFlags(args) {
|
||||||
|
const packageArguments = args.filter(arg => !arg.startsWith('-'));
|
||||||
|
const flagArguments = args.filter(arg => arg.startsWith('-'));
|
||||||
|
|
||||||
|
return {
|
||||||
|
packages: packageArguments,
|
||||||
|
flags: flagArguments.join(' ')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// VERSION RESOLUTION
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async function fetchLatestVersion(packageName) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://api.osv.dev/v1/query', {
|
const response = await fetch(`${NPM_REGISTRY_URL}/${packageName}/latest`, {
|
||||||
method: 'POST',
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ package: { name, ecosystem: 'npm' }, version }),
|
|
||||||
signal: AbortSignal.timeout(10000)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) return null;
|
if (!response.ok) {
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
for (const vuln of data.vulns || []) {
|
|
||||||
const summary = [vuln.summary || '', vuln.details || ''].join(' ').toLowerCase();
|
|
||||||
if (MALWARE_KEYWORDS.some(kw => summary.includes(kw))) {
|
|
||||||
return vuln.summary || 'Malware detected';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkGitHub(name) {
|
const packageData = await response.json();
|
||||||
try {
|
return packageData.version;
|
||||||
const response = await fetch(
|
} catch {
|
||||||
'https://api.github.com/advisories?ecosystem=npm&type=malware&per_page=100',
|
|
||||||
{
|
|
||||||
headers: { 'Accept': 'application/vnd.github+json' },
|
|
||||||
signal: AbortSignal.timeout(10000)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) return null;
|
|
||||||
|
|
||||||
const advisories = await response.json();
|
|
||||||
for (const advisory of advisories) {
|
|
||||||
for (const vuln of advisory.vulnerabilities || []) {
|
|
||||||
if (vuln.package?.name === name) {
|
|
||||||
return advisory.summary || 'Malware advisory';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolvePackageVersion(packageName, requestedVersion) {
|
||||||
|
if (requestedVersion !== 'latest') {
|
||||||
|
return requestedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchLatestVersion(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PACKAGE INSTALLATION
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function installPackages(packageNames, flags) {
|
||||||
|
const packageList = packageNames.join(' ');
|
||||||
|
const installCommand = `pnpm add ${packageList} ${flags}`.trim();
|
||||||
|
|
||||||
|
execSync(installCommand, { stdio: 'inherit' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAIN
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async function validateAndCheckPackage(packageArgument) {
|
||||||
|
const parsedPackage = parsePackageArgument(packageArgument);
|
||||||
|
|
||||||
|
if (!parsedPackage) {
|
||||||
|
console.error(`Invalid package argument: ${packageArgument}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedVersion = await resolvePackageVersion(parsedPackage.name, parsedPackage.version);
|
||||||
|
|
||||||
|
if (!resolvedVersion) {
|
||||||
|
console.error(`Could not resolve version for ${parsedPackage.name}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const malwareReason = await checkSinglePackageForMalware(parsedPackage.name, resolvedVersion);
|
||||||
|
|
||||||
|
if (malwareReason) {
|
||||||
|
printInstallBlockedWarning(parsedPackage.name, resolvedVersion, malwareReason);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
printPackageClean(parsedPackage.name, resolvedVersion);
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const args = process.argv.slice(2);
|
const commandLineArgs = process.argv.slice(2);
|
||||||
const packages = args.filter(arg => !arg.startsWith('-'));
|
const { packages, flags } = separatePackagesAndFlags(commandLineArgs);
|
||||||
const flags = args.filter(arg => arg.startsWith('-')).join(' ');
|
|
||||||
|
|
||||||
if (!packages.length) {
|
if (!packages.length) {
|
||||||
console.log('Usage: pnpm run add <package>[@version] [-- -D]');
|
console.log('Usage: pnpm run add <package>[@version] [-- -D]');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('\n🔒 Checking packages against security databases...\n');
|
printSecurityCheckHeader();
|
||||||
|
|
||||||
for (const arg of packages) {
|
for (const packageArgument of packages) {
|
||||||
const pkg = parsePackageArg(arg);
|
await validateAndCheckPackage(packageArgument);
|
||||||
if (!pkg) {
|
|
||||||
console.error(`Invalid package: ${arg}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = await resolveVersion(pkg.name, pkg.version);
|
|
||||||
if (!version) {
|
|
||||||
console.error(`Could not resolve version for ${pkg.name}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(` 📦 ${pkg.name}@${version}`);
|
|
||||||
|
|
||||||
const [osvResult, ghResult] = await Promise.all([
|
|
||||||
checkOSV(pkg.name, version),
|
|
||||||
checkGitHub(pkg.name)
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (osvResult || ghResult) {
|
|
||||||
console.error(`\n❌ BLOCKED: ${pkg.name}@${version}`);
|
|
||||||
console.error(` ${osvResult || ghResult}\n`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(` ✅ Clean\n`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Installing...\n');
|
console.log('Installing...\n');
|
||||||
execSync(`pnpm add ${packages.join(' ')} ${flags}`, { stdio: 'inherit' });
|
installPackages(packages, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lockfile parser - extracts package information from pnpm-lock.yaml.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PACKAGE NAME PARSING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function parsePackagePathEntry(fullPath) {
|
||||||
|
const lastAtIndex = fullPath.lastIndexOf('@');
|
||||||
|
|
||||||
|
if (lastAtIndex <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageName = fullPath.substring(0, lastAtIndex);
|
||||||
|
const packageVersion = fullPath.substring(lastAtIndex + 1);
|
||||||
|
|
||||||
|
return { name: packageName, version: packageVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePackageFromDiffLine(line) {
|
||||||
|
const packagePathMatch = line.match(/^\+\s+'?\/([^']+)'?:/);
|
||||||
|
|
||||||
|
if (!packagePathMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullPath = packagePathMatch[1];
|
||||||
|
const versionMatch = fullPath.match(/@(\d+\.\d+\.\d+[^/]*)/);
|
||||||
|
|
||||||
|
if (!versionMatch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isScoped = fullPath.startsWith('@');
|
||||||
|
const packageName = isScoped
|
||||||
|
? fullPath.replace(/@[^/]+$/, '')
|
||||||
|
: fullPath.split('@')[0];
|
||||||
|
|
||||||
|
return { name: packageName, version: versionMatch[1] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// LOCKFILE READING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export function readAllPackagesFromLockfile(lockfilePath) {
|
||||||
|
try {
|
||||||
|
const lockfileContent = readFileSync(lockfilePath, 'utf-8');
|
||||||
|
const packages = [];
|
||||||
|
const packagePathRegex = /^\s+'?\/([^'(]+)'/gm;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = packagePathRegex.exec(lockfileContent)) !== null) {
|
||||||
|
const parsedPackage = parsePackagePathEntry(match[1]);
|
||||||
|
|
||||||
|
if (parsedPackage) {
|
||||||
|
packages.push(parsedPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readChangedPackagesFromGitDiff() {
|
||||||
|
try {
|
||||||
|
const diffOutput = execSync('git diff --cached pnpm-lock.yaml', { encoding: 'utf-8' });
|
||||||
|
const changedPackages = [];
|
||||||
|
const diffLines = diffOutput.split('\n');
|
||||||
|
|
||||||
|
for (const line of diffLines) {
|
||||||
|
const isAddedLine = line.startsWith('+') && !line.startsWith('+++');
|
||||||
|
|
||||||
|
if (isAddedLine) {
|
||||||
|
const parsedPackage = parsePackageFromDiffLine(line);
|
||||||
|
|
||||||
|
if (parsedPackage) {
|
||||||
|
changedPackages.push(parsedPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changedPackages;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Malware checker - queries OSV and GitHub Advisory databases for known malware.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const MALWARE_KEYWORDS = ['malware', 'malicious', 'compromised', 'supply chain', 'backdoor'];
|
||||||
|
const OSV_API_URL = 'https://api.osv.dev/v1/querybatch';
|
||||||
|
const OSV_SINGLE_API_URL = 'https://api.osv.dev/v1/query';
|
||||||
|
const GITHUB_API_URL = 'https://api.github.com/advisories?ecosystem=npm&type=malware&per_page=100';
|
||||||
|
const REQUEST_TIMEOUT_MS = 10000;
|
||||||
|
const BATCH_SIZE = 1000;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// UTILITY FUNCTIONS
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function containsMalwareKeyword(text) {
|
||||||
|
const lowerText = text.toLowerCase();
|
||||||
|
return MALWARE_KEYWORDS.some(keyword => lowerText.includes(keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitIntoBatches(items, batchSize) {
|
||||||
|
const batches = [];
|
||||||
|
for (let index = 0; index < items.length; index += batchSize) {
|
||||||
|
batches.push(items.slice(index, index + batchSize));
|
||||||
|
}
|
||||||
|
return batches;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deduplicateFindings(findings) {
|
||||||
|
return findings.filter((finding, index, array) =>
|
||||||
|
array.findIndex(other =>
|
||||||
|
other.name === finding.name && other.version === finding.version
|
||||||
|
) === index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OSV API
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function buildOsvQuery(packageName, packageVersion) {
|
||||||
|
return {
|
||||||
|
package: { name: packageName, ecosystem: 'npm' },
|
||||||
|
version: packageVersion
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildOsvBatchPayload(packages) {
|
||||||
|
return {
|
||||||
|
queries: packages.map(pkg => buildOsvQuery(pkg.name, pkg.version))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMalwareFromOsvVulnerability(vulnerability) {
|
||||||
|
const summaryText = [vulnerability.summary || '', vulnerability.details || ''].join(' ');
|
||||||
|
|
||||||
|
if (containsMalwareKeyword(summaryText)) {
|
||||||
|
return vulnerability.summary || 'Malware detected';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchOsvBatch(packages) {
|
||||||
|
const response = await fetch(OSV_API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(buildOsvBatchPayload(packages)),
|
||||||
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS * 3)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchOsvSingle(packageName, packageVersion) {
|
||||||
|
const response = await fetch(OSV_SINGLE_API_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(buildOsvQuery(packageName, packageVersion)),
|
||||||
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function processBatchResults(batchResults, packages) {
|
||||||
|
const findings = [];
|
||||||
|
const results = batchResults.results || [];
|
||||||
|
|
||||||
|
for (let index = 0; index < results.length; index++) {
|
||||||
|
const vulnerabilities = results[index].vulns || [];
|
||||||
|
|
||||||
|
for (const vulnerability of vulnerabilities) {
|
||||||
|
const malwareReason = extractMalwareFromOsvVulnerability(vulnerability);
|
||||||
|
|
||||||
|
if (malwareReason) {
|
||||||
|
findings.push({
|
||||||
|
name: packages[index].name,
|
||||||
|
version: packages[index].version,
|
||||||
|
reason: malwareReason,
|
||||||
|
source: 'OSV'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return findings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkPackagesWithOsv(packages) {
|
||||||
|
if (!packages.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFindings = [];
|
||||||
|
const batches = splitIntoBatches(packages, BATCH_SIZE);
|
||||||
|
|
||||||
|
for (const batch of batches) {
|
||||||
|
try {
|
||||||
|
const batchResults = await fetchOsvBatch(batch);
|
||||||
|
|
||||||
|
if (batchResults) {
|
||||||
|
const findings = processBatchResults(batchResults, batch);
|
||||||
|
allFindings.push(...findings);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Network errors are non-fatal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allFindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkSinglePackageWithOsv(packageName, packageVersion) {
|
||||||
|
try {
|
||||||
|
const data = await fetchOsvSingle(packageName, packageVersion);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const vulnerability of data.vulns || []) {
|
||||||
|
const malwareReason = extractMalwareFromOsvVulnerability(vulnerability);
|
||||||
|
|
||||||
|
if (malwareReason) {
|
||||||
|
return malwareReason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Network errors are non-fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GITHUB ADVISORY API
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
async function fetchGitHubAdvisories() {
|
||||||
|
const response = await fetch(GITHUB_API_URL, {
|
||||||
|
headers: { 'Accept': 'application/vnd.github+json' },
|
||||||
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMalwarePackageNames(advisories) {
|
||||||
|
const malwarePackageNames = new Set();
|
||||||
|
|
||||||
|
for (const advisory of advisories) {
|
||||||
|
const vulnerabilities = advisory.vulnerabilities || [];
|
||||||
|
|
||||||
|
for (const vulnerability of vulnerabilities) {
|
||||||
|
const packageName = vulnerability.package?.name;
|
||||||
|
|
||||||
|
if (packageName) {
|
||||||
|
malwarePackageNames.add(packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return malwarePackageNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkPackagesWithGitHub(packages) {
|
||||||
|
if (!packages.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const findings = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const advisories = await fetchGitHubAdvisories();
|
||||||
|
|
||||||
|
if (!advisories) {
|
||||||
|
return findings;
|
||||||
|
}
|
||||||
|
|
||||||
|
const malwarePackageNames = extractMalwarePackageNames(advisories);
|
||||||
|
|
||||||
|
for (const pkg of packages) {
|
||||||
|
if (malwarePackageNames.has(pkg.name)) {
|
||||||
|
findings.push({
|
||||||
|
name: pkg.name,
|
||||||
|
version: pkg.version,
|
||||||
|
reason: 'Known malware package',
|
||||||
|
source: 'GitHub Advisory'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Network errors are non-fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
return findings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkSinglePackageWithGitHub(packageName) {
|
||||||
|
try {
|
||||||
|
const advisories = await fetchGitHubAdvisories();
|
||||||
|
|
||||||
|
if (!advisories) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const advisory of advisories) {
|
||||||
|
const vulnerabilities = advisory.vulnerabilities || [];
|
||||||
|
|
||||||
|
for (const vulnerability of vulnerabilities) {
|
||||||
|
if (vulnerability.package?.name === packageName) {
|
||||||
|
return advisory.summary || 'Malware advisory';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Network errors are non-fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// COMBINED CHECK
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export async function checkPackagesForMalware(packages) {
|
||||||
|
const [osvFindings, githubFindings] = await Promise.all([
|
||||||
|
checkPackagesWithOsv(packages),
|
||||||
|
checkPackagesWithGitHub(packages)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allFindings = [...osvFindings, ...githubFindings];
|
||||||
|
return deduplicateFindings(allFindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkSinglePackageForMalware(packageName, packageVersion) {
|
||||||
|
const [osvResult, githubResult] = await Promise.all([
|
||||||
|
checkSinglePackageWithOsv(packageName, packageVersion),
|
||||||
|
checkSinglePackageWithGitHub(packageName)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return osvResult || githubResult || null;
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report printer - formats and prints security findings to console.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SEPARATOR_CHAR = '=';
|
||||||
|
const WARNING_CHAR = '!';
|
||||||
|
const SEPARATOR_WIDTH = 70;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FORMATTING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function createSeparator(char = SEPARATOR_CHAR) {
|
||||||
|
return char.repeat(SEPARATOR_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPackageIdentifier(packageName, packageVersion) {
|
||||||
|
return `${packageName}@${packageVersion}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFindingLine(finding) {
|
||||||
|
const packageId = formatPackageIdentifier(finding.name, finding.version);
|
||||||
|
return ` ❌ ${packageId}\n ${finding.reason} (${finding.source})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// REPORT PRINTING
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export function printMalwareWarning(findings) {
|
||||||
|
console.error('\n' + createSeparator(WARNING_CHAR));
|
||||||
|
console.error('🚨 WARNING: MALICIOUS PACKAGES DETECTED');
|
||||||
|
console.error(createSeparator(WARNING_CHAR) + '\n');
|
||||||
|
|
||||||
|
for (const finding of findings) {
|
||||||
|
console.error(formatFindingLine(finding) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Remove these packages immediately:');
|
||||||
|
console.error(' pnpm remove <package-name>\n');
|
||||||
|
console.error(createSeparator(WARNING_CHAR) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printCommitBlockedWarning(findings) {
|
||||||
|
console.error(createSeparator());
|
||||||
|
console.error('🚨 MALICIOUS PACKAGES DETECTED - COMMIT BLOCKED');
|
||||||
|
console.error(createSeparator() + '\n');
|
||||||
|
|
||||||
|
for (const finding of findings) {
|
||||||
|
console.error(formatFindingLine(finding) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Remove these packages before committing:');
|
||||||
|
console.error(' pnpm remove <package-name>\n');
|
||||||
|
console.error(createSeparator() + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printInstallBlockedWarning(packageName, packageVersion, reason) {
|
||||||
|
console.error(`\n❌ BLOCKED: ${formatPackageIdentifier(packageName, packageVersion)}`);
|
||||||
|
console.error(` ${reason}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printCheckingMessage(packageCount) {
|
||||||
|
console.log(`\n🔍 Checking ${packageCount} packages against security databases...\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printPackageClean(packageName, packageVersion) {
|
||||||
|
console.log(` 📦 ${formatPackageIdentifier(packageName, packageVersion)}`);
|
||||||
|
console.log(` ✅ Clean\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printAllPackagesClean() {
|
||||||
|
console.log('✅ All new packages passed security checks\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printSecurityCheckHeader() {
|
||||||
|
console.log('\n🔒 Checking packages against security databases...\n');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user