@ez1219/sanitize-url 7.1.2-rc.0 → 7.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/.cz-config.js +0 -72
- package/.eslintignore +0 -2
- package/.eslintrc +0 -6
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report_form.yml +0 -47
- package/.github/ISSUE_TEMPLATE/config.yml +0 -1
- package/.github/ISSUE_TEMPLATE/feature_request_form.yml +0 -30
- package/.github/dependabot.yml +0 -16
- package/.github/pull_request_template.md +0 -17
- package/.github/workflows/ci.yml +0 -30
- package/.github/workflows/enforce-conventional-pr-title.yml +0 -39
- package/.github/workflows/publish.yml +0 -65
- package/.github/workflows/security.yml +0 -22
- package/.github/workflows/stale-cleanup.yml +0 -22
- package/.husky/commit-msg +0 -2
- package/.nvmrc +0 -1
- package/.prettierignore +0 -3
- package/CHANGELOG.md +0 -155
- package/CONTRIBUTING.md +0 -79
- package/SECURITY.md +0 -41
- package/commitlint.config.js +0 -70
- package/src/__tests__/index.test.ts +0 -299
- package/src/constants.ts +0 -10
- package/src/index.ts +0 -114
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -17
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ez1219/sanitize-url",
|
|
3
|
-
"version": "7.1.2
|
|
3
|
+
"version": "7.1.2",
|
|
4
4
|
"description": "A url sanitizer",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"author": "",
|
|
8
11
|
"scripts": {
|
|
9
12
|
"prepublishOnly": "npm run build",
|
package/.cz-config.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
types: [
|
|
3
|
-
{ value: "feat", name: "feat: A new feature" },
|
|
4
|
-
{ value: "fix", name: "fix: A bug fix" },
|
|
5
|
-
{
|
|
6
|
-
value: "review",
|
|
7
|
-
name: "review: Commits added during the review process",
|
|
8
|
-
},
|
|
9
|
-
{ value: "docs", name: "docs: Documentation only changes" },
|
|
10
|
-
{
|
|
11
|
-
value: "style",
|
|
12
|
-
name: "style: Changes that do not affect the meaning of the code (formatting, semicolons)",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
value: "refactor",
|
|
16
|
-
name: "refactor: A code change that neither fixes a bug nor adds a feature",
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
value: "perf",
|
|
20
|
-
name: "perf: A code change that improves performance",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
value: "test",
|
|
24
|
-
name: "test: Adding missing tests or correcting existing tests",
|
|
25
|
-
},
|
|
26
|
-
{ value: "build", name: "build: Build system changes" },
|
|
27
|
-
{
|
|
28
|
-
value: "dx",
|
|
29
|
-
name: "dx: Changes that affect the development cycle",
|
|
30
|
-
},
|
|
31
|
-
{ value: "ci", name: "ci: CI configuration changes" },
|
|
32
|
-
{
|
|
33
|
-
value: "chore",
|
|
34
|
-
name: "chore: Other maintenance, including dependency updates",
|
|
35
|
-
},
|
|
36
|
-
{ value: "revert", name: "revert: Revert a commit" },
|
|
37
|
-
],
|
|
38
|
-
|
|
39
|
-
scopes: [
|
|
40
|
-
{ name: "american-express" },
|
|
41
|
-
{ name: "apple-pay" },
|
|
42
|
-
{ name: "client" },
|
|
43
|
-
{ name: "data-collector" },
|
|
44
|
-
{ name: "fastlane" },
|
|
45
|
-
{ name: "google-payment" },
|
|
46
|
-
{ name: "hosted-fields" },
|
|
47
|
-
{ name: "instant-verification" },
|
|
48
|
-
{ name: "local-payment" },
|
|
49
|
-
{ name: "payment-ready" },
|
|
50
|
-
{ name: "payment-request" },
|
|
51
|
-
{ name: "paypal-checkout" },
|
|
52
|
-
{ name: "paypal-checkout-v6" },
|
|
53
|
-
{ name: "sepa" },
|
|
54
|
-
{ name: "three-d-secure" },
|
|
55
|
-
{ name: "us-bank-account" },
|
|
56
|
-
{ name: "vault-manager" },
|
|
57
|
-
{ name: "venmo" },
|
|
58
|
-
{ name: "deps" },
|
|
59
|
-
{ name: "other" },
|
|
60
|
-
],
|
|
61
|
-
|
|
62
|
-
// When type is "review", skip the scope prompt entirely
|
|
63
|
-
scopeOverrides: {
|
|
64
|
-
review: [],
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
// Skip questions you don't need
|
|
68
|
-
skipQuestions: ["footer"],
|
|
69
|
-
|
|
70
|
-
// Limit subject length
|
|
71
|
-
subjectLimit: 85,
|
|
72
|
-
};
|
package/.eslintignore
DELETED
package/.eslintrc
DELETED
package/.github/CODEOWNERS
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
* @ez1219
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
name: Bug Report
|
|
2
|
-
description: File a bug report.
|
|
3
|
-
title: "[Bug]: "
|
|
4
|
-
labels: ["bug", "triage"]
|
|
5
|
-
projects: []
|
|
6
|
-
body:
|
|
7
|
-
- type: markdown
|
|
8
|
-
attributes:
|
|
9
|
-
value: |
|
|
10
|
-
Thanks for taking the time to fill out this bug report!
|
|
11
|
-
- type: textarea
|
|
12
|
-
id: what-happened
|
|
13
|
-
attributes:
|
|
14
|
-
label: What happened?
|
|
15
|
-
description: Detail the bad behavior and the steps required to duplicate it
|
|
16
|
-
placeholder: Tell us what you see!
|
|
17
|
-
value: "A bug happened!"
|
|
18
|
-
validations:
|
|
19
|
-
required: true
|
|
20
|
-
- type: textarea
|
|
21
|
-
id: expected-behavior
|
|
22
|
-
attributes:
|
|
23
|
-
label: What did you expect to happen?
|
|
24
|
-
description: What did you expect to happen when performing the action?
|
|
25
|
-
placeholder: Expected behavior
|
|
26
|
-
value: "Expected behavior"
|
|
27
|
-
validations:
|
|
28
|
-
required: true
|
|
29
|
-
- type: input
|
|
30
|
-
id: version
|
|
31
|
-
attributes:
|
|
32
|
-
label: Version
|
|
33
|
-
description: What version of this library are you running?
|
|
34
|
-
placeholder: "v0.0.0"
|
|
35
|
-
value:
|
|
36
|
-
validations:
|
|
37
|
-
required: true
|
|
38
|
-
- type: textarea
|
|
39
|
-
id: browsers
|
|
40
|
-
attributes:
|
|
41
|
-
label: What browsers are you seeing the problem on?
|
|
42
|
-
description: |
|
|
43
|
-
List browsers where you see the problem reported. For example:
|
|
44
|
-
* Mac OS 15.3.5, Chrome 130.0.1234.987
|
|
45
|
-
* Android 14, Firefox 133.0
|
|
46
|
-
* iPadOS 17.1, Safari
|
|
47
|
-
placeholder: "For each browser specify 1) operating system and version 2) browser name and version"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
blank_issues_enabled: true
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
name: Feature Request
|
|
2
|
-
description: Request a new feature.
|
|
3
|
-
title: "[FR]: "
|
|
4
|
-
labels: ["feature", "request", "enhancement", "triage"]
|
|
5
|
-
projects: []
|
|
6
|
-
body:
|
|
7
|
-
- type: markdown
|
|
8
|
-
attributes:
|
|
9
|
-
value: |
|
|
10
|
-
Thanks for taking the time to fill out this feature request!
|
|
11
|
-
- type: textarea
|
|
12
|
-
id: feat-summary
|
|
13
|
-
attributes:
|
|
14
|
-
label: Feature Summary
|
|
15
|
-
description: In a few sentences or less, provide a concise description of the feature being requested.
|
|
16
|
-
placeholder: "New feature that does ___; it can be used when ..."
|
|
17
|
-
validations:
|
|
18
|
-
required: true
|
|
19
|
-
- type: textarea
|
|
20
|
-
id: detailed-description
|
|
21
|
-
attributes:
|
|
22
|
-
label: Detailed description of new feature.
|
|
23
|
-
description: |
|
|
24
|
-
Please provide a detailed description of proposed feature.
|
|
25
|
-
- What problem would this new feature solve?
|
|
26
|
-
- How will this change affect users?
|
|
27
|
-
- Is it a modification to existing functionality? Are you requesting a new/modified function input or output? Please provide an example of what the change might look like?
|
|
28
|
-
placeholder: "New feature details."
|
|
29
|
-
validations:
|
|
30
|
-
required: true
|
package/.github/dependabot.yml
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
updates:
|
|
3
|
-
- package-ecosystem: "npm"
|
|
4
|
-
directory: "/"
|
|
5
|
-
schedule:
|
|
6
|
-
interval: "weekly"
|
|
7
|
-
open-pull-requests-limit: 3
|
|
8
|
-
allow:
|
|
9
|
-
- dependency-type: "development"
|
|
10
|
-
update-types:
|
|
11
|
-
- "version-update:semver-minor"
|
|
12
|
-
groups:
|
|
13
|
-
security-updates:
|
|
14
|
-
applies-to: security-updates
|
|
15
|
-
patterns:
|
|
16
|
-
- "*"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Summary of changes
|
|
2
|
-
|
|
3
|
-
-
|
|
4
|
-
|
|
5
|
-
## Checklist
|
|
6
|
-
|
|
7
|
-
- [ ] Added a changelog entry
|
|
8
|
-
- [ ] Relevant test coverage
|
|
9
|
-
- [ ] Tested and confirmed flows affected by this change are functioning as expected
|
|
10
|
-
|
|
11
|
-
## Authors
|
|
12
|
-
|
|
13
|
-
> List GitHub usernames for everyone who contributed to this pull request.
|
|
14
|
-
|
|
15
|
-
### Reviewers
|
|
16
|
-
|
|
17
|
-
@ez1219
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
name: "CI"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- "*"
|
|
11
|
-
|
|
12
|
-
permissions:
|
|
13
|
-
contents: read
|
|
14
|
-
|
|
15
|
-
jobs:
|
|
16
|
-
ci:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
strategy:
|
|
19
|
-
matrix:
|
|
20
|
-
node-version: [18, 20, 22, 24]
|
|
21
|
-
steps:
|
|
22
|
-
- uses: actions/checkout@v4
|
|
23
|
-
- uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: ${{ matrix.node-version }}
|
|
26
|
-
cache: "npm"
|
|
27
|
-
- run: npm ci
|
|
28
|
-
- run: npm test
|
|
29
|
-
- run: npm run build
|
|
30
|
-
- run: npm run lint
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
name: "PR Title Check"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
types:
|
|
6
|
-
- opened
|
|
7
|
-
- edited
|
|
8
|
-
- reopened
|
|
9
|
-
- synchronize
|
|
10
|
-
- labeled
|
|
11
|
-
- ready_for_review
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
pr-title-check:
|
|
15
|
-
name: "Validate PR title"
|
|
16
|
-
if: >-
|
|
17
|
-
github.actor != 'dependabot[bot]' &&
|
|
18
|
-
!contains(github.actor, '[bot]') &&
|
|
19
|
-
github.event.pull_request.draft == false
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
env:
|
|
22
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
23
|
-
steps:
|
|
24
|
-
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50
|
|
25
|
-
with:
|
|
26
|
-
types: |
|
|
27
|
-
feat
|
|
28
|
-
fix
|
|
29
|
-
docs
|
|
30
|
-
style
|
|
31
|
-
refactor
|
|
32
|
-
perf
|
|
33
|
-
test
|
|
34
|
-
build
|
|
35
|
-
ci
|
|
36
|
-
chore
|
|
37
|
-
revert
|
|
38
|
-
requireScope: false
|
|
39
|
-
ignoreLabels: ignore-semantic-pull-request
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
name: "Publish to npm"
|
|
2
|
-
run-name: Deploy ${{ github.repository }} to npmjs by @${{ github.actor }}
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
release:
|
|
6
|
-
types: [published]
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
inputs:
|
|
9
|
-
version_type:
|
|
10
|
-
description: "Version bump type (major, minor, patch)"
|
|
11
|
-
required: false
|
|
12
|
-
type: choice
|
|
13
|
-
default: "none"
|
|
14
|
-
options:
|
|
15
|
-
- none
|
|
16
|
-
- patch
|
|
17
|
-
- minor
|
|
18
|
-
- major
|
|
19
|
-
|
|
20
|
-
jobs:
|
|
21
|
-
publish:
|
|
22
|
-
runs-on: ubuntu-latest
|
|
23
|
-
permissions:
|
|
24
|
-
contents: write
|
|
25
|
-
id-token: write
|
|
26
|
-
steps:
|
|
27
|
-
- uses: actions/checkout@v4
|
|
28
|
-
with:
|
|
29
|
-
fetch-depth: 0
|
|
30
|
-
|
|
31
|
-
- uses: actions/setup-node@v4
|
|
32
|
-
with:
|
|
33
|
-
node-version: 20
|
|
34
|
-
cache: "npm"
|
|
35
|
-
|
|
36
|
-
- name: Install dependencies
|
|
37
|
-
run: npm ci
|
|
38
|
-
|
|
39
|
-
- name: Run tests
|
|
40
|
-
run: npm test
|
|
41
|
-
|
|
42
|
-
- name: Bump version & create GitHub Release
|
|
43
|
-
if: ${{ github.event_name == 'workflow_dispatch' && inputs.version_type != 'none' }}
|
|
44
|
-
env:
|
|
45
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
46
|
-
run: |
|
|
47
|
-
git config user.name "github-actions[bot]"
|
|
48
|
-
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
49
|
-
npm version ${{ inputs.version_type }} -m "chore(release): %s [skip ci]"
|
|
50
|
-
git push
|
|
51
|
-
git push --tags
|
|
52
|
-
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
53
|
-
gh release create "v$NEW_VERSION" --title "v$NEW_VERSION" --generate-notes
|
|
54
|
-
|
|
55
|
-
- name: Build package
|
|
56
|
-
run: npm run build
|
|
57
|
-
|
|
58
|
-
- name: Publish to npm (with Provenance / OIDC)
|
|
59
|
-
run: |
|
|
60
|
-
if [ -n "$NODE_AUTH_TOKEN" ]; then
|
|
61
|
-
npm config set //registry.npmjs.org/:_authToken "$NODE_AUTH_TOKEN"
|
|
62
|
-
fi
|
|
63
|
-
npm publish --access public --provenance
|
|
64
|
-
env:
|
|
65
|
-
NODE_AUTH_TOKEN: ${{ coalesce(secrets.NPM_TOKEN, secrets.NODE_AUTH_TOKEN) }}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: Security
|
|
2
|
-
|
|
3
|
-
permissions:
|
|
4
|
-
contents: read
|
|
5
|
-
security-events: write
|
|
6
|
-
|
|
7
|
-
on:
|
|
8
|
-
pull_request:
|
|
9
|
-
branches: [main]
|
|
10
|
-
push:
|
|
11
|
-
branches: [main]
|
|
12
|
-
workflow_dispatch:
|
|
13
|
-
|
|
14
|
-
jobs:
|
|
15
|
-
codeql:
|
|
16
|
-
runs-on: ubuntu-latest
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
- uses: github/codeql-action/init@v3
|
|
20
|
-
with:
|
|
21
|
-
languages: javascript-typescript
|
|
22
|
-
- uses: github/codeql-action/analyze@v3
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
name: "Stale Cleanup"
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
schedule:
|
|
5
|
-
# Runs at midnight UTC every Wednesday
|
|
6
|
-
- cron: "0 0 * * 3"
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
issues: write
|
|
11
|
-
pull-requests: write
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
stale:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/stale@v9
|
|
18
|
-
with:
|
|
19
|
-
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
|
20
|
-
stale-pr-message: "This PR is stale because it has been open for 60 days with no activity."
|
|
21
|
-
days-before-stale: 60
|
|
22
|
-
days-before-close: 7
|
package/.husky/commit-msg
DELETED
package/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
v24
|
package/.prettierignore
DELETED
package/CHANGELOG.md
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# CHANGELOG
|
|
2
|
-
|
|
3
|
-
## UNRELEASED
|
|
4
|
-
|
|
5
|
-
- feat(deps): Add [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) to the repo
|
|
6
|
-
|
|
7
|
-
## 7.1.2
|
|
8
|
-
|
|
9
|
-
- Update dependencies
|
|
10
|
-
- @types/jest to 30.0.0
|
|
11
|
-
- @vitest/coverage-v8 to 4.0.16
|
|
12
|
-
- chai to 6.2.2
|
|
13
|
-
- eslint-plugin-prettier to 5.5.4
|
|
14
|
-
- happy-dom to 20.0.11
|
|
15
|
-
- prettier to 3.7.4
|
|
16
|
-
- typescript to 5.9.3
|
|
17
|
-
- vitest to 4.0.16
|
|
18
|
-
- Update Node to v24
|
|
19
|
-
- Add @types/node 24.0.0
|
|
20
|
-
|
|
21
|
-
## 7.1.1
|
|
22
|
-
|
|
23
|
-
- DevDependency Changes
|
|
24
|
-
- happy-dom to 15.11.6
|
|
25
|
-
|
|
26
|
-
- Update (sub-)dependencies
|
|
27
|
-
- cross-spawn to 7.0.6
|
|
28
|
-
- micromatch to 4.0.8
|
|
29
|
-
- vite to 4.5.5
|
|
30
|
-
|
|
31
|
-
## 7.1.0
|
|
32
|
-
|
|
33
|
-
- Updated to handle back-slashes
|
|
34
|
-
|
|
35
|
-
## 7.0.4
|
|
36
|
-
|
|
37
|
-
- Updates get-func-name to 2.0.2
|
|
38
|
-
|
|
39
|
-
## 7.0.3
|
|
40
|
-
|
|
41
|
-
- Dependencies
|
|
42
|
-
- Update braces to 3.0.3
|
|
43
|
-
|
|
44
|
-
## 7.0.2
|
|
45
|
-
|
|
46
|
-
- Improve sanitization of whitespace escapes
|
|
47
|
-
|
|
48
|
-
## 7.0.1
|
|
49
|
-
|
|
50
|
-
- Improve sanitization of HTML entities
|
|
51
|
-
|
|
52
|
-
## 7.0.0
|
|
53
|
-
|
|
54
|
-
- Move constant declarations from index file to `constants.ts` file
|
|
55
|
-
- Update to node v18
|
|
56
|
-
|
|
57
|
-
- Dev Dependency Updates
|
|
58
|
-
- Update to TypeScript 5
|
|
59
|
-
- Other minor dependency updates
|
|
60
|
-
|
|
61
|
-
## 6.0.4
|
|
62
|
-
|
|
63
|
-
- Add additional null byte sanitization prior to html decoding (#48)
|
|
64
|
-
|
|
65
|
-
## 6.0.3
|
|
66
|
-
|
|
67
|
-
- Add null check to beginning of `sanitizeUrl` function ([#54](https://github.com/braintree/sanitize-url/issues/54))
|
|
68
|
-
|
|
69
|
-
## 6.0.2
|
|
70
|
-
|
|
71
|
-
- Fix issue where urls in the form `https://example.com

/something` were not properly sanitized
|
|
72
|
-
|
|
73
|
-
## 6.0.1
|
|
74
|
-
|
|
75
|
-
- Fix issue where urls in the form `javascript:alert('xss');` were not properly sanitized
|
|
76
|
-
- Fix issue where urls in the form `javasc	ript:alert('XSS');` were not properly sanitized
|
|
77
|
-
|
|
78
|
-
## 6.0.0
|
|
79
|
-
|
|
80
|
-
- BREAKING CHANGES
|
|
81
|
-
- Decode HTML characters automatically that would result in an XSS vulnerability when rendering links via a server rendered HTML file
|
|
82
|
-
|
|
83
|
-
```js
|
|
84
|
-
// decodes to javacript:alert('XSS')
|
|
85
|
-
const vulnerableUrl =
|
|
86
|
-
"javascript:alert('XSS')";
|
|
87
|
-
|
|
88
|
-
sanitizeUrl(vulnerableUrl); // 'about:blank'
|
|
89
|
-
|
|
90
|
-
const okUrl = "https://example.com/" + vulnerableUrl;
|
|
91
|
-
|
|
92
|
-
// since the javascript bit is in the path instead of the protocol
|
|
93
|
-
// this is successfully sanitized
|
|
94
|
-
sanitizeUrl(okUrl); // 'https://example.com/javascript:alert('XSS');
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## 5.0.2
|
|
98
|
-
|
|
99
|
-
- Fix issue where certain invisible white space characters were not being sanitized (#35)
|
|
100
|
-
|
|
101
|
-
## 5.0.1
|
|
102
|
-
|
|
103
|
-
- Fix issue where certain safe characters were being filtered out (#31 thanks @akirchmyer)
|
|
104
|
-
|
|
105
|
-
## 5.0.0
|
|
106
|
-
|
|
107
|
-
- BREAKING CHANGES
|
|
108
|
-
- Sanitize vbscript urls (thanks @vicnicius)
|
|
109
|
-
|
|
110
|
-
## 4.1.1
|
|
111
|
-
|
|
112
|
-
- Fixup path to type declaration (closes #25)
|
|
113
|
-
|
|
114
|
-
## 4.1.0
|
|
115
|
-
|
|
116
|
-
- Add typescript types
|
|
117
|
-
|
|
118
|
-
## 4.0.1
|
|
119
|
-
|
|
120
|
-
- Fix issue where urls with accented characters were incorrectly sanitized
|
|
121
|
-
|
|
122
|
-
## 4.0.0
|
|
123
|
-
|
|
124
|
-
- BREAKING CHANGES
|
|
125
|
-
- Protocol-less urls (ie: `www.example.com`) will be sanitised and passed on instead of sending out `about:blank` (Thanks @chawes13 #18)
|
|
126
|
-
|
|
127
|
-
## 3.1.0
|
|
128
|
-
|
|
129
|
-
- Trim whitespace from urls
|
|
130
|
-
|
|
131
|
-
## 3.0.0
|
|
132
|
-
|
|
133
|
-
- BREAKING CHANGES
|
|
134
|
-
- Replace blank strings with about:blank
|
|
135
|
-
- Replace null values with about:blank
|
|
136
|
-
|
|
137
|
-
## 2.1.0
|
|
138
|
-
|
|
139
|
-
- Allow relative urls to be sanitized
|
|
140
|
-
|
|
141
|
-
## 2.0.2
|
|
142
|
-
|
|
143
|
-
- Sanitize malicious URLs that begin with `\s`
|
|
144
|
-
|
|
145
|
-
## 2.0.1
|
|
146
|
-
|
|
147
|
-
- Sanitize malicious URLs that begin with %20
|
|
148
|
-
|
|
149
|
-
## 2.0.0
|
|
150
|
-
|
|
151
|
-
- sanitize data: urls
|
|
152
|
-
|
|
153
|
-
## 1.0.0
|
|
154
|
-
|
|
155
|
-
- sanitize javascript: urls
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# Contributing
|
|
2
|
-
|
|
3
|
-
Thanks for considering contributing to this project. Ways you can help:
|
|
4
|
-
|
|
5
|
-
- [Create a pull request](https://help.github.com/articles/creating-a-pull-request)
|
|
6
|
-
- [Add an issue](https://github.com/ez1219/sanitize-url/issues)
|
|
7
|
-
|
|
8
|
-
## Development
|
|
9
|
-
|
|
10
|
-
Clone this repo, then install the project's dependencies:
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
npm install
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Branch and PR Lifecycle
|
|
17
|
-
|
|
18
|
-
To keep the repository healthy, an automated workflow runs weekly:
|
|
19
|
-
|
|
20
|
-
- **Branches**: flagged after 21 days of inactivity; deleted 7 days later if
|
|
21
|
-
still inactive. To keep a branch, add new commits or remove the bot's comment.
|
|
22
|
-
- **Pull requests**: commented on after 14 days of inactivity; closed 7 days
|
|
23
|
-
later if still inactive. Closed PRs can be reopened at any time.
|
|
24
|
-
- **Issues**: commented on after 14 days of inactivity; closed 7 days later if
|
|
25
|
-
still inactive.
|
|
26
|
-
|
|
27
|
-
You can open a fresh PR or issue at any time if automated cleanup closes yours.
|
|
28
|
-
|
|
29
|
-
## Tests
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
npm test
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Test Stability, Quarantine & Remediation Policy
|
|
36
|
-
|
|
37
|
-
Flaky tests are any test that passes and fails intermittently without code changes (this could be due to things like timeouts, inconsistent return values, poor assertions, etc). Flaky tests erode confidence in the test suite and slow down development. This section defines how we detect, quarantine, and remediate them.
|
|
38
|
-
|
|
39
|
-
#### Detection
|
|
40
|
-
|
|
41
|
-
- **Automatic retries**: Jest unit tests can be configured to automatically retry on failure via `jest.retryTimes()`. Playwright integration tests (where applicable) have retries configured per-project. A test that fails once but passes on retry can be considered as potentially flaky.
|
|
42
|
-
- **Repeat failures**: If a test fails intermittently across multiple CI runs or PRs, it should be reported by opening a GitHub issue with the `flaky-test` label. Include the test name, file path, failure frequency, and any relevant error output.
|
|
43
|
-
|
|
44
|
-
#### Quarantine
|
|
45
|
-
|
|
46
|
-
When a test is confirmed flaky:
|
|
47
|
-
|
|
48
|
-
1. **Open a tracking issue** in GitHub Issues. Use the `flaky-test` label and include:
|
|
49
|
-
- Test file path and test name
|
|
50
|
-
- Observed failure rate and pattern (e.g., "fails ~10% of runs", "only in CI")
|
|
51
|
-
- Stack trace or error message
|
|
52
|
-
2. **Skip the test** using `.skip` and add a comment referencing the tracking issue:
|
|
53
|
-
|
|
54
|
-
```javascript
|
|
55
|
-
// Quarantined: flaky due to timing sensitivity (https://github.com/braintree/sanitize-url/issues/123)
|
|
56
|
-
it.skip("should handle the edge case", function () {
|
|
57
|
-
// ...
|
|
58
|
-
});
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
For `describe` blocks with multiple flaky tests, you may quarantine the entire block:
|
|
62
|
-
|
|
63
|
-
```javascript
|
|
64
|
-
// Quarantined: intermittent failures under load (https://github.com/braintree/sanitize-url/issues/123)
|
|
65
|
-
describe.skip("feature under investigation", function () {
|
|
66
|
-
// ...
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
3. **Skipped tests are excluded from coverage reporting.** Since quarantined tests do not execute, their associated source code is not counted toward coverage metrics. This is expected and acceptable while the test is in quarantine.
|
|
71
|
-
|
|
72
|
-
#### Remediation SLA
|
|
73
|
-
|
|
74
|
-
Quarantined tests must be remedied within **30 days** of being quarantined. Remediation means one of the following:
|
|
75
|
-
|
|
76
|
-
- **Fixed**: The root cause is identified and corrected, the `.skip` is removed, and the test passes reliably.
|
|
77
|
-
- **Deleted**: If the test is no longer valuable or the feature it covers has changed, delete the test and close the tracking issue.
|
|
78
|
-
|
|
79
|
-
If a quarantined test exceeds **14 days** without progress, it should be escalated and prioritized. Tests that remain skipped indefinitely are not acceptable.
|
package/SECURITY.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Security Policy
|
|
2
|
-
|
|
3
|
-
This repository adheres to the [PayPal Vulnerability Reporting Policy](https://hackerone.com/paypal).
|
|
4
|
-
|
|
5
|
-
## Reporting a Vulnerability
|
|
6
|
-
|
|
7
|
-
If you think you have found a vulnerability in this repository, please report it to us through coordinated disclosure.
|
|
8
|
-
|
|
9
|
-
**Please do not report security vulnerabilities through public issues, discussions, or pull requests.**
|
|
10
|
-
|
|
11
|
-
Instead, report it using one of the following ways:
|
|
12
|
-
|
|
13
|
-
- Email the PayPal Security Team at [security@paypal.com](mailto:security@paypal.com)
|
|
14
|
-
- Submit through the [PayPal Bug Bounty Program](https://hackerone.com/paypal) on HackerOne
|
|
15
|
-
- Report a [vulnerability](https://github.com/ez1219/sanitize-url/security/advisories/new) directly via private vulnerability reporting on GitHub
|
|
16
|
-
|
|
17
|
-
Please include the following in your report:
|
|
18
|
-
|
|
19
|
-
- The type of issue and affected version(s)
|
|
20
|
-
- Step-by-step instructions to reproduce the issue
|
|
21
|
-
- Impact of the issue and how an attacker might exploit it
|
|
22
|
-
|
|
23
|
-
## Supported Security Updates
|
|
24
|
-
|
|
25
|
-
Only the latest release receives security patches and new versions in the case of a security issue.
|
|
26
|
-
|
|
27
|
-
## Disclosure Policy
|
|
28
|
-
|
|
29
|
-
We are committed to working with security researchers in good faith. To support responsible disclosure, our team will:
|
|
30
|
-
|
|
31
|
-
- Acknowledge your report in a timely manner
|
|
32
|
-
- Keep you informed of our progress toward a fix
|
|
33
|
-
- Notify you before any public disclosure
|
|
34
|
-
|
|
35
|
-
We ask that you:
|
|
36
|
-
|
|
37
|
-
- Do not publicly disclose the issue before it has been resolved
|
|
38
|
-
- Avoid accessing, modifying, or deleting data that does not belong to you
|
|
39
|
-
- Make a good faith effort to avoid disruption to production systems
|
|
40
|
-
|
|
41
|
-
We appreciate responsible disclosure and your efforts to keep Braintree SDK users safe.
|
package/commitlint.config.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
// commitlint.config.js
|
|
2
|
-
module.exports = {
|
|
3
|
-
extends: ["@commitlint/config-conventional"],
|
|
4
|
-
|
|
5
|
-
// Customize rules
|
|
6
|
-
rules: {
|
|
7
|
-
// Type must be one of the specified values
|
|
8
|
-
"type-enum": [
|
|
9
|
-
2, // Error level (0=off, 1=warn, 2=error)
|
|
10
|
-
"always",
|
|
11
|
-
[
|
|
12
|
-
"feat", // New feature
|
|
13
|
-
"fix", // Bug fix
|
|
14
|
-
"review",
|
|
15
|
-
"docs", // Documentation
|
|
16
|
-
"style", // Formatting
|
|
17
|
-
"refactor", // Code restructuring
|
|
18
|
-
"perf", // Performance
|
|
19
|
-
"test", // Tests
|
|
20
|
-
"build", // Build system
|
|
21
|
-
"dx", // Developer experience
|
|
22
|
-
"ci", // CI configuration
|
|
23
|
-
"chore", // Maintenance
|
|
24
|
-
"revert", // Revert commit
|
|
25
|
-
],
|
|
26
|
-
],
|
|
27
|
-
|
|
28
|
-
"scope-enum": [
|
|
29
|
-
2,
|
|
30
|
-
"always",
|
|
31
|
-
[
|
|
32
|
-
"american-express",
|
|
33
|
-
"apple-pay",
|
|
34
|
-
"client",
|
|
35
|
-
"data-collector",
|
|
36
|
-
"fastlane",
|
|
37
|
-
"google-payment",
|
|
38
|
-
"hosted-fields",
|
|
39
|
-
"instant-verification",
|
|
40
|
-
"local-payment",
|
|
41
|
-
"payment-ready",
|
|
42
|
-
"payment-request",
|
|
43
|
-
"paypal-checkout",
|
|
44
|
-
"paypal-checkout-v6",
|
|
45
|
-
"sepa",
|
|
46
|
-
"three-d-secure",
|
|
47
|
-
"us-bank-account",
|
|
48
|
-
"vault-manager",
|
|
49
|
-
"venmo",
|
|
50
|
-
"deps",
|
|
51
|
-
"dev-deps",
|
|
52
|
-
"other",
|
|
53
|
-
],
|
|
54
|
-
],
|
|
55
|
-
// Scope is optional but recommended
|
|
56
|
-
"scope-empty": [1, "never"],
|
|
57
|
-
|
|
58
|
-
// Subject configuration
|
|
59
|
-
"subject-empty": [2, "never"],
|
|
60
|
-
"subject-case": [0, "always", "sentence-case"],
|
|
61
|
-
"subject-full-stop": [1, "never", "."],
|
|
62
|
-
"subject-max-length": [2, "always", 85],
|
|
63
|
-
|
|
64
|
-
// Body configuration
|
|
65
|
-
"body-max-line-length": [2, "always", 100],
|
|
66
|
-
|
|
67
|
-
// Footer configuration
|
|
68
|
-
"footer-max-line-length": [2, "always", 100],
|
|
69
|
-
},
|
|
70
|
-
};
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-script-url */
|
|
2
|
-
import { sanitizeUrl } from "..";
|
|
3
|
-
import { BLANK_URL } from "../constants";
|
|
4
|
-
|
|
5
|
-
describe("sanitizeUrl", () => {
|
|
6
|
-
it("does not alter http URLs with alphanumeric characters", () => {
|
|
7
|
-
expect(sanitizeUrl("http://example.com/path/to:something")).toBe(
|
|
8
|
-
"http://example.com/path/to:something",
|
|
9
|
-
);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("does not alter http URLs with ports with alphanumeric characters", () => {
|
|
13
|
-
expect(sanitizeUrl("http://example.com:4567/path/to:something")).toBe(
|
|
14
|
-
"http://example.com:4567/path/to:something",
|
|
15
|
-
);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("does not alter https URLs with alphanumeric characters", () => {
|
|
19
|
-
expect(sanitizeUrl("https://example.com")).toBe("https://example.com/");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("does not alter https URLs with ports with alphanumeric characters", () => {
|
|
23
|
-
expect(sanitizeUrl("https://example.com:4567/path/to:something")).toBe(
|
|
24
|
-
"https://example.com:4567/path/to:something",
|
|
25
|
-
);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("does not alter relative-path reference URLs with alphanumeric characters", () => {
|
|
29
|
-
expect(sanitizeUrl("./path/to/my.json")).toBe("./path/to/my.json");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("does not alter absolute-path reference URLs with alphanumeric characters", () => {
|
|
33
|
-
expect(sanitizeUrl("/path/to/my.json")).toBe("/path/to/my.json");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("does not alter protocol-less network-path URLs with alphanumeric characters", () => {
|
|
37
|
-
expect(sanitizeUrl("//google.com/robots.txt")).toBe(
|
|
38
|
-
"//google.com/robots.txt",
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("does not alter protocol-less URLs with alphanumeric characters", () => {
|
|
43
|
-
expect(sanitizeUrl("www.example.com")).toBe("www.example.com");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("does not alter deep-link urls with alphanumeric characters", () => {
|
|
47
|
-
expect(sanitizeUrl("com.braintreepayments.demo://example")).toBe(
|
|
48
|
-
"com.braintreepayments.demo://example",
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("does not alter mailto urls with alphanumeric characters", () => {
|
|
53
|
-
expect(sanitizeUrl("mailto:test@example.com?subject=hello+world")).toBe(
|
|
54
|
-
"mailto:test@example.com?subject=hello+world",
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("does not alter urls with accented characters", () => {
|
|
59
|
-
expect(sanitizeUrl("www.example.com/with-áccêntš")).toBe(
|
|
60
|
-
"www.example.com/with-áccêntš",
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("does not strip harmless unicode characters", () => {
|
|
65
|
-
expect(sanitizeUrl("www.example.com/лот.рфшишкиü–")).toBe(
|
|
66
|
-
"www.example.com/лот.рфшишкиü–",
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("strips out ctrl chars", () => {
|
|
71
|
-
expect(
|
|
72
|
-
sanitizeUrl("www.example.com/\u200D\u0000\u001F\x00\x1F\uFEFFfoo"),
|
|
73
|
-
).toBe("www.example.com/foo");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it(`replaces blank urls with ${BLANK_URL}`, () => {
|
|
77
|
-
expect(sanitizeUrl("")).toBe(BLANK_URL);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it(`replaces null values with ${BLANK_URL}`, () => {
|
|
81
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
82
|
-
// @ts-ignore
|
|
83
|
-
expect(sanitizeUrl(null)).toBe(BLANK_URL);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it(`replaces undefined values with ${BLANK_URL}`, () => {
|
|
87
|
-
expect(sanitizeUrl()).toBe(BLANK_URL);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("removes whitespace from urls", () => {
|
|
91
|
-
expect(sanitizeUrl(" http://example.com/path/to:something ")).toBe(
|
|
92
|
-
"http://example.com/path/to:something",
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("removes newline entities from urls", () => {
|
|
97
|
-
expect(sanitizeUrl("https://example.com

/something")).toBe(
|
|
98
|
-
"https://example.com/something",
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("decodes html entities", () => {
|
|
103
|
-
// all these decode to javascript:alert('xss');
|
|
104
|
-
const attackVectors = [
|
|
105
|
-
"javascript:alert('XSS')",
|
|
106
|
-
"javascript:alert('XSS')",
|
|
107
|
-
"javascript:alert('XSS')",
|
|
108
|
-
"jav	ascript:alert('XSS');",
|
|
109
|
-
"  javascript:alert('XSS');",
|
|
110
|
-
"javasc	ript: alert('XSS');",
|
|
111
|
-
"javasc&#\u0000x09;ript:alert(1)",
|
|
112
|
-
"java&NewLine&newline;;script:alert('XSS')",
|
|
113
|
-
"java&NewLine&newline;;script:alert('XSS')",
|
|
114
|
-
];
|
|
115
|
-
|
|
116
|
-
attackVectors.forEach((vector) => {
|
|
117
|
-
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// https://example.com/javascript:alert('XSS')
|
|
121
|
-
// since the javascript is the url path, and not the protocol,
|
|
122
|
-
// this url is technically sanitized
|
|
123
|
-
expect(
|
|
124
|
-
sanitizeUrl(
|
|
125
|
-
"https://example.com/javascript:alert('XSS')",
|
|
126
|
-
),
|
|
127
|
-
).toBe("https://example.com/javascript:alert('XSS')");
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("removes whitespace escape sequences", () => {
|
|
131
|
-
const attackVectors = [
|
|
132
|
-
"javascri\npt:alert('xss')",
|
|
133
|
-
"javascri\rpt:alert('xss')",
|
|
134
|
-
"javascri\tpt:alert('xss')",
|
|
135
|
-
"javascrip\\%74t:alert('XSS')",
|
|
136
|
-
"javascrip%5c%72t:alert()",
|
|
137
|
-
"javascrip%5Ctt:alert()",
|
|
138
|
-
"javascrip%255Ctt:alert()",
|
|
139
|
-
"javascrip%25%35Ctt:alert()",
|
|
140
|
-
"javascrip%25%35%43tt:alert()",
|
|
141
|
-
"javascrip%25%32%35%25%33%35%25%34%33rt:alert()",
|
|
142
|
-
"javascrip%255Crt:alert('%25xss')",
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
attackVectors.forEach((vector) => {
|
|
146
|
-
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it("backslash prefixed attack vectors", () => {
|
|
151
|
-
const attackVectors = [
|
|
152
|
-
"\fjavascript:alert()",
|
|
153
|
-
"\vjavascript:alert()",
|
|
154
|
-
"\tjavascript:alert()",
|
|
155
|
-
"\njavascript:alert()",
|
|
156
|
-
"\rjavascript:alert()",
|
|
157
|
-
"\u0000javascript:alert()",
|
|
158
|
-
"\u0001javascript:alert()",
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
attackVectors.forEach((vector) => {
|
|
162
|
-
expect(sanitizeUrl(vector)).toBe(BLANK_URL);
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("reverses backslashes", () => {
|
|
167
|
-
const attack = "\\j\\av\\a\\s\\cript:alert()";
|
|
168
|
-
|
|
169
|
-
expect(sanitizeUrl(attack)).toBe("/j/av/a/s/cript:alert()");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe("invalid protocols", () => {
|
|
173
|
-
describe.each(["javascript", "data", "vbscript"])("%s", (protocol) => {
|
|
174
|
-
it(`replaces ${protocol} urls with ${BLANK_URL}`, () => {
|
|
175
|
-
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
|
176
|
-
BLANK_URL,
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it(`allows ${protocol} urls that start with a letter prefix`, () => {
|
|
181
|
-
expect(sanitizeUrl(`not_${protocol}:alert(document.domain)`)).toBe(
|
|
182
|
-
`not_${protocol}:alert(document.domain)`,
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it(`disallows ${protocol} urls that start with non-\w characters as a suffix for the protocol`, () => {
|
|
187
|
-
expect(sanitizeUrl(`&!*${protocol}:alert(document.domain)`)).toBe(
|
|
188
|
-
BLANK_URL,
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it(`disallows ${protocol} urls that use : for the colon portion of the url`, () => {
|
|
193
|
-
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
|
194
|
-
BLANK_URL,
|
|
195
|
-
);
|
|
196
|
-
expect(sanitizeUrl(`${protocol}:alert(document.domain)`)).toBe(
|
|
197
|
-
BLANK_URL,
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it(`disregards capitalization for ${protocol} urls`, () => {
|
|
202
|
-
// upper case every other letter in protocol name
|
|
203
|
-
const mixedCapitalizationProtocol = protocol
|
|
204
|
-
.split("")
|
|
205
|
-
.map((character, index) => {
|
|
206
|
-
if (index % 2 === 0) {
|
|
207
|
-
return character.toUpperCase();
|
|
208
|
-
}
|
|
209
|
-
return character;
|
|
210
|
-
})
|
|
211
|
-
.join("");
|
|
212
|
-
|
|
213
|
-
expect(
|
|
214
|
-
sanitizeUrl(`${mixedCapitalizationProtocol}:alert(document.domain)`),
|
|
215
|
-
).toBe(BLANK_URL);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it(`ignores invisible ctrl characters in ${protocol} urls`, () => {
|
|
219
|
-
const protocolWithControlCharacters = protocol
|
|
220
|
-
.split("")
|
|
221
|
-
.map((character, index) => {
|
|
222
|
-
if (index === 1) {
|
|
223
|
-
return character + "%EF%BB%BF%EF%BB%BF";
|
|
224
|
-
} else if (index === 2) {
|
|
225
|
-
return character + "%e2%80%8b";
|
|
226
|
-
}
|
|
227
|
-
return character;
|
|
228
|
-
})
|
|
229
|
-
.join("");
|
|
230
|
-
|
|
231
|
-
expect(
|
|
232
|
-
sanitizeUrl(
|
|
233
|
-
decodeURIComponent(
|
|
234
|
-
`${protocolWithControlCharacters}:alert(document.domain)`,
|
|
235
|
-
),
|
|
236
|
-
),
|
|
237
|
-
).toBe(BLANK_URL);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it(`replaces ${protocol} urls with ${BLANK_URL} when url begins with %20`, () => {
|
|
241
|
-
expect(
|
|
242
|
-
sanitizeUrl(
|
|
243
|
-
decodeURIComponent(
|
|
244
|
-
`%20%20%20%20${protocol}:alert(document.domain)`,
|
|
245
|
-
),
|
|
246
|
-
),
|
|
247
|
-
).toBe(BLANK_URL);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it(`replaces ${protocol} urls with ${BLANK_URL} when ${protocol} url begins with spaces`, () => {
|
|
251
|
-
expect(sanitizeUrl(` ${protocol}:alert(document.domain)`)).toBe(
|
|
252
|
-
BLANK_URL,
|
|
253
|
-
);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it(`does not replace ${protocol}: if it is not in the scheme of the URL`, () => {
|
|
257
|
-
expect(sanitizeUrl(`http://example.com#${protocol}:foo`)).toBe(
|
|
258
|
-
`http://example.com#${protocol}:foo`,
|
|
259
|
-
);
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("replaces invalid http/https URLs with BLANK_URL", () => {
|
|
265
|
-
expect(sanitizeUrl("http://[invalid]")).toBe(BLANK_URL);
|
|
266
|
-
expect(sanitizeUrl("https://[invalid]")).toBe(BLANK_URL);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
describe("when URL.canParse is undefined", () => {
|
|
270
|
-
let originalCanParse: typeof URL.canParse;
|
|
271
|
-
|
|
272
|
-
beforeEach(() => {
|
|
273
|
-
originalCanParse = URL.canParse;
|
|
274
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
275
|
-
// @ts-ignore
|
|
276
|
-
URL.canParse = null;
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
afterEach(() => {
|
|
280
|
-
URL.canParse = originalCanParse;
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("sanitizes valid http/https URLs properly using fallback", () => {
|
|
284
|
-
expect(sanitizeUrl("http://example.com/path")).toBe(
|
|
285
|
-
"http://example.com/path",
|
|
286
|
-
);
|
|
287
|
-
expect(sanitizeUrl("https://example.com")).toBe("https://example.com/");
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("replaces invalid http/https URLs with BLANK_URL using fallback", () => {
|
|
291
|
-
expect(sanitizeUrl("http://[invalid]")).toBe(BLANK_URL);
|
|
292
|
-
expect(sanitizeUrl("https://[invalid]")).toBe(BLANK_URL);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
it("replaces javascript URLs with BLANK_URL", () => {
|
|
296
|
-
expect(sanitizeUrl("javascript:alert(1)")).toBe(BLANK_URL);
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
});
|
package/src/constants.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const invalidProtocolRegex = /^([^\w]*)(javascript|data|vbscript)/im;
|
|
2
|
-
export const htmlEntitiesRegex = /&#(\w+)(^\w|;)?/g;
|
|
3
|
-
export const htmlCtrlEntityRegex = /&(newline|tab);/gi;
|
|
4
|
-
export const ctrlCharactersRegex =
|
|
5
|
-
/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;
|
|
6
|
-
export const urlSchemeRegex = /^.+(:|:)/gim;
|
|
7
|
-
export const whitespaceEscapeCharsRegex =
|
|
8
|
-
/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g;
|
|
9
|
-
export const relativeFirstCharacters = [".", "/"];
|
|
10
|
-
export const BLANK_URL = "about:blank";
|
package/src/index.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BLANK_URL,
|
|
3
|
-
ctrlCharactersRegex,
|
|
4
|
-
htmlCtrlEntityRegex,
|
|
5
|
-
htmlEntitiesRegex,
|
|
6
|
-
invalidProtocolRegex,
|
|
7
|
-
relativeFirstCharacters,
|
|
8
|
-
whitespaceEscapeCharsRegex,
|
|
9
|
-
urlSchemeRegex,
|
|
10
|
-
} from "./constants";
|
|
11
|
-
|
|
12
|
-
function isRelativeUrlWithoutProtocol(url: string): boolean {
|
|
13
|
-
return relativeFirstCharacters.indexOf(url[0]) > -1;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function decodeHtmlCharacters(str: string) {
|
|
17
|
-
const removedNullByte = str.replace(ctrlCharactersRegex, "");
|
|
18
|
-
return removedNullByte.replace(htmlEntitiesRegex, (match, dec) => {
|
|
19
|
-
return String.fromCharCode(dec);
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function isValidUrl(url: string): boolean {
|
|
24
|
-
if (typeof URL !== "undefined" && typeof URL.canParse === "function") {
|
|
25
|
-
return URL.canParse(url);
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
return Boolean(new URL(url));
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function decodeURI(uri: string): string {
|
|
35
|
-
try {
|
|
36
|
-
return decodeURIComponent(uri);
|
|
37
|
-
} catch (e: unknown) {
|
|
38
|
-
// Ignoring error
|
|
39
|
-
// It is possible that the URI contains a `%` not associated
|
|
40
|
-
// with URI/URL-encoding.
|
|
41
|
-
return uri;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function sanitizeUrl(url?: string): string {
|
|
46
|
-
if (!url) {
|
|
47
|
-
return BLANK_URL;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let charsToDecode;
|
|
51
|
-
let decodedUrl = decodeURI(url.trim());
|
|
52
|
-
|
|
53
|
-
do {
|
|
54
|
-
decodedUrl = decodeHtmlCharacters(decodedUrl)
|
|
55
|
-
.replace(htmlCtrlEntityRegex, "")
|
|
56
|
-
.replace(ctrlCharactersRegex, "")
|
|
57
|
-
.replace(whitespaceEscapeCharsRegex, "")
|
|
58
|
-
.trim();
|
|
59
|
-
|
|
60
|
-
decodedUrl = decodeURI(decodedUrl);
|
|
61
|
-
|
|
62
|
-
charsToDecode =
|
|
63
|
-
decodedUrl.match(ctrlCharactersRegex) ||
|
|
64
|
-
decodedUrl.match(htmlEntitiesRegex) ||
|
|
65
|
-
decodedUrl.match(htmlCtrlEntityRegex) ||
|
|
66
|
-
decodedUrl.match(whitespaceEscapeCharsRegex);
|
|
67
|
-
} while (charsToDecode && charsToDecode.length > 0);
|
|
68
|
-
|
|
69
|
-
const sanitizedUrl = decodedUrl;
|
|
70
|
-
|
|
71
|
-
if (!sanitizedUrl) {
|
|
72
|
-
return BLANK_URL;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
|
76
|
-
return sanitizedUrl;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Remove any leading whitespace before checking the URL scheme
|
|
80
|
-
const trimmedUrl = sanitizedUrl.trimStart();
|
|
81
|
-
const urlSchemeParseResults = trimmedUrl.match(urlSchemeRegex);
|
|
82
|
-
|
|
83
|
-
if (!urlSchemeParseResults) {
|
|
84
|
-
return sanitizedUrl;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const urlScheme = urlSchemeParseResults[0].toLowerCase().trim();
|
|
88
|
-
|
|
89
|
-
if (invalidProtocolRegex.test(urlScheme)) {
|
|
90
|
-
return BLANK_URL;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const backSanitized = trimmedUrl.replace(/\\/g, "/");
|
|
94
|
-
|
|
95
|
-
// Handle special cases for mailto: and custom deep-link protocols
|
|
96
|
-
if (urlScheme === "mailto:" || urlScheme.includes("://")) {
|
|
97
|
-
return backSanitized;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// For http and https URLs, perform additional validation
|
|
101
|
-
if (urlScheme === "http:" || urlScheme === "https:") {
|
|
102
|
-
if (!isValidUrl(backSanitized)) {
|
|
103
|
-
return BLANK_URL;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const url = new URL(backSanitized);
|
|
107
|
-
url.protocol = url.protocol.toLowerCase();
|
|
108
|
-
url.hostname = url.hostname.toLowerCase();
|
|
109
|
-
|
|
110
|
-
return url.toString();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return backSanitized;
|
|
114
|
-
}
|
package/tsconfig.json
DELETED
package/vitest.config.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "vitest/config";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
test: {
|
|
5
|
-
globals: true,
|
|
6
|
-
environment: "happy-dom",
|
|
7
|
-
coverage: {
|
|
8
|
-
enabled: true,
|
|
9
|
-
thresholds: {
|
|
10
|
-
lines: 80,
|
|
11
|
-
statements: 80,
|
|
12
|
-
branches: 80,
|
|
13
|
-
functions: 80,
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
});
|