master 3ee19be37690 cached
11 files
35.5 KB
8.8k tokens
7 symbols
1 requests
Download .txt
Repository: brannondorsey/host-validation
Branch: master
Commit: 3ee19be37690
Files: 11
Total size: 35.5 KB

Directory structure:
gitextract__lqq0zf9/

├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── example.js
├── index.js
├── package.json
└── test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/
coverage/
.nyc_output/

================================================
FILE: .npmignore
================================================
coverage/
.nyc_output/
.travis.yml



================================================
FILE: .travis.yml
================================================
sudo: false
language: node_js
node_js:
  - "8"
script:
  - npm test
after_script:
  - npm run coveralls
cache:
  directories:
    - "node_modules"

================================================
FILE: CHANGELOG.md
================================================
# Host Validation CHANGELOG

## v2.0.1

- Improve performance in `isAllowed()` thanks to [Evan Hahn](https://github.com/EvanHahn) ([#6](https://github.com/brannondorsey/host-validation/pull/6)).

## v2.0.0

- Add custom error handler ([#1](https://github.com/brannondorsey/host-validation/issues/1))
- Change several error messages (this change required the breaking semver bump)
- Add tests to improve testing coverage

## v1.2.0

- Add CI with `.travis.yml`.
- Add test coverage with Coveralls.
- Add `coverage` and `coveralls` npm tasks.
- Fix missing "," syntax error in `example.js` and README.

## v1.1.0

- Server now responds to invalid Host/Referer requests with `403 Forbidden` instead of `401 Unauthorized` per [this discussion](https://stackoverflow.com/questions/49481293/what-is-the-most-appropriate-http-status-code-for-invalid-host-referer-request-h).
- Add `CODE_OF_CONDUCT.md`
- Remove commented-out code in `index.js`

## v1.0.1

- Update README
- Update `tests.js`, server now closes.

## v1.0.0

Initial release. Supports Host and Referer validation via string matches and regular expressions.

================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct

## 1. Purpose

A primary goal of Host Validation is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof).

This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior.

We invite all those who participate in Host Validation to help us create safe and positive experiences for everyone.

## 2. Open Source Citizenship

A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community.

Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society.

If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know.

## 3. Expected Behavior

The following behaviors are expected and requested of all community members:

*   Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community.
*   Exercise consideration and respect in your speech and actions.
*   Attempt collaboration before conflict.
*   Refrain from demeaning, discriminatory, or harassing behavior and speech.
*   Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential.
*   Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations.

## 4. Unacceptable Behavior

The following behaviors are considered harassment and are unacceptable within our community:

*   Violence, threats of violence or violent language directed against another person.
*   Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language.
*   Posting or displaying sexually explicit or violent material.
*   Posting or threatening to post other people’s personally identifying information ("doxing").
*   Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability.
*   Inappropriate photography or recording.
*   Inappropriate physical contact. You should have someone’s consent before touching them.
*   Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances.
*   Deliberate intimidation, stalking or following (online or in person).
*   Advocating for, or encouraging, any of the above behavior.
*   Sustained disruption of community events, including talks and presentations.

## 5. Consequences of Unacceptable Behavior

Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.

Anyone asked to stop unacceptable behavior is expected to comply immediately.

If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event).

## 6. Reporting Guidelines

If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. brannon@brannondorsey.com.



Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress.

## 7. Addressing Grievances

If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify Brannondorsey with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies.



## 8. Scope

We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business.

This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members.

## 9. Contact info

brannon@brannondorsey.com

## 10. License and attribution

This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/).

Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy).

Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/)


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Host Validation

