@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 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
+ }