mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Implement modification of request headerrs
This commit is contained in:
parent
76a1100b8c
commit
3bf7279195
3 changed files with 84 additions and 16 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import chalk from "chalk";
|
||||||
import { isMalwarePackage } from "../../scanning/audit/index.js";
|
import { isMalwarePackage } from "../../scanning/audit/index.js";
|
||||||
import { createInterceptorBuilder } from "./interceptorBuilder.js";
|
import { createInterceptorBuilder } from "./interceptorBuilder.js";
|
||||||
|
|
||||||
|
|
@ -32,6 +33,15 @@ function buildNpmInterceptor(registry) {
|
||||||
if (await isMalwarePackage(packageName, version)) {
|
if (await isMalwarePackage(packageName, version)) {
|
||||||
req.blockMalware(packageName, version, req.targetUrl);
|
req.blockMalware(packageName, version, req.targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.modifyRequestHeaders((headers) => {
|
||||||
|
if (headers["accept"]?.includes("application/vnd.npm.install-v1+json")) {
|
||||||
|
// The npm registry sometimes serves a more compact format that lacks
|
||||||
|
// the time metadata we need to filter out too new packages.
|
||||||
|
// Force the registry to return the full metadata by changing the Accept header.
|
||||||
|
headers["accept"] = "application/json";
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,13 @@
|
||||||
* @property {string} targetUrl
|
* @property {string} targetUrl
|
||||||
* @property {(statusCode: number, message: string) => void} blockRequest
|
* @property {(statusCode: number, message: string) => void} blockRequest
|
||||||
* @property {(packageName: string | undefined, version: string | undefined, url: string) => void} blockMalware
|
* @property {(packageName: string | undefined, version: string | undefined, url: string) => void} blockMalware
|
||||||
|
* @property {(modificationFunc: (headers: NodeJS.Dict<string | string[]>) => void) => void} modifyRequestHeaders
|
||||||
* @property {() => RequestInterceptor} build
|
* @property {() => RequestInterceptor} build
|
||||||
*
|
*
|
||||||
* @typedef {Object} RequestInterceptor
|
* @typedef {Object} RequestInterceptor
|
||||||
* @property {{statusCode: number, message: string} | undefined} blockResponse
|
* @property {{statusCode: number, message: string} | undefined} blockResponse
|
||||||
|
* @property {(headers: NodeJS.Dict<string | string[]> | undefined) => void} modifyRequestHeaders
|
||||||
|
* @property {() => boolean} modifiesResponse
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -18,6 +21,15 @@ export function createRequestInterceptorBuilder(targetUrl, eventEmitter) {
|
||||||
/** @type {{statusCode: number, message: string} | undefined} */
|
/** @type {{statusCode: number, message: string} | undefined} */
|
||||||
let blockResponse = undefined;
|
let blockResponse = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {{
|
||||||
|
* requestHeaders: Array<(headers: NodeJS.Dict<string | string[]>) => void>
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
let modificationFuncs = {
|
||||||
|
requestHeaders: [],
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} statusCode
|
* @param {number} statusCode
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
|
|
@ -47,10 +59,43 @@ export function createRequestInterceptorBuilder(targetUrl, eventEmitter) {
|
||||||
targetUrl,
|
targetUrl,
|
||||||
blockRequest,
|
blockRequest,
|
||||||
blockMalware,
|
blockMalware,
|
||||||
|
modifyRequestHeaders(modificationFunc) {
|
||||||
|
modificationFuncs.requestHeaders.push(modificationFunc);
|
||||||
|
},
|
||||||
build() {
|
build() {
|
||||||
return {
|
return createRequestInterceptor(
|
||||||
blockResponse,
|
blockResponse,
|
||||||
};
|
modificationFuncs.requestHeaders
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{statusCode: number, message: string} | undefined} blockResponse
|
||||||
|
* @param {Array<(headers: NodeJS.Dict<string | string[]>) => void>} requestHeadersModficationFuncs
|
||||||
|
* @returns {RequestInterceptor}
|
||||||
|
*/
|
||||||
|
function createRequestInterceptor(
|
||||||
|
blockResponse,
|
||||||
|
requestHeadersModficationFuncs
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* @param {NodeJS.Dict<string | string[]> | undefined} headers
|
||||||
|
*/
|
||||||
|
function modifyRequestHeaders(headers) {
|
||||||
|
if (!headers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const modificationFunc of requestHeadersModficationFuncs) {
|
||||||
|
modificationFunc(headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifiesResponse() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { blockResponse, modifyRequestHeaders, modifiesResponse };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import("./interceptors/interceptorBuilder.js").Interceptor} Interceptor
|
* @typedef {import("./interceptors/interceptorBuilder.js").Interceptor} Interceptor
|
||||||
|
* @typedef {import("./interceptors/requestInterceptorBuilder.js").RequestInterceptor} RequestInterceptor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,18 +69,20 @@ function createHttpsServer(hostname, interceptor) {
|
||||||
const pathAndQuery = getRequestPathAndQuery(req.url);
|
const pathAndQuery = getRequestPathAndQuery(req.url);
|
||||||
const targetUrl = `https://${hostname}${pathAndQuery}`;
|
const targetUrl = `https://${hostname}${pathAndQuery}`;
|
||||||
|
|
||||||
const interceptorResult = await interceptor.handleRequest(targetUrl);
|
const requestInterceptor = await interceptor.handleRequest(targetUrl);
|
||||||
const blockResponse = interceptorResult.blockResponse;
|
|
||||||
|
|
||||||
if (blockResponse) {
|
if (requestInterceptor.blockResponse) {
|
||||||
ui.writeVerbose(`Safe-chain: Blocking request to ${targetUrl}`);
|
ui.writeVerbose(`Safe-chain: Blocking request to ${targetUrl}`);
|
||||||
res.writeHead(blockResponse.statusCode, blockResponse.message);
|
res.writeHead(
|
||||||
res.end(blockResponse.message);
|
requestInterceptor.blockResponse.statusCode,
|
||||||
|
requestInterceptor.blockResponse.message
|
||||||
|
);
|
||||||
|
res.end(requestInterceptor.blockResponse.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect request body
|
// Collect request body
|
||||||
forwardRequest(req, hostname, res);
|
forwardRequest(req, hostname, res, requestInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = https.createServer(
|
const server = https.createServer(
|
||||||
|
|
@ -109,9 +112,10 @@ function getRequestPathAndQuery(url) {
|
||||||
* @param {import("http").IncomingMessage} req
|
* @param {import("http").IncomingMessage} req
|
||||||
* @param {string} hostname
|
* @param {string} hostname
|
||||||
* @param {import("http").ServerResponse} res
|
* @param {import("http").ServerResponse} res
|
||||||
|
* @param {RequestInterceptor} requestInterceptor
|
||||||
*/
|
*/
|
||||||
function forwardRequest(req, hostname, res) {
|
function forwardRequest(req, hostname, res, requestInterceptor) {
|
||||||
const proxyReq = createProxyRequest(hostname, req, res);
|
const proxyReq = createProxyRequest(hostname, req, res, requestInterceptor);
|
||||||
|
|
||||||
proxyReq.on("error", (err) => {
|
proxyReq.on("error", (err) => {
|
||||||
ui.writeVerbose(
|
ui.writeVerbose(
|
||||||
|
|
@ -142,10 +146,17 @@ function forwardRequest(req, hostname, res) {
|
||||||
* @param {string} hostname
|
* @param {string} hostname
|
||||||
* @param {import("http").IncomingMessage} req
|
* @param {import("http").IncomingMessage} req
|
||||||
* @param {import("http").ServerResponse} res
|
* @param {import("http").ServerResponse} res
|
||||||
|
* @param {RequestInterceptor} requestInterceptor
|
||||||
*
|
*
|
||||||
* @returns {import("http").ClientRequest}
|
* @returns {import("http").ClientRequest}
|
||||||
*/
|
*/
|
||||||
function createProxyRequest(hostname, req, res) {
|
function createProxyRequest(hostname, req, res, requestInterceptor) {
|
||||||
|
const headers = { ...req.headers };
|
||||||
|
if (headers.host) {
|
||||||
|
delete headers.host;
|
||||||
|
}
|
||||||
|
requestInterceptor.modifyRequestHeaders(headers);
|
||||||
|
|
||||||
/** @type {import("http").RequestOptions} */
|
/** @type {import("http").RequestOptions} */
|
||||||
const options = {
|
const options = {
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
|
|
@ -155,10 +166,6 @@ function createProxyRequest(hostname, req, res) {
|
||||||
headers: { ...req.headers },
|
headers: { ...req.headers },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.headers && "host" in options.headers) {
|
|
||||||
delete options.headers.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
|
||||||
if (httpsProxy) {
|
if (httpsProxy) {
|
||||||
options.agent = new HttpsProxyAgent(httpsProxy);
|
options.agent = new HttpsProxyAgent(httpsProxy);
|
||||||
|
|
@ -183,7 +190,13 @@ function createProxyRequest(hostname, req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
res.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||||
proxyRes.pipe(res);
|
|
||||||
|
if (!requestInterceptor.modifiesResponse) {
|
||||||
|
// If the response is not being modified, we can
|
||||||
|
// just pipe without the need for
|
||||||
|
proxyRes.pipe(res);
|
||||||
|
} else {
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return proxyReq;
|
return proxyReq;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue