From 7cfa8220d2577c82226e944d594314ceba3d7fed Mon Sep 17 00:00:00 2001 From: Clement Wong Date: Wed, 22 Jul 2026 12:29:40 +0200 Subject: [PATCH] Serve /.well-known files by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1.0 removal of the deprecated `hidden` option also removed the documented default of not ignoring files within dot directories, which silently broke RFC 8615 well-known URIs (ACME challenges, Android's assetlinks.json, Apple's apple-app-site-association) for consumers of the default dotfiles handling — while the README still documents the old default. Restore the safe subset of that behavior: when no explicit `dotfiles` option is provided, files within the top-level `.well-known` directory are served. Other dotfiles remain ignored, and an explicit `dotfiles` setting behaves exactly as before. --- README.md | 7 +++++-- index.js | 28 ++++++++++++++++++++++++-- test/fixtures/.well-known/.hidden | 1 + test/fixtures/.well-known/security.txt | 1 + test/send.js | 24 ++++++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/.well-known/.hidden create mode 100644 test/fixtures/.well-known/security.txt diff --git a/README.md b/README.md index 350fccd5..4bc25bf8 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,11 @@ to "deny"). - `'ignore'` Pretend like the dotfile does not exist and 404. The default value is _similar_ to `'ignore'`, with the exception that -this default will not ignore the files within a directory that begins -with a dot, for backward-compatibility. +files within the top-level `.well-known` directory ([RFC 8615 +well-known URIs][rfc-8615], such as ACME challenges) are not ignored. +Set this option explicitly to disable that exception. + +[rfc-8615]: https://www.rfc-editor.org/rfc/rfc8615 ##### end diff --git a/index.js b/index.js index 1655053d..01bc0bfe 100644 --- a/index.js +++ b/index.js @@ -119,6 +119,10 @@ function SendStream (req, path, options) { throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"') } + // serve well-known URIs (RFC 8615, "/.well-known/*") when no explicit + // dotfiles option was provided + this._wellKnown = opts.dotfiles === undefined + this._extensions = opts.extensions !== undefined ? normalizeList(opts.extensions, 'extensions option') : [] @@ -456,8 +460,16 @@ SendStream.prototype.pipe = function pipe (res) { // dotfile handling if (containsDotFile(parts)) { - debug('%s dotfile "%s"', this._dotfiles, path) - switch (this._dotfiles) { + var access = this._dotfiles + + // serve well-known URIs (RFC 8615, "/.well-known/*") when no explicit + // dotfiles option was provided + if (this._wellKnown && isWellKnown(parts)) { + access = 'allow' + } + + debug('%s dotfile "%s"', access, path) + switch (access) { case 'allow': break case 'deny': @@ -815,6 +827,18 @@ function containsDotFile (parts) { return false } +/** + * Determine if path parts are within the well-known directory + * (RFC 8615, "/.well-known/") and contain no other dotfile parts. + * + * @api private + */ + +function isWellKnown (parts) { + return parts.length > 1 && parts[0] === '.well-known' && + !containsDotFile(parts.slice(1)) +} + /** * Create a Content-Range header. * diff --git a/test/fixtures/.well-known/.hidden b/test/fixtures/.well-known/.hidden new file mode 100644 index 00000000..536aca34 --- /dev/null +++ b/test/fixtures/.well-known/.hidden @@ -0,0 +1 @@ +secret \ No newline at end of file diff --git a/test/fixtures/.well-known/security.txt b/test/fixtures/.well-known/security.txt new file mode 100644 index 00000000..b30bf83d --- /dev/null +++ b/test/fixtures/.well-known/security.txt @@ -0,0 +1 @@ +Contact: mailto:security@example.com \ No newline at end of file diff --git a/test/send.js b/test/send.js index b8242821..314fab09 100644 --- a/test/send.js +++ b/test/send.js @@ -941,6 +941,24 @@ describe('send(file, options)', function () { .expect(404, done) }) + it('should serve file within well-known directory', function (done) { + request(createServer({ root: fixtures })) + .get('/.well-known/security.txt') + .expect(200, 'Contact: mailto:security@example.com', done) + }) + + it('should ignore dotfile within well-known directory', function (done) { + request(createServer({ root: fixtures })) + .get('/.well-known/.hidden') + .expect(404, done) + }) + + it('should ignore well-known directory when dotfiles is explicitly "ignore"', function (done) { + request(createServer({ dotfiles: 'ignore', root: fixtures })) + .get('/.well-known/security.txt') + .expect(404, done) + }) + it('should reject bad value', function (done) { request(createServer({ dotfiles: 'bogus' })) .get('/name.txt') @@ -974,6 +992,12 @@ describe('send(file, options)', function () { .expect(403, done) }) + it('should 403 for file within well-known directory', function (done) { + request(createServer({ dotfiles: 'deny', root: fixtures })) + .get('/.well-known/security.txt') + .expect(403, done) + }) + it('should 403 for dotfile directory', function (done) { request(createServer({ dotfiles: 'deny', root: fixtures })) .get('/.mine')