mirror of
https://github.com/nunocoracao/blowfish.git
synced 2026-01-30 15:31:52 +00:00
config redirect
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
dist
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "braintree/client",
|
||||
"rules": {
|
||||
"no-control-regex": 0
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
* @braintree/team-sdk-js
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
name: "Unit Tests"
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Unit Tests on Ubuntu"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14.x"
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
+1
@@ -0,0 +1 @@
|
||||
v16
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
*-lock.json
|
||||
dist
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# 6.0.0
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
- Decode HTML characters automatically that would result in an XSS vulnerability when rendering links via a server rendered HTML file
|
||||
|
||||
```js
|
||||
// decodes to javacript:alert('XSS')
|
||||
const vulnerableUrl =
|
||||
"javascript:alert('XSS')";
|
||||
|
||||
sanitizeUrl(vulnerableUrl); // 'about:blank'
|
||||
|
||||
const okUrl = "https://example.com/" + vulnerableUrl;
|
||||
|
||||
// since the javascript bit is in the path instead of the protocol
|
||||
// this is successfully sanitized
|
||||
sanitizeUrl(okUrl); // 'https://example.com/javascript:alert('XSS');
|
||||
```
|
||||
|
||||
# 5.0.2
|
||||
|
||||
- Fix issue where certain invisible white space characters were not being sanitized (#35)
|
||||
|
||||
# 5.0.1
|
||||
|
||||
- Fix issue where certain safe characters were being filtered out (#31 thanks @akirchmyer)
|
||||
|
||||
# 5.0.0
|
||||
|
||||
_Breaking Changes_
|
||||
|
||||
- Sanitize vbscript urls (thanks @vicnicius)
|
||||
|
||||
# 4.1.1
|
||||
|
||||
- Fixup path to type declaration (closes #25)
|
||||
|
||||
# 4.1.0
|
||||
|
||||
- Add typescript types
|
||||
|
||||
# CHANGELOG
|
||||
|
||||
## 4.0.1
|
||||
|
||||
- Fix issue where urls with accented characters were incorrectly sanitized
|
||||
|
||||
## 4.0.0
|
||||
|
||||
_Breaking Changes_
|
||||
|
||||
- Protocol-less urls (ie: www.example.com) will be sanitised and passed on instead of sending out `about:blank` (Thanks @chawes13 #18)
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- Trim whitespace from urls
|
||||
|
||||
## 3.0.0
|
||||
|
||||
_breaking changes_
|
||||
|
||||
- Replace blank strings with about:blank
|
||||
- Replace null values with about:blank
|
||||
|
||||
## 2.1.0
|
||||
|
||||
- Allow relative urls to be sanitized
|
||||
|
||||
## 2.0.2
|
||||
|
||||
- Sanitize malicious URLs that begin with `\s`
|
||||
|
||||
## 2.0.1
|
||||
|
||||
- Sanitize malicious URLs that begin with %20
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- sanitize data: urls
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- sanitize javascript: urls
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Braintree
|
||||
|
||||
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.
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# sanitize-url
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install -S @braintree/sanitize-url
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
var sanitizeUrl = require("@braintree/sanitize-url").sanitizeUrl;
|
||||
|
||||
sanitizeUrl("https://example.com"); // 'https://example.com'
|
||||
sanitizeUrl("http://example.com"); // 'http://example.com'
|
||||
sanitizeUrl("www.example.com"); // 'www.example.com'
|
||||
sanitizeUrl("mailto:hello@example.com"); // 'mailto:hello@example.com'
|
||||
sanitizeUrl(
|
||||
"https://example.com"
|
||||
); // https://example.com
|
||||
|
||||
sanitizeUrl("javascript:alert(document.domain)"); // 'about:blank'
|
||||
sanitizeUrl("jAvasCrIPT:alert(document.domain)"); // 'about:blank'
|
||||
sanitizeUrl(decodeURIComponent("JaVaScRiP%0at:alert(document.domain)")); // 'about:blank'
|
||||
// HTML encoded javascript:alert('XSS')
|
||||
sanitizeUrl(
|
||||
"javascript:alert('XSS')"
|
||||
); // 'about:blank'
|
||||
```
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function sanitizeUrl(url?: string): string;
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeUrl = void 0;
|
||||
var invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
|
||||
var htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
|
||||
var ctrlCharactersRegex = /[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
|
||||
var urlSchemeRegex = /^([^:]+):/gm;
|
||||
var relativeFirstCharacters = [".", "/"];
|
||||
function isRelativeUrlWithoutProtocol(url) {
|
||||
return relativeFirstCharacters.indexOf(url[0]) > -1;
|
||||
}
|
||||
// adapted from https://stackoverflow.com/a/29824550/2601552
|
||||
function decodeHtmlCharacters(str) {
|
||||
return str.replace(htmlEntitiesRegex, function (match, dec) {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
function sanitizeUrl(url) {
|
||||
var sanitizedUrl = decodeHtmlCharacters(url || "")
|
||||
.replace(ctrlCharactersRegex, "")
|
||||
.trim();
|
||||
if (!sanitizedUrl) {
|
||||
return "about:blank";
|
||||
}
|
||||
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
var urlSchemeParseResults = sanitizedUrl.match(urlSchemeRegex);
|
||||
if (!urlSchemeParseResults) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
var urlScheme = urlSchemeParseResults[0];
|
||||
if (invalidProtocolRegex.test(urlScheme)) {
|
||||
return "about:blank";
|
||||
}
|
||||
return sanitizedUrl;
|
||||
}
|
||||
exports.sanitizeUrl = sanitizeUrl;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@braintree/sanitize-url",
|
||||
"version": "6.0.0",
|
||||
"description": "A url sanitizer",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"prepublishOnly": "npm run build",
|
||||
"prebuild": "prettier --write .",
|
||||
"build": "tsc --declaration",
|
||||
"lint": "eslint --ext js,ts .",
|
||||
"posttest": "npm run lint",
|
||||
"test": "jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/braintree/sanitize-url.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/braintree/sanitize-url/issues"
|
||||
},
|
||||
"homepage": "https://github.com/braintree/sanitize-url#readme",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
||||
"chai": "^4.3.4",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-braintree": "^6.0.0-typescript-prep-rc.2",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^27.4.5",
|
||||
"prettier": "^2.5.1",
|
||||
"ts-jest": "^27.1.2",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"preset": "ts-jest",
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsconfig": "src/__tests__/tsconfig.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/* eslint-disable no-script-url */
|
||||
import { sanitizeUrl } from "..";
|
||||
|
||||
describe("sanitizeUrl", () => {
|
||||
it("does not alter http URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("http://example.com/path/to:something")).toBe(
|
||||
"http://example.com/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter http URLs with ports with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("http://example.com:4567/path/to:something")).toBe(
|
||||
"http://example.com:4567/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter https URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("https://example.com")).toBe("https://example.com");
|
||||
});
|
||||
|
||||
it("does not alter https URLs with ports with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("https://example.com:4567/path/to:something")).toBe(
|
||||
"https://example.com:4567/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter relative-path reference URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("./path/to/my.json")).toBe("./path/to/my.json");
|
||||
});
|
||||
|
||||
it("does not alter absolute-path reference URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("/path/to/my.json")).toBe("/path/to/my.json");
|
||||
});
|
||||
|
||||
it("does not alter protocol-less network-path URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("//google.com/robots.txt")).toBe(
|
||||
"//google.com/robots.txt"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter protocol-less URLs with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("www.example.com")).toBe("www.example.com");
|
||||
});
|
||||
|
||||
it("does not alter deep-link urls with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("com.braintreepayments.demo://example")).toBe(
|
||||
"com.braintreepayments.demo://example"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter mailto urls with alphanumeric characters", () => {
|
||||
expect(sanitizeUrl("mailto:test@example.com?subject=hello+world")).toBe(
|
||||
"mailto:test@example.com?subject=hello+world"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not alter urls with accented characters", () => {
|
||||
expect(sanitizeUrl("www.example.com/with-áccêntš")).toBe(
|
||||
"www.example.com/with-áccêntš"
|
||||
);
|
||||
});
|
||||
|
||||
it("does not strip harmless unicode characters", () => {
|
||||
expect(sanitizeUrl("www.example.com/лот.рфшишкиü–")).toBe(
|
||||
"www.example.com/лот.рфшишкиü–"
|
||||
);
|
||||
});
|
||||
|
||||
it("strips out ctrl chars", () => {
|
||||
expect(
|
||||
sanitizeUrl("www.example.com/\u200D\u0000\u001F\x00\x1F\uFEFFfoo")
|
||||
).toBe("www.example.com/foo");
|
||||
});
|
||||
|
||||
it("replaces blank urls with about:blank", () => {
|
||||
expect(sanitizeUrl("")).toBe("about:blank");
|
||||
});
|
||||
|
||||
it("replaces null values with about:blank", () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
expect(sanitizeUrl(null)).toBe("about:blank");
|
||||
});
|
||||
|
||||
it("replaces undefined values with about:blank", () => {
|
||||
expect(sanitizeUrl()).toBe("about:blank");
|
||||
});
|
||||
|
||||
it("removes whitespace from urls", () => {
|
||||
expect(sanitizeUrl(" http://example.com/path/to:something ")).toBe(
|
||||
"http://example.com/path/to:something"
|
||||
);
|
||||
});
|
||||
|
||||
it("decodes html entities", () => {
|
||||
// all these decode to javascript:alert('xss');
|
||||
const attackVectors = [
|
||||
"javascript:alert('XSS')",
|
||||
"javascript:alert('XSS')",
|
||||
"javascript:alert('XSS')",
|
||||
"jav	ascript:alert('XSS');",
|
||||
"  javascript:alert('XSS');",
|
||||
];
|
||||
|
||||
attackVectors.forEach((vector) => {
|
||||
expect(sanitizeUrl(vector)).toBe("about:blank");
|
||||
});
|
||||
|
||||
// https://example.com/javascript:alert('XSS')
|
||||
// since the javascript is the url path, and not the protocol,
|
||||
// this url is technically sanitized
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
"https://example.com/javascript:alert('XSS')"
|
||||
)
|
||||
).toBe("https://example.com/javascript:alert('XSS')");
|
||||
});
|
||||
|
||||
describe("invalid protocols", () => {
|
||||
describe.each(["javascript", "data", "vbscript"])("%s", (protocol) => {
|
||||
it(`replaces ${protocol} urls with about:blank`, () => {
|
||||
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
||||
"about:blank"
|
||||
);
|
||||
});
|
||||
|
||||
it(`allows ${protocol} urls that start with a letter prefix`, () => {
|
||||
expect(sanitizeUrl(`not_${protocol}:alert(document.domain)`)).toBe(
|
||||
`not_${protocol}:alert(document.domain)`
|
||||
);
|
||||
});
|
||||
|
||||
it(`disallows ${protocol} urls that start with non-\w characters as a suffix for the protocol`, () => {
|
||||
expect(sanitizeUrl(`&!*${protocol}:alert(document.domain)`)).toBe(
|
||||
"about:blank"
|
||||
);
|
||||
});
|
||||
|
||||
it(`disregards capitalization for ${protocol} urls`, () => {
|
||||
// upper case every other letter in protocol name
|
||||
const mixedCapitalizationProtocol = protocol
|
||||
.split("")
|
||||
.map((character, index) => {
|
||||
if (index % 2 === 0) {
|
||||
return character.toUpperCase();
|
||||
}
|
||||
return character;
|
||||
})
|
||||
.join("");
|
||||
|
||||
expect(
|
||||
sanitizeUrl(`${mixedCapitalizationProtocol}:alert(document.domain)`)
|
||||
).toBe("about:blank");
|
||||
});
|
||||
|
||||
it(`ignores invisible ctrl characters in ${protocol} urls`, () => {
|
||||
const protocolWithControlCharacters = protocol
|
||||
.split("")
|
||||
.map((character, index) => {
|
||||
if (index === 1) {
|
||||
return character + "%EF%BB%BF%EF%BB%BF";
|
||||
} else if (index === 2) {
|
||||
return character + "%e2%80%8b";
|
||||
}
|
||||
return character;
|
||||
})
|
||||
.join("");
|
||||
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
decodeURIComponent(
|
||||
`${protocolWithControlCharacters}:alert(document.domain)`
|
||||
)
|
||||
)
|
||||
).toBe("about:blank");
|
||||
});
|
||||
|
||||
it(`replaces ${protocol} urls with about:blank when url begins with %20`, () => {
|
||||
expect(
|
||||
sanitizeUrl(
|
||||
decodeURIComponent(`%20%20%20%20${protocol}:alert(document.domain)`)
|
||||
)
|
||||
).toBe("about:blank");
|
||||
});
|
||||
|
||||
it(`replaces ${protocol} urls with about:blank when ${protocol} url begins with spaces`, () => {
|
||||
expect(sanitizeUrl(` ${protocol}:alert(document.domain)`)).toBe(
|
||||
"about:blank"
|
||||
);
|
||||
});
|
||||
|
||||
it(`does not replace ${protocol}: if it is not in the scheme of the URL`, () => {
|
||||
expect(sanitizeUrl(`http://example.com#${protocol}:foo`)).toBe(
|
||||
`http://example.com#${protocol}:foo`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": false
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
const invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
|
||||
const htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
|
||||
const ctrlCharactersRegex =
|
||||
/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
|
||||
const urlSchemeRegex = /^([^:]+):/gm;
|
||||
const relativeFirstCharacters = [".", "/"];
|
||||
|
||||
function isRelativeUrlWithoutProtocol(url: string): boolean {
|
||||
return relativeFirstCharacters.indexOf(url[0]) > -1;
|
||||
}
|
||||
|
||||
// adapted from https://stackoverflow.com/a/29824550/2601552
|
||||
function decodeHtmlCharacters(str: string) {
|
||||
return str.replace(htmlEntitiesRegex, (match, dec) => {
|
||||
return String.fromCharCode(dec);
|
||||
});
|
||||
}
|
||||
|
||||
export function sanitizeUrl(url?: string): string {
|
||||
const sanitizedUrl = decodeHtmlCharacters(url || "")
|
||||
.replace(ctrlCharactersRegex, "")
|
||||
.trim();
|
||||
|
||||
if (!sanitizedUrl) {
|
||||
return "about:blank";
|
||||
}
|
||||
|
||||
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
|
||||
const urlSchemeParseResults = sanitizedUrl.match(urlSchemeRegex);
|
||||
|
||||
if (!urlSchemeParseResults) {
|
||||
return sanitizedUrl;
|
||||
}
|
||||
|
||||
const urlScheme = urlSchemeParseResults[0];
|
||||
|
||||
if (invalidProtocolRegex.test(urlScheme)) {
|
||||
return "about:blank";
|
||||
}
|
||||
|
||||
return sanitizedUrl;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"target": "es5",
|
||||
"lib": ["es2015", "dom"]
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["**/__tests__/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user