diff --git a/Dockerfile b/Dockerfile index 82c5c39..39dae90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine3.17 +FROM node:22-alpine ENV NODE_ENV production diff --git a/index.js b/index.js index cf6f695..1aa5c5c 100644 --- a/index.js +++ b/index.js @@ -37,7 +37,7 @@ app.use( }), ); -app.use(express.urlencoded()); +app.use(express.urlencoded({ limit: process.env.EXPRESS_JSON_LIMIT || '100kb', extended: false })); if (process.env.RATE_LIMIT_PER_MIN) { const limitMax = parseInt(process.env.RATE_LIMIT_PER_MIN, 10); @@ -122,7 +122,7 @@ function failSvg(res, msg, statusCode = 500) { -

${msg}

+

${String(msg).replace(/&/g,'&').replace(//g,'>')}

`); } @@ -538,24 +538,24 @@ app.get('/chart/render/:key', async (req, res) => { db.get('SELECT config FROM charts WHERE id = ?', [key], function(err, row) { if (err) { - res.status(500).json({ error: err.message }); + return res.status(500).json({ error: err.message }); // add return — else falls through to row check with null row } if (!row) { return res.status(404).json({ error: 'Template not found' }); } - //return res.status(200).json({status: 'success'}); let chartConfig = JSON.parse(row.config); chartConfig = applyTemplateOverrides(chartConfig, req.query); - if (chartConfig.format === 'pdf') { + const fmt = chartConfig.format; + if (fmt === 'pdf') { renderChartToPdf(req, res, chartConfig); - } else if (chartConfig.format === 'svg') { + } else if (fmt === 'svg') { renderChartToSvg(req, res, chartConfig); - } else if (!chartConfig.format || chartConfig.format === 'png') { + } else if (!fmt || fmt === 'png') { renderChartToPng(req, res, chartConfig); } else { - logger.error(`Request for unsupported format ${outputFormat}`); - res.status(500).end(`Unsupported format ${outputFormat}`); + logger.error(`Request for unsupported format ${fmt}`); // was: outputFormat (ReferenceError) + res.status(500).end(`Unsupported format ${fmt}`); } telemetry.count('chartCount'); diff --git a/lib/charts.js b/lib/charts.js index 367fc7d..fab0794 100644 --- a/lib/charts.js +++ b/lib/charts.js @@ -129,6 +129,22 @@ async function renderChartJs( untrustedChart, ) { let chart; + if (typeof untrustedChart === 'string') { + // Try to parse as strict JSON first — if it succeeds, treat as a safe + // object so we never reach new Function(). This is the common case for + // internal callers that always send JSON chart configs (not JS functions). + try { + const parsed = JSON.parse(untrustedChart); + if (parsed && typeof parsed === 'object') { + untrustedChart = parsed; + } + } catch (_) { + // Not valid JSON — fall through to the new Function path below. + // Log so operators can see if unexpected JS function configs arrive. + logger.warn('chart input is not valid JSON; running via new Function (JS function config)'); + } + } + if (typeof untrustedChart === 'string') { // The chart could contain Javascript - run it in a VM. try {