From cd4683d75eb92491f8e4ea181d5a9307816e538b Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Sat, 11 Apr 2026 21:31:12 -0700 Subject: [PATCH] test: remove insecure TLS bypasses and expand HTTPS coverage --- test/certs/server.crt | 34 ++++---- test/certs/server.key | 52 ++++++------ test/test.js | 186 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 205 insertions(+), 67 deletions(-) diff --git a/test/certs/server.crt b/test/certs/server.crt index 942b529..7fbaf2b 100644 --- a/test/certs/server.crt +++ b/test/certs/server.crt @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- -MIIDEDCCAfgCAQEwDQYJKoZIhvcNAQELBQAwTjELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAldBMREwDwYDVQQHDAhCZWxsZXZ1ZTEPMA0GA1UECgwGR29vZ2xlMQ4wDAYD -VQQLDAVDbG91ZDAeFw0yNTEwMTQxNjMzMTNaFw0zNTEwMTIxNjMzMTNaME4xCzAJ -BgNVBAYTAlVTMQswCQYDVQQIDAJXQTERMA8GA1UEBwwIQmVsbGV2dWUxDzANBgNV -BAoMBkdvb2dsZTEOMAwGA1UECwwFQ2xvdWQwggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQCpmjJqERR7zxw/Jcn+JkFxeFD+8vrKgjWDYUrIDUeFctv1trYC -yiz7T3ffF8Pavir322KV22haf/O0r4AO3ZJI3oQcwitB/o9BzvwKK4UcQlHhOTas -RxMF4gE7VyNLJv4tWM6SCOX58kCLpFEq6HyyP4UNdzxQX1+ZcsljVw+cYpbC0PjD -kIhc0fsGFcNdEtO31KpBVYeSpIMNmSIWyN7RESW6NkVvuV1Dwfw8P4n6llyNaVK/ -bKJsX8XeMFEZ3q7hbTsOVCoCuiEgdR3HYsSLx16aKaRfFZzXoQg7Osyct3VApRUy -drfcqEbh0b+xKxB+sW56hC42Zl0/Q52QPDEZAgMBAAEwDQYJKoZIhvcNAQELBQAD -ggEBAEZNCifdR2DZ29H4xp+Dkza7JiT1SbddGEIn2iXdFPfFGvRL68LTFr2hZkf+ -ga8HwRtxVvktvX41byzjjYoz5ALJ75UvHKw1Q/yqS08HwODNyMzRdROFGr6UzLtj -GGfYnzUhmUUKBTNF3GHSxZRsARtMnH38lNfRJ5xXXjWa18VyKXAFA6CKWRD+1ey8 -/ehZX7BHZGZpSb/PLEEa3PRZMb9rAjk+pFNOrCF1QEkxJ+ZlYmRWTcpuxinPuY+8 -5y5FyRYObGYzXxdFotxrc9Jg6orT5YhDW8hORWMs1cHQKyKIiqZpiS53rK+4+uYR -1IlO4QjunTe6r3xRt8G2ef47VGg= +MIIDJTCCAg2gAwIBAgIUCHJi6SkNut17iBSU+6zwrzsTwHwwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDQxMjAwNTkyNFoXDTM2MDQw +OTAwNTkyNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAy4X8/+UjtlhXxHzHr34HWQmSNeHR9FL7RUvsDxlZZ6VL +TMC1hCXUO0iKmBigtgQ54jJ9753T5yMmDGk4mTU33acoYm3d/EHhxMv4Pu2o0R0Q +yhU6HStxcCACBOI7Kehuy0ZDFf2YiIsZHMTLZvfBKuZP9LtPmaDrOSV+cXaEsvnc +CCaB855HprGFE3fukslCQ8sPM8xudRF0raj5hL1/aAMbKaGtaBEc1tj2k3GlArBw +Uu89qZeC77MSmlDxoFWpcEbMZ7lPzYAoC6vMIHHBCuHXvJUudIPIdZseYE57aW+5 +jyBQ/6kRZSpOSyvhIOovFmjNnSSio+/IsXQX3UrLDQIDAQABo28wbTAdBgNVHQ4E +FgQUGh54PJnDeLmHtM5N27Pl+pvtIg0wHwYDVR0jBBgwFoAUGh54PJnDeLmHtM5N +27Pl+pvtIg0wDwYDVR0TAQH/BAUwAwEB/zAaBgNVHREEEzARgglsb2NhbGhvc3SH +BH8AAAEwDQYJKoZIhvcNAQELBQADggEBACoXlufc6bCHtuGEfKw9+x9b4ZiZrNX+ +rIDS4+tzq0SkbRF9naNepUfNU3Z0cx+kTDvTmH8bjBRxm2YORLUOot/mtrsI6hrK +poCcSrJA15STe2ftvNVlft9v4xPswzusWrsxTZ2oS5C1ys1UbOjFTb2qCkeYEjBw +4dfl/0lat9x9XRbfYBCWCdFo5izRhGXbgtBAl25fkEyKG7bz8q6zWFKm0xw8978U +kCKz2laAcuETBm1jCuVEd3OHAlv2/7vDKH2PpOvKwtWe2kBhILnjuDsa5LyzNJDR +ECVpOnoxrGGd8c2ezkbfH5r55aFRjFWNivt6cdbXpAgU+iqE8fISlmA= -----END CERTIFICATE----- diff --git a/test/certs/server.key b/test/certs/server.key index 88cd5c5..65b45f2 100644 --- a/test/certs/server.key +++ b/test/certs/server.key @@ -1,28 +1,28 @@ -----BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCpmjJqERR7zxw/ -Jcn+JkFxeFD+8vrKgjWDYUrIDUeFctv1trYCyiz7T3ffF8Pavir322KV22haf/O0 -r4AO3ZJI3oQcwitB/o9BzvwKK4UcQlHhOTasRxMF4gE7VyNLJv4tWM6SCOX58kCL -pFEq6HyyP4UNdzxQX1+ZcsljVw+cYpbC0PjDkIhc0fsGFcNdEtO31KpBVYeSpIMN -mSIWyN7RESW6NkVvuV1Dwfw8P4n6llyNaVK/bKJsX8XeMFEZ3q7hbTsOVCoCuiEg -dR3HYsSLx16aKaRfFZzXoQg7Osyct3VApRUydrfcqEbh0b+xKxB+sW56hC42Zl0/ -Q52QPDEZAgMBAAECggEAHeOIPev7paQGsNQ6iDj6sIIJGBz++IBA6QHU2XOJpqlq -vR8xbUVu7uz+zYiVIfAAh4SYl5s+hTBmZPHGyhffJ0tbkevgDrXLTkgpX6tyvlur -rVrVWT//SLqfB/Ofyyc4hQpRAShjSwyXtmRDT6IAE7OhAZIBRZLBOLwjZIfbvu3R -e3uDzeqVq0MsiJiQIzrjMIfvtVFd35TjsfdHNv88jWGnysy8Xc95JIXeTaWSzXaL -+VNGqQnXDOFKuI6vHCfCwtTAuWQqb0K7oHBMxXRHrfRH8p/zFeTuiKYwvFgGq9qm -omQa476QHA0JZ6O196uEZGGKQfhU8lvLLm+d5tjkWQKBgQDTZ228nuHk/Xv6KiJg -YC8y68y2JyWV0i9w3vu0vrT6fnXg6t+FOrc+Mc9I6hmVFG/C+o0bKywIXaiQcCeQ -hxz/l1S5UpNVYqe55jWVIxdNcxMZooiKgzR/CLaFh+yrOuQlsU1x7Ym8CFMXINLB -cbb/efojCQzsc0LOr8IX3ehMNQKBgQDNYVP6jqWJkGnsbX+C9HzncKxfIjh5S3N6 -5RqRvqZCqte1zKJR/P/tkyDMIoGffqQzro+/IAFtog2QKAmrHA1kiM0v82QYGTxU -6kExBvO+EKuJ046qb8RWD/pRK8Hx0VUtsK9Z8wDTNWzh5NAZiUPEPVEnNquDiLdr -+jQwyBDF1QKBgQCFWEX1pdoi0Gj1AMKyO7lJy0ZS1xp0CCH4dg4akfgh0MaV2lCm -/sQ6rLxs54y/Ziagu91pd6/MjxcWFEhAd5ko9tFwG8/nGdVmAvllWr8GEUHI96Zc -iCoCvwIx8+yqjPj/dXi+FfC89BtFCWUms42UU+IdW0YVlxZavK4W09gEyQKBgQCL -otbEDeRCqWs7Eh+V28BoYtTvQYcAAOqc7dOor+S+gwVyV5UtBUTENDoiUHutAx+E -+/RDz/DopzttfatFKSd56QEIzwSI1e+NFFAKk115JkBazvm6q47jrK1WLtgIH3k4 -PV6bW3p+H17OHxHVqtvmOoJIlQT7wyJiSZTebcfpmQKBgQDNIkhkoH8CEgK4ecbe -Q3CoWALKYc82b5y1Dnej2ZehKG+ImDz50NIupotqZeaMQiu2NtQGLyYTgOekVFnu -AcLXsQAYVvTzPid1JDOic6tAuSGgcY/OuXnZc9/rBrOsL/xteJmJCaeFNjLKl1JM -rdhCOgf3FSzPy3+PzriASBICTw== +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLhfz/5SO2WFfE +fMevfgdZCZI14dH0UvtFS+wPGVlnpUtMwLWEJdQ7SIqYGKC2BDniMn3vndPnIyYM +aTiZNTfdpyhibd38QeHEy/g+7ajRHRDKFTodK3FwIAIE4jsp6G7LRkMV/ZiIixkc +xMtm98Eq5k/0u0+ZoOs5JX5xdoSy+dwIJoHznkemsYUTd+6SyUJDyw8zzG51EXSt +qPmEvX9oAxspoa1oERzW2PaTcaUCsHBS7z2pl4LvsxKaUPGgValwRsxnuU/NgCgL +q8wgccEK4de8lS50g8h1mx5gTntpb7mPIFD/qRFlKk5LK+Eg6i8WaM2dJKKj78ix +dBfdSssNAgMBAAECggEAH1y3Xut9dFTquXGNB+MA2NybtkflJaQ/6i7H4o/CcjFL +iDlN/14g9T25dbSd106RaOTk+S61/7Ev5Mu2GA3WXQasN3a8tWmlk8DmsTPIRukW +tD1PJj2SjnewN/ZwrYWOxS4/mjzo62mb5g4BVg2pq6Wv/oN6wa7FcJnxgSSFUFrg +DpUhecBXTSLHMisls2Oyn90g2NcKbvuxjPxet1F9+Swa0wU1kjr9u9ecElTkO4yg +Qjy34rQmEX0h7T5F2OPiPpoYS/78dVP7c5vCKDWp7QT7Z4P7DgIyXRXtLEsrfQpp +EAWU75B2ypwfqPY1zAw2ijfR5Z5Fl3DV8ZjLD5j2QQKBgQDlvsXTYlVklzYBkZIz +ZFBuP0Op7QjrRLJ0OfZTTRUUlccfpk1JvNIP9PCcteilBMpYIayUN7UzVpp8Qybn +vmrgGD3nBpE4E0v6iuM6l9rq/TmqUqUOt6nqmOWwUw/e9FBOLZ7jhGYQ4MkYANTG +MkOZ+i0l6MnGurvcCPEM2iaR0QKBgQDiyBfAUJk9+bSkXHK50w2gcYk6Yvrkp8mH +v3fY2OxYZqamvhz8lqyIpAWbJAGlqXb4Gp/RB4TA3eNk+fNiPsCNNCHEGAr0gnM5 +9baaGjLbKBKu71CdA1CkkO7dQJ5PMDOExQjx2bo8It02dzYbW0vk32/Yin/WhNXL +L8F2BqoYfQKBgQC+XCnSEmIq7NeEyTdYeb/i+Wx5ObvwJIWwo+4j63SSD+BjqwnT +FS8ApbVQQ7G2OZfnGk/Cp73uAc4TNBjiX/ZyI+P2roxY6DRGLhpFDFoJ5zOGmt/E +qA2UIof+Z3R5CfoYLNjAL43aYkZ9KwMiDbfRt2b9SDsX/NV3ZblFKuRWYQKBgGlU +dCufg71USEF6qtKCIzcc5JbYuB0RjTnehSSThBp++vJBJKdwuAvy2qO28ojmD9qm +SwpECrWlmWMh8Jf1+2raBsDURepQ2IHYDQrAFlTR5POZNYDntEHrCvZ6d8zh31vQ +RBpIfQZHTyVn8xp7qeFQodsaYMvbAI2RzbIq4D/lAoGAcAGtnnCta3vsqInMICOP +cgElgTgrBvcCYOQAwMZimhwl6gJjmm0PXEAplHK0BIeO4Xu18ZjvIN/pJynToo9W +bvpgh3W9P9LkVdkaHEr9zlSvhAGE80NNjUeO3Ur7GonkclwNXwpbET/Qiv594Osn +gm6J9stVFd5jicl3SyYh5iw= -----END PRIVATE KEY----- diff --git a/test/test.js b/test/test.js index 075eb2b..d39f516 100644 --- a/test/test.js +++ b/test/test.js @@ -1,12 +1,11 @@ import fs from 'node:fs'; import https from 'node:https'; -import process from 'node:process'; import express from 'express'; import { describe, it } from 'mocha'; import request from 'supertest'; import yes from '../lib/index.js'; -process.env.NODE_TLS_REJECT_UNAUTHORIZED = 0; +const TEST_SERVER_CERT = fs.readFileSync('./test/certs/server.crt'); describe('yes', () => { it('should perform the 301 for an http request', (done) => { @@ -40,16 +39,15 @@ describe('yes', () => { // Verify the request returns the right header when using https const server = createSecureServer(app); - request('https://localhost:8443') + request(server) .get('/test') + .ca(TEST_SERVER_CERT) .expect('Strict-Transport-Security', 'max-age=86400; includeSubDomains') .expect(200) .end((error) => { if (error) { throw error; } - - server.close(); done(); }); }).timeout(60_000); @@ -66,16 +64,15 @@ describe('yes', () => { }); const server = createSecureServer(app); - request('https://localhost:8443') + request(server) .get('/test') + .ca(TEST_SERVER_CERT) .expect('Strict-Transport-Security', 'max-age=86400') .expect(200) .end((error) => { if (error) { throw error; } - - server.close(); done(); }); }).timeout(60_000); @@ -92,16 +89,15 @@ describe('yes', () => { }); const server = createSecureServer(app); - request('https://localhost:8443') + request(server) .get('/test') + .ca(TEST_SERVER_CERT) .expect('Strict-Transport-Security', 'max-age=86400') .expect(200) .end((error) => { if (error) { throw error; } - - server.close(); done(); }); }).timeout(60_000); @@ -131,20 +127,162 @@ describe('yes', () => { done(); }); }); + + it('should include preload when configured', (done) => { + const app = express(); + app.use(yes({ preload: true })); + app.get('/test', (_request, response) => { + response.sendStatus(200); + }); + + const server = createSecureServer(app); + request(server) + .get('/test') + .ca(TEST_SERVER_CERT) + .expect( + 'Strict-Transport-Security', + 'max-age=86400; includeSubDomains; preload', + ) + .expect(200) + .end((error) => { + if (error) { + throw error; + } + done(); + }); + }).timeout(60_000); + + it('should omit includeSubDomains when disabled', (done) => { + const app = express(); + app.use(yes({ includeSubDomains: false })); + app.get('/test', (_request, response) => { + response.sendStatus(200); + }); + + const server = createSecureServer(app); + request(server) + .get('/test') + .ca(TEST_SERVER_CERT) + .expect('Strict-Transport-Security', 'max-age=86400') + .expect(200) + .end((error) => { + if (error) { + throw error; + } + done(); + }); + }).timeout(60_000); + + it('should include includeSubDomains when explicitly enabled', (done) => { + const app = express(); + app.use(yes({ includeSubDomains: true })); + app.get('/test', (_request, response) => { + response.sendStatus(200); + }); + + const server = createSecureServer(app); + request(server) + .get('/test') + .ca(TEST_SERVER_CERT) + .expect('Strict-Transport-Security', 'max-age=86400; includeSubDomains') + .expect(200) + .end((error) => { + if (error) { + throw error; + } + done(); + }); + }).timeout(60_000); + + describe('includeSubDomains', () => { + it('should include the directive by default over a secure connection', () => { + return expectSecureHeader({}, 'max-age=86400; includeSubDomains'); + }); + + it('should include the directive when explicitly enabled over a secure connection', () => { + return expectSecureHeader( + { includeSubDomains: true }, + 'max-age=86400; includeSubDomains', + ); + }); + + it('should omit the directive when disabled over a secure connection', () => { + return expectSecureHeader({ includeSubDomains: false }, 'max-age=86400'); + }); + + it('should compose correctly with preload and maxAge when enabled', () => { + return expectSecureHeader( + { includeSubDomains: true, preload: true, maxAge: 31_536_000 }, + 'max-age=31536000; includeSubDomains; preload', + ); + }); + + it('should compose correctly with preload and maxAge when disabled', () => { + return expectSecureHeader( + { includeSubDomains: false, preload: true, maxAge: 31_536_000 }, + 'max-age=31536000; preload', + ); + }); + + it('should include the directive by default for forwarded https requests', () => { + return expectForwardedSecureHeader( + {}, + 'max-age=86400; includeSubDomains', + ); + }); + + it('should honor an explicit true value for forwarded https requests', () => { + return expectForwardedSecureHeader( + { includeSubDomains: true }, + 'max-age=86400; includeSubDomains', + ); + }); + + it('should honor an explicit false value for forwarded https requests', () => { + return expectForwardedSecureHeader( + { includeSubDomains: false }, + 'max-age=86400', + ); + }); + }); }); function createSecureServer(app) { - // Server the app over https - return https - .createServer( - { - key: fs.readFileSync('./test/certs/server.key'), - cert: fs.readFileSync('./test/certs/server.crt'), - ca: fs.readFileSync('./test/certs/ca.crt'), - requestCert: true, - rejectUnauthorized: false, - }, - app, - ) - .listen('8443'); + return https.createServer( + { + key: fs.readFileSync('./test/certs/server.key'), + cert: fs.readFileSync('./test/certs/server.crt'), + }, + app, + ); +} + +function expectSecureHeader(options, expectedHeader) { + const app = express(); + app.use(yes(options)); + app.get('/test', (_request, response) => { + response.sendStatus(200); + }); + + const server = createSecureServer(app); + return request(server) + .get('/test') + .ca(TEST_SERVER_CERT) + .expect('Strict-Transport-Security', expectedHeader) + .expect(200); +} + +function expectForwardedSecureHeader(options, expectedHeader) { + const app = express(); + app.use(yes(options)); + app.get('/test', (_request, response) => { + response.sendStatus(200); + }); + + return request(app) + .get('/test') + .set('X-Forwarded-Proto', 'https') + .set('Host', 'example.com') + .expect('Strict-Transport-Security', expectedHeader) + .expect(200); }