mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
[ci:force] - Using audit in place of custom scripts
This commit is contained in:
@@ -43,6 +43,9 @@ runs:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Security audit
|
||||
shell: bash
|
||||
run: pnpm audit --audit-level=critical
|
||||
- name: Restore nx cache
|
||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Build affected libs
|
||||
env:
|
||||
BASE_REF: ${{ inputs.base_ref }}
|
||||
run: npx nx affected --target=build --base=origin/$BASE_REF --head=HEAD --configuration=production --exclude=stories
|
||||
run: pnpm nx affected --target=build --base=origin/$BASE_REF --head=HEAD --configuration=production --exclude=stories
|
||||
- name: Save nx cache
|
||||
if: ${{ success() }}
|
||||
uses: ./.github/actions/save-nx-cache
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
- name: Run lint
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref || 'develop' }}
|
||||
run: npx nx affected --target=lint --base=origin/$BASE_REF --head=HEAD
|
||||
run: pnpm nx affected --target=lint --base=origin/$BASE_REF --head=HEAD
|
||||
- name: Save nx cache
|
||||
if: ${{ success() }}
|
||||
uses: ./.github/actions/save-nx-cache
|
||||
@@ -243,7 +243,7 @@ jobs:
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref || 'develop' }}
|
||||
run: |
|
||||
npx nx affected --target=build-storybook --base=origin/$BASE_REF --head=HEAD --configuration=ci
|
||||
pnpm nx affected --target=build-storybook --base=origin/$BASE_REF --head=HEAD --configuration=ci
|
||||
- name: Save nx cache
|
||||
if: ${{ success() }}
|
||||
uses: ./.github/actions/save-nx-cache
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
BASE_REF: ${{ inputs.base_ref }}
|
||||
run: |
|
||||
echo "Base ref is $BASE_REF"
|
||||
AFFECTED_UNIT=$(npx nx show projects --affected --target=test --base=origin/$BASE_REF --head=HEAD --select=projects --plain --exclude=cli,stories,eslint-angular)
|
||||
AFFECTED_UNIT=$(pnpm nx show projects --affected --target=test --base=origin/$BASE_REF --head=HEAD --select=projects --plain --exclude=cli,stories,eslint-angular)
|
||||
echo "Affected projects for UNIT: $AFFECTED_UNIT"
|
||||
|
||||
if [ -z "$AFFECTED_UNIT" ]; then
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
env:
|
||||
NODE_OPTIONS: "--max-old-space-size=5120"
|
||||
run: |
|
||||
xvfb-run --auto-servernum npx nx run ${{ matrix.project }}:test
|
||||
xvfb-run --auto-servernum pnpm nx run ${{ matrix.project }}:test
|
||||
- name: Save nx cache
|
||||
if: ${{ success() }}
|
||||
uses: ./.github/actions/save-nx-cache
|
||||
|
||||
@@ -2,9 +2,4 @@
|
||||
|
||||
export NODE_OPTIONS=--max_old_space_size=8192
|
||||
|
||||
# Check new packages against security databases
|
||||
if git diff --cached --name-only | grep -q "pnpm-lock.yaml"; then
|
||||
node scripts/check-new-packages.mjs
|
||||
fi
|
||||
|
||||
lint-staged
|
||||
|
||||
+1
-2
@@ -5,8 +5,7 @@
|
||||
"author": "Hyland Software, Inc. and its affiliates",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare": "husky && nx run eslint-angular:build && node scripts/post-install-check.mjs",
|
||||
"add": "node scripts/safe-add.mjs",
|
||||
"prepare": "husky && nx run eslint-angular:build && pnpm audit --audit-level=critical",
|
||||
"bundle:js-api": "nx run js-api:bundle",
|
||||
"bundle:cli": "nx run cli:bundle",
|
||||
"test:affected": "nx affected:test",
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Pre-commit security check.
|
||||
*
|
||||
* Runs via husky pre-commit hook.
|
||||
* Blocks commits containing known malicious packages.
|
||||
*/
|
||||
|
||||
import { readChangedPackagesFromGitDiff } from './security/lockfile-parser.mjs';
|
||||
import { checkPackagesForMalware } from './security/malware-checker.mjs';
|
||||
import {
|
||||
printCheckingMessage,
|
||||
printCommitBlockedWarning,
|
||||
printAllPackagesClean
|
||||
} from './security/report-printer.mjs';
|
||||
|
||||
async function main() {
|
||||
const changedPackages = readChangedPackagesFromGitDiff();
|
||||
|
||||
if (!changedPackages.length) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
printCheckingMessage(changedPackages.length);
|
||||
|
||||
const malwareFindings = await checkPackagesForMalware(changedPackages);
|
||||
|
||||
if (malwareFindings.length > 0) {
|
||||
printCommitBlockedWarning(malwareFindings);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
printAllPackagesClean();
|
||||
}
|
||||
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Security check error:', error.message);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Post-install security check.
|
||||
*
|
||||
* Runs after pnpm install via the prepare hook.
|
||||
* Warns if any installed packages are known malware.
|
||||
*/
|
||||
|
||||
import { join, dirname } from 'node:path';
|
||||
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 ROOT_DIR = join(__dirname, '..');
|
||||
const LOCKFILE_PATH = join(ROOT_DIR, 'pnpm-lock.yaml');
|
||||
|
||||
async function main() {
|
||||
const installedPackages = readAllPackagesFromLockfile(LOCKFILE_PATH);
|
||||
|
||||
if (!installedPackages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const malwareFindings = await checkPackagesForMalware(installedPackages);
|
||||
|
||||
if (malwareFindings.length > 0) {
|
||||
printMalwareWarning(malwareFindings);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(() => {});
|
||||
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Safe add package.
|
||||
*
|
||||
* Checks packages against security databases before installing.
|
||||
* Usage: pnpm run add <package>[@version] [-- -D]
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
import { checkSinglePackageForMalware } from './security/malware-checker.mjs';
|
||||
import {
|
||||
printSecurityCheckHeader,
|
||||
printPackageClean,
|
||||
printInstallBlockedWarning
|
||||
} from './security/report-printer.mjs';
|
||||
|
||||
const NPM_REGISTRY_URL = 'https://registry.npmjs.org';
|
||||
const REQUEST_TIMEOUT_MS = 5000;
|
||||
|
||||
// ============================================================================
|
||||
// ARGUMENT PARSING
|
||||
// ============================================================================
|
||||
|
||||
function parsePackageArgument(argument) {
|
||||
const packageMatch = argument.match(/^(@?[^@]+)(?:@(.+))?$/);
|
||||
|
||||
if (!packageMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 {
|
||||
const response = await fetch(`${NPM_REGISTRY_URL}/${packageName}/latest`, {
|
||||
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const packageData = await response.json();
|
||||
return packageData.version;
|
||||
} catch {
|
||||
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() {
|
||||
const commandLineArgs = process.argv.slice(2);
|
||||
const { packages, flags } = separatePackagesAndFlags(commandLineArgs);
|
||||
|
||||
if (!packages.length) {
|
||||
console.log('Usage: pnpm run add <package>[@version] [-- -D]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
printSecurityCheckHeader();
|
||||
|
||||
for (const packageArgument of packages) {
|
||||
await validateAndCheckPackage(packageArgument);
|
||||
}
|
||||
|
||||
console.log('Installing...\n');
|
||||
installPackages(packages, flags);
|
||||
}
|
||||
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*!
|
||||
* @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 lastAtIndex = fullPath.lastIndexOf('@');
|
||||
const packageName = isScoped
|
||||
? fullPath.substring(0, lastAtIndex)
|
||||
: fullPath.split('@')[0];
|
||||
|
||||
return { name: packageName, version: versionMatch[1] };
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LOCKFILE READING
|
||||
// ============================================================================
|
||||
|
||||
function extractPackagePathFromLine(line) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (!trimmed.startsWith("'/") && !trimmed.startsWith('/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const startIndex = trimmed.indexOf('/') + 1;
|
||||
const endQuoteIndex = trimmed.indexOf("'", startIndex);
|
||||
const endParenIndex = trimmed.indexOf('(', startIndex);
|
||||
|
||||
let endIndex = trimmed.length;
|
||||
if (endQuoteIndex > 0) endIndex = Math.min(endIndex, endQuoteIndex);
|
||||
if (endParenIndex > 0) endIndex = Math.min(endIndex, endParenIndex);
|
||||
|
||||
return trimmed.substring(startIndex, endIndex);
|
||||
}
|
||||
|
||||
export function readAllPackagesFromLockfile(lockfilePath) {
|
||||
try {
|
||||
const lockfileContent = readFileSync(lockfilePath, 'utf-8');
|
||||
const packages = [];
|
||||
const lines = lockfileContent.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const packagePath = extractPackagePathFromLine(line);
|
||||
|
||||
if (packagePath) {
|
||||
const parsedPackage = parsePackagePathEntry(packagePath);
|
||||
|
||||
if (parsedPackage) {
|
||||
packages.push(parsedPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packages;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function readChangedPackagesFromGitDiff() {
|
||||
try {
|
||||
const diffOutput = execSync('/usr/bin/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 [];
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
/*!
|
||||
* @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;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*!
|
||||
* @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