@webklex/sinker 1.0.0
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/LICENSE +21 -0
- package/README.md +180 -0
- package/dist/sinker.min.js +21 -0
- package/dist/sinker.min.js.map +7 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Webklex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Sinker
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
[![License: MIT][ico-license]][link-license]
|
|
5
|
+
[![Hits][ico-hits]][link-hits]
|
|
6
|
+
[![Snyk][ico-snyk]][link-snyk]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Sinker is a minimalistic security tool designed to scan your codebase for potentially dangerous "sinks" (e.g.,
|
|
10
|
+
`document.URL`, `innerHTML`, `localStorage`) that could lead to vulnerabilities like DOM XSS or data leakage.
|
|
11
|
+
|
|
12
|
+
Designed for modern web development, Sinker integrates seamlessly into CI/CD pipelines to alert developers of
|
|
13
|
+
unprotected sinks before they reach production.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Recursive Scanning**: Automatically scans `.ts`, `.js`, `.html`, and other relevant files.
|
|
18
|
+
- **Highly Customizable**: Load project-specific sinks and sources via a configuration file.
|
|
19
|
+
- **Safe Bypassing**: Explicitly flag intentional usage of sinks as safe with inline comments.
|
|
20
|
+
- **CI Ready**: Returns non-zero exit codes on violations, making it perfect for automated security gates.
|
|
21
|
+
- **Contextual Reports**: See the exact line and surrounding code for every finding.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Using npm
|
|
29
|
+
npm install @webklex/sinker --save-dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Usage
|
|
33
|
+
|
|
34
|
+
Run a scan on your project:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx sinker .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### CLI Options
|
|
41
|
+
|
|
42
|
+
| Option | Description |
|
|
43
|
+
|:--------------------|:---------------------------------------------------|
|
|
44
|
+
| `target` | Path to a file or directory to scan (default: `.`) |
|
|
45
|
+
| `-nc`, `--no-color` | Disable colored output |
|
|
46
|
+
| `--context-depth=N` | Number of context lines to show (overrides config) |
|
|
47
|
+
| `-h`, `--help` | Show help message |
|
|
48
|
+
|
|
49
|
+
**Example:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx sinker src --context-depth=5 --no-color
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Sinker looks for a `sinker.config.js` file in your project root.
|
|
58
|
+
|
|
59
|
+
### Example `sinker.config.js`
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
module.exports = {
|
|
63
|
+
// Define custom sinks to watch for
|
|
64
|
+
sinks: [
|
|
65
|
+
{
|
|
66
|
+
name: 'CUSTOM_STORAGE',
|
|
67
|
+
description: 'Potential sensitive data leakage to custom storage',
|
|
68
|
+
link: 'https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage',
|
|
69
|
+
displayContextBefore: true,
|
|
70
|
+
displayContextAfter: true,
|
|
71
|
+
sinks: ['myApp.storage.set'],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
// Ignore specific built-in sinks
|
|
75
|
+
ignoredSinks: ['document.URL'],
|
|
76
|
+
// Paths to exclude from scanning
|
|
77
|
+
ignored: [
|
|
78
|
+
'**/node_modules/**',
|
|
79
|
+
'**/dist/**',
|
|
80
|
+
'**/coverage/**',
|
|
81
|
+
'**/*.test.ts',
|
|
82
|
+
'**/*.spec.ts',
|
|
83
|
+
],
|
|
84
|
+
colors: true,
|
|
85
|
+
contextDepth: 3,
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Configuration Options
|
|
90
|
+
|
|
91
|
+
- `sinks` (Array): Custom sink definitions.
|
|
92
|
+
- `ignoredSinks` (String[]): List of sink names/patterns to ignore globally.
|
|
93
|
+
- `ignored` (String[]): Glob patterns for files or directories to skip.
|
|
94
|
+
- `colors` (Boolean): Toggle colored output.
|
|
95
|
+
- `contextDepth` (Number): Number of lines of context to display around violations.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Suppressing Findings
|
|
100
|
+
|
|
101
|
+
If a sink usage is intentional and properly protected (e.g., sanitized with DOMPurify), you can suppress the alert by
|
|
102
|
+
adding a `@safe-sink` comment on the line immediately preceding the sink.
|
|
103
|
+
|
|
104
|
+
### JavaScript / TypeScript
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
// @safe-sink: Content is sanitized via DOMPurify
|
|
108
|
+
element.innerHTML = sanitizedContent;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### HTML
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<!-- @safe-sink: URL is validated against an allowlist -->
|
|
115
|
+
<script>
|
|
116
|
+
const url = document.URL;
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Contributing
|
|
123
|
+
|
|
124
|
+
### Adding Default Sinks
|
|
125
|
+
|
|
126
|
+
To contribute to the built-in sink definitions:
|
|
127
|
+
|
|
128
|
+
1. Create a new definition file in `src/sinks/` (e.g., `src/sinks/framework_sinks.ts`):
|
|
129
|
+
```typescript
|
|
130
|
+
import { Sink } from './types';
|
|
131
|
+
export const frameworkSinks: Sink = {
|
|
132
|
+
name: 'Framework Sinks',
|
|
133
|
+
description: 'Dangerous sinks specific to X framework.',
|
|
134
|
+
link: 'https://example.com/security-docs',
|
|
135
|
+
displayContextBefore: true,
|
|
136
|
+
displayContextAfter: true,
|
|
137
|
+
sinks: ['X.unsafeMethod'],
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
2. Register it in `src/sinks/index.ts`:
|
|
141
|
+
```typescript
|
|
142
|
+
import { frameworkSinks } from './framework_sinks';
|
|
143
|
+
export const allSinkGroups: Sink[] = [
|
|
144
|
+
// ... existing groups
|
|
145
|
+
frameworkSinks,
|
|
146
|
+
];
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Development
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Install dependencies
|
|
153
|
+
npm install
|
|
154
|
+
|
|
155
|
+
# Build the project
|
|
156
|
+
npm run build
|
|
157
|
+
|
|
158
|
+
# Run tests
|
|
159
|
+
npm test
|
|
160
|
+
|
|
161
|
+
# Generate coverage report
|
|
162
|
+
npm run test:coverage
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Inspiration
|
|
166
|
+
|
|
167
|
+
- [Sources and Sinks Cheatsheet](https://github.com/Sivnerof/Sources-And-Sinks-Cheatsheet)
|
|
168
|
+
- [eslint-plugin-no-unsanitized](https://github.com/mozilla/eslint-plugin-no-unsanitized)
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
The MIT License (MIT). Please see [License File][link-license] for more information.
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
[ico-license]: https://img.shields.io/badge/License-MIT-green.svg
|
|
175
|
+
[ico-hits]: https://hits.webklex.com/svg/webklex/sinker
|
|
176
|
+
[ico-snyk]: https://snyk-widget.herokuapp.com/badge/npm/%40webklex%2Fsinker/badge.svg
|
|
177
|
+
|
|
178
|
+
[link-license]: LICENSE
|
|
179
|
+
[link-hits]: https://hits.webklex.com
|
|
180
|
+
[link-snyk]: https://snyk.io/vuln/npm:%40webklex%2Fsinker
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var ut=Object.create;var M=Object.defineProperty;var ft=Object.getOwnPropertyDescriptor;var xt=Object.getOwnPropertyNames;var gt=Object.getPrototypeOf,kt=Object.prototype.hasOwnProperty;var bt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var St=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of xt(t))!kt.call(e,i)&&i!==n&&M(e,i,{get:()=>t[i],enumerable:!(o=ft(t,i))||o.enumerable});return e};var m=(e,t,n)=>(n=e!=null?ut(gt(e)):{},St(t||!e||!e.__esModule?M(n,"default",{value:e,enumerable:!0}):n,e));var b=bt((qt,k)=>{var f=process||{},v=f.argv||[],u=f.env||{},ht=!(u.NO_COLOR||v.includes("--no-color"))&&(!!u.FORCE_COLOR||v.includes("--color")||f.platform==="win32"||(f.stdout||{}).isTTY&&u.TERM!=="dumb"||!!u.CI),yt=(e,t,n=e)=>o=>{let i=""+o,r=i.indexOf(t,e.length);return~r?e+Ct(i,t,n,r)+t:e+i+t},Ct=(e,t,n,o)=>{let i="",r=0;do i+=e.substring(r,o)+n,r=o+t.length,o=e.indexOf(t,r);while(~o);return i+e.substring(r)},R=(e=ht)=>{let t=e?yt:()=>String;return{isColorSupported:e,reset:t("\x1B[0m","\x1B[0m"),bold:t("\x1B[1m","\x1B[22m","\x1B[22m\x1B[1m"),dim:t("\x1B[2m","\x1B[22m","\x1B[22m\x1B[2m"),italic:t("\x1B[3m","\x1B[23m"),underline:t("\x1B[4m","\x1B[24m"),inverse:t("\x1B[7m","\x1B[27m"),hidden:t("\x1B[8m","\x1B[28m"),strikethrough:t("\x1B[9m","\x1B[29m"),black:t("\x1B[30m","\x1B[39m"),red:t("\x1B[31m","\x1B[39m"),green:t("\x1B[32m","\x1B[39m"),yellow:t("\x1B[33m","\x1B[39m"),blue:t("\x1B[34m","\x1B[39m"),magenta:t("\x1B[35m","\x1B[39m"),cyan:t("\x1B[36m","\x1B[39m"),white:t("\x1B[37m","\x1B[39m"),gray:t("\x1B[90m","\x1B[39m"),bgBlack:t("\x1B[40m","\x1B[49m"),bgRed:t("\x1B[41m","\x1B[49m"),bgGreen:t("\x1B[42m","\x1B[49m"),bgYellow:t("\x1B[43m","\x1B[49m"),bgBlue:t("\x1B[44m","\x1B[49m"),bgMagenta:t("\x1B[45m","\x1B[49m"),bgCyan:t("\x1B[46m","\x1B[49m"),bgWhite:t("\x1B[47m","\x1B[49m"),blackBright:t("\x1B[90m","\x1B[39m"),redBright:t("\x1B[91m","\x1B[39m"),greenBright:t("\x1B[92m","\x1B[39m"),yellowBright:t("\x1B[93m","\x1B[39m"),blueBright:t("\x1B[94m","\x1B[39m"),magentaBright:t("\x1B[95m","\x1B[39m"),cyanBright:t("\x1B[96m","\x1B[39m"),whiteBright:t("\x1B[97m","\x1B[39m"),bgBlackBright:t("\x1B[100m","\x1B[49m"),bgRedBright:t("\x1B[101m","\x1B[49m"),bgGreenBright:t("\x1B[102m","\x1B[49m"),bgYellowBright:t("\x1B[103m","\x1B[49m"),bgBlueBright:t("\x1B[104m","\x1B[49m"),bgMagentaBright:t("\x1B[105m","\x1B[49m"),bgCyanBright:t("\x1B[106m","\x1B[49m"),bgWhiteBright:t("\x1B[107m","\x1B[49m")}};k.exports=R();k.exports.createColors=R});var dt=m(b());var D=m(b());function A(e){if(e)return D.default;let t=n=>String(n??"");return{red:t,yellow:t,blue:t,cyan:t,gray:t,bold:t,dim:t,green:t}}var j=m(require("node:path"));function wt(e,t){let n=j.default.resolve(process.cwd(),t.file);console.error(e.red(e.bold(`Violation found in ${n}:${t.line}`))),console.error(` ${e.cyan("Sink:")} ${e.yellow(t.sink.sink)}`),console.error(` ${e.cyan("Category:")} ${t.sink.metadata.name}`),console.error(` ${e.cyan("Description:")} ${t.sink.metadata.description}`),console.error(` ${e.cyan("Link:")} ${t.sink.metadata.link}`)}function B(e,t){for(let n of t??[])console.error(` ${e.gray(`Line ${n.line+1}:`)} ${e.dim(n.text)}`)}function Mt(e,t){B(e,t.context.before),console.error(" Add "+e.yellow(e.bold("// @safe-sink: a short explanation why its safe"))+" here to suppress this finding."),console.error(` ${e.gray(`Line ${t.line}:`)} ${e.red("(>>) "+e.bold(t.context.offendingLine))}`),B(e,t.context.after)}function L(e,t){if(e.violations.length>0){console.error(t.red(t.bold(`Found ${e.violations.length} violations`)));for(let n of e.violations)wt(t,n),Mt(t,n),console.log("")}}var $={name:"Common Source",description:"A source that can be controlled by an attacker and can lead to XSS if not properly sanitized.",link:"https://portswigger.net/web-security/dom-based",displayContextBefore:!0,displayContextAfter:!0,sinks:["document.URL","document.documentURI","document.URLUnencoded","document.baseURI","document.referrer","window.name","localStorage","sessionStorage","mozIndexedDB","webkitIndexedDB","msIndexedDB","IndexedDB","Database"]};var O={name:"DOM-XSS Sinks",description:"Applying user input to DOM without proper sanitization can lead to DOM-based XSS.",link:"https://portswigger.net/web-security/cross-site-scripting/dom-based",displayContextBefore:!0,displayContextAfter:!1,sinks:["document.write(","document.writeln(",".innerHTML",".outerHTML",".insertAdjacentHTML",".onevent"]};var P={name:"jQuery DOM-XSS Sinks",description:"Applying user input to DOM without proper sanitization can lead to jQuery DOM-based XSS.",link:"https://portswigger.net/web-security/cross-site-scripting/dom-based",displayContextBefore:!0,displayContextAfter:!1,sinks:["add(","after(","append(","animate(","insertAfter(","insertBefore(","before(","html(","prepend(","replaceAll(","replaceWith(","wrap(","wrapInner(","wrapAll(","has(","constructor(","init(","index(","jQuery.parseHTML(","$.parseHTML(","jQuery.globalEval(","$.globalEval("]};var I={name:"Open Redirect Sinks",description:"This sink can be used to perform open redirects.",link:"https://portswigger.net/web-security/dom-based/open-redirection",displayContextBefore:!0,displayContextAfter:!0,sinks:[".location","location.host","location.hostname","location.href","location.pathname","location.search","location.protocol","location.assign(","location.replace(","open(",".srcdoc","jQuery.ajax(","$.ajax("]};var T={name:"Cookie Sinks",description:"An improper sanitized or validated value can lead to cookie theft, XSS, open redirects and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/cookie-manipulation",displayContextBefore:!0,displayContextAfter:!0,sinks:["document.cookie"]};var F={name:"JavaScript Injection Sinks",description:"One of the most dangerous sinks in DOM-based XSS attacks, allowing for arbitrary code execution.",link:"https://portswigger.net/web-security/dom-based/javascript-injection",displayContextBefore:!0,displayContextAfter:!1,sinks:["eval(","Function(","setTimeout(","setInterval(","setImmediate(","execCommand(","execScript(","msSetImmediate(","range.createContextualFragment(","crypto.generateCRMFRequest("]};var E={name:"Document-Domain Manipulation Sinks",description:"Manipulating the document domain can lead to cross-domain attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/document-domain-manipulation",displayContextBefore:!0,displayContextAfter:!1,sinks:["document.domain"]};var X={name:"WebSocket-URL Poisoning Sinks",description:"Manipulating the WebSocket URL can lead to cross-domain attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/websocket-url-poisoning",displayContextBefore:!0,displayContextAfter:!1,sinks:["WebSocket"]};var z={name:"Link Manipulation Sinks",description:"Manipulating links can lead to cross-site scripting (XSS) attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/link-manipulation",displayContextBefore:!0,displayContextAfter:!0,sinks:[".href",".src",".action"]};var q={name:"Ajax Request-Header Manipulation Sinks",description:"Manipulating request headers can lead to cross-site scripting (XSS) attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation",displayContextBefore:!0,displayContextAfter:!1,sinks:["XMLHttpRequest.setRequestHeader(","XMLHttpRequest.open(","XMLHttpRequest.send("]};var V={name:"Local File-Path Manipulation Sinks",description:"Manipulating local file paths can lead to cross-site scripting (XSS) attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/local-file-path-manipulation",displayContextBefore:!0,displayContextAfter:!0,sinks:["FileReader.readAsArrayBuffer(","FileReader.readAsBinaryString(","FileReader.readAsDataURL(","FileReader.readAsText(","FileReader.readAsFile(","FileReader.root.getFile("]};var W={name:"Client-Side SQL-Injection Sink",description:"Manipulating SQL queries can lead to SQL injection vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/client-side-sql-injection",displayContextBefore:!0,displayContextAfter:!1,sinks:["executeSql"]};var _={name:"HTML5 Storage Manipulation Sinks",description:"Manipulating HTML5 storage can lead to cross-site scripting (XSS) attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/html5-storage-manipulation",displayContextBefore:!0,displayContextAfter:!1,sinks:["sessionStorage.setItem","localStorage.setItem"]};var H={name:"XPath Injection Sinks",description:"Manipulating XPath expressions can lead to XPath injection vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/client-side-xpath-injection",displayContextBefore:!0,displayContextAfter:!1,sinks:[".evaluate"]};var N={name:"Client-Side JSON Injection Sinks",description:"Manipulating the document domain can lead to cross-domain attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/client-side-json-injection",displayContextBefore:!0,displayContextAfter:!0,sinks:["JSON.parse","jQuery.parseJSON","$.parseJSON"]};var U={name:"DOM-Data Manipulation Sinks",description:"Manipulating the document dom can lead to cross-domain attacks and other vulnerabilities.",link:"https://portswigger.net/web-security/dom-based/dom-data-manipulation",displayContextBefore:!0,displayContextAfter:!1,sinks:[".setAttribute",".search",".text",".textContent",".innerText",".outerText",".value",".name",".target",".method",".type",".backgroundImage",".cssText",".codebase","document.title","document.implementation.createHTMLDocument","history.pushState","history.replaceState"]};var Q={name:"Denial Of Service Sinks",description:"Denial of Service attacks can cause significant disruptions to services and systems.",link:"https://portswigger.net/web-security/dom-based/denial-of-service",displayContextBefore:!0,displayContextAfter:!1,sinks:["RegExp(","requestFileSystem"]};var J=[$,O,P,I,T,F,E,X,z,q,V,W,_,H,N,U,Q];function vt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Y(e){let t=Rt(new Set(e?.ignoredSinks??[]));if(e?.sinks){let o=new Map;for(let i of t)o.set(i.sink,i);for(let i of e.sinks)for(let r of i.sinks)K(r,o,i);return{sinks:G([...o.values()]),count:o.size}}let n=G(t);return{sinks:n,count:n.length}}function K(e,t,n){if(!e)throw new Error("Sink cannot be empty");if(t.has(e))throw new Error(`Sink "${e}" is already defined`);t.set(e,{sink:e,metadata:n})}function Rt(e){let t=new Map;for(let n of J)for(let o of n.sinks)e.has(o)||K(o,t,n);return[...t.values()]}function G(e){return e.map(t=>{let n=vt(t.sink),o=t.sink.startsWith(".")?"(^|[^\\w]|(?<=\\w))":"(^|[^\\w])",i=new RegExp(o+n+"([^\\w]|$)");return{...t,regex:i}})}var et=m(require("node:path")),x=m(require("node:fs")),h=require("node:fs");function Dt(e,t,n){let o=e.lastIndex,i=t.index,r=n.slice(o,i),a=n.slice(0,o).split(/\r?\n/).length;return{code:r,startLine:a}}function At(){return{scriptOpenRe:/<script\b[^>]*>/gi,scriptCloseRe:/<\/script\s*>/gi}}function Bt(e,t,n){let o=t.exec(n);return o?Dt(e,o,n):null}function Z(e){let t=[],{scriptOpenRe:n,scriptCloseRe:o}=At();for(;n.exec(e)!==null;){o.lastIndex=n.lastIndex;let i=Bt(n,o,e);if(!i)break;t.push(i),n.lastIndex=o.lastIndex}return t}function tt(e){return e.startsWith("//")||e.startsWith("/*")||e.startsWith("*")||e.startsWith("<!--")}function jt(e,t){let n=t;for(;n>=0;){let o=e[n].trim();if(o.includes("@safe-sink"))return!0;if(!(o===""||tt(o)))return!1;n--}return!1}function Lt(e,t,n,o,i,r){let s={before:null,offendingLine:e[t].trim(),after:null};if(r.metadata.displayContextBefore){let a=Math.max(0,t-i.contextDepth);s.before=Array.from({length:t-a},(c,l)=>{let p=a+l;return{line:o+p,text:e[p].trim()}})}if(r.metadata.displayContextAfter){let a=Math.max(t+1),c=Math.min(n,a+i.contextDepth);s.after=Array.from({length:c-a},(l,p)=>{let d=a+p;return{line:o+d,text:e[d].trim()}})}return s}function S(e){let{text:t,displayPath:n,compiledSinks:o,lineOffset:i,options:r}=e,s=t.split(/\r?\n/),a=[],c=s.length;for(let l=0;l<c;l++){let p=s[l],d=p.trim();if(!tt(d))for(let g of o)g.regex.test(p)&&(jt(s,l-1)||a.push({file:n,line:i+l+1,sink:g,context:Lt(s,l,c,i,r,g)}))}return a}var $t=[".ts",".js",".mts",".cts",".mjs",".cjs",".tsx",".jsx"];function nt(e){return x.existsSync(e)}function ot(e){return x.statSync(e)}function it(e){return $t.some(t=>e.endsWith(t))}function Ot(e,t,n){return!e.startsWith(".")&&!y(t,n)}function y(e,t){return t.some(n=>{let o="^"+n.split("*").map(s=>s.replace(/[|\\{}()[\]^$+?.]/g,"\\$&")).join(".*")+"$",i=new RegExp(o),r=e.split(/[/\\]/).pop()||"";return i.test(e)||i.test(r)||e.endsWith(n)})}async function C(e){let{filePath:t,compiledSinks:n,contextDepth:o,ignored:i=[]}=e;if(y(t,i))return{violations:[],count:0};let r=await h.promises.readFile(t,"utf-8");if(it(t))return{violations:S({text:r,displayPath:t,compiledSinks:n,lineOffset:0,options:{contextDepth:o}}),count:1};if(t.endsWith(".html")){let s=Z(r),a=[];for(let c of s)a.push(...S({text:c.code,displayPath:`${t} (<script>)`,compiledSinks:n,lineOffset:c.startLine-1,options:{contextDepth:o}}));return{violations:a,count:1}}return{violations:[],count:1}}async function Pt(e,t,n,o,i){let r=et.join(t,e.name);if(e.isDirectory()){if(Ot(e.name,r,i))return w({dirPath:r,compiledSinks:n,contextDepth:o,ignored:i})}else if(e.isFile()&&(it(e.name)||e.name.endsWith(".html")))return C({filePath:r,compiledSinks:n,contextDepth:o,ignored:i});return{violations:[],count:0}}async function w(e){let{dirPath:t,compiledSinks:n,contextDepth:o,ignored:i=[]}=e;if(y(t,i))return{violations:[],count:0};let r=await h.promises.readdir(t,{withFileTypes:!0}),s={violations:[],count:0};for(let a of r){let c=await Pt(a,t,n,o,i);s.violations.push(...c.violations),s.count+=c.count}return s}var st=m(require("node:path")),at=m(require("node:fs")),ct=require("node:url"),rt={ignoredSinks:[],ignored:["**/dist/**","**/node_modules/**","**/coverage/**","*.test.js","*.spec.js","*.min.js","*.map"],sinks:[],colors:!0,contextDepth:3};async function lt(){let e=st.resolve(process.cwd(),"sinker.config.js");if(at.existsSync(e))try{let n=await import((0,ct.pathToFileURL)(e).href),o=n.default||n;return{...rt,...o}}catch(t){console.warn(`Failed to load config from ${e}:`,t)}return rt}function It(e){let t=e.find(i=>i.startsWith("--context-depth=")),n=t?t.split("=")[1]:void 0,o=n?Number.parseInt(n,10):-1;return Number.isFinite(o)&&o>=0?o:-1}function pt(e){let t=e.slice(2),n=!t.includes("--no-color")&&!t.includes("-nc"),o=t.includes("--help")||t.includes("-h"),i=t.find(s=>!s.startsWith("--")&&!s.startsWith("-"))??".",r=It(t);return{targetLocation:i,options:{useColor:n,contextDepth:r,help:o}}}async function Tt(e){let t=pt(e),n=await lt(),o=t.options.useColor&&n.colors,i=t.options.contextDepth>=0?t.options.contextDepth:n.contextDepth,r=A(o),{sinks:s,count:a}=Y(n);return{args:t,config:n,useColor:o,contextDepth:i,c:r,sinks:s,count:a}}function Ft(e,t){return e===0?(console.error(t.red("No sink rules found")),!1):!0}function Et(e,t){return nt(e)?!0:(console.error(t.red(`Path does not exist: ${e}`)),!1)}function Xt(e){console.log(`
|
|
3
|
+
${e.blue("sinker")} - Minimalistic security tool to scan for potentially dangerous sinks and sources.
|
|
4
|
+
|
|
5
|
+
${e.bold("Usage:")}
|
|
6
|
+
sinker [target] [options]
|
|
7
|
+
|
|
8
|
+
${e.bold("Arguments:")}
|
|
9
|
+
target Path to a file or directory to scan (default: .)
|
|
10
|
+
|
|
11
|
+
${e.bold("Options:")}
|
|
12
|
+
-h, --help Show this help message
|
|
13
|
+
-nc, --no-color Disable colored output
|
|
14
|
+
--context-depth=N Number of lines of context to show (default: from config)
|
|
15
|
+
|
|
16
|
+
${e.bold("Examples:")}
|
|
17
|
+
sinker .
|
|
18
|
+
sinker src/index.ts --context-depth=5
|
|
19
|
+
sinker --no-color
|
|
20
|
+
`)}async function mt(e){let{args:t,config:n,contextDepth:o,c:i,sinks:r,count:s}=await Tt(e);if(t.options.help)return Xt(i),0;if(!Ft(s,i)||!Et(t.targetLocation,i))return 1;console.log(i.blue(`Scanning ${t.targetLocation} for ${s} sinks (context-depth: ${o})...`));let a=ot(t.targetLocation).isDirectory()?await w({dirPath:t.targetLocation,compiledSinks:r,contextDepth:o,ignored:n.ignored}):await C({filePath:t.targetLocation,compiledSinks:r,contextDepth:o,ignored:n.ignored});return console.log(i.blue(`Successfully scanned ${a.count} files`)),a.violations.length>0?(L(a,i),1):(console.log(i.green("Scan completed successfully. No violations found.")),0)}mt(process.argv).then(e=>{process.exitCode=e}).catch(e=>{let t=e instanceof Error?e.message:String(e);console.error(dt.default.red(`Unexpected error: ${t}`)),process.exitCode=1});
|
|
21
|
+
//# sourceMappingURL=sinker.min.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../node_modules/picocolors/picocolors.js", "../src/index.ts", "../src/output/colors.ts", "../src/output/reporter.ts", "../src/sinks/common.ts", "../src/sinks/domXss.ts", "../src/sinks/jqueryDomXss.ts", "../src/sinks/openRedirect.ts", "../src/sinks/cookie.ts", "../src/sinks/jsInjection.ts", "../src/sinks/domain.ts", "../src/sinks/webSocket.ts", "../src/sinks/link.ts", "../src/sinks/ajax.ts", "../src/sinks/localFile.ts", "../src/sinks/sql.ts", "../src/sinks/html5.ts", "../src/sinks/xpath.ts", "../src/sinks/json.ts", "../src/sinks/dom.ts", "../src/sinks/dos.ts", "../src/sinks/index.ts", "../src/scanner/sinks.ts", "../src/scanner/scanFs.ts", "../src/scanner/html.ts", "../src/scanner/scanText.ts", "../src/config.ts", "../src/cli/args.ts", "../src/cli/main.ts"],
|
|
4
|
+
"sourcesContent": ["let p = process || {}, argv = p.argv || [], env = p.env || {}\nlet isColorSupported =\n\t!(!!env.NO_COLOR || argv.includes(\"--no-color\")) &&\n\t(!!env.FORCE_COLOR || argv.includes(\"--color\") || p.platform === \"win32\" || ((p.stdout || {}).isTTY && env.TERM !== \"dumb\") || !!env.CI)\n\nlet formatter = (open, close, replace = open) =>\n\tinput => {\n\t\tlet string = \"\" + input, index = string.indexOf(close, open.length)\n\t\treturn ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close\n\t}\n\nlet replaceClose = (string, close, replace, index) => {\n\tlet result = \"\", cursor = 0\n\tdo {\n\t\tresult += string.substring(cursor, index) + replace\n\t\tcursor = index + close.length\n\t\tindex = string.indexOf(close, cursor)\n\t} while (~index)\n\treturn result + string.substring(cursor)\n}\n\nlet createColors = (enabled = isColorSupported) => {\n\tlet f = enabled ? formatter : () => String\n\treturn {\n\t\tisColorSupported: enabled,\n\t\treset: f(\"\\x1b[0m\", \"\\x1b[0m\"),\n\t\tbold: f(\"\\x1b[1m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[1m\"),\n\t\tdim: f(\"\\x1b[2m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[2m\"),\n\t\titalic: f(\"\\x1b[3m\", \"\\x1b[23m\"),\n\t\tunderline: f(\"\\x1b[4m\", \"\\x1b[24m\"),\n\t\tinverse: f(\"\\x1b[7m\", \"\\x1b[27m\"),\n\t\thidden: f(\"\\x1b[8m\", \"\\x1b[28m\"),\n\t\tstrikethrough: f(\"\\x1b[9m\", \"\\x1b[29m\"),\n\n\t\tblack: f(\"\\x1b[30m\", \"\\x1b[39m\"),\n\t\tred: f(\"\\x1b[31m\", \"\\x1b[39m\"),\n\t\tgreen: f(\"\\x1b[32m\", \"\\x1b[39m\"),\n\t\tyellow: f(\"\\x1b[33m\", \"\\x1b[39m\"),\n\t\tblue: f(\"\\x1b[34m\", \"\\x1b[39m\"),\n\t\tmagenta: f(\"\\x1b[35m\", \"\\x1b[39m\"),\n\t\tcyan: f(\"\\x1b[36m\", \"\\x1b[39m\"),\n\t\twhite: f(\"\\x1b[37m\", \"\\x1b[39m\"),\n\t\tgray: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\n\t\tbgBlack: f(\"\\x1b[40m\", \"\\x1b[49m\"),\n\t\tbgRed: f(\"\\x1b[41m\", \"\\x1b[49m\"),\n\t\tbgGreen: f(\"\\x1b[42m\", \"\\x1b[49m\"),\n\t\tbgYellow: f(\"\\x1b[43m\", \"\\x1b[49m\"),\n\t\tbgBlue: f(\"\\x1b[44m\", \"\\x1b[49m\"),\n\t\tbgMagenta: f(\"\\x1b[45m\", \"\\x1b[49m\"),\n\t\tbgCyan: f(\"\\x1b[46m\", \"\\x1b[49m\"),\n\t\tbgWhite: f(\"\\x1b[47m\", \"\\x1b[49m\"),\n\n\t\tblackBright: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\t\tredBright: f(\"\\x1b[91m\", \"\\x1b[39m\"),\n\t\tgreenBright: f(\"\\x1b[92m\", \"\\x1b[39m\"),\n\t\tyellowBright: f(\"\\x1b[93m\", \"\\x1b[39m\"),\n\t\tblueBright: f(\"\\x1b[94m\", \"\\x1b[39m\"),\n\t\tmagentaBright: f(\"\\x1b[95m\", \"\\x1b[39m\"),\n\t\tcyanBright: f(\"\\x1b[96m\", \"\\x1b[39m\"),\n\t\twhiteBright: f(\"\\x1b[97m\", \"\\x1b[39m\"),\n\n\t\tbgBlackBright: f(\"\\x1b[100m\", \"\\x1b[49m\"),\n\t\tbgRedBright: f(\"\\x1b[101m\", \"\\x1b[49m\"),\n\t\tbgGreenBright: f(\"\\x1b[102m\", \"\\x1b[49m\"),\n\t\tbgYellowBright: f(\"\\x1b[103m\", \"\\x1b[49m\"),\n\t\tbgBlueBright: f(\"\\x1b[104m\", \"\\x1b[49m\"),\n\t\tbgMagentaBright: f(\"\\x1b[105m\", \"\\x1b[49m\"),\n\t\tbgCyanBright: f(\"\\x1b[106m\", \"\\x1b[49m\"),\n\t\tbgWhiteBright: f(\"\\x1b[107m\", \"\\x1b[49m\"),\n\t}\n}\n\nmodule.exports = createColors()\nmodule.exports.createColors = createColors\n", "import pc from 'picocolors';\n\nimport { main } from './cli/main';\n\nmain(process.argv)\n .then(code => {\n process.exitCode = code;\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n // Keep output simple and safe for CI logs\n console.error(pc.red(`Unexpected error: ${message}`));\n process.exitCode = 1;\n });\n", "import pc from 'picocolors';\n\nexport type Colorizer = Pick<\n typeof pc,\n 'red' | 'yellow' | 'blue' | 'cyan' | 'gray' | 'bold' | 'dim' | 'green'\n>;\n\nexport function createColorizer(useColor: boolean): Colorizer {\n if (useColor) return pc;\n\n type FormatterInput = Parameters<typeof pc.red>[0];\n const ident = (input: FormatterInput): string => String(input ?? '');\n\n return {\n red: ident,\n yellow: ident,\n blue: ident,\n cyan: ident,\n gray: ident,\n bold: ident,\n dim: ident,\n green: ident,\n };\n}\n", "import path from 'node:path';\n\nimport {\n type ScanResult,\n Violation,\n ViolationContextLine,\n} from '../scanner/types';\n\nimport type { Colorizer } from './colors';\n\nfunction _printViolationHeader(c: Colorizer, v: Violation): void {\n const file = path.resolve(process.cwd(), v.file);\n console.error(c.red(c.bold(`Violation found in ${file}:${v.line}`)));\n console.error(` ${c.cyan('Sink:')}\\t\\t${c.yellow(v.sink.sink)}`);\n // @safe-sink: metadata.name is an attribute only set internally without any outside access\n console.error(` ${c.cyan('Category:')}\\t${v.sink.metadata.name}`);\n console.error(\n ` ${c.cyan('Description:')}\\t${v.sink.metadata.description}`\n );\n console.error(` ${c.cyan('Link:')}\\t\\t${v.sink.metadata.link}`);\n}\n\nfunction _printCodeLines(\n c: Colorizer,\n lines: ViolationContextLine[] | null\n): void {\n for (const ctx of lines ?? []) {\n console.error(\n // @safe-sink: ctx.text gets not interpreted\n ` ${c.gray(`Line ${ctx.line + 1}:`)}\\t${c.dim(ctx.text)}`\n );\n }\n}\n\nfunction _printViolationContext(c: Colorizer, v: Violation): void {\n _printCodeLines(c, v.context.before);\n console.error(\n '\\t\\tAdd ' +\n c.yellow(\n c.bold(`// @safe-sink: a short explanation why its safe`)\n ) +\n ` here to suppress this finding.`\n );\n console.error(\n ` ${c.gray(`Line ${v.line}:`)}\\t${c.red('(>>) ' + c.bold(v.context.offendingLine))}`\n );\n _printCodeLines(c, v.context.after);\n}\n\nexport function printViolations(scanResult: ScanResult, c: Colorizer): void {\n if (scanResult.violations.length > 0) {\n console.error(\n c.red(c.bold(`Found ${scanResult.violations.length} violations`))\n );\n\n for (const v of scanResult.violations) {\n _printViolationHeader(c, v);\n _printViolationContext(c, v);\n console.log('');\n }\n }\n}\n", "import { Sink } from './types';\n\nexport const commonSinks: Sink = {\n name: 'Common Source',\n description:\n 'A source that can be controlled by an attacker and can lead to XSS if not properly sanitized.',\n link: 'https://portswigger.net/web-security/dom-based',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: [\n 'document.URL',\n 'document.documentURI',\n 'document.URLUnencoded',\n 'document.baseURI',\n 'document.referrer',\n 'window.name',\n 'localStorage',\n 'sessionStorage',\n 'mozIndexedDB',\n 'webkitIndexedDB',\n 'msIndexedDB',\n 'IndexedDB',\n 'Database',\n ],\n};\n", "import { Sink } from './types';\n\nexport const domXssSinks: Sink = {\n name: 'DOM-XSS Sinks',\n description:\n 'Applying user input to DOM without proper sanitization can lead to DOM-based XSS.',\n link: 'https://portswigger.net/web-security/cross-site-scripting/dom-based',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: [\n 'document.write(',\n 'document.writeln(',\n '.innerHTML',\n '.outerHTML',\n '.insertAdjacentHTML',\n '.onevent',\n ],\n};\n", "import { Sink } from './types';\n\nexport const jqueryDomXssSinks: Sink = {\n name: 'jQuery DOM-XSS Sinks',\n description:\n 'Applying user input to DOM without proper sanitization can lead to jQuery DOM-based XSS.',\n link: 'https://portswigger.net/web-security/cross-site-scripting/dom-based',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: [\n 'add(',\n 'after(',\n 'append(',\n 'animate(',\n 'insertAfter(',\n 'insertBefore(',\n 'before(',\n 'html(',\n 'prepend(',\n 'replaceAll(',\n 'replaceWith(',\n 'wrap(',\n 'wrapInner(',\n 'wrapAll(',\n 'has(',\n 'constructor(',\n 'init(',\n 'index(',\n 'jQuery.parseHTML(',\n '$.parseHTML(',\n 'jQuery.globalEval(',\n '$.globalEval(',\n ],\n};\n", "import { Sink } from './types';\n\nexport const openRedirectSinks: Sink = {\n name: 'Open Redirect Sinks',\n description: 'This sink can be used to perform open redirects.',\n link: 'https://portswigger.net/web-security/dom-based/open-redirection',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: [\n '.location',\n 'location.host',\n 'location.hostname',\n 'location.href',\n 'location.pathname',\n 'location.search',\n 'location.protocol',\n 'location.assign(',\n 'location.replace(',\n 'open(',\n '.srcdoc',\n 'jQuery.ajax(',\n '$.ajax(',\n ],\n};\n", "import { Sink } from './types';\n\nexport const cookieSinks: Sink = {\n name: 'Cookie Sinks',\n description:\n 'An improper sanitized or validated value can lead to cookie theft, XSS, open redirects and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/cookie-manipulation',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: ['document.cookie'],\n};\n", "import { Sink } from './types';\n\nexport const jsInjectionSinks: Sink = {\n name: 'JavaScript Injection Sinks',\n description:\n 'One of the most dangerous sinks in DOM-based XSS attacks, allowing for arbitrary code execution.',\n link: 'https://portswigger.net/web-security/dom-based/javascript-injection',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: [\n 'eval(',\n 'Function(',\n 'setTimeout(',\n 'setInterval(',\n 'setImmediate(',\n 'execCommand(',\n 'execScript(',\n 'msSetImmediate(',\n 'range.createContextualFragment(',\n 'crypto.generateCRMFRequest(',\n ],\n};\n", "import { Sink } from './types';\n\nexport const domainSinks: Sink = {\n name: 'Document-Domain Manipulation Sinks',\n description:\n 'Manipulating the document domain can lead to cross-domain attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/document-domain-manipulation',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['document.domain'],\n};\n", "import { Sink } from './types';\n\nexport const webSocketSinks: Sink = {\n name: 'WebSocket-URL Poisoning Sinks',\n description:\n 'Manipulating the WebSocket URL can lead to cross-domain attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/websocket-url-poisoning',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['WebSocket'],\n};\n", "import { Sink } from './types';\n\nexport const linkSinks: Sink = {\n name: 'Link Manipulation Sinks',\n description:\n 'Manipulating links can lead to cross-site scripting (XSS) attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/link-manipulation',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: ['.href', '.src', '.action'],\n};\n", "import { Sink } from './types';\n\nexport const ajaxSinks: Sink = {\n name: 'Ajax Request-Header Manipulation Sinks',\n description:\n 'Manipulating request headers can lead to cross-site scripting (XSS) attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/ajax-request-header-manipulation',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: [\n 'XMLHttpRequest.setRequestHeader(',\n 'XMLHttpRequest.open(',\n 'XMLHttpRequest.send(',\n ],\n};\n", "import { Sink } from './types';\n\nexport const localFileSinks: Sink = {\n name: 'Local File-Path Manipulation Sinks',\n description:\n 'Manipulating local file paths can lead to cross-site scripting (XSS) attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/local-file-path-manipulation',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: [\n 'FileReader.readAsArrayBuffer(',\n 'FileReader.readAsBinaryString(',\n 'FileReader.readAsDataURL(',\n 'FileReader.readAsText(',\n 'FileReader.readAsFile(',\n 'FileReader.root.getFile(',\n ],\n};\n", "import { Sink } from './types';\n\nexport const sqlSinks: Sink = {\n name: 'Client-Side SQL-Injection Sink',\n description:\n 'Manipulating SQL queries can lead to SQL injection vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/client-side-sql-injection',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['executeSql'],\n};\n", "import { Sink } from './types';\n\nexport const html5Sinks: Sink = {\n name: 'HTML5 Storage Manipulation Sinks',\n description:\n 'Manipulating HTML5 storage can lead to cross-site scripting (XSS) attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/html5-storage-manipulation',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['sessionStorage.setItem', 'localStorage.setItem'],\n};\n", "import { Sink } from './types';\n\nexport const xpathSinks: Sink = {\n name: 'XPath Injection Sinks',\n description:\n 'Manipulating XPath expressions can lead to XPath injection vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/client-side-xpath-injection',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['.evaluate'],\n};\n", "import { Sink } from './types';\n\nexport const jsonSinks: Sink = {\n name: 'Client-Side JSON Injection Sinks',\n description:\n 'Manipulating the document domain can lead to cross-domain attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/client-side-json-injection',\n displayContextBefore: true,\n displayContextAfter: true,\n sinks: ['JSON.parse', 'jQuery.parseJSON', '$.parseJSON'],\n};\n", "import { Sink } from './types';\n\nexport const domSinks: Sink = {\n name: 'DOM-Data Manipulation Sinks',\n description:\n 'Manipulating the document dom can lead to cross-domain attacks and other vulnerabilities.',\n link: 'https://portswigger.net/web-security/dom-based/dom-data-manipulation',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: [\n '.setAttribute',\n '.search',\n '.text',\n '.textContent',\n '.innerText',\n '.outerText',\n '.value',\n '.name',\n '.target',\n '.method',\n '.type',\n '.backgroundImage',\n '.cssText',\n '.codebase',\n 'document.title',\n 'document.implementation.createHTMLDocument',\n 'history.pushState',\n 'history.replaceState',\n ],\n};\n", "import { Sink } from './types';\n\nexport const dosSinks: Sink = {\n name: 'Denial Of Service Sinks',\n description:\n 'Denial of Service attacks can cause significant disruptions to services and systems.',\n link: 'https://portswigger.net/web-security/dom-based/denial-of-service',\n displayContextBefore: true,\n displayContextAfter: false,\n sinks: ['RegExp(', 'requestFileSystem'],\n};\n", "import { Sink } from './types';\nimport { commonSinks } from './common';\nimport { domXssSinks } from './domXss';\nimport { jqueryDomXssSinks } from './jqueryDomXss';\nimport { openRedirectSinks } from './openRedirect';\nimport { cookieSinks } from './cookie';\nimport { jsInjectionSinks } from './jsInjection';\nimport { domainSinks } from './domain';\nimport { webSocketSinks } from './webSocket';\nimport { linkSinks } from './link';\nimport { ajaxSinks } from './ajax';\nimport { localFileSinks } from './localFile';\nimport { sqlSinks } from './sql';\nimport { html5Sinks } from './html5';\nimport { xpathSinks } from './xpath';\nimport { jsonSinks } from './json';\nimport { domSinks } from './dom';\nimport { dosSinks } from './dos';\n\nexport const allSinkGroups: Sink[] = [\n commonSinks,\n domXssSinks,\n jqueryDomXssSinks,\n openRedirectSinks,\n cookieSinks,\n jsInjectionSinks,\n domainSinks,\n webSocketSinks,\n linkSinks,\n ajaxSinks,\n localFileSinks,\n sqlSinks,\n html5Sinks,\n xpathSinks,\n jsonSinks,\n domSinks,\n dosSinks,\n];\n\nexport * from './types';\n", "import { allSinkGroups, Sink } from '../sinks';\nimport type { SinkerConfig } from '../config';\n\nimport type { CompiledSink, SinkMatch } from './types';\n\nfunction escapeRegExpLiteral(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport function loadSinks(config: SinkerConfig | null): {\n sinks: CompiledSink[];\n count: number;\n} {\n const sinks = getUniqueSinks(new Set(config?.ignoredSinks ?? []));\n if (config?.sinks) {\n const uniqueMap = new Map<string, SinkMatch>();\n for (const s of sinks) {\n uniqueMap.set(s.sink, s);\n }\n\n for (const def of config.sinks) {\n for (const rawSink of def.sinks) {\n addSinkToMap(rawSink, uniqueMap, def);\n }\n }\n return {\n sinks: compileSinks([...uniqueMap.values()]),\n count: uniqueMap.size,\n };\n }\n\n const compiledSinks = compileSinks(sinks);\n\n return {\n sinks: compiledSinks,\n count: compiledSinks.length,\n };\n}\n\nfunction addSinkToMap(\n sink: string,\n sinks: Map<string, SinkMatch>,\n def: Sink\n): asserts sink is SinkMatch['sink'] {\n if (!sink) throw new Error('Sink cannot be empty');\n if (sinks.has(sink)) throw new Error(`Sink \"${sink}\" is already defined`);\n\n sinks.set(sink, {\n sink: sink,\n metadata: def,\n });\n}\n\nexport function getUniqueSinks(ignoredSinks: Set<string>): SinkMatch[] {\n const uniqueSinks = new Map<string, SinkMatch>();\n\n for (const def of allSinkGroups) {\n for (const rawSink of def.sinks) {\n if (ignoredSinks.has(rawSink)) continue;\n addSinkToMap(rawSink, uniqueSinks, def);\n }\n }\n\n return [...uniqueSinks.values()];\n}\n\nexport function compileSinks(sinks: SinkMatch[]): CompiledSink[] {\n return sinks.map(s => {\n const escaped = escapeRegExpLiteral(s.sink);\n // If the sink starts with a dot, we want to match it even if preceded by a word character (e.g., el.innerHTML)\n // while ensuring it's not part of a larger word (handled by the escaped dot itself).\n // If it doesn't start with a dot, we want to ensure it's not part of another word,\n // but we allow it to be preceded by a dot (e.g., window.localStorage).\n const prefix = s.sink.startsWith('.')\n ? '(^|[^\\\\w]|(?<=\\\\w))'\n : '(^|[^\\\\w])';\n const regex = new RegExp(prefix + escaped + '([^\\\\w]|$)');\n return { ...s, regex };\n });\n}\n", "import * as path from 'node:path';\nimport * as fsSync from 'node:fs';\nimport { promises as fs } from 'node:fs';\n\nimport type { CompiledSink, ScanResult, Violation } from './types';\nimport { extractHtmlScriptBlocks } from './html';\nimport { scanText } from './scanText';\n\nconst JS_TS_EXTENSIONS = [\n '.ts',\n '.js',\n '.mts',\n '.cts',\n '.mjs',\n '.cjs',\n '.tsx',\n '.jsx',\n];\n\nexport function pathExistsSync(p: string): boolean {\n return fsSync.existsSync(p);\n}\n\nexport function statSync(p: string): fsSync.Stats {\n return fsSync.statSync(p);\n}\n\nfunction isJsTsFile(fileNameOrPath: string): boolean {\n return JS_TS_EXTENSIONS.some(ext => fileNameOrPath.endsWith(ext));\n}\n\nfunction shouldRecurseIntoDir(\n dirName: string,\n fullPath: string,\n ignored: string[]\n): boolean {\n return !dirName.startsWith('.') && !isIgnored(fullPath, ignored);\n}\n\nfunction isIgnored(path: string, ignored: string[]): boolean {\n return ignored.some(pattern => {\n const _str: string =\n '^' +\n pattern\n .split('*')\n .map(s => s.replace(/[|\\\\{}()[\\]^$+?.]/g, '\\\\$&'))\n .join('.*') +\n '$';\n const regex = new RegExp(_str);\n const fileName = path.split(/[/\\\\]/).pop() || '';\n return (\n regex.test(path) || regex.test(fileName) || path.endsWith(pattern)\n );\n });\n}\n\nexport async function scanFile(params: {\n filePath: string;\n compiledSinks: CompiledSink[];\n contextDepth: number;\n ignored?: string[];\n}): Promise<ScanResult> {\n const { filePath, compiledSinks, contextDepth, ignored = [] } = params;\n\n if (isIgnored(filePath, ignored)) {\n return { violations: [], count: 0 };\n }\n\n const content = await fs.readFile(filePath, 'utf-8');\n\n if (isJsTsFile(filePath)) {\n return {\n violations: scanText({\n text: content,\n displayPath: filePath,\n compiledSinks,\n lineOffset: 0,\n options: { contextDepth },\n }),\n count: 1,\n };\n }\n\n if (filePath.endsWith('.html')) {\n const blocks = extractHtmlScriptBlocks(content);\n const all: Violation[] = [];\n\n for (const b of blocks) {\n all.push(\n ...scanText({\n text: b.code,\n displayPath: `${filePath} (<script>)`,\n compiledSinks,\n lineOffset: b.startLine - 1,\n options: { contextDepth },\n })\n );\n }\n\n return {\n violations: all,\n count: 1,\n };\n }\n\n return { violations: [], count: 1 };\n}\n\nasync function scanEntry(\n entry: fsSync.Dirent,\n dirPath: string,\n compiledSinks: CompiledSink[],\n contextDepth: number,\n ignored: string[]\n): Promise<ScanResult> {\n // @safe-sink: entry.name is only set internally\n const fullPath = path.join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // @safe-sink: entry.name is only set internally\n if (shouldRecurseIntoDir(entry.name, fullPath, ignored)) {\n return scanDir({\n dirPath: fullPath,\n compiledSinks,\n contextDepth,\n ignored,\n });\n }\n } else if (\n entry.isFile() &&\n // @safe-sink: entry.name is only set internally\n (isJsTsFile(entry.name) || entry.name.endsWith('.html'))\n ) {\n return scanFile({\n filePath: fullPath,\n compiledSinks,\n contextDepth,\n ignored,\n });\n }\n\n return {\n violations: [],\n count: 0,\n };\n}\n\nexport async function scanDir(params: {\n dirPath: string;\n compiledSinks: CompiledSink[];\n contextDepth: number;\n ignored?: string[];\n}): Promise<ScanResult> {\n const { dirPath, compiledSinks, contextDepth, ignored = [] } = params;\n\n if (isIgnored(dirPath, ignored)) {\n return { violations: [], count: 0 };\n }\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n const scanResult: ScanResult = {\n violations: [],\n count: 0,\n };\n\n for (const entry of entries) {\n const result = await scanEntry(\n entry,\n dirPath,\n compiledSinks,\n contextDepth,\n ignored\n );\n scanResult.violations.push(...result.violations);\n scanResult.count += result.count;\n }\n\n return scanResult;\n}\n", "function _sliceBlock(\n scriptOpenRe: RegExp,\n closeMatch: RegExpExecArray,\n html: string\n): {\n code: string;\n startLine: number;\n} {\n const codeStartIndex = scriptOpenRe.lastIndex;\n const codeEndIndex = closeMatch.index;\n const code = html.slice(codeStartIndex, codeEndIndex);\n\n const prefix = html.slice(0, codeStartIndex);\n const startLine = prefix.split(/\\r?\\n/).length;\n return {\n code,\n startLine,\n };\n}\n\nfunction _getScriptRegex(): {\n scriptOpenRe: RegExp;\n scriptCloseRe: RegExp;\n} {\n return {\n scriptOpenRe: /<script\\b[^>]*>/gi,\n scriptCloseRe: /<\\/script\\s*>/gi,\n };\n}\n\nfunction _getNextBlock(\n scriptOpenRe: RegExp,\n scriptCloseRe: RegExp,\n html: string\n): {\n code: string;\n startLine: number;\n} | null {\n const closeMatch = scriptCloseRe.exec(html);\n if (!closeMatch) return null;\n return _sliceBlock(scriptOpenRe, closeMatch, html);\n}\n\nexport function extractHtmlScriptBlocks(\n html: string\n): Array<{ code: string; startLine: number }> {\n const blocks: Array<{ code: string; startLine: number }> = [];\n const { scriptOpenRe, scriptCloseRe } = _getScriptRegex();\n\n while (scriptOpenRe.exec(html) !== null) {\n scriptCloseRe.lastIndex = scriptOpenRe.lastIndex;\n const block = _getNextBlock(scriptOpenRe, scriptCloseRe, html);\n if (!block) break;\n blocks.push(block);\n scriptOpenRe.lastIndex = scriptCloseRe.lastIndex;\n }\n\n return blocks;\n}\n", "import { CompiledSink, Violation, ViolationContext } from './types';\n\nexport interface ScanTextOptions {\n contextDepth: number;\n}\n\nfunction isIgnorableCommentLine(trimmedLine: string): boolean {\n return (\n trimmedLine.startsWith('//') ||\n trimmedLine.startsWith('/*') ||\n trimmedLine.startsWith('*') ||\n trimmedLine.startsWith('<!--')\n );\n}\n\nfunction hasSafeSinkerAbove(\n lines: string[],\n fromIndexExclusive: number\n): boolean {\n let i = fromIndexExclusive;\n\n while (i >= 0) {\n const prev = lines[i].trim();\n\n if (prev.includes('@safe-sink')) return true;\n\n const canSkip = prev === '' || isIgnorableCommentLine(prev);\n if (!canSkip) return false;\n\n i--;\n }\n\n return false;\n}\n\nfunction getViolationContext(\n lines: string[],\n currentLineIndex: number,\n totalLines: number,\n lineOffset: number,\n options: ScanTextOptions,\n sink: CompiledSink\n): ViolationContext {\n const context: ViolationContext = {\n before: null,\n offendingLine: lines[currentLineIndex].trim(),\n after: null,\n };\n\n if (sink.metadata.displayContextBefore) {\n const start = Math.max(0, currentLineIndex - options.contextDepth);\n context.before = Array.from(\n { length: currentLineIndex - start },\n (_, k) => {\n const i = start + k;\n return { line: lineOffset + i, text: lines[i].trim() };\n }\n );\n }\n\n if (sink.metadata.displayContextAfter) {\n const start = Math.max(currentLineIndex + 1);\n const end = Math.min(totalLines, start + options.contextDepth);\n context.after = Array.from({ length: end - start }, (_, k) => {\n const i = start + k;\n return { line: lineOffset + i, text: lines[i].trim() };\n });\n }\n\n return context;\n}\n\nexport function scanText(params: {\n text: string;\n displayPath: string;\n compiledSinks: CompiledSink[];\n lineOffset: number;\n options: ScanTextOptions;\n}): Violation[] {\n const { text, displayPath, compiledSinks, lineOffset, options } = params;\n\n const lines = text.split(/\\r?\\n/);\n const violations: Violation[] = [];\n const totalLines = lines.length;\n\n for (\n let currentLineIndex = 0;\n currentLineIndex < totalLines;\n currentLineIndex++\n ) {\n const line = lines[currentLineIndex];\n const trimmed = line.trim();\n\n if (isIgnorableCommentLine(trimmed)) continue;\n\n for (const sink of compiledSinks) {\n if (!sink.regex.test(line)) continue;\n if (hasSafeSinkerAbove(lines, currentLineIndex - 1)) continue;\n\n violations.push({\n file: displayPath,\n line: lineOffset + currentLineIndex + 1,\n sink: sink,\n context: getViolationContext(\n lines,\n currentLineIndex,\n totalLines,\n lineOffset,\n options,\n sink\n ),\n });\n }\n }\n\n return violations;\n}\n", "import * as path from 'node:path';\nimport * as fs from 'node:fs';\nimport { pathToFileURL } from 'node:url';\n\nimport { Sink } from './sinks';\n\nexport interface SinkerConfig {\n ignoredSinks: string[];\n sinks: Sink[];\n ignored: string[];\n colors: boolean;\n contextDepth: number;\n}\n\nexport const defaultConfig: SinkerConfig = {\n ignoredSinks: [],\n ignored: [\n '**/dist/**',\n '**/node_modules/**',\n '**/coverage/**',\n '*.test.js',\n '*.spec.js',\n '*.min.js',\n '*.map'\n ],\n sinks: [],\n colors: true,\n contextDepth: 3,\n};\n\nexport async function loadConfig(): Promise<SinkerConfig> {\n const configPath = path.resolve(process.cwd(), 'sinker.config.js');\n if (fs.existsSync(configPath)) {\n try {\n const configUrl = pathToFileURL(configPath).href;\n const module = await import(configUrl);\n const conf = module.default || module;\n return {\n ...defaultConfig,\n ...conf,\n };\n } catch (err) {\n console.warn(`Failed to load config from ${configPath}:`, err);\n }\n }\n return defaultConfig;\n}\n", "export interface ScanOptions {\n useColor: boolean;\n contextDepth: number;\n help: boolean;\n}\n\nexport interface ParsedArgs {\n targetLocation: string;\n options: ScanOptions;\n}\n\nfunction _parseContextDepth(args: string[]): number {\n const contextDepthArg = args.find(a => a.startsWith('--context-depth='));\n const rawDepth = contextDepthArg\n ? contextDepthArg.split('=')[1]\n : undefined;\n const parsedDepth = rawDepth ? Number.parseInt(rawDepth, 10) : -1;\n return Number.isFinite(parsedDepth) && parsedDepth >= 0 ? parsedDepth : -1;\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n const args = argv.slice(2);\n const useColor = !args.includes('--no-color') && !args.includes('-nc');\n const help = args.includes('--help') || args.includes('-h');\n const targetLocation =\n args.find(a => !a.startsWith('--') && !a.startsWith('-')) ?? '.';\n const contextDepth = _parseContextDepth(args);\n\n return {\n targetLocation,\n options: {\n useColor,\n contextDepth,\n help,\n },\n };\n}\n", "import { Colorizer, createColorizer } from '../output/colors';\nimport { printViolations } from '../output/reporter';\nimport { loadSinks } from '../scanner/sinks';\nimport { pathExistsSync, scanDir, scanFile, statSync } from '../scanner/scanFs';\nimport { loadConfig, SinkerConfig } from '../config';\nimport { CompiledSink } from '../scanner/types';\n\nimport { parseArgs, ParsedArgs } from './args';\n\nasync function _setup(argv: string[]): Promise<{\n args: ParsedArgs;\n config: SinkerConfig;\n useColor: boolean;\n contextDepth: number;\n c: Colorizer;\n sinks: CompiledSink[];\n count: number;\n}> {\n const args = parseArgs(argv);\n const config = await loadConfig();\n\n const useColor = args.options.useColor && config.colors;\n const contextDepth =\n args.options.contextDepth >= 0\n ? args.options.contextDepth\n : config.contextDepth;\n\n const c = createColorizer(useColor);\n\n const { sinks, count } = loadSinks(config);\n\n return {\n args,\n config,\n useColor,\n contextDepth,\n c,\n sinks,\n count,\n };\n}\n\nfunction _validateCount(count: number, c: Colorizer): boolean {\n if (count === 0) {\n console.error(c.red('No sink rules found'));\n return false;\n }\n return true;\n}\n\nfunction _validateTarget(target: string, c: Colorizer): boolean {\n if (!pathExistsSync(target)) {\n console.error(c.red(`Path does not exist: ${target}`));\n return false;\n }\n return true;\n}\n\nfunction _printHelp(c: Colorizer): void {\n console.log(`\n${c.blue('sinker')} - Minimalistic security tool to scan for potentially dangerous sinks and sources.\n\n${c.bold('Usage:')}\n sinker [target] [options]\n\n${c.bold('Arguments:')}\n target Path to a file or directory to scan (default: .)\n\n${c.bold('Options:')}\n -h, --help Show this help message\n -nc, --no-color Disable colored output\n --context-depth=N Number of lines of context to show (default: from config)\n\n${c.bold('Examples:')}\n sinker .\n sinker src/index.ts --context-depth=5\n sinker --no-color\n`);\n}\n\nexport async function main(argv: string[]): Promise<number> {\n const { args, config, contextDepth, c, sinks, count } = await _setup(argv);\n\n if (args.options.help) {\n _printHelp(c);\n return 0;\n }\n\n if (!_validateCount(count, c) || !_validateTarget(args.targetLocation, c)) {\n return 1;\n }\n\n console.log(\n c.blue(\n `Scanning ${args.targetLocation} for ${count} sinks (context-depth: ${contextDepth})...`\n )\n );\n\n const scanResult = statSync(args.targetLocation).isDirectory()\n ? await scanDir({\n dirPath: args.targetLocation,\n compiledSinks: sinks,\n contextDepth,\n ignored: config.ignored,\n })\n : await scanFile({\n filePath: args.targetLocation,\n compiledSinks: sinks,\n contextDepth,\n ignored: config.ignored,\n });\n\n console.log(c.blue(`Successfully scanned ${scanResult.count} files`));\n if (scanResult.violations.length > 0) {\n printViolations(scanResult, c);\n return 1;\n }\n\n console.log(c.green('Scan completed successfully. No violations found.'));\n return 0;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["require_picocolors", "__commonJSMin", "exports", "module", "p", "argv", "env", "isColorSupported", "formatter", "open", "close", "replace", "input", "string", "index", "replaceClose", "result", "cursor", "createColors", "enabled", "f", "import_picocolors", "import_picocolors", "createColorizer", "useColor", "pc", "ident", "input", "import_node_path", "_printViolationHeader", "c", "v", "file", "path", "_printCodeLines", "lines", "ctx", "_printViolationContext", "printViolations", "scanResult", "commonSinks", "domXssSinks", "jqueryDomXssSinks", "openRedirectSinks", "cookieSinks", "jsInjectionSinks", "domainSinks", "webSocketSinks", "linkSinks", "ajaxSinks", "localFileSinks", "sqlSinks", "html5Sinks", "xpathSinks", "jsonSinks", "domSinks", "dosSinks", "allSinkGroups", "commonSinks", "domXssSinks", "jqueryDomXssSinks", "openRedirectSinks", "cookieSinks", "jsInjectionSinks", "domainSinks", "webSocketSinks", "linkSinks", "ajaxSinks", "localFileSinks", "sqlSinks", "html5Sinks", "xpathSinks", "jsonSinks", "domSinks", "dosSinks", "escapeRegExpLiteral", "value", "loadSinks", "config", "sinks", "getUniqueSinks", "uniqueMap", "s", "def", "rawSink", "addSinkToMap", "compileSinks", "compiledSinks", "sink", "ignoredSinks", "uniqueSinks", "allSinkGroups", "escaped", "prefix", "regex", "path", "fsSync", "import_node_fs", "_sliceBlock", "scriptOpenRe", "closeMatch", "html", "codeStartIndex", "codeEndIndex", "code", "startLine", "_getScriptRegex", "_getNextBlock", "scriptCloseRe", "extractHtmlScriptBlocks", "blocks", "block", "isIgnorableCommentLine", "trimmedLine", "hasSafeSinkerAbove", "lines", "fromIndexExclusive", "i", "prev", "getViolationContext", "currentLineIndex", "totalLines", "lineOffset", "options", "sink", "context", "start", "_", "k", "end", "scanText", "params", "text", "displayPath", "compiledSinks", "violations", "line", "trimmed", "JS_TS_EXTENSIONS", "pathExistsSync", "p", "statSync", "isJsTsFile", "fileNameOrPath", "ext", "shouldRecurseIntoDir", "dirName", "fullPath", "ignored", "isIgnored", "path", "pattern", "_str", "regex", "fileName", "scanFile", "params", "filePath", "compiledSinks", "contextDepth", "content", "fs", "scanText", "blocks", "extractHtmlScriptBlocks", "all", "b", "scanEntry", "entry", "dirPath", "scanDir", "entries", "scanResult", "result", "path", "fs", "import_node_url", "defaultConfig", "loadConfig", "configPath", "module", "conf", "err", "_parseContextDepth", "args", "contextDepthArg", "a", "rawDepth", "parsedDepth", "parseArgs", "argv", "useColor", "help", "targetLocation", "contextDepth", "_setup", "argv", "args", "parseArgs", "config", "loadConfig", "useColor", "contextDepth", "c", "createColorizer", "sinks", "count", "loadSinks", "_validateCount", "_validateTarget", "target", "pathExistsSync", "_printHelp", "main", "scanResult", "statSync", "scanDir", "scanFile", "printViolations", "main", "code", "err", "message", "pc"]
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webklex/sinker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Minimalistic security tool to scan for dangerous sinks (XSS, data leakage) in codebases.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/webklex/sinker.git"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["security", "static-analysis", "xss", "dom-xss", "cli"],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/sinker.min.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"sinker": "dist/sinker.min.js"
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist/", "README.md", "LICENSE"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "esbuild src/index.ts --bundle --minify --sourcemap --format=cjs --platform=node --banner:js='#!/usr/bin/env node' --outfile=dist/sinker.min.js",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"lint": "eslint . && prettier --check .",
|
|
23
|
+
"lint:fix": "eslint . --fix",
|
|
24
|
+
"format": "prettier --write .",
|
|
25
|
+
"example": "node dist/sinker.min.js example",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:coverage": "vitest run --coverage"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"picocolors": "^1.1.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^25.2.3",
|
|
34
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
35
|
+
"esbuild": "^0.27.3",
|
|
36
|
+
"eslint": "9.39.2",
|
|
37
|
+
"eslint-config-prettier": "10.1.8",
|
|
38
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
39
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
40
|
+
"eslint-plugin-prettier": "5.5.5",
|
|
41
|
+
"eslint-plugin-security": "^3.0.1",
|
|
42
|
+
"globals": "^17.3.0",
|
|
43
|
+
"jiti": "^2.6.1",
|
|
44
|
+
"typescript": ">=5.9.3",
|
|
45
|
+
"typescript-eslint": "^8.55.0",
|
|
46
|
+
"vitest": "^4.0.18"
|
|
47
|
+
},
|
|
48
|
+
"private": false
|
|
49
|
+
}
|