Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
It's a subset of Edge Side Include standard implemented with promise-based interface.
Let's say you want to use ESI in your project, but also want to retain good developer experience.
Rather than having to configure Varnish or Ngnix to take care of server-rendered ESI tags locally you can simply pass the server output through esi.process
function right before pushing it out to the client.
const response = obtainServerResponseWithEsiTags();
return Promise.resolve()
.then(function() {
if(process.env.NODE_ENV !== 'production') {
return esi.process(response);
}
return response;
});
It also improves code mobility - if for whatever reason you decide to move from ESI-enabled environment into one that doesn't support it (yet?), all you have to do is to process the response directly on the server. This module should be performant enough for that use case.
<esi:include>
tags
alt
as fallback URL…and more, take a look at test cases for complete list.
nodesi
does not support the entire ESI spec, but aims to provide a usable subset. This includes, of course <esi:include src="…">
, but also some more advanced features like:
alt
<esi:include src="http://example.com/1.html" alt="http://bak.example.com/2.html"/>
Will try to include http://example.com/1.html
first, and if that fails, fall back to http://bak.example.com/2.html
. If both requests fail, the standard error handling described below will kick in.
npm install nodesi
const ESI = require('nodesi');
const esi = new ESI({
allowedHosts: ['http://full-resource-path']
});
esi.process('<esi:include src="http://full-resource-path/stuff.html" />').then(function(result) {
// result is a fetched html
});
const esiMiddleware = require('nodesi').middleware;
const app = require('express')();
// inject the middleware before your route handlers
app.use(esiMiddleware());
All the ESI constructor options described below are also applicable for the middleware function.
Just pass them like that: esiMiddleWare({baseUrl: ..., allowedHosts: [...]});
If you'd like to pass options like headers to ESI middleware, use req.esiOptions
object:
...
app.use(esiMiddleware());
app.get('/example', function(req, res) {
req.esiOptions = {
headers: {
'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
}
};
res.render('example');
});
If you'd like to adjust the baseUrl dynamically, use req.esiOptions
object:
...
app.use(esiMiddleware());
app.get('/example', function(req, res) {
req.esiOptions = {
baseUrl: req.url
};
res.render('example');
});
const ESI = require('nodesi');
const esi = new ESI({
baseUrl: 'http://full-resource-path'
});
esi.process('<esi:include src="/stuff.html" />').then(function(result) {
// result is a fetched html
});
const ESI = require('nodesi');
const esi = new ESI({
baseUrl: 'http://full-resource-path'
});
esi.process('<esi:include src="/stuff.html" />', {
headers: {
'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
}
}).then(function(result) {
// result is a fetched html
});
By default, nodesi uses a native fetch
to fetch the data.
It uses a limited set of features of the fetch API, so it's easy to provide a custom fetch implementation.
The required subset of the fetch interface looks like this in TS types:
type Options = {
headers: Record<string, string>; // HTTP request headers to be attached
// plus any other custom options you set:
// * calling esi.process
// * adding req.esiOptions = { ... }
};
type Response = {
status: number; // http status code: 200, 400, 500, etc
statusText: string; // status description
text: () => Promise<string>; // returns the fetched text content
};
// https://developer.mozilla.org/en-US/docs/Web/API/fetch
type FetchFunction = (resource: string, options: Options) => Promise<Response>;
const ESI = require('nodesi');
const esi = new ESI({
httpClient: (url, options) => {
return Promise.resolve({
status: 200,
statusText: 'ok', // this is used only when status >= 400
text: () => Promise.resolve('custom static response'),
});
},
});
Since this module performs HTTP calls to external services, it is possible for a malicious agent to exploit that, especially if content of a esi:include tag can be provided by user.
In order to mitigate that risk you should use allowedHosts
configuration option. It's supposed to be a list of trusted hosts (protocol + hostname + port), represented as strings or regular expressions.
const esi = new ESI({
allowedHosts: ['https://exact-host:3000', /^http(s)?:\/\/other-host$/]
});
If you're using baseUrl
option then it's host will automatically be added to allowedHosts
.
In case some url gets blocked you'll receive an error in your onError
handler (see below) with blocked
property set to true
.
You can provide onError callback to a ESI constructor. It will recieve two arguments: source URL and error object.
It should return a string that will be put in place of errorous content.
const esi = new ESI({
onError: function(src, error) {
if(error.statusCode === 404) {
return 'Not found';
}
return '';
}
});
It's a common anti-pattern that libraries write to stdout w/o users permission.
We want to be nice so you can provide your own logging output with logTo
configuration option.
It's expected to be an object with "write" method on it that accepts a single string.
Logging to a custom object
const esi = new ESI({
logTo: {
write: function(log) {
// do some stuff with log string here
}
}
});
Logging to a standard output (same as console.log):
const esi = new ESI({
logTo: process.stdout
});
Logging to a file (possible, but please don't do that):
const logFile = require('fs').createWriteStream('./log.txt');
const esi = new ESI({
logTo: logFile
});
By default url passed as an argument in ESI tag gets decoded.
You might want to not have it decoded from some purposes, so you can pass decodeUrl: false
config item.
const ESI = require('nodesi');
const esi = new ESI({
baseUrl: 'https://example.com',
decodeUrl: false,
});
esi.process('<esi:include src="/path?foo=bar&baz=bat" />').then(function(result) {
// result is a fetched content
// when decodeUrl is set to false, https://example.com/path?foo=bar&baz=bat will be fetched
// when decodeUrl is set to true or not set, https://example.com/path?foo=bar&baz=bat will be fetched
});
You can run performance tests with npm run perf /test
and npm run perf /noop
that will test the base performance of your system
without nodesi.
nodesi
is made available under the conditions of the ISC license
FAQs
ESI: the good parts in node.js
The npm package nodesi receives a total of 5,419 weekly downloads. As such, nodesi popularity was classified as popular.
We found that nodesi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 4 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.