Compare commits

..

No commits in common. "master" and "v5.0.2" have entirely different histories.

12 changed files with 602 additions and 2792 deletions

View File

@ -1,4 +1,4 @@
name: CodeQL name: "CodeQL"
on: on:
push: push:
@ -9,37 +9,20 @@ on:
schedule: schedule:
- cron: '0 13 * * 6' - cron: '0 13 * * 6'
permissions: {}
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
steps: steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
objects.githubusercontent.com:443
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
persist-credentials: false
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 uses: github/codeql-action/init@v2
with: with:
languages: javascript-typescript languages: 'javascript'
config-file: .github/codeql/codeql-config.yml config-file: ./.github/codeql/codeql-config.yml
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6 uses: github/codeql-action/analyze@v2

View File

@ -9,49 +9,34 @@ on:
schedule: schedule:
- cron: '0 2 * * 1' # At 02:00 on Monday - cron: '0 2 * * 1' # At 02:00 on Monday
permissions: {} env:
NODE_OPTIONS: --max-old-space-size=4096
jobs: jobs:
test: test:
name: Test name: Test
timeout-minutes: 5 timeout-minutes: 15
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
node-version: node-version: [16, 18]
- 16
- 18
- 20
- 21
include: include:
- os: macos-latest - os: macos-latest
node-version: 20 # LTS node-version: 16 # LTS
- os: windows-latest
node-version: 20 # LTS
fail-fast: false fail-fast: false
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
if: ${{ matrix.os == 'ubuntu-latest' }}
with: with:
disable-sudo: true fetch-depth: 0
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
nodejs.org:443
registry.npmjs.org:443
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: npm
- name: Bootstrap project - name: Bootstrap project
run: npm ci --ignore-scripts --prefer-offline run: |
- uses: Yuri6037/Action-FakeTTY@1abc69c7d530815855caedcd73842bae5687c1a6 # v1.1 npm ci --ignore-scripts
- uses: Yuri6037/Action-FakeTTY@v1.1
- name: Run tests - name: Run tests
run: faketty npm test --ignore-scripts run: faketty npm test --ignore-scripts
@ -59,102 +44,31 @@ jobs:
name: Code Lint name: Code Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
- name: Use Node.js 16
uses: actions/setup-node@v3
with: with:
disable-sudo: true node-version: 16
egress-policy: block
allowed-endpoints: >
api.github.com:443
github.com:443
nodejs.org:443
registry.npmjs.org:443
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- name: Use Node.js 20
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 20
cache: 'npm'
- name: Bootstrap project - name: Bootstrap project
run: | run: |
npm ci \ npm ci --ignore-scripts
--ignore-scripts \
--prefer-offline
- name: Verify code linting - name: Verify code linting
run: npm run lint --ignore-scripts run: npm run lint
commit-lint: commit-lint:
name: Commit Lint name: Commit Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.pull_request }} if: ${{ github.event.pull_request }}
steps: steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
github.com:443
registry.npmjs.org:443
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false - name: Use Node.js 16
- name: Use Node.js 20 uses: actions/setup-node@v3
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with: with:
node-version: 20 node-version: 16
cache: npm
- name: Bootstrap project - name: Bootstrap project
run: | run: |
npm ci \ npm ci --ignore-scripts
--ignore-scripts \
--prefer-offline
- name: Verify commit linting - name: Verify commit linting
run: | run: npx commitlint --from origin/master --to HEAD --verbose
npm exec \
--no-install \
--package=@commitlint/cli \
-- \
commitlint \
--from=origin/master \
--to=HEAD \
--verbose
lockfile-lint:
name: Lockfile Lint
runs-on: ubuntu-latest
steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
github.com:443
registry.npmjs.org:443
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- name: Use Node.js 20
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 20
cache: npm
- name: Bootstrap project
run: |
npm ci \
--ignore-scripts \
--prefer-offline
- name: Verify commit linting
run: |
npm exec \
--no-install \
--package=lockfile-lint \
-- \
lockfile-lint \
--path=package-lock.json \
--allowed-hosts=npm \
--validate-https \
--validate-integrity \
--validate-package-names

View File

