Simplify interceptor code and rename variables for clarity.

This commit is contained in:
Sander Declerck 2025-11-12 14:03:33 +01:00
parent 2cf23d5109
commit ad6d9bcdd5
No known key found for this signature in database
4 changed files with 69 additions and 96 deletions

View file

@ -1,41 +1,32 @@
/** /**
* @typedef {import('./requestInterceptorBuilder.js').RequestInterceptorBuilder} RequestInterceptorBuilder
* @typedef {import('./requestInterceptorBuilder.js').RequestInterceptor} RequestInterceptor
*
* @typedef {Object} InterceptorBuilder
* @property {(requestFunc: (requestHandlerBuilder: RequestInterceptorBuilder) => Promise<void>) => void} onRequest
* @property {() => Interceptor} build
*
* @typedef {Object} Interceptor * @typedef {Object} Interceptor
* @property {(targetUrl: string) => Promise<RequestInterceptor>} handleRequest * @property {(targetUrl: string) => Promise<RequestInterceptionHandler>} handleRequest
* @property {(event: string, listener: (...args: any[]) => void) => Interceptor} on * @property {(event: string, listener: (...args: any[]) => void) => Interceptor} on
* @property {(event: string, ...args: any[]) => boolean} emit * @property {(event: string, ...args: any[]) => boolean} emit
*
*
* @typedef {Object} RequestInterceptionContext
* @property {string} targetUrl
* @property {(packageName: string | undefined, version: string | undefined) => void} blockMalware
* @property {() => RequestInterceptionHandler} build
*
*
* @typedef {Object} RequestInterceptionHandler
* @property {{statusCode: number, message: string} | undefined} blockResponse
*/ */
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { createRequestInterceptorBuilder } from "./requestInterceptorBuilder.js";
/** /**
* @returns {InterceptorBuilder} * @param {(requestHandlerBuilder: RequestInterceptionContext) => Promise<void>} requestInterceptionFunc
* @returns {Interceptor}
*/ */
export function createInterceptorBuilder() { export function interceptRequests(requestInterceptionFunc) {
/** return buildInterceptor([requestInterceptionFunc]);
* @type {Array<(requestHandlerBuilder: RequestInterceptorBuilder) => Promise<void>>}
*/
const requestHandlers = [];
return {
onRequest(requestFunc) {
requestHandlers.push(requestFunc);
},
build() {
return buildInterceptor(requestHandlers);
},
};
} }
/** /**
* @param {Array<(requestHandlerBuilder: RequestInterceptorBuilder) => Promise<void>>} requestHandlers * @param {Array<(requestHandlerBuilder: RequestInterceptionContext) => Promise<void>>} requestHandlers
* @returns {Interceptor} * @returns {Interceptor}
*/ */
function buildInterceptor(requestHandlers) { function buildInterceptor(requestHandlers) {
@ -43,7 +34,7 @@ function buildInterceptor(requestHandlers) {
return { return {
async handleRequest(targetUrl) { async handleRequest(targetUrl) {
const reqInterceptorBuilder = createRequestInterceptorBuilder( const reqInterceptorBuilder = createRequestContext(
targetUrl, targetUrl,
eventEmitter eventEmitter
); );
@ -63,3 +54,47 @@ function buildInterceptor(requestHandlers) {
}, },
}; };
} }
/**
* @param {string} targetUrl
* @param {import('events').EventEmitter} eventEmitter
* @returns {RequestInterceptionContext}
*/
function createRequestContext(targetUrl, eventEmitter) {
/** @type {{statusCode: number, message: string} | undefined} */
let blockResponse = undefined;
/**
* @param {number} statusCode
* @param {string} message
*/
function blockRequest(statusCode, message) {
blockResponse = { statusCode, message };
}
/**
* @param {string | undefined} packageName
* @param {string | undefined} version
*/
function blockMalware(packageName, version) {
blockRequest(403, "Forbidden - blocked by safe-chain");
// Emit the malwareBlocked event
eventEmitter.emit("malwareBlocked", {
packageName,
version,
targetUrl,
timestamp: Date.now(),
});
}
return {
targetUrl,
blockMalware,
build() {
return {
blockResponse,
};
},
};
}

View file

@ -1,5 +1,5 @@
import { isMalwarePackage } from "../../scanning/audit/index.js"; import { isMalwarePackage } from "../../scanning/audit/index.js";
import { createInterceptorBuilder } from "./interceptorBuilder.js"; import { interceptRequests } from "./interceptorBuilder.js";
const knownJsRegistries = ["registry.npmjs.org", "registry.yarnpkg.com"]; const knownJsRegistries = ["registry.npmjs.org", "registry.yarnpkg.com"];
@ -22,19 +22,15 @@ export function npmInterceptorForUrl(url) {
* @returns {import("./interceptorBuilder.js").Interceptor | undefined} * @returns {import("./interceptorBuilder.js").Interceptor | undefined}
*/ */
function buildNpmInterceptor(registry) { function buildNpmInterceptor(registry) {
const builder = createInterceptorBuilder(); return interceptRequests(async (reqContext) => {
builder.onRequest(async (req) => {
const { packageName, version } = parseNpmPackageUrl( const { packageName, version } = parseNpmPackageUrl(
req.targetUrl, reqContext.targetUrl,
registry registry
); );
if (await isMalwarePackage(packageName, version)) { if (await isMalwarePackage(packageName, version)) {
req.blockMalware(packageName, version, req.targetUrl); reqContext.blockMalware(packageName, version);
} }
}); });
return builder.build();
} }
/** /**

View file

@ -1,5 +1,5 @@
import { isMalwarePackage } from "../../scanning/audit/index.js"; import { isMalwarePackage } from "../../scanning/audit/index.js";
import { createInterceptorBuilder } from "./interceptorBuilder.js"; import { interceptRequests } from "./interceptorBuilder.js";
const knownPipRegistries = [ const knownPipRegistries = [
"files.pythonhosted.org", "files.pythonhosted.org",
@ -27,19 +27,15 @@ export function pipInterceptorForUrl(url) {
* @returns {import("./interceptorBuilder.js").Interceptor | undefined} * @returns {import("./interceptorBuilder.js").Interceptor | undefined}
*/ */
function buildPipInterceptor(registry) { function buildPipInterceptor(registry) {
const builder = createInterceptorBuilder(); return interceptRequests(async (reqContext) => {
builder.onRequest(async (req) => {
const { packageName, version } = parsePipPackageFromUrl( const { packageName, version } = parsePipPackageFromUrl(
req.targetUrl, reqContext.targetUrl,
registry registry
); );
if (await isMalwarePackage(packageName, version)) { if (await isMalwarePackage(packageName, version)) {
req.blockMalware(packageName, version, req.targetUrl); reqContext.blockMalware(packageName, version);
} }
}); });
return builder.build();
} }
/** /**

View file

@ -1,54 +0,0 @@
/**
* @typedef {Object} RequestInterceptorBuilder
* @property {string} targetUrl
* @property {(packageName: string | undefined, version: string | undefined, url: string) => void} blockMalware
* @property {() => RequestInterceptor} build
*
* @typedef {Object} RequestInterceptor
* @property {{statusCode: number, message: string} | undefined} blockResponse
*/
/**
* @param {string} targetUrl
* @param {import('events').EventEmitter} eventEmitter
* @returns {RequestInterceptorBuilder}
*/
export function createRequestInterceptorBuilder(targetUrl, eventEmitter) {
/** @type {{statusCode: number, message: string} | undefined} */
let blockResponse = undefined;
/**
* @param {number} statusCode
* @param {string} message
*/
function blockRequest(statusCode, message) {
blockResponse = { statusCode, message };
}
/**
* @param {string | undefined} packageName
* @param {string | undefined} version
* @param {string} url
*/
function blockMalware(packageName, version, url) {
blockRequest(403, "Forbidden - blocked by safe-chain");
// Emit the malwareBlocked event
eventEmitter.emit("malwareBlocked", {
packageName,
version,
url,
timestamp: Date.now(),
});
}
return {
targetUrl,
blockMalware,
build() {
return {
blockResponse,
};
},
};
}