@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 CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@ez1219/sanitize-url",
3
- "version": "7.1.2-rc.0",
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
@@ -1,2 +0,0 @@
1
- dist
2
- coverage
package/.eslintrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "braintree/client",
3
- "rules": {
4
- "no-control-regex": 0
5
- }
6
- }
@@ -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
@@ -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
@@ -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
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env bash
2
- npx --no -- commitlint --edit "$1"
package/.nvmrc DELETED
@@ -1 +0,0 @@
1
- v24
package/.prettierignore DELETED
@@ -1,3 +0,0 @@
1
- *-lock.json
2
- dist
3
- coverage
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
- "&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041";
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.
@@ -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
- "&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
106
- "javascript:alert('XSS')",
107
- "&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29",
108
- "jav	ascript:alert('XSS');",
109
- "  javascript:alert('XSS');",
110
- "javasc	ript: alert('XSS');",
111
- "javasc&#\u0000x09;ript:alert(1)",
112
- "java&&#78&#59;ewLine&newline&#59;&#59;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&#0000058//example.com/&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041",
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
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "outDir": "./dist",
4
- "allowJs": true,
5
- "strict": true,
6
- "target": "es5",
7
- "resolveJsonModule": true,
8
- "lib": ["es2015", "dom"]
9
- },
10
- "include": ["./src/**/*"],
11
- "exclude": ["**/__tests__/*"]
12
- }
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
- });