@ -1,78 +0,0 @@
# Based on `scorecard.yml` Github Actions starter workflow:
# https://github.com/actions/starter-workflows/blob/b1df8a546ed4d0f27d46aaf2f8ac1118bc522638/code-scanning/scorecard.yml
# This is separate from the CI workflow due to certain restrictions imposed by the GitHub Action action:
# https://github.com/ossf/scorecard-action/tree/99cc02c8ee27bab5f5f41e79066e0de91d313dec#workflow-restrictions
# For consistency, we should keep it a separate workflow across all our Github repositories, regardless if it's actually needed.
name: OSSF Scorecard
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule: {}
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '30 6 * * 5'
push:
branches: [master]
# Declare default permissions as read only.
# permissions: read-all
permissions: {}
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
steps:
- uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
disable-sudo: true
egress-policy: block
allowed-endpoints: >
api.github.com:443
api.osv.dev:443
api.securityscorecards.dev:443
fulcio.sigstore.dev:443
github.com:443
oss-fuzz-build-logs.storage.googleapis.com:443
rekor.sigstore.dev:443
tuf-repo-cdn.sigstore.dev:443
www.bestpractices.dev:443
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
persist-credentials: false
- uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with:
results_file: results.sarif
results_format: sarif
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with:
name: OSSF Scorecard SARIF file
path: results.sarif
retention-days: 90
# Upload the results to GitHub's code scanning dashboard.
- uses: github/codeql-action/upload-sarif@9fdb3e49720b44c48891d036bb502feb25684276 # v3.25.6
with:
sarif_file: results.sarif

View File

@ -1,93 +1,3 @@
2024-02-12, Version 5.0.7
=========================
* chore: lock file maintenance (renovate[bot])
* chore: update dependency lockfile-lint to ^4.13.1 (renovate[bot])
* chore: update dependency lockfile-lint to ^4.13.0 (renovate[bot])
* chore: update dependency mocha to ^10.3.0 (renovate[bot])
* chore: update actions/setup-node action to v4.0.2 (renovate[bot])
* chore: update step-security/harden-runner action to v2.7.0 (renovate[bot])
* chore: update github/codeql-action action to v3.24.0 (renovate[bot])
* chore: update github/codeql-action action to v3.23.2 (renovate[bot])
* chore: update commitlint monorepo to ^18.6.0 (renovate[bot])
* chore: update github/codeql-action action to v3.23.1 (renovate[bot])
* chore: update dependency supertest to ^6.3.4 (renovate[bot])
* chore: update dependency chai to ^4.4.1 (renovate[bot])
* chore: update github/codeql-action action to v3 (renovate[bot])
* chore: update github/codeql-action action to v2.23.0 (renovate[bot])
* chore: update dependency chai to ^4.4.0 (renovate[bot])
* chore: update commitlint monorepo to ^18.4.4 (renovate[bot])
* chore: update dependency eslint to ^8.56.0 (renovate[bot])
* chore: update actions/setup-node action to v4.0.1 (renovate[bot])
* chore: update github/codeql-action action to v2.22.12 (renovate[bot])
* chore: update github/codeql-action action to v2.22.10 (renovate[bot])
* chore: update github/codeql-action action to v2.22.9 (renovate[bot])
* chore: update step-security/harden-runner action to v2.6.1 (renovate[bot])
* chore: add badges (Rifa Achrinza)
* ci: further harden workflows (Rifa Achrinza)
* ci: fix Scorecard issues (Rifa Achrinza)
* chore: update dependency eslint to ^8.55.0 (renovate[bot])
* chore: update github/codeql-action action to v2.22.8 (renovate[bot])
* chore: update commitlint monorepo to ^18.4.3 (renovate[bot])
* chore: update dependency eslint to ^8.54.0 (renovate[bot])
* chore: update commitlint monorepo to ^18.4.2 (renovate[bot])
* chore: update github/codeql-action action to v2.22.7 (renovate[bot])
* chore: update github/codeql-action action to v2.22.6 (renovate[bot])
* chore: update commitlint monorepo (renovate[bot])
* fix(cve-2023-29827): replace EJS with Handlebars to resolve security warning (KalleV)
* ci: align CI configuration (Rifa Achrinza)
* chore: update dependency @types/express to ^4.17.21 (renovate[bot])
* chore: update dependency eslint to ^8.53.0 (renovate[bot])
* chore: update dependency @commitlint/config-conventional to ^18.1.0 (renovate[bot])
* chore: update dependency @commitlint/config-conventional to v18 (renovate[bot])
* chore: update dependency eslint to ^8.52.0 (renovate[bot])
* chore: update dependency @commitlint/config-conventional to ^17.8.1 (renovate[bot])
* chore: update dependency @types/express to ^4.17.20 (renovate[bot])
* chore: update dependency http-status to ^1.7.3 (renovate[bot])
2023-10-16, Version 5.0.2 2023-10-16, Version 5.0.2
========================= =========================

View File