[![Build Status](https://travis-ci.com/brannondorsey/host-validation.svg?branch=master)](https://travis-ci.com/brannondorsey/host-validation) [![Coverage Status](https://coveralls.io/repos/github/brannondorsey/host-validation/badge.svg?branch=master)](https://coveralls.io/github/brannondorsey/host-validation?branch=master)

[![NPM](https://nodei.co/npm/host-validation.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/host-validation/)

Express.js middleware that protects Node.js servers from [DNS Rebinding](https://en.wikipedia.org/wiki/DNS_rebinding) attacks by validating Host and Referer [sic] headers from incoming requests. If a request doesn't contain a whitelisted Host/Referer header, `host-validation` will respond with a 403 Forbidden HTTP error.

DNS Rebinding is a savvy exploit that hasn't gotten the attention it deserves over the years. For this reason tons of services are vulnerable to it because of lack of developer knowledge of the attack or simply negligence and indifference to patch against it. Don't be *that person*.

## Getting Started

```bash
# install using npm
npm install host-validation
```

```javascript
const express        = require('express')
const hostValidation = require('host-validation')

const app = express()

// allow development hosts, a domain name, and a regex for all subdomains.
// any requests that don't supply a whitelisted Host will be rejected
// with a 403 HTTP status code 
// NOTE: custom errors can be returned by a config.fail function. see "custom 
// failure handlers" below.
app.use(hostValidation({ hosts: ['127.0.0.1:3000',
                                 'localhost:3000',
                                 'mydomain.com', 
                                 /.*\.mydomain\.com$/] }))

app.get('/', (req, res) => {
    res.send('Hello trusted client, thanks for including a whitelisted Host header.')
})

app.listen(3000, () => {
    console.log('server accepting requests w/ valid Host headers port 3000')
})
```

## What is DNS Rebinding and why should I care?

DNS Rebinding is a clever technique used to bypass the browser's [Same-Origin Policy](https://en.wikipedia.org/wiki/Same-origin_policy). This policy blocks malicious websites or HTML advertisements from making arbitrary requests to other servers. Imagine a scenario where you are surfing a news website that serves banner ads. One of those ads contains malicious JavaScript that POSTs default router credentials to `http://192.168.1.1/router_login.php`, a common IP address for routers on home networks, in an attempt to open an inbound connections from the Internet. Even if 5% of users don't change there router admin panel login (not their WiFi network login) that attack could still be successful hundreds of thousands of times a day depending on the reach of the malicious ad.

Now, because of Same-Origin Policy, your web browser actually blocks that request to 192.168.1.1 from ever taking place. It notices that `http://malicious-ad.com` is a different host than `192.168.1.1` (which doesn't have Cross Origin Resource Sharing (CORS) headers enabled) and stops the request dead in it's tracks. All, good right?

Not so fast. Enter DNS Rebinding. In the above scenario, your browser won't allow the malicious ad to make a request to `http://192.168.1.1/router_login.php`, but it would allow a request to `http://malicious-ad.com/router_login.php`. That doesn't seem like a problem because there is no way that `http://malicious-ad.com` could be hosting our router login page, right? Technically, yes, that is true, but what if `http://malicious-ad.com` could act as a proxy to your home router. Better yet, what if the domain name `http://malicious-ad.com` actually resolved to `192.168.1.1` so that it could trick your browser into thinking it is making a request to the same host but the request is actually made to your home router. This is exactly what DNS Rebinding does. It uses a malicious DNS server (like [FakeDNS](https://github.com/Crypt0s/FakeDns)) to respond to the same DNS request with different results at different times. A common DNS Rebinding server might be configured to respond to `http://2dfaa01a-59bf-47db-a7cc-ddf4245e68b9.malicious-ad.com` with `157.218.13.52`, the real IP address of the HTTP server serving the malicious ad the first time it is requested, and then `192.168.1.1` every time that same domain name is requested thereafter. 

### Who is vulnerable to DNS Rebinding?

Any HTTP server that 1) doesn't use HTTPS or 2) has no user authentication and 3) doesn't validate the Host header of incoming requests is vulnerable to DNS Rebind attacks.

This package protects you from #2. If you are using HTTPS you don't need to use this package (good job!). But hey... I bet you don't use HTTPS when when you are developing on your local machine/network. Local networks are among the top targets for DNS Rebind attacks, so you should probably validate Host headers in that circumstance too.

The reason that "Host" header validation mitigates against DNS rebinding is that malicious requests sent from web browsers will have "Host" values that don't match the ones you would expect your server to have. For instance, your home router should expect to see "Host" values like `192.168.1.1`, `192.168.0.1`, or maybe `router.asus.com`, but definitely not `http://malicious-ad.com`. Simply checking that the "Host" header contains the value that you expect will prevent DNS rebinding attacks and leave your users protected. 

## Examples and usage

This package is dead simple. Include a few new lines of code and protect yourself from DNS Rebind attacks without ever thinking about it again.

### Simple Host validation

```javascript
// host and referrer headers can accept strings or regular expressions
app.use(hostValidation({ hosts: ['mydomain.com', /.*\.mydomain\.com$/] }))
```

### Simple Referer validation

```javascript
// host and referrer headers can accept strings or regular expressions
app.use(hostValidation({ referers: ['http://trusted-site.com/login.php', 
                                    /^http:\/\/othersite\.com\/login\/.*/] }))
```

```javascript
// only accept POSTs from HTTPS referrers
app.use(hostValidation({ referers: [/^https:\/\//]}))
```

### Host and/or Referer validation

```javascript
// you can include both host and referer values in the config
// by default, only requests that match BOTH Host and Referer values will be allowed
app.use(hostValidation({ hosts: ['trusted-host.com'], 
                         referers: ['https://trusted-host.com/login.php'] }))
```

```javascript
// you can use the { mode: 'either' } value in the config accept requests that match
// either the hosts or the referers requirements. Accepted values for mode include 
// 'both' and 'either'. The default value is 'both' if none is specified.  
app.use(hostValidation({ hosts: ['trusted-host.com'], 
                         referers: ['https://trusted-host.com/login.php'],
                         mode: 'either' }))
```

### Custom rules for custom routes

```javascript
// route-specific rules can be specified like any Express.js middleware
app.use('/login', hostValidation({ hosts: ['trusted-host.com'] }))
app.use('/from-twitter', hostValidation({ referrers: [/^https:\/\/twitter.com\//] }))
```

## Custom failure handlers

Add a custom error handler that's run when host or referer validation fails. This function overwrites the default behavior of responding to failed requests with a `403 Forbidden` error.

```javascript
// 
app.use('/brew-tea', hostValidation({ 
	hosts: ['office-teapot'],
	fail: (req, res, next) => {
        // send a 418 "I'm a Teapot" Error
		res.status(418).send('I\'m the office teapot. Refer to me only as such.')
	}
}))
```

## Testing

For more example usage see [`test.js`](test.js)

```bash
# navigate to the module directory
cd node_modules/host-validation

# install the dev dependencies
npm install --dev

# run the tests in test.js
npm test
```


================================================
FILE: example.js
================================================
// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

const express        = require('express')
const hostValidation = require('./index.js')

const app = express()

// allow development hosts, a domain name, and a regex for all subdomains
app.use(hostValidation({ hosts: ['127.0.0.1:3000',
								 'localhost:3000',
								 'mydomain.com', 
								 /.*\.mydomain\.com/] }))

app.get('/', (req, res) => {
	res.send('Hello trusted client, thanks for including 127.0.0.1 in your Host header.')
})

app.listen(3000, () => {
	console.log('server allowing HTTP requests from 127.0.0.1 on port 3000')
})

================================================
FILE: index.js
================================================
// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

module.exports = function(config) {

	if (!config) {
		throw Error('a config object must be provided as the first argument to this function.')
	}

	// account for correct referrers spelling
	if (config.referrers && !config.referers) {
		config.referers = config.referrers
	}

	if (!Array.isArray(config.hosts) && !Array.isArray(config.referers)) {
		throw Error('either config.hosts or config.referers must included in the config object.')
	}

	if (config.hosts) {

		if (config.hosts.length < 1) {
			throw Error('config.hosts must be an array with at least one element.')
		}

		// throws an error if hosts contains an element that is not a string or RegExp
		config.hosts.forEach(host => checkAllowedType(host))
	}

	if (config.referers){
		
		if (config.referers.length < 1) {
			throw Error('config.referers must be an array with at least one element.')
		}

		// throws an error if referers contains an element that is not a string or RegExp
		config.referers.forEach(referer => checkAllowedType(referer))
	}

	if (config.mode) {
		if (!['both', 'either'].includes(config.mode)) {
			throw Error(`${config.mode} is an unsupported config.mode. Value must be exactly "either" or "both".`)
		}
	} else {
		config.mode = 'both' // set default mode to both
	}

	if (config.fail != null && typeof config.fail !== 'function') {
		throw Error(`config.fail must be a function if it is defined.`)
	}

	return function(req, res, next) {
		
		let allowed = true

		if (config.mode == 'both') {
			if (config.hosts && config.referers) {
				allowed = (isAllowed(req.headers.host, config.hosts) && 
				           isAllowed(req.headers.referer, config.referers))
			} 
			else if (config.hosts)    allowed = isAllowed(req.headers.host, config.hosts)
			else if (config.referers) allowed = isAllowed(req.headers.referer, config.referers)
		} else { // mode is either 
			allowed = (isAllowed(req.headers.host, config.hosts) ||
				       isAllowed(req.headers.referer, config.referers))
		}

		if (allowed) next()
		else if (typeof config.fail === 'function') config.fail(req, res, next) 
		else fail(req, res, next)
	}

	function isAllowed(headerValue, allowedValues) {
		if (!headerValue || !allowedValues) return false
		return allowedValues.some(candidate => {
			if (typeof candidate === 'string') {
				return candidate === headerValue
			} else if (candidate instanceof RegExp){
				return candidate.test(headerValue)
			}
			return false
		})
	}

	function checkAllowedType(type) {
		if (! (typeof type === 'string' || type instanceof RegExp)) {
			let message = `${type} is not an allowed Host/Referer type. `
			message    += 'Host/Referer values must be either strings or '
			message    += 'regular expression objects.'
			throw Error(message) 
		}
	}

	function fail(req, res, next) {
		res.status(403).send('Forbidden')
	}
}

================================================
FILE: package.json
================================================
{
  "name": "host-validation",
  "version": "2.0.1",
  "description": "Node.js middleware to validate Host and Referer headers in HTTP requests and protect against DNS rebinding attacks.",
  "main": "index.js",
  "scripts": {
    "test": "node test.js",
    "coverage": "nyc --reporter=html --reporter=text node test.js && rm -rf .nyc_output/",
    "coveralls": "istanbul cover test.js -x test.js --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/brannondorsey/host-validation.git"
  },
  "keywords": [
    "express",
    "middleware",
    "headers",
    "host",
    "header",
    "dns",
    "rebind",
    "validation"
  ],
  "author": "Brannon Dorsey <brannon@brannondorsey.com>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/brannondorsey/host-validation/issues"
  },
  "homepage": "https://github.com/brannondorsey/host-validation#readme",
  "devDependencies": {
    "coveralls": "^3.0.2",
    "express": "^4.16.3",
    "istanbul": "^0.4.5",
    "nyc": "^13.0.1",
    "request": "^2.85.0"
  }
}


================================================
FILE: test.js
================================================
// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

const assert         = require('assert')
const request        = require('request')
const express        = require('express')
const hostValidation = require('./index.js')

// ASSERTIONS

assert.throws(() => hostValidation(), (err) => {
	return err.message === 'a config object must be provided as the first argument to this function.'
})

assert.throws(() => hostValidation({}), (err) => {
	return err.message === 'either config.hosts or config.referers must included in the config object.'
})

assert.throws(() => hostValidation({ hosts: [] }), (err) => {
	return err.message === 'config.hosts must be an array with at least one element.'
})

assert.throws(() => hostValidation({ referers: [] }), (err) => {
	return err.message === 'config.referers must be an array with at least one element.'
})

assert.throws(() => hostValidation({ referrers: [] }), (err) => {
	return err.message === 'config.referers must be an array with at least one element.'
})

assert.throws(() => hostValidation({ hosts: [5] }), (err) => {
	return err.message === '5 is not an allowed Host/Referer type. Host/Referer values must be either strings or regular expression objects.'
})

assert.throws(() => hostValidation({ hosts: ['test.com'], mode: 'fakemode' }), (err) => {
	return err.message === 'fakemode is an unsupported config.mode. Value must be exactly "either" or "both".'
})

assert.throws(() => hostValidation({ hosts: ['test.com'], fail: 5 }), (err) => {
	return err.message === 'config.fail must be a function if it is defined.'
})

// SERVER --------------------------------------------------------------------------------
const app = express()

app.use('/dev-host-test', hostValidation({ hosts: ['127.0.0.1:4322', 'localhost:4322'] }))
app.get('/dev-host-test', (req, res) => allowed(res))

app.use('/host-test', hostValidation({ hosts: ['mydomain.com', 
	                                           'myseconddomain.com',
	                                           'subdomain.mydomain.com',
	                                           'subdomain.mythirddomain.com',
	                                           /^.*.regexdomain\.com$/] }))
app.get('/host-test', (req, res) => allowed(res))


app.use('/referers-test', hostValidation({ referers: ['https://camefromhere.com',
	                                                  'https://camefromhere.com/specific-page',
	                                                  /https:\/\/camefromhere.com\/allowed\/.*/ ]}))
app.get('/referers-test', (req, res) => allowed(res))


app.use('/host-and-referers-test', hostValidation({ 
	hosts: ['trusted-host.com'], 
	referers: ['http://trusted-host.com/login.php'] 
}))

app.get('/host-and-referers-test', (req, res) => allowed(res))

app.use('/host-or-referers-test', hostValidation({ 
	hosts: ['trusted-host.com'], 
	referrers: ['http://trusted-host.com/login.php'], // account for correct spelling of referers
	mode: 'either' 
}))
app.get('/host-or-referers-test', (req, res) => allowed(res))

// regex to match '192.168.1.1-255' (actually matches '192.168.1.001-255' too, but w/e...)
const lanHostRegex = /^192\.168\.1\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/
app.use('/lan-host-regex-test', hostValidation({ hosts: [lanHostRegex] }))
app.get('/lan-host-regex-test', (req, res) => allowed(res))


const lanRefererRegex = /^http:\/\/192\.168\.1\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])(\/.*){0,1}$/
app.use('/lan-referer-regex-test', hostValidation({ referers: [lanRefererRegex] }))
app.get('/lan-referer-regex-test', (req, res) => allowed(res))

app.use('/https-referer', hostValidation({ referers: [/^https:\/\//] }))
app.get('/https-referer', (req, res) => allowed(res))


app.use('/custom-fail-test', hostValidation({ 
	referers: [/^https:\/\//], 
	fail: (req, res, next) => {
		// using 401 instead of 403 for testing purposes only
		res.status(401).send('Forbidden: Referer must be an HTTPS site.')
	}
}))
app.get('/custom-fail-test', (req, res) => allowed(res))

app.use('/custom-fail-teapot-test', hostValidation({ 
	hosts: ['office-teapot'],
	fail: (req, res, next) => {
        // send a 418 "I'm a Teapot" Error
		res.status(418).send('I\'m the office teapot. Refer to me only as such.')
	}
}))
app.get('/custom-fail-teapot-test', (req, res) => allowed(res))

const server = app.listen(4322, () => {
	console.log('server allowing HTTP requests from 127.0.0.1 on port 4322')
	runClientTests()
})

// this is a terrible hack, but it's the simplest way to have the tests finish
// please, nobody stone me for this...
setTimeout(() => server.close(() => process.exit(0)), 2000)

function allowed(res) {
	res.send('Hello trusted client, thanks for sending the right Host/Referer headers.')
} 

// CLIENT --------------------------------------------------------------------------------

function runClientTests() {

	var options = {
		url: null,
		headers: {
		}
	}

	const server = 'http://127.0.0.1:4322'
	options.url = `${server}/dev-host-test`

	options.headers.Host = '127.0.0.1:4322'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'localhost:4322'
	request(options, expect(clone(options), 200))

	options.headers['Host'] = 'DNSRebind-attack.com'
	request(options, expect(clone(options), 403))

	options.url = `${server}/host-test`
	options.headers.Host = 'mydomain.com'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'subdomain.mydomain.com'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'myseconddomain.com'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'mythirddomain.com'
	request(options, expect(clone(options), 403))

	options.headers.Host = 'subdomain.mythirddomain.com'
	request(options, expect(clone(options), 200))


	options.url = `${server}/referers-test`
	options.headers.Host = null
	options.headers.Referer = 'https://camefromhere.com'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://camefromhere.com' //non-HTTP
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'https://camefromhere.com/specific-page'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'https://camefromhere.com/different-page'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'https://camefromhere.com/allowed/page'
	request(options, expect(clone(options), 200))

	// not this shouldn't be allowed given the regex
	options.headers.Referer = 'https://camefromhere.com/allowed'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://shouldnt-be-allowed-to-come-from-here.com'
	request(options, expect(clone(options), 403))


	options.url = `${server}/host-and-referers-test`
	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))

	options.headers.Host = null
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 403))

	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/index.php'
	request(options, expect(clone(options), 403))

	options.headers.Host = 'untrusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 403))


	options.url = `${server}/host-or-referers-test`
	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = null
	request(options, expect(clone(options), 200))

	options.headers.Host = null
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'trusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/index.php'
	request(options, expect(clone(options), 200))

	options.headers.Host = 'untrusted-host.com'
	options.headers.Referer = 'http://trusted-host.com/login.php'
	request(options, expect(clone(options), 200))

	options.headers.Host = null
	options.headers.Referer = 'http://trusted-host.com/index.php'
	request(options, expect(clone(options), 403))

	options.headers.Host = 'untrusted-host.com'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))


	options.url = `${server}/lan-host-regex-test`
	options.headers.Host = '192.168.1.83'
	options.headers.Referer = null
	request(options, expect(clone(options), 200))

	options.headers.Host = '192.168.1.1'
	options.headers.Referer = null
	request(options, expect(clone(options), 200))

	options.headers.Host = '192.168.1.255'
	options.headers.Referer = null
	request(options, expect(clone(options), 200))

	options.headers.Host = '192.168.2.1'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))

	options.headers.Host = '10.0.0.1'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))

	options.headers.Host = '192.168.1.256'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))

	options.headers.Host = '192.168.1.2556'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))

	options.headers.Host = 'mydomain.com'
	options.headers.Referer = null
	request(options, expect(clone(options), 403))


	options.url = `${server}/lan-referer-regex-test`
	options.headers.Host = null
	options.headers.Referer = 'http://192.168.1.83'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://192.168.1.83/'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://192.168.1.1/router_login.html'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://192.168.1.1/login'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://192.168.2.1'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://10.0.0.1'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://10.0.0.1/login'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://192.168.1.2556'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://mydomain.com'
	request(options, expect(clone(options), 403))


	options.url = `${server}/https-referer`
	options.headers.Host = null
	options.headers.Referer = 'https://google.com'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'https://localhost'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'https://github.com/login'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://google.com'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://localhost'
	request(options, expect(clone(options), 403))

	options.headers.Referer = 'http://github.com/login'
	request(options, expect(clone(options), 403))


	options.url = `${server}/custom-fail-test`
	options.headers.Host = null
	options.headers.Referer = 'https://google.com'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'https://github.com/login'
	request(options, expect(clone(options), 200))

	options.headers.Referer = 'http://localhost'
	request(options, expect(clone(options), 401))

	options.headers.Referer = 'http://google.com'
	request(options, expect(clone(options), 401))

	options.url = `${server}/custom-fail-teapot-test`
	options.headers.Host = 'office-teapot'
	options.headers.Referer = null
	request(options, expect(clone(options), 200))

	options.headers.Host = 'office-coffeepot'
	request(options, expect(clone(options), 418))
}