@ -1,10 +1,5 @@
# strong-error-handler # strong-error-handler
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8058/badge)](https://www.bestpractices.dev/projects/8058)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/loopbackio/strong-error-handler/badge)](https://securityscorecards.dev/viewer/?uri=github.com/loopbackio/strong-error-handler)
[![Continuous Integration](https://github.com/loopbackio/strong-error-handler/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/loopbackio/strong-error-handler/actions/workflows/continuous-integration.yml)
[![CodeQL](https://github.com/loopbackio/strong-error-handler/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/loopbackio/strong-error-handler/actions/workflows/codeql-analysis.yml)
This package is an error handler for use in both development (debug) and production environments. This package is an error handler for use in both development (debug) and production environments.
In production mode, `strong-error-handler` omits details from error responses to prevent leaking sensitive information: In production mode, `strong-error-handler` omits details from error responses to prevent leaking sensitive information:

View File

@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT // License text available at https://opensource.org/licenses/MIT
'use strict'; 'use strict';
const handlebars = require('handlebars'); const ejs = require('ejs');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@ -16,13 +16,6 @@ const compiledTemplates = {
module.exports = sendHtml; module.exports = sendHtml;
/**
* Sends HTML response to the client.
*
* @param {Object} res - The response object.
* @param {Object} data - The data object to be rendered in the HTML.
* @param {Object} options - The options object.
*/
function sendHtml(res, data, options) { function sendHtml(res, data, options) {
const toRender = {options, data}; const toRender = {options, data};
// TODO: ability to call non-default template functions from options // TODO: ability to call non-default template functions from options
@ -30,35 +23,6 @@ function sendHtml(res, data, options) {
sendResponse(res, body); sendResponse(res, body);
} }
/**
* Returns the content of a Handlebars partial file as a string.
* @param {string} name - The name of the Handlebars partial file.
* @returns {string} The content of the Handlebars partial file as a string.
*/
function partial(name) {
const partialPath = path.resolve(assetDir, `${name}.hbs`);
const partialContent = fs.readFileSync(partialPath, 'utf8');
return partialContent;
}
handlebars.registerHelper('partial', partial);
/**
* Checks if the given property is a standard property.
* @param {string} prop - The property to check.
* @param {Object} options - The Handlebars options object.
* @returns {string} - The result of the Handlebars template.
*/
function standardProps(prop, options) {
const standardProps = ['name', 'statusCode', 'message', 'stack'];
if (standardProps.indexOf(prop) === -1) {
return options.fn(this);
}
return options.inverse(this);
}
handlebars.registerHelper('standardProps', standardProps);
/** /**
* Compile and cache the file with the `filename` key in options * Compile and cache the file with the `filename` key in options
* *
@ -68,23 +32,15 @@ handlebars.registerHelper('standardProps', standardProps);
function compileTemplate(filepath) { function compileTemplate(filepath) {
const options = {cache: true, filename: filepath}; const options = {cache: true, filename: filepath};
const fileContent = fs.readFileSync(filepath, 'utf8'); const fileContent = fs.readFileSync(filepath, 'utf8');
return handlebars.compile(fileContent, options); return ejs.compile(fileContent, options);
} }
/** // loads and cache default error templates
* Loads the default error handlebars template from the asset directory and compiles it.
* @returns {Function} The compiled handlebars template function.
*/
function loadDefaultTemplates() { function loadDefaultTemplates() {
const defaultTemplate = path.resolve(assetDir, 'default-error.hbs'); const defaultTemplate = path.resolve(assetDir, 'default-error.ejs');
return compileTemplate(defaultTemplate); return compileTemplate(defaultTemplate);
} }
/**
* Sends an HTML response with the given body to the provided response object.
* @param {Object} res - The response object to send the HTML response to.
* @param {string} body - The HTML body to send in the response.
*/
function sendResponse(res, body) { function sendResponse(res, body) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(body); res.end(body);

2906
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "strong-error-handler", "name": "strong-error-handler",
"description": "Error handler for use in development and production environments.", "description": "Error handler for use in development and production environments.",
"license": "MIT", "license": "MIT",
"version": "5.0.7", "version": "5.0.2",
"engines": { "engines": {
"node": ">=16" "node": ">=16"
}, },
@ -19,23 +19,21 @@
"dependencies": { "dependencies": {
"accepts": "^1.3.8", "accepts": "^1.3.8",
"debug": "^4.3.4", "debug": "^4.3.4",
"ejs": "^3.1.9",
"fast-safe-stringify": "^2.1.1", "fast-safe-stringify": "^2.1.1",
"handlebars": "^4.7.8", "http-status": "^1.7.0",
"http-status": "^1.7.4",
"js2xmlparser": "^5.0.0", "js2xmlparser": "^5.0.0",
"strong-globalize": "^6.0.6" "strong-globalize": "^6.0.6"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^17.8.0",
"@commitlint/config-conventional": "^19.2.2", "@types/express": "^4.17.19",
"@types/express": "^4.17.21", "chai": "^4.3.10",
"chai": "^5.1.1", "eslint": "^8.51.0",
"eslint": "^8.57.0",
"eslint-config-loopback": "^13.1.0", "eslint-config-loopback": "^13.1.0",
"express": "^4.19.2", "express": "^4.18.2",
"lockfile-lint": "^4.13.2", "mocha": "^10.2.0",
"mocha": "^10.4.0", "supertest": "^6.3.3"
"supertest": "^7.0.0"
}, },
"browser": { "browser": {
"strong-error-handler": false "strong-error-handler": false

View File

@ -5,15 +5,13 @@
'use strict'; 'use strict';
import cloneAllProperties from '../lib/clone.js'; const cloneAllProperties = require('../lib/clone.js');
import debugFactory from 'debug'; const debug = require('debug')('test');
import express from 'express'; const expect = require('chai').expect;
import strongErrorHandler from '../lib/handler.js'; const express = require('express');
import supertest from 'supertest'; const strongErrorHandler = require('..');
import util from 'node:util'; const supertest = require('supertest');
import {expect} from 'chai'; const util = require('util');
const debug = debugFactory('test');
describe('strong-error-handler', function() { describe('strong-error-handler', function() {
before(setupHttpServerAndClient); before(setupHttpServerAndClient);
@ -139,7 +137,8 @@ describe('strong-error-handler', function() {
// the error name & message // the error name & message
expect(msg).to.contain('TypeError: ERROR-NAME'); expect(msg).to.contain('TypeError: ERROR-NAME');
// the stack // the stack
expect(msg).to.contain(import.meta.url); expect(msg).to.contain(__filename);
done(); done();
}); });
}); });
@ -162,7 +161,7 @@ describe('strong-error-handler', function() {
expect(msg).to.contain('TypeError: ERR1'); expect(msg).to.contain('TypeError: ERR1');
expect(msg).to.contain('Error: ERR2'); expect(msg).to.contain('Error: ERR2');
// verify that stacks are included too // verify that stacks are included too
expect(msg).to.contain(import.meta.url); expect(msg).to.contain(__filename);
done(); done();
}); });
@ -608,12 +607,10 @@ describe('strong-error-handler', function() {
expect(res.statusCode).to.eql(404); expect(res.statusCode).to.eql(404);
const body = res.error.text; const body = res.error.text;
expect(body).to.match( expect(body).to.match(
// eslint-disable-next-line max-len /<title>Error&lt;img onerror=alert\(1\) src=a&gt;<\/title>/,
/<title>Error&lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt;<\/title>/,
); );
expect(body).to.match( expect(body).to.match(
// eslint-disable-next-line max-len /with id &lt;img onerror=alert\(1\) src=a&gt; found for Model/,
/with id &lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt; found for Model/,
); );
done(); done();
}); });
@ -630,8 +627,7 @@ describe('strong-error-handler', function() {
.expect(500) .expect(500)
.expect(/<title>ErrorWithProps<\/title>/) .expect(/<title>ErrorWithProps<\/title>/)
.expect( .expect(
// eslint-disable-next-line max-len /500(.*?)a test error message&lt;img onerror=alert\(1\) src=a&gt;/,
/500(.*?)a test error message&lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt;/,
done, done,
); );
}); });

25
views/default-error.ejs Normal file
View File

@ -0,0 +1,25 @@
<html>
<head>
<meta charset='utf-8'>
<title><%= data.name || data.message %></title>
<style><%- include('style.css') %></style>
</head>
<body>
<div id="wrapper">
<h1><%= data.name %></h1>
<h2><em><%= data.statusCode %></em> <%= data.message %></h2>
<%
// display all the non-standard properties
var standardProps = ['name', 'statusCode', 'message', 'stack'];
for (var prop in data) {
if (standardProps.indexOf(prop) == -1 && data[prop]) { %>
<div><b><%= prop %></b>: <%= data[prop] %></div>
<% }
}
if (data.stack) { %>
<pre id="stacktrace"><%- data.stack %></pre>
<% }
%>
</div>
</body>
</html>

View File

@ -1,25 +0,0 @@
<html>
<head>
<meta charset="utf-8" />
<title>{{ data.name }}{{#unless data.name}}{{ data.message }}{{/unless}}</title>
<style>
{{partial 'style'}}
</style>
</head>
<body>
<div id="wrapper">
<h1>{{ data.name }}</h1>
<h2>
<em>{{ data.statusCode }}</em> {{ data.message }}
</h2>
{{#each data}}
{{#standardProps @key}}
<div><b>{{@key}}</b>: {{this}}</div>
{{/standardProps}}
{{/each}}
{{#if data.stack}}
<pre id="stacktrace">{{{data.stack}}}</pre>
{{/if}}
</div>
</body>
</html>