function expect(options, status) {
	// console.log(`testing with ${options.url}`)
	return function(error, response, body){
		// console.log(`in here with ${options.url}`)
		if (error) throw error
		
		let print = `URL: ${options.url} Status: ${response.statusCode} `
		if (options.headers.Host) print += `Host: ${options.headers.Host} `
		if (options.headers.Referer) print += `Referer: ${options.headers.Referer} `
		console.log(print)

		if (response.statusCode != status) {
			throw new Error(`Expected status ${status} but received ${response.statusCode}`)
		}
	}
}

function clone(obj) {
	return JSON.parse(JSON.stringify(obj))
}
Download .txt
gitextract__lqq0zf9/

├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── example.js
├── index.js
├── package.json
└── test.js
Download .txt
SYMBOL INDEX (7 symbols across 2 files)

FILE: index.js
  function isAllowed (line 89) | function isAllowed(headerValue, allowedValues) {
  function checkAllowedType (line 101) | function checkAllowedType(type) {
  function fail (line 110) | function fail(req, res, next) {

FILE: test.js
  function allowed (line 135) | function allowed(res) {
  function runClientTests (line 141) | function runClientTests() {
  function expect (line 363) | function expect(options, status) {
  function clone (line 380) | function clone(obj) {
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (38K chars).
[
  {
    "path": ".gitignore",
    "chars": 36,
    "preview": "node_modules/\ncoverage/\n.nyc_output/"
  },
  {
    "path": ".npmignore",
    "chars": 36,
    "preview": "coverage/\n.nyc_output/\n.travis.yml\n\n"
  },
  {
    "path": ".travis.yml",
    "chars": 146,
    "preview": "sudo: false\nlanguage: node_js\nnode_js:\n  - \"8\"\nscript:\n  - npm test\nafter_script:\n  - npm run coveralls\ncache:\n  directo"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1114,
    "preview": "# Host Validation CHANGELOG\n\n## v2.0.1\n\n- Improve performance in `isAllowed()` thanks to [Evan Hahn](https://github.com/"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5491,
    "preview": "# Code of Conduct\n\n## 1. Purpose\n\nA primary goal of Host Validation is to be inclusive to the largest number of contribu"
  },
  {
    "path": "LICENSE",
    "chars": 1099,
    "preview": "MIT License\n\nCopyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "README.md",
    "chars": 8021,
    "preview": "# Host Validation\n\n[![Build Status](https://travis-ci.com/brannondorsey/host-validation.svg?branch=master)](https://trav"
  },
  {
    "path": "example.js",
    "chars": 1686,
    "preview": "// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>\n//\n// Permission is hereby granted, free of charge, to "
  },
  {
    "path": "index.js",
    "chars": 3970,
    "preview": "// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>\n//\n// Permission is hereby granted, free of charge, to "
  },
  {
    "path": "package.json",
    "chars": 1166,
    "preview": "{\n  \"name\": \"host-validation\",\n  \"version\": \"2.0.1\",\n  \"description\": \"Node.js middleware to validate Host and Referer h"
  },
  {
    "path": "test.js",
    "chars": 13587,
    "preview": "// Copyright (c) 2018 Brannon Dorsey <brannon@brannondorsey.com>\n//\n// Permission is hereby granted, free of charge, to "
  }
]

About this extraction

This page contains the full source code of the brannondorsey/host-validation GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (35.5 KB), approximately 8.8k tokens, and a symbol index with 7 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!