thuban 0.4.1 → 0.4.3

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.
Files changed (35) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/package.json +63 -0
  3. package/dist/packages/crucible/banner.js +1 -1
  4. package/dist/packages/crucible/rule-loader.js +1 -1
  5. package/dist/packages/crucible/rules/code-scanner-core.json +147 -0
  6. package/dist/packages/crucible/rules/command-injection.json +411 -0
  7. package/dist/packages/crucible/rules/crypto-misuse.json +35 -0
  8. package/dist/packages/crucible/rules/deserialization.json +152 -0
  9. package/dist/packages/crucible/rules/env-injection.json +46 -0
  10. package/dist/packages/crucible/rules/eval-abuse.json +104 -0
  11. package/dist/packages/crucible/rules/file-upload.json +46 -0
  12. package/dist/packages/crucible/rules/hallucination.json +22 -0
  13. package/dist/packages/crucible/rules/hardcoded-secret.json +397 -0
  14. package/dist/packages/crucible/rules/hardcoded-url.json +13 -0
  15. package/dist/packages/crucible/rules/insecure-crypto.json +302 -0
  16. package/dist/packages/crucible/rules/integer-overflow.json +35 -0
  17. package/dist/packages/crucible/rules/log-injection.json +33 -0
  18. package/dist/packages/crucible/rules/mass-assignment.json +112 -0
  19. package/dist/packages/crucible/rules/open-redirect.json +196 -0
  20. package/dist/packages/crucible/rules/path-traversal.json +422 -0
  21. package/dist/packages/crucible/rules/prototype-pollution.json +22 -0
  22. package/dist/packages/crucible/rules/sql-injection.json +619 -0
  23. package/dist/packages/crucible/rules/ssrf.json +557 -0
  24. package/dist/packages/crucible/rules/timing-attack.json +55 -0
  25. package/dist/packages/crucible/rules/unsafe-block.json +24 -0
  26. package/dist/packages/crucible/rules/xss.json +663 -0
  27. package/dist/packages/crucible/rules/xxe.json +66 -0
  28. package/dist/packages/crucible/rules/yaml-deserialization.json +22 -0
  29. package/dist/packages/crucible/rules/yaml-injection.json +22 -0
  30. package/dist/packages/scanner/drift-detector.js +1 -1
  31. package/dist/packages/scanner/export-verifier.js +1 -1
  32. package/dist/packages/scanner/license-manager.js +1 -1
  33. package/dist/packages/scanner/monitor-service.js +1 -1
  34. package/dist/packages/scanner/taint-tracker.js +1 -1
  35. package/package.json +4 -2
@@ -0,0 +1,66 @@
1
+ [
2
+ {
3
+ "id": "XXE001",
4
+ "pattern": "XMLReaderFactory|DocumentBuilderFactory|XmlReader.*DtdProcessing",
5
+ "flags": "",
6
+ "category": "xxe",
7
+ "description": "XXE",
8
+ "language": [
9
+ "js",
10
+ "ts",
11
+ "python",
12
+ "java",
13
+ "go",
14
+ "csharp",
15
+ "php",
16
+ "ruby",
17
+ "rust",
18
+ "kotlin"
19
+ ],
20
+ "severity": "critical"
21
+ },
22
+ {
23
+ "id": "XXE002",
24
+ "pattern": "DtdProcessing\\s*=\\s*DtdProcessing\\.Parse",
25
+ "flags": "i",
26
+ "category": "xxe",
27
+ "description": "C# XXE: XmlDocument/XmlReader",
28
+ "language": [
29
+ "csharp"
30
+ ],
31
+ "severity": "critical"
32
+ },
33
+ {
34
+ "id": "XXE003",
35
+ "pattern": "XmlResolver\\s*=\\s*new XmlUrlResolver\\s*\\(\\)",
36
+ "flags": "i",
37
+ "category": "xxe",
38
+ "description": "C# XXE: XmlDocument/XmlReader",
39
+ "language": [
40
+ "csharp"
41
+ ],
42
+ "severity": "critical"
43
+ },
44
+ {
45
+ "id": "XXE004",
46
+ "pattern": "new XPathDocument\\s*\\(",
47
+ "flags": "i",
48
+ "category": "xxe",
49
+ "description": "C# XXE: XPathDocument (no resolver set = default unsafe on older .NET)",
50
+ "language": [
51
+ "csharp"
52
+ ],
53
+ "severity": "critical"
54
+ },
55
+ {
56
+ "id": "XXE005",
57
+ "pattern": "SAXParserFactory\\.newInstance\\s*\\(\\s*\\)",
58
+ "flags": "",
59
+ "category": "xxe",
60
+ "description": "Java: XXE via SAXParserFactory without secure processing",
61
+ "language": [
62
+ "java"
63
+ ],
64
+ "severity": "critical"
65
+ }
66
+ ]
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "id": "YAMLD001",
4
+ "pattern": "YAML\\.load\\s*\\(",
5
+ "flags": "",
6
+ "category": "yaml_deserialization",
7
+ "description": "YAML deserialization",
8
+ "language": [
9
+ "js",
10
+ "ts",
11
+ "python",
12
+ "java",
13
+ "go",
14
+ "csharp",
15
+ "php",
16
+ "ruby",
17
+ "rust",
18
+ "kotlin"
19
+ ],
20
+ "severity": "medium"
21
+ }
22
+ ]
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "id": "YAML001",
4
+ "pattern": "yaml\\.load\\s*\\((?![^)]*[Ll]oader\\s*=\\s*yaml\\.(?:Safe|Base)Loader)|YAML\\.load\\s*\\(",
5
+ "flags": "i",
6
+ "category": "yaml_injection",
7
+ "description": "YAML injection (yaml.load without safe_load)",
8
+ "language": [
9
+ "js",
10
+ "ts",
11
+ "python",
12
+ "java",
13
+ "go",
14
+ "csharp",
15
+ "php",
16
+ "ruby",
17
+ "rust",
18
+ "kotlin"
19
+ ],
20
+ "severity": "medium"
21
+ }
22
+ ]
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DriftDetector extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),widgetPattern:e.widgetPattern||/\/\*\*[\s\S]*?@(purpose|module|layer|imports-from|exports-to|depends-on|critical-for)[\s\S]*?\*\//,ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],...e},this.analysisCache=new Map}async detectAll(){const e=Date.now(),t={filesAnalyzed:0,filesWithWidgets:0,filesWithoutWidgets:0,issues:[],moduleMap:{},duration:0};try{const s=await this._discoverFiles(this.config.rootPath);for(const e of s){const s=await this.analyzeFile(e);t.filesAnalyzed++,s.hasWidget?t.filesWithWidgets++:(t.filesWithoutWidgets++,t.issues.push({file:e,category:"drift",severity:"info",id:"DRIFT001",name:"Missing Widget",message:"File has no widget declaration - add @purpose, @module, @layer etc."})),t.issues.push(...s.issues),s.module&&(t.moduleMap[s.module]||(t.moduleMap[s.module]=[]),t.moduleMap[s.module].push(e))}return t.issues.push(...this._analyzeCrossFile(t.moduleMap)),t.duration=Date.now()-e,t}catch(e){return console.error("[DRIFT-DETECTOR] Detection failed"),t.error="Detection failed",t}}async analyzeFile(e){const t={file:e,hasWidget:!1,widget:null,actualExports:[],actualImports:[],module:null,issues:[]};try{const s=fs.readFileSync(e,"utf8");return t.widget=this._parseWidget(s),t.hasWidget=!!t.widget,t.actualExports=this._extractExports(s),t.actualImports=this._extractImports(s),t.widget&&(t.module=t.widget.module,t.issues.push(...this._compareWidgetToCode(e,t.widget,{exports:t.actualExports,imports:t.actualImports}))),t.drifts=t.issues,this.analysisCache.set(e,t),t}catch(s){const i="ENOENT"===s.code?"File not found":"EACCES"===s.code?"Permission denied":"Analysis error";return t.issues.push({file:e,category:"drift",severity:"error",id:"DRIFT000",message:`Failed to analyze file: ${i}`}),t.drifts=t.issues,t}}async checkFileDrift(e){const t=await this.analyzeFile(e);return{hasDrift:t.issues.length>0,issues:t.issues,widget:t.widget}}async suggestWidget(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractExports(t),i=this._extractImports(t),o=path.relative(this.config.rootPath,e),r=o.split(path.sep)[0];let n="application";return o.includes("routes")||o.includes("api")?n="presentation":o.includes("core")||o.includes("engine")?n="infrastructure":(o.includes("model")||o.includes("domain"))&&(n="domain"),{purpose:s.length>0?`Provides ${s.join(", ")}`:"Unknown purpose - needs description",module:r,layer:n,importsFrom:i.map(e=>`${e.module} - ${e.specifiers.join(", ")}`),exportsTo:"To be determined",dataFlow:"To be documented",sideEffects:t.includes("fs.")?"File system operations":"None documented",criticalFor:"To be documented"}}catch(e){return{error:e.message}}}async _discoverFiles(e,t=[],s=0){if(s>100)return t;try{const i=fs.readdirSync(e,{withFileTypes:!0});for(const o of i){const i=path.join(e,o.name),r=path.relative(this.config.rootPath,i);this._shouldIgnore(r)||(o.isDirectory()?await this._discoverFiles(i,t,s+1):o.isFile()&&o.name.endsWith(".js")&&t.push(i))}}catch(e){}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),s=["node_modules",".git","dist",".vite"];for(const e of s)if(t.includes("/"+e+"/")||t.startsWith(e+"/")||t===e||t.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const s=e.replace(/\\/g,"/"),i=s.split("/")[0].replace(/\*/g,"");if(i&&(t===i||t.startsWith(i+"/")))return!0;let o=s.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+o).test(t))return!0}return!1}_parseWidget(e){const t=e.match(/\/\*\*[\s\S]*?\*\//);if(!t)return null;const s=t[0],i={},o=["purpose","module","layer","imports-from","exports-to","data-flow","side-effects","depends-on-env","critical-for","known-issues","last-audit","last-modified"];for(const e of o){const t=s.match(new RegExp(`@${e}\\s+(.+?)(?=\\n\\s*\\*\\s*@|\\n\\s*\\*\\/|$)`,"s"));t&&(i[e.replace(/-/g,"_")]=t[1].trim().replace(/\n\s*\*\s*/g," ").trim())}return Object.keys(i).length>0?i:null}_extractExports(e){const t=[],s=e.match(/module\.exports\s*=\s*\{([^}]+)\}/);if(s){const e=s[1].match(/\w+/g)||[];t.push(...e)}const i=e.match(/module\.exports\s*=\s*(class\s+)?(\w+)/);i&&i[2]&&t.push(i[2]);const o=e.match(/exports\.(\w+)\s*=/g)||[];for(const e of o){const s=e.match(/exports\.(\w+)/);s&&t.push(s[1])}return[...new Set(t)]}_extractImports(e){const t=[],s=e.matchAll(/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(['"]([^'"]+)['"]\)/g);for(const e of s){const s=e[1]?e[1].split(",").map(e=>e.trim().split(":")[0].trim()):[e[2]];t.push({module:e[3],specifiers:s.filter(Boolean)})}return t}_compareWidgetToCode(e,t,s){const i=[];if(t.exports_to){const o=t.exports_to.split(",").map(e=>e.trim().split(" ")[0]).filter(e=>!s.exports.some(t=>t.toLowerCase().includes(e.toLowerCase())));o.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT002",name:"Missing Declared Export",message:`Widget declares exports not found in code: ${o.join(", ")}`})}if(t.imports_from){const o=t.imports_from.split(",").map(e=>e.trim().split(" ")[0]),r=s.imports.map(e=>e.module),n=o.filter(e=>!r.some(t=>t.includes(e)||e.includes(t.split("/").pop())));n.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT003",name:"Missing Declared Import",message:`Widget declares imports not found in code: ${n.join(", ")}`})}return!/fs\.(write|append|unlink|mkdir|rmdir)/i.test(fs.readFileSync(e,"utf8"))||t.side_effects&&"None"!==t.side_effects||i.push({file:e,category:"drift",severity:"warning",id:"DRIFT004",name:"Undeclared Side Effects",message:"File has file system operations but widget declares no side effects"}),i}_analyzeCrossFile(e){const t=[];for(const[s,i]of Object.entries(e))1===i.length&&t.push({file:i[0],category:"drift",severity:"info",id:"DRIFT005",name:"Orphaned Module",message:`Module "${s}" only has one file - verify module organization`});return t}}module.exports=DriftDetector;
1
+ const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DriftDetector extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),widgetPattern:e.widgetPattern||/\/\*\*[\s\S]*?@(purpose|module|layer|imports-from|exports-to|depends-on|critical-for)[\s\S]*?\*\//,ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],...e},this.analysisCache=new Map}async detectAll(){const e=Date.now(),t={filesAnalyzed:0,filesWithWidgets:0,filesWithoutWidgets:0,issues:[],moduleMap:{},duration:0};try{const s=await this._discoverFiles(this.config.rootPath);for(const e of s){const s=await this.analyzeFile(e);t.filesAnalyzed++,s.hasWidget?t.filesWithWidgets++:(t.filesWithoutWidgets++,t.issues.push({file:e,category:"drift",severity:"info",id:"DRIFT001",name:"Missing Widget",message:"File has no widget declaration - add @purpose, @module, @layer etc."})),t.issues.push(...s.issues),s.module&&(t.moduleMap[s.module]||(t.moduleMap[s.module]=[]),t.moduleMap[s.module].push(e))}return t.issues.push(...this._analyzeCrossFile(t.moduleMap)),t.duration=Date.now()-e,t}catch(e){return console.error("[DRIFT-DETECTOR] Detection failed"),t.error="Detection failed",t}}async analyzeFile(e){const t={file:e,hasWidget:!1,widget:null,actualExports:[],actualImports:[],module:null,issues:[]};try{const s=fs.readFileSync(e,"utf8");return t.widget=this._parseWidget(s),t.hasWidget=!!t.widget,t.actualExports=this._extractExports(s),t.actualImports=this._extractImports(s),t.widget&&(t.module=t.widget.module,t.issues.push(...this._compareWidgetToCode(e,t.widget,{exports:t.actualExports,imports:t.actualImports}))),t.drifts=t.issues,this.analysisCache.set(e,t),t}catch(s){const i="ENOENT"===s.code?"File not found":"EACCES"===s.code?"Permission denied":"Analysis error";return t.issues.push({file:e,category:"drift",severity:"error",id:"DRIFT000",message:`Failed to analyze file: ${i}`}),t.drifts=t.issues,t}}async checkFileDrift(e){const t=await this.analyzeFile(e);return{hasDrift:t.issues.length>0,issues:t.issues,widget:t.widget}}async suggestWidget(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractExports(t),i=this._extractImports(t),o=path.relative(this.config.rootPath,e),r=o.split(path.sep)[0];let n="application";return o.includes("routes")||o.includes("api")?n="presentation":o.includes("core")||o.includes("engine")?n="infrastructure":(o.includes("model")||o.includes("domain"))&&(n="domain"),{purpose:s.length>0?`Provides ${s.join(", ")}`:"Unknown purpose - needs description",module:r,layer:n,importsFrom:i.map(e=>`${e.module} - ${e.specifiers.join(", ")}`),exportsTo:"To be determined",dataFlow:"To be documented",sideEffects:t.includes("fs.")?"File system operations":"None documented",criticalFor:"To be documented"}}catch(e){return{error:e.message}}}async _discoverFiles(e,t=[],s=0){if(s>100)return t;try{const i=fs.readdirSync(e,{withFileTypes:!0});for(const o of i){const i=path.join(e,o.name),r=path.relative(this.config.rootPath,i);this._shouldIgnore(r)||(o.isDirectory()?await this._discoverFiles(i,t,s+1):o.isFile()&&o.name.endsWith(".js")&&t.push(i))}}catch(e){}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),s=["node_modules",".git","dist",".vite"];for(const e of s)if(t.includes("/"+e+"/")||t.startsWith(e+"/")||t===e||t.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const s=e.replace(/\\/g,"/"),i=s.split("/")[0].replace(/\*/g,"");if(i&&(t===i||t.startsWith(i+"/")))return!0;let o=s.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+o).test(t))return!0}return!1}_parseWidget(e){const t=e.match(/\/\*\*[\s\S]*?\*\//);if(t){const e=t[0],s=this._extractWidgetFields(e,t=>{const s=e.match(new RegExp(`@${t}\\s+(.+?)(?=\\n\\s*\\*\\s*@|\\n\\s*\\*\\/|$)`,"s"));return s?s[1].trim().replace(/\n\s*\*\s*/g," ").trim():null});if(s)return s}const s=e.match(/"{3}[\s\S]*?@(?:purpose|module|layer)[\s\S]*?"{3}|'{3}[\s\S]*?@(?:purpose|module|layer)[\s\S]*?'{3}/);if(s){const e=s[0],t=this._extractWidgetFields(e,t=>{const s=e.match(new RegExp(`@${t}\\s+(.+?)(?=\\n\\s*@|$)`,"s"));return s?s[1].trim():null});if(t)return t}const i=e.match(/(?:^|\n)((?:\s*#[^\n]*\n)+)/);if(i){const e=i[0];if(/@(?:purpose|module|layer)/.test(e)){const t=this._extractWidgetFields(e,t=>{const s=e.match(new RegExp(`@${t}\\s+(.+?)(?=\\n\\s*#\\s*@|\\n[^#]|$)`));return s?s[1].trim():null});if(t)return t}}const o=e.match(/(?:^|\n)((?:\s*\/\/[^\n]*\n)+)/);if(o){const e=o[0];if(/@(?:purpose|module|layer)/.test(e)){const t=this._extractWidgetFields(e,t=>{const s=e.match(new RegExp(`@${t}\\s+(.+?)(?=\\n\\s*//\\s*@|\\n[^/]|$)`));return s?s[1].trim():null});if(t)return t}}return null}_extractWidgetFields(e,t){const s=["purpose","module","layer","imports-from","exports-to","data-flow","side-effects","depends-on-env","critical-for","known-issues","last-audit","last-modified"],i={};for(const e of s){const s=t(e);s&&(i[e.replace(/-/g,"_")]=s)}return Object.keys(i).length>0?i:null}_extractExports(e){const t=[],s=e.match(/module\.exports\s*=\s*\{([^}]+)\}/);if(s){const e=s[1].match(/\w+/g)||[];t.push(...e)}const i=e.match(/module\.exports\s*=\s*(class\s+)?(\w+)/);i&&i[2]&&t.push(i[2]);const o=e.match(/exports\.(\w+)\s*=/g)||[];for(const e of o){const s=e.match(/exports\.(\w+)/);s&&t.push(s[1])}const r=e.match(/^(?:def|class)\s+([a-zA-Z_]\w*)/gm)||[];for(const e of r){const s=e.match(/^(?:def|class)\s+([a-zA-Z_]\w*)/);s&&!s[1].startsWith("_")&&t.push(s[1])}const n=e.match(/^func\s+([A-Z][a-zA-Z0-9]*)/gm)||[];for(const e of n){const s=e.match(/^func\s+([A-Z][a-zA-Z0-9]*)/);s&&t.push(s[1])}const a=e.match(/(?:public\s+function|class)\s+([a-zA-Z_]\w*)/g)||[];for(const e of a){const s=e.match(/(?:public\s+function|class)\s+([a-zA-Z_]\w*)/);s&&t.push(s[1])}return[...new Set(t)]}_extractImports(e){const t=[],s=e.matchAll(/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(['"]([^'"]+)['"]\)/g);for(const e of s){const s=e[1]?e[1].split(",").map(e=>e.trim().split(":")[0].trim()):[e[2]];t.push({module:e[3],specifiers:s.filter(Boolean)})}const i=e.matchAll(/^(?:import\s+(\S+)|from\s+(\S+)\s+import)/gm);for(const e of i)t.push({module:e[1]||e[2],specifiers:[]});const o=e.matchAll(/"([^"]+)"/g);for(const e of o)!e[1].includes("/")&&e[1].includes(" ")||t.push({module:e[1],specifiers:[]});return t}_compareWidgetToCode(e,t,s){const i=[];if(t.exports_to){const o=t.exports_to.split(",").map(e=>e.trim().split(" ")[0]).filter(e=>!s.exports.some(t=>t.toLowerCase().includes(e.toLowerCase())));o.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT002",name:"Missing Declared Export",message:`Widget declares exports not found in code: ${o.join(", ")}`})}if(t.imports_from){const o=t.imports_from.split(",").map(e=>e.trim().split(" ")[0]),r=s.imports.map(e=>e.module),n=o.filter(e=>!r.some(t=>t.includes(e)||e.includes(t.split("/").pop())));n.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT003",name:"Missing Declared Import",message:`Widget declares imports not found in code: ${n.join(", ")}`})}return!/fs\.(write|append|unlink|mkdir|rmdir)/i.test(fs.readFileSync(e,"utf8"))||t.side_effects&&"None"!==t.side_effects||i.push({file:e,category:"drift",severity:"warning",id:"DRIFT004",name:"Undeclared Side Effects",message:"File has file system operations but widget declares no side effects"}),i}_analyzeCrossFile(e){const t=[];for(const[s,i]of Object.entries(e))1===i.length&&t.push({file:i[0],category:"drift",severity:"info",id:"DRIFT005",name:"Orphaned Module",message:`Module "${s}" only has one file - verify module organization`});return t}}module.exports=DriftDetector;
@@ -1 +1 @@
1
- "use strict";const fs=require("fs"),path=require("path"),nodeModule=require("module"),parser=require("@babel/parser"),{_internal:_internal}=require("./ast-analyzer"),{walk:walk}=_internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"];function parseProgram(e){return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0}).program}function extractNamedImports(e){const t=[];let r;try{r=parseProgram(e)}catch(e){return t}for(const e of r.body)if("ImportDeclaration"===e.type&&e.source){const r=e.source.value;for(const n of e.specifiers||[])if("ImportSpecifier"===n.type){const i=n.imported.name||n.imported.value;t.push({imported:i,local:n.local.name,source:r,line:e.loc?e.loc.start.line:0,kind:"import"})}}return walk(r,e=>{if("VariableDeclarator"===e.type&&e.init&&"CallExpression"===e.init.type&&"Identifier"===e.init.callee.type&&"require"===e.init.callee.name&&e.init.arguments[0]&&("Literal"===e.init.arguments[0].type||"StringLiteral"===e.init.arguments[0].type)&&"ObjectPattern"===e.id.type){const r=e.init.arguments[0].value,n=e.loc?e.loc.start.line:0;for(const i of e.id.properties)if(("RestElement"!==i.type&&("ObjectProperty"===i.type!=0||"Property"===i.type)||"RestElement"!==i.type)&&i.key&&i.value){const e=void 0!==i.key.name?i.key.name:i.key.value,o="Identifier"===i.value.type?i.value.name:e;t.push({imported:e,local:o,source:r,line:n,kind:"require"})}}}),t}const builtinModuleCache=new Map;function getBuiltinExports(e){if(builtinModuleCache.has(e))return builtinModuleCache.get(e);let t;try{const r=require(e);t={names:new Set(Object.keys(r)),uncertain:!1}}catch(e){t={names:null,uncertain:!0}}return builtinModuleCache.set(e,t),t}function isBuiltinSpecifier(e){const t=e.startsWith("node:")?e.slice(5):e;return nodeModule.builtinModules.includes(t)||nodeModule.builtinModules.includes(e)}function collectObjectKeys(e,t,r){for(const n of e.properties||[]){if("SpreadElement"===n.type||"SpreadProperty"===n.type||"ExperimentalSpreadProperty"===n.type){r.value=!0;continue}if(n.computed){r.value=!0;continue}const e=n.key;e&&("Identifier"===e.type?t.add(e.name):"Literal"!==e.type&&"StringLiteral"!==e.type||t.add(String(e.value)))}}const moduleExportsCache=new Map;function getModuleExports(e,t){t=t||new Set;const r=e;if(moduleExportsCache.has(r))return moduleExportsCache.get(r);if(t.has(e))return{names:new Set,uncertain:!0};let n;t.add(e);const i=path.extname(e).toLowerCase();if(".json"===i){try{const t=JSON.parse(fs.readFileSync(e,"utf-8"));n={names:t&&"object"==typeof t?new Set(Object.keys(t)):new Set,uncertain:!1}}catch(e){n={names:null,uncertain:!0}}return moduleExportsCache.set(r,n),n}if(".node"===i||![".js",".jsx",".ts",".tsx",".mjs",".cjs"].includes(i)&&""!==i)return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n;let o,a;try{o=fs.readFileSync(e,"utf-8")}catch(e){return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n}try{a=parseProgram(o)}catch(e){return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n}const s=new Set,l={value:!1},c=path.dirname(e);function u(e,r){const n=function(e){try{return require.resolve(e,{paths:[c]})}catch(e){return null}}(e);if(!n)return void(l.value=!0);const i=getModuleExports(n,t);if(i.uncertain||null===i.names)l.value=!0;else if(r)for(const e of r)i.names.has(e)&&s.add(e);else for(const e of i.names)s.add(e)}let p=null;for(const e of a.body){if("ExportNamedDeclaration"===e.type)if(e.declaration){const t=e.declaration;if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type?s.add(e.id.name):e.id&&"ObjectPattern"===e.id.type&&(l.value=!0);else"FunctionDeclaration"!==t.type&&"ClassDeclaration"!==t.type||!t.id||s.add(t.id.name)}else if(e.source){const t=(e.specifiers||[]).map(e=>e.local.name);u(e.source.value,t);for(const t of e.specifiers||[])s.add(t.exported.name||t.exported.value)}else for(const t of e.specifiers||[])s.add(t.exported.name||t.exported.value);if("ExportAllDeclaration"===e.type&&e.source&&u(e.source.value,null),"ExportDefaultDeclaration"===e.type&&s.add("default"),"ExpressionStatement"===e.type){const t=e.expression;if(t&&"AssignmentExpression"===t.type&&"="===t.operator){const e=t.left;"MemberExpression"===e.type&&"Identifier"===e.object.type&&"module"===e.object.name&&"Identifier"===e.property.type&&"exports"===e.property.name?"ObjectExpression"===t.right.type?collectObjectKeys(t.right,s,l):"CallExpression"!==t.right.type||"Identifier"!==t.right.callee.type||"require"!==t.right.callee.name||!t.right.arguments[0]||"Literal"!==t.right.arguments[0].type&&"StringLiteral"!==t.right.arguments[0].type?("Identifier"===t.right.type||"FunctionExpression"===t.right.type||t.right.type,l.value=!0):p=t.right.arguments[0].value:"MemberExpression"===e.type&&!e.computed&&"Identifier"===e.property.type&&("Identifier"===e.object.type&&"exports"===e.object.name||"MemberExpression"===e.object.type&&"Identifier"===e.object.object.type&&"module"===e.object.object.name&&"Identifier"===e.object.property.type&&"exports"===e.object.property.name)&&s.add(e.property.name)}t&&"CallExpression"===t.type&&"MemberExpression"===t.callee.type&&"Identifier"===t.callee.object.type&&"Object"===t.callee.object.name&&"Identifier"===t.callee.property.type&&"defineProperty"===t.callee.property.name&&t.arguments[0]&&"Identifier"===t.arguments[0].type&&"exports"===t.arguments[0].name&&t.arguments[1]&&("Literal"===t.arguments[1].type||"StringLiteral"===t.arguments[1].type)&&s.add(t.arguments[1].value)}}return p&&u(p,null),n={names:s,uncertain:l.value},moduleExportsCache.set(r,n),n}function verifyFile(e,t){const r=[],n=extractNamedImports(t);if(0===n.length)return r;const i=path.dirname(e);for(const t of n){if(t.source.startsWith(".")||t.source.startsWith("/"))continue;if("default"===t.imported)continue;let n;if(isBuiltinSpecifier(t.source))n=getBuiltinExports(t.source.startsWith("node:")?t.source.slice(5):t.source);else{let e;try{e=require.resolve(t.source,{paths:[i]})}catch(e){continue}n=getModuleExports(e,new Set)}n&&null!==n.names&&!n.uncertain&&(n.names.has(t.imported)||r.push({file:e,line:t.line,source:t.source,importedName:t.imported,message:`'${t.source}' does not export '${t.imported}' — this import will be undefined at runtime`}))}return r}module.exports={verifyFile:verifyFile,extractNamedImports:extractNamedImports,getModuleExports:getModuleExports,getBuiltinExports:getBuiltinExports,isBuiltinSpecifier:isBuiltinSpecifier,_internal:{parseProgram:parseProgram,collectObjectKeys:collectObjectKeys}};
1
+ "use strict";const fs=require("fs"),path=require("path"),nodeModule=require("module"),parser=require("@babel/parser"),{_internal:_internal}=require("./ast-analyzer"),{walk:walk}=_internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"];function parseProgram(e){return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0}).program}function extractNamedImports(e){const t=[];let r;try{r=parseProgram(e)}catch(e){return t}for(const e of r.body)if("ImportDeclaration"===e.type&&e.source){const r=e.source.value;for(const n of e.specifiers||[])if("ImportSpecifier"===n.type){const i=n.imported.name||n.imported.value;t.push({imported:i,local:n.local.name,source:r,line:e.loc?e.loc.start.line:0,kind:"import"})}}return walk(r,e=>{if("VariableDeclarator"===e.type&&e.init&&"CallExpression"===e.init.type&&"Identifier"===e.init.callee.type&&"require"===e.init.callee.name&&e.init.arguments[0]&&("Literal"===e.init.arguments[0].type||"StringLiteral"===e.init.arguments[0].type)&&"ObjectPattern"===e.id.type){const r=e.init.arguments[0].value,n=e.loc?e.loc.start.line:0;for(const i of e.id.properties)if(("RestElement"!==i.type&&("ObjectProperty"===i.type!=0||"Property"===i.type)||"RestElement"!==i.type)&&i.key&&i.value){const e=void 0!==i.key.name?i.key.name:i.key.value,o="Identifier"===i.value.type?i.value.name:e;t.push({imported:e,local:o,source:r,line:n,kind:"require"})}}}),t}const builtinModuleCache=new Map;function getBuiltinExports(e){if(builtinModuleCache.has(e))return builtinModuleCache.get(e);let t;try{const r=require(e);t={names:new Set(Object.keys(r)),uncertain:!1}}catch(e){t={names:null,uncertain:!0}}return builtinModuleCache.set(e,t),t}function isBuiltinSpecifier(e){const t=e.startsWith("node:")?e.slice(5):e;return nodeModule.builtinModules.includes(t)||nodeModule.builtinModules.includes(e)}function collectObjectKeys(e,t,r){for(const n of e.properties||[]){if("SpreadElement"===n.type||"SpreadProperty"===n.type||"ExperimentalSpreadProperty"===n.type){r.value=!0;continue}if(n.computed){r.value=!0;continue}const e=n.key;e&&("Identifier"===e.type?t.add(e.name):"Literal"!==e.type&&"StringLiteral"!==e.type||t.add(String(e.value)))}}const moduleExportsCache=new Map;function getModuleExports(e,t){t=t||new Set;const r=e;if(moduleExportsCache.has(r))return moduleExportsCache.get(r);if(t.has(e))return{names:new Set,uncertain:!0};let n;t.add(e);const i=path.extname(e).toLowerCase();if(".json"===i){try{const t=JSON.parse(fs.readFileSync(e,"utf-8"));n={names:t&&"object"==typeof t?new Set(Object.keys(t)):new Set,uncertain:!1}}catch(e){n={names:null,uncertain:!0}}return moduleExportsCache.set(r,n),n}if(".node"===i||![".js",".jsx",".ts",".tsx",".mjs",".cjs"].includes(i)&&""!==i)return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n;let o,a;try{o=fs.readFileSync(e,"utf-8")}catch(e){return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n}try{a=parseProgram(o)}catch(e){return n={names:null,uncertain:!0},moduleExportsCache.set(r,n),n}const s=new Set,l={value:!1},c=path.dirname(e);function u(e,r){const n=function(e){try{return require.resolve(e,{paths:[c]})}catch(e){return null}}(e);if(!n)return void(l.value=!0);let i;try{i=getModuleExports(n,t)}catch(e){return void(l.value=!0)}if(i&&!i.uncertain&&null!==i.names&&"function"==typeof i.names.has)if(r)for(const e of r)i.names.has(e)&&s.add(e);else for(const e of i.names)s.add(e);else l.value=!0}let p=null;for(const e of a.body){if("ExportNamedDeclaration"===e.type)if(e.declaration){const t=e.declaration;if("VariableDeclaration"===t.type)for(const e of t.declarations)e.id&&"Identifier"===e.id.type?s.add(e.id.name):e.id&&"ObjectPattern"===e.id.type&&(l.value=!0);else"FunctionDeclaration"!==t.type&&"ClassDeclaration"!==t.type||!t.id||s.add(t.id.name)}else if(e.source){const t=(e.specifiers||[]).map(e=>e.local.name);u(e.source.value,t);for(const t of e.specifiers||[])s.add(t.exported.name||t.exported.value)}else for(const t of e.specifiers||[])s.add(t.exported.name||t.exported.value);if("ExportAllDeclaration"===e.type&&e.source&&u(e.source.value,null),"ExportDefaultDeclaration"===e.type&&s.add("default"),"ExpressionStatement"===e.type){const t=e.expression;if(t&&"AssignmentExpression"===t.type&&"="===t.operator){const e=t.left;"MemberExpression"===e.type&&"Identifier"===e.object.type&&"module"===e.object.name&&"Identifier"===e.property.type&&"exports"===e.property.name?"ObjectExpression"===t.right.type?collectObjectKeys(t.right,s,l):"CallExpression"!==t.right.type||"Identifier"!==t.right.callee.type||"require"!==t.right.callee.name||!t.right.arguments[0]||"Literal"!==t.right.arguments[0].type&&"StringLiteral"!==t.right.arguments[0].type?("Identifier"===t.right.type||"FunctionExpression"===t.right.type||t.right.type,l.value=!0):p=t.right.arguments[0].value:"MemberExpression"===e.type&&!e.computed&&"Identifier"===e.property.type&&("Identifier"===e.object.type&&"exports"===e.object.name||"MemberExpression"===e.object.type&&"Identifier"===e.object.object.type&&"module"===e.object.object.name&&"Identifier"===e.object.property.type&&"exports"===e.object.property.name)&&s.add(e.property.name)}t&&"CallExpression"===t.type&&"MemberExpression"===t.callee.type&&"Identifier"===t.callee.object.type&&"Object"===t.callee.object.name&&"Identifier"===t.callee.property.type&&"defineProperty"===t.callee.property.name&&t.arguments[0]&&"Identifier"===t.arguments[0].type&&"exports"===t.arguments[0].name&&t.arguments[1]&&("Literal"===t.arguments[1].type||"StringLiteral"===t.arguments[1].type)&&s.add(t.arguments[1].value)}}return p&&u(p,null),n={names:s,uncertain:l.value},moduleExportsCache.set(r,n),n}function verifyFile(e,t){const r=[],n=extractNamedImports(t);if(0===n.length)return r;const i=path.dirname(e);for(const t of n){if(t.source.startsWith(".")||t.source.startsWith("/"))continue;if("default"===t.imported)continue;let n;if(isBuiltinSpecifier(t.source))n=getBuiltinExports(t.source.startsWith("node:")?t.source.slice(5):t.source);else{let e;try{e=require.resolve(t.source,{paths:[i]})}catch(e){continue}try{n=getModuleExports(e,new Set)}catch(e){continue}}n&&null!==n.names&&!n.uncertain&&"function"==typeof n.names.has&&(n.names.has(t.imported)||r.push({file:e,line:t.line,source:t.source,importedName:t.imported,message:`'${t.source}' does not export '${t.imported}' — this import will be undefined at runtime`}))}return r}module.exports={verifyFile:verifyFile,extractNamedImports:extractNamedImports,getModuleExports:getModuleExports,getBuiltinExports:getBuiltinExports,isBuiltinSpecifier:isBuiltinSpecifier,_internal:{parseProgram:parseProgram,collectObjectKeys:collectObjectKeys}};
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),THUBAN_DIR=path.join(os.homedir(),".thuban"),USAGE_FILE=path.join(THUBAN_DIR,"usage.json"),LICENSE_FILE=path.join(THUBAN_DIR,"license.json"),ED25519_PUBLIC_KEY_DER_B64="MCowBQYDK2VwAyEA9/1g/P5f3OwZ/u1LNS1MY6ytTg7GLn3VIWOMxm4qwMQ=",TIERS={free:{name:"Free",maxFiles:100,scansPerMonth:5,showFullDetails:!1,showDashboard:!1,showAutoFix:!1,showCIAction:!1,teaserIssues:3,maxHallucinationDetail:3},pro:{name:"Pro",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!1,teaserIssues:1/0,maxHallucinationDetail:1/0},team:{name:"Team",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0},enterprise:{name:"Enterprise",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0}};class LicenseManager{constructor(){this._ensureDir(),this.machineId=this._getMachineId(),this.usage=this._loadUsage(),this.license=this._loadLicense()}getTier(){return"true"!==process.env.THUBAN_DEV_MODE||__filename.includes("node_modules")?this.license&&this.license.key&&this._validateKey(this.license.key)&&this.license.tier||"free":"enterprise"}getTierConfig(){return"true"===process.env.THUBAN_DEV_MODE?TIERS.pro:TIERS[this.getTier()]||TIERS.free}canScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return{allowed:!0,remaining:1/0,devMode:!0};const e=this.getTierConfig();if(e.scansPerMonth===1/0)return{allowed:!0,remaining:1/0};const t=this._currentMonth(),s=this.usage.scans?.[t]||0,n=Math.max(0,e.scansPerMonth-s);return{allowed:n>0,remaining:n,limit:e.scansPerMonth,used:s,resetsOn:this._nextMonthDate()}}recordScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return;const e=this._currentMonth();this.usage.scans||(this.usage.scans={}),this.usage.scans[e]=(this.usage.scans[e]||0)+1,this.usage.lastScan=(new Date).toISOString(),this.usage.totalScans=(this.usage.totalScans||0)+1,this._saveUsage()}activate(e){const t=this._validateKey(e);return t?(this.license={key:e,tier:t.tier,activatedAt:(new Date).toISOString(),machineId:this.machineId},this._saveLicense(),{success:!0,tier:t.tier,tierName:TIERS[t.tier]?.name||t.tier}):{success:!1,error:"Invalid license key format"}}deactivate(){return this.license={},this._saveLicense(),{success:!0}}getStatus(){const e=this.getTier(),t=this.getTierConfig(),s=this.canScan();let n=!1;return n="true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"),{tier:e,tierName:t.name,machineId:this.machineId.substring(0,8)+"...",licensed:"free"!==e,devMode:n,scansUsed:n?0:s.used||0,scansRemaining:s.remaining,scansLimit:n?1/0:s.limit||t.scansPerMonth,totalScans:this.usage.totalScans||0,features:{fullDetails:t.showFullDetails,dashboard:t.showDashboard,autoFix:t.showAutoFix,ciAction:t.showCIAction,scheduledScans:"free"!==e}}}canUseProFeature(e="This feature"){const t=this.getTier();return"free"===t?{allowed:!1,message:`${e} is available on Thuban Pro and above.`}:{allowed:!0,tier:t}}_ensureDir(){try{fs.existsSync(THUBAN_DIR)||fs.mkdirSync(THUBAN_DIR,{recursive:!0})}catch(e){}}_getMachineId(){const e=[os.hostname(),os.userInfo().username,os.platform(),os.arch()].join("|");try{const t=os.networkInterfaces(),s=Object.values(t).flat().filter(e=>!e.internal&&e.mac&&"00:00:00:00:00:00"!==e.mac).map(e=>e.mac).sort();if(s.length>0)return crypto.createHash("sha256").update(e+"|"+s[0]).digest("hex")}catch(e){}return crypto.createHash("sha256").update(e).digest("hex")}_loadUsage(){try{return JSON.parse(fs.readFileSync(USAGE_FILE,"utf-8"))}catch(e){return{scans:{},totalScans:0}}}_atomicWriteSync(e,t){const s=e+".tmp."+process.pid+"."+Date.now();try{fs.writeFileSync(s,t,"utf-8"),fs.renameSync(s,e)}catch(e){try{fs.unlinkSync(s)}catch{}}}_saveUsage(){try{this._atomicWriteSync(USAGE_FILE,JSON.stringify(this.usage,null,2))}catch(e){}}_loadLicense(){try{return JSON.parse(fs.readFileSync(LICENSE_FILE,"utf-8"))}catch(e){return{}}}_saveLicense(){try{this._atomicWriteSync(LICENSE_FILE,JSON.stringify(this.license,null,2))}catch(e){}}_validateKey(e){if(!e||"string"!=typeof e)return null;const t=e.match(/^THUBAN-(FREE|PRO|TEAM|ENT)-([A-Za-z0-9]{16})-([A-Fa-f0-9]{8,})$/);if(!t)return null;const s=t[1],n=t[2],a=t[3],r={FREE:"free",PRO:"pro",TEAM:"team",ENT:"enterprise"};try{const e=crypto.createPublicKey({key:Buffer.from(ED25519_PUBLIC_KEY_DER_B64,"base64"),format:"der",type:"spki"}),t=Buffer.from(`THUBAN-${s}-${n}`),i=Buffer.from(a,"hex");if(crypto.verify(null,t,e,i))return{tier:r[s]||"free",valid:!0,method:"ed25519"}}catch(e){}return null}_currentMonth(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}`}_nextMonthDate(){const e=new Date;return new Date(e.getFullYear(),e.getMonth()+1,1).toISOString().split("T")[0]}static generateKey(e="PRO"){const t=process.env.THUBAN_SIGNING_PRIVATE_KEY;if(!t)throw new Error("THUBAN_SIGNING_PRIVATE_KEY environment variable is required to generate license keys. This keeps the signing key out of source control and off end-user machines.");const s=crypto.createPrivateKey({key:Buffer.from(t,"base64"),format:"der",type:"pkcs8"}),n=crypto.randomBytes(8).toString("hex"),a=Buffer.from(`THUBAN-${e}-${n}`);return`THUBAN-${e}-${n}-${crypto.sign(null,a,s).toString("hex")}`}}module.exports=LicenseManager;
1
+ const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),TRIAL_HMAC_SECRET_DEFAULT="thuban-trial-silverwings-2026",THUBAN_DIR=path.join(os.homedir(),".thuban"),USAGE_FILE=path.join(THUBAN_DIR,"usage.json"),LICENSE_FILE=path.join(THUBAN_DIR,"license.json"),ED25519_PUBLIC_KEY_DER_B64="MCowBQYDK2VwAyEA9/1g/P5f3OwZ/u1LNS1MY6ytTg7GLn3VIWOMxm4qwMQ=";function _trialBase64urlDecode(e){const t=e+"===".slice((e.length+3)%4);return Buffer.from(t.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf-8")}function _trialHmac8(e){const t=process.env.TRIAL_HMAC_SECRET||TRIAL_HMAC_SECRET_DEFAULT;return crypto.createHmac("sha256",t).update(e).digest("hex").substring(0,8)}function validateTrialKey(e){const t=e&&e.match(/^THUBAN-TRIAL-([A-Za-z0-9_-]+)-([A-Fa-f0-9]{8})$/);if(!t)return null;const s=t[1],a=t[2],r=_trialHmac8(`THUBAN-TRIAL-${s}`);if(a.toLowerCase()!==r.toLowerCase())return null;let i;try{i=JSON.parse(_trialBase64urlDecode(s))}catch(e){return null}if(!i||!i.x||!i.e)return null;const n=Math.floor(Date.now()/1e3),o=n>i.x,c=Math.max(0,Math.ceil((i.x-n)/86400));return{valid:!o,expired:o,email:i.e,name:i.n,invitedBy:i.i,expiresAt:new Date(1e3*i.x).toISOString(),daysRemaining:c,tier:"trial"}}const TIERS={free:{name:"Free",maxFiles:100,scansPerMonth:5,showFullDetails:!1,showDashboard:!1,showAutoFix:!1,showCIAction:!1,teaserIssues:3,maxHallucinationDetail:3},pro:{name:"Pro",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!1,teaserIssues:1/0,maxHallucinationDetail:1/0},team:{name:"Team",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0},enterprise:{name:"Enterprise",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!0,teaserIssues:1/0,maxHallucinationDetail:1/0},trial:{name:"Trial",maxFiles:1/0,scansPerMonth:1/0,showFullDetails:!0,showDashboard:!0,showAutoFix:!0,showCIAction:!1,teaserIssues:1/0,maxHallucinationDetail:1/0}};class LicenseManager{constructor(){this._ensureDir(),this.machineId=this._getMachineId(),this.usage=this._loadUsage(),this.license=this._loadLicense()}getTier(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return"enterprise";if(!this.license||!this.license.key)return"free";if(this.license.key.startsWith("THUBAN-TRIAL-")){const e=validateTrialKey(this.license.key);return e?e.expired?(this._trialExpired=!0,"free"):(this._trialInfo=e,"trial"):"free"}return this._validateKey(this.license.key)&&this.license.tier||"free"}getTierConfig(){return"true"===process.env.THUBAN_DEV_MODE?TIERS.pro:TIERS[this.getTier()]||TIERS.free}canScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return{allowed:!0,remaining:1/0,devMode:!0};const e=this.getTierConfig();if(e.scansPerMonth===1/0)return{allowed:!0,remaining:1/0};const t=this._currentMonth(),s=this.usage.scans?.[t]||0,a=Math.max(0,e.scansPerMonth-s);return{allowed:a>0,remaining:a,limit:e.scansPerMonth,used:s,resetsOn:this._nextMonthDate()}}recordScan(){if("true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"))return;const e=this._currentMonth();this.usage.scans||(this.usage.scans={}),this.usage.scans[e]=(this.usage.scans[e]||0)+1,this.usage.lastScan=(new Date).toISOString(),this.usage.totalScans=(this.usage.totalScans||0)+1,this._saveUsage()}activate(e){if(e&&e.startsWith("THUBAN-TRIAL-")){const t=validateTrialKey(e);return t?t.expired?{success:!1,error:`Trial key expired on ${new Date(t.expiresAt).toLocaleDateString("en-GB")}. Upgrade at thuban.dev/pricing`}:(this.license={key:e,tier:"trial",activatedAt:(new Date).toISOString(),machineId:this.machineId,trialExpiresAt:t.expiresAt,trialEmail:t.email,trialName:t.name},this._saveLicense(),{success:!0,tier:"trial",tierName:"Trial (Pro features)",daysRemaining:t.daysRemaining,expiresAt:t.expiresAt}):{success:!1,error:"Invalid trial key format or signature"}}const t=this._validateKey(e);return t?(this.license={key:e,tier:t.tier,activatedAt:(new Date).toISOString(),machineId:this.machineId},this._saveLicense(),{success:!0,tier:t.tier,tierName:TIERS[t.tier]?.name||t.tier}):{success:!1,error:"Invalid license key format"}}deactivate(){return this.license={},this._saveLicense(),{success:!0}}getStatus(){const e=this.getTier(),t=this.getTierConfig(),s=this.canScan();let a=!1;return a="true"===process.env.THUBAN_DEV_MODE&&!__filename.includes("node_modules"),{tier:e,tierName:t.name,machineId:this.machineId.substring(0,8)+"...",licensed:"free"!==e,devMode:a,scansUsed:a?0:s.used||0,scansRemaining:s.remaining,scansLimit:a?1/0:s.limit||t.scansPerMonth,totalScans:this.usage.totalScans||0,..."trial"===e&&this._trialInfo?{trialDaysRemaining:this._trialInfo.daysRemaining,trialExpiresAt:this._trialInfo.expiresAt,trialName:this._trialInfo.name,trialEmail:this._trialInfo.email}:{},...this._trialExpired?{trialExpired:!0}:{},features:{fullDetails:t.showFullDetails,dashboard:t.showDashboard,autoFix:t.showAutoFix,ciAction:t.showCIAction,scheduledScans:"free"!==e&&"trial"!==e}}}canUseProFeature(e="This feature"){const t=this.getTier();return"free"===t?{allowed:!1,message:`${e} is available on Thuban Pro and above.`}:{allowed:!0,tier:t}}_ensureDir(){try{fs.existsSync(THUBAN_DIR)||fs.mkdirSync(THUBAN_DIR,{recursive:!0})}catch(e){}}_getMachineId(){const e=[os.hostname(),os.userInfo().username,os.platform(),os.arch()].join("|");try{const t=os.networkInterfaces(),s=Object.values(t).flat().filter(e=>!e.internal&&e.mac&&"00:00:00:00:00:00"!==e.mac).map(e=>e.mac).sort();if(s.length>0)return crypto.createHash("sha256").update(e+"|"+s[0]).digest("hex")}catch(e){}return crypto.createHash("sha256").update(e).digest("hex")}_loadUsage(){try{return JSON.parse(fs.readFileSync(USAGE_FILE,"utf-8"))}catch(e){return{scans:{},totalScans:0}}}_atomicWriteSync(e,t){const s=e+".tmp."+process.pid+"."+Date.now();try{fs.writeFileSync(s,t,"utf-8"),fs.renameSync(s,e)}catch(e){try{fs.unlinkSync(s)}catch{}}}_saveUsage(){try{this._atomicWriteSync(USAGE_FILE,JSON.stringify(this.usage,null,2))}catch(e){}}_loadLicense(){try{return JSON.parse(fs.readFileSync(LICENSE_FILE,"utf-8"))}catch(e){return{}}}_saveLicense(){try{this._atomicWriteSync(LICENSE_FILE,JSON.stringify(this.license,null,2))}catch(e){}}_validateKey(e){if(!e||"string"!=typeof e)return null;const t=e.match(/^THUBAN-(FREE|PRO|TEAM|ENT)-([A-Za-z0-9]{16})-([A-Fa-f0-9]{8,})$/);if(!t)return null;const s=t[1],a=t[2],r=t[3],i={FREE:"free",PRO:"pro",TEAM:"team",ENT:"enterprise"};try{const e=crypto.createPublicKey({key:Buffer.from(ED25519_PUBLIC_KEY_DER_B64,"base64"),format:"der",type:"spki"}),t=Buffer.from(`THUBAN-${s}-${a}`),n=Buffer.from(r,"hex");if(crypto.verify(null,t,e,n))return{tier:i[s]||"free",valid:!0,method:"ed25519"}}catch(e){}return null}_currentMonth(){const e=new Date;return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}`}_nextMonthDate(){const e=new Date;return new Date(e.getFullYear(),e.getMonth()+1,1).toISOString().split("T")[0]}static generateKey(e="PRO"){const t=process.env.THUBAN_SIGNING_PRIVATE_KEY;if(!t)throw new Error("THUBAN_SIGNING_PRIVATE_KEY environment variable is required to generate license keys. This keeps the signing key out of source control and off end-user machines.");const s=crypto.createPrivateKey({key:Buffer.from(t,"base64"),format:"der",type:"pkcs8"}),a=crypto.randomBytes(8).toString("hex"),r=Buffer.from(`THUBAN-${e}-${a}`);return`THUBAN-${e}-${a}-${crypto.sign(null,r,s).toString("hex")}`}}module.exports=LicenseManager;
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),AlertManager=require("./alert-manager.js"),MonitorStore=require("./monitor-store.js"),MonitorNotifier=require("./monitor-notifier.js"),{runScanPipeline:runScanPipeline}=require("./scan-runner.js"),{diffRuns:diffRuns}=require("./scan-diff.js"),{collectFiles:collectFiles}=require("./file-collector.js"),MIN_INTERVAL_MS=3e5;function resolveIntervalMs(e,t){return"weekly"===e?6048e5:"custom"===e?Math.max(3e5,Number(t)||864e5):864e5}class MonitorService{constructor(e={}){this.store=e.store||new MonitorStore,this.timers=new Map}configureRepo({repoPath:e,frequency:t="daily",intervalMs:r,notification:o}){const n=path.resolve(e);try{if(!fs.statSync(n).isDirectory())throw new Error(`[MonitorService] repoPath is not a directory: ${n}`)}catch(e){if("ENOENT"===e.code)throw new Error(`[MonitorService] repoPath does not exist: ${n}`);throw e}const s=this.store.getRepoId(n),i=resolveIntervalMs(t,r);return this.store.upsertRepo({repoId:s,repoPath:n,frequency:t,intervalMs:i,notification:o,nextRunAt:new Date(Date.now()+i).toISOString()})}async runRepoScan(e){const t=path.resolve(e.repoPath),r=this._collectFiles(t),o=Date.now(),n=await runScanPipeline({rootPath:t,files:r}),s={repoId:e.repoId,repoPath:t,timestamp:(new Date).toISOString(),durationMs:Date.now()-o,frequency:e.frequency,metrics:n.metrics,summary:n.summary,issues:n.issues},i=this.store.getLatestRun(e.repoId),a=diffRuns(i,s);s.diff=a;const c=this.store.saveRun(e.repoId,s),l=this.store.upsertRepo({...e,lastRunAt:s.timestamp,nextRunAt:new Date(Date.now()+e.intervalMs).toISOString()}),u=new MonitorNotifier({alertManager:new AlertManager({rootPath:t,consoleOutput:!0,fileOutput:!0}),store:this.store,repoConfig:l});return s.notifications=await u.notify(a,s),{run:s,runPath:c,previousRun:i,diff:a}}start(e,{once:t=!1}={}){const r=async()=>{try{await this.runRepoScan(e)}catch(e){console.error("[THUBAN MONITOR] Scheduled scan failed")}};if(r(),t)return;const o=setInterval(r,e.intervalMs);return this.timers.set(e.repoId,o),o}stopAll(){for(const e of this.timers.values())clearInterval(e);this.timers.clear()}_collectFiles(e){return collectFiles(e)}}module.exports={MonitorService:MonitorService,resolveIntervalMs:resolveIntervalMs};
1
+ const fs=require("fs"),path=require("path"),AlertManager=require("./alert-manager.js"),MonitorStore=require("./monitor-store.js"),MonitorNotifier=require("./monitor-notifier.js"),{runScanPipeline:runScanPipeline}=require("./scan-runner.js"),{diffRuns:diffRuns}=require("./scan-diff.js"),{collectFiles:collectFiles}=require("./file-collector.js"),MIN_INTERVAL_MS=3e5;function resolveIntervalMs(e,t){return"weekly"===e?6048e5:"custom"===e?Math.max(3e5,Number(t)||864e5):864e5}class MonitorService{constructor(e={}){this.store=e.store||new MonitorStore,this.timers=new Map}configureRepo({repoPath:e,frequency:t="daily",intervalMs:r,notification:o}){const s=path.resolve(e);try{if(!fs.statSync(s).isDirectory())throw new Error(`[MonitorService] repoPath is not a directory: ${s}`)}catch(e){if("ENOENT"===e.code)throw new Error(`[MonitorService] repoPath does not exist: ${s}`);throw e}const i=this.store.getRepoId(s),n=resolveIntervalMs(t,r);return this.store.upsertRepo({repoId:i,repoPath:s,frequency:t,intervalMs:n,notification:o,nextRunAt:new Date(Date.now()+n).toISOString()})}async runRepoScan(e){const t=path.resolve(e.repoPath),r=this._collectFiles(t),o=Date.now(),s=await runScanPipeline({rootPath:t,files:r}),i=s.metrics.issueCount||0,n=s.summary.bySeverity&&s.summary.bySeverity.critical||0,a=s.summary.bySeverity&&s.summary.bySeverity.high||0,c=s.summary.bySeverity&&s.summary.bySeverity.warning||0,u=s.metrics.filesScanned||1,l=n/u,f=a/u,h=c/u,m=Math.max(0,Math.min(100,Math.round(100-20*l-10*f-2*h))),p={repoId:e.repoId,repoPath:t,timestamp:(new Date).toISOString(),durationMs:Date.now()-o,frequency:e.frequency,metrics:{...s.metrics,trustScore:m,totalIssues:i},summary:s.summary,issues:s.issues},y=this.store.getLatestRun(e.repoId),v=diffRuns(y,p);p.diff=v;const M=this.store.saveRun(e.repoId,p),S=this.store.upsertRepo({...e,lastRunAt:p.timestamp,nextRunAt:new Date(Date.now()+e.intervalMs).toISOString()}),d=new MonitorNotifier({alertManager:new AlertManager({rootPath:t,consoleOutput:!0,fileOutput:!0}),store:this.store,repoConfig:S});return p.notifications=await d.notify(v,p),{run:p,runPath:M,previousRun:y,diff:v}}start(e,{once:t=!1}={}){const r=async()=>{try{await this.runRepoScan(e)}catch(e){console.error("[THUBAN MONITOR] Scheduled scan failed")}};if(r(),t)return;const o=setInterval(r,e.intervalMs);return this.timers.set(e.repoId,o),o}stopAll(){for(const e of this.timers.values())clearInterval(e);this.timers.clear()}_collectFiles(e){return collectFiles(e)}}module.exports={MonitorService:MonitorService,resolveIntervalMs:resolveIntervalMs};
@@ -1 +1 @@
1
- "use strict";const parser=require("@babel/parser"),path=require("path"),fs=require("fs"),{joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_TAINT_FILE_SIZE=2e4,MAX_FIXPOINT_ITERATIONS=10;function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const i=e[n];if(Array.isArray(i))for(const e of i)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else i&&"object"==typeof i&&"string"==typeof i.type&&walk(i,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkTaintIssue(e,t,n,i){const r={file:e,line:t,category:"security",type:i.type,severity:i.severity||"critical",id:i.id,name:i.name,message:i.message,code:snippet(n,t),source:"taint",taintPath:i.taintPath};return i.crossFile&&(r.crossFile=!0,r.sourceFile=i.sourceFile),r}function fileTooLargeIssue(e,t){return{file:e,line:0,category:"quality",type:"file_too_large",severity:"info",id:"TAINT003",name:"Taint Analysis Skipped",message:`File too large (${t} chars) - taint analysis skipped (cap: 20000 chars)`,code:"",source:"taint"}}const SANITIZER_FUNCS=new Set(["parseInt","parseFloat","Number","Boolean","encodeURIComponent","encodeURI","escape","sanitize","sanitizeHtml","stripTags"]),SANITIZER_MEMBER=new Set(["sanitize","escape","escapeHtml","stripTags","clean"]),SANITIZER_RECEIVER_NAMES=new Set(["dompurify","sanitizehtml","sanitize-html","html","xss","validator","bleach","markupsafe","purify","sqlstring","sanitizer","striptags","xssfilters","insane"]);function hasKnownSanitizerReceiver(e){let t=e,n=0;for(;t&&n++<20;){if("Identifier"===t.type)return SANITIZER_RECEIVER_NAMES.has(t.name.toLowerCase());if("MemberExpression"!==t.type)return!1;t=t.object}return!1}const JS_SOURCE_PATH_PATTERNS=[{re:/^req\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^request\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^process\.argv/,severity:"critical"},{re:/^process\.env/,severity:"warning"},{re:/^document\.location/,severity:"critical"},{re:/^window\.location/,severity:"critical"},{re:/^location\.(search|hash|href)/,severity:"critical"}];function memberChainToPath(e){const t=[];let n=e,i=0;for(;n&&i++<20;){if("Identifier"===n.type){t.unshift(n.name);break}if("ThisExpression"===n.type){t.unshift("this");break}if("MemberExpression"!==n.type)return null;n.computed||"Identifier"!==n.property.type?t.unshift("*"):t.unshift(n.property.name),n=n.object}return t.length?t.join("."):null}function jsSourceSeverity(e){if(!e)return null;if("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name)return"critical";if("NewExpression"===e.type&&"Identifier"===e.callee.type&&("URLSearchParams"===e.callee.name||"FormData"===e.callee.name))return"critical";const t=memberChainToPath(e);if(t)for(const e of JS_SOURCE_PATH_PATTERNS)if(e.re.test(t))return e.severity;return null}function isJSSourceExpr(e){return null!==jsSourceSeverity(e)}function describeJSSource(e){return memberChainToPath(e)||("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name?"prompt()":"NewExpression"===e.type&&"Identifier"===e.callee.type?`new ${e.callee.name}()`:"tainted source")}function isSanitizerCall(e){return!(!e||"CallExpression"!==e.type)&&(!("Identifier"!==e.callee.type||!SANITIZER_FUNCS.has(e.callee.name))||!("MemberExpression"!==e.callee.type||"Identifier"!==e.callee.property.type||!SANITIZER_MEMBER.has(e.callee.property.name))&&hasKnownSanitizerReceiver(e.callee.object))}function isTaintedExpr(e,t,n){if(n=n||0,!e||n>12)return!1;if("Identifier"===e.type)return t.has(e.name);if("CallExpression"===e.type){if(isSanitizerCall(e))return!1;if(isJSSourceExpr(e))return!0;if("MemberExpression"===e.callee.type&&isTaintedExpr(e.callee.object,t,n+1))return!0;for(const i of e.arguments)if(isTaintedExpr(i,t,n+1))return!0;return!1}return"MemberExpression"===e.type?!!isJSSourceExpr(e)||isTaintedExpr(e.object,t,n+1):"NewExpression"===e.type?isJSSourceExpr(e):"BinaryExpression"===e.type&&"+"===e.operator?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):"TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t,n+1)):"AssignmentExpression"===e.type?isTaintedExpr(e.right,t,n+1):"ConditionalExpression"===e.type?isTaintedExpr(e.consequent,t,n+1)||isTaintedExpr(e.alternate,t,n+1):"LogicalExpression"===e.type?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):!("SequenceExpression"!==e.type||!e.expressions.length)&&isTaintedExpr(e.expressions[e.expressions.length-1],t,n+1)}function isDynamicTaintedBuild(e,t){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t)):"BinaryExpression"===e.type&&"+"===e.operator&&(isTaintedExpr(e.left,t)||isTaintedExpr(e.right,t)))}function collectFunctionReturnNodes(e){const t=[];return e.body&&"BlockStatement"===e.body.type?function n(i){if(i&&"object"==typeof i&&"string"==typeof i.type&&(i===e||"FunctionDeclaration"!==i.type&&"FunctionExpression"!==i.type&&"ArrowFunctionExpression"!==i.type)){"ReturnStatement"===i.type&&i.argument&&t.push(i.argument);for(const e in i){if("loc"===e||"range"===e||"start"===e||"end"===e)continue;const t=i[e];if(Array.isArray(t))for(const e of t)e&&"object"==typeof e&&"string"==typeof e.type&&n(e);else t&&"object"==typeof t&&"string"==typeof t.type&&n(t)}}}(e.body):e.body&&t.push(e.body),t}function findTaintOrigin(e,t){let n=null;return walk(e,e=>{if(!n)if("Identifier"===e.type&&t.has(e.name))n=t.get(e.name);else{const t=jsSourceSeverity(e);t&&(n={desc:describeJSSource(e),line:lineOf(e),severity:t})}}),n}function collectTaintedVars(e,t){const n=new Set,i=new Map,r=new Set,a=new Map;if(t)for(const[e,r]of t)n.add(e),i.set(e,r);let s=!0,o=0;function l(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||!r.has(e.callee.name))}for(;s&&o<10;)s=!1,o++,walk(e,e=>{if("FunctionDeclaration"!==e.type&&"FunctionExpression"!==e.type&&"ArrowFunctionExpression"!==e.type)return;const t=e.id&&e.id.name;if(t&&!r.has(t))for(const o of collectFunctionReturnNodes(e))if(isTaintedExpr(o,n)||l(o)){r.add(t),a.set(t,findTaintOrigin(o,i)||(l(o)?a.get(o.callee.name):null)||{desc:describeJSSource(o),line:lineOf(o),severity:jsSourceSeverity(o)||"critical"}),s=!0;break}}),walk(e,e=>{let t=null,r=null;if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.id.type?(t=e.id.name,r=e.init):"AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&(t=e.left.name,r=e.right),!t||n.has(t))return;const o=l(r);if(isTaintedExpr(r,n)||o){n.add(t),s=!0;const l=findTaintOrigin(r,i)||(o?a.get(r.callee.name):null)||{desc:describeJSSource(r),line:lineOf(e),severity:jsSourceSeverity(r)||"critical"};i.set(t,l)}});return{taintedVars:n,origins:i}}const SQL_METHODS=new Set(["query","execute","raw","where"]);function parseJS(e){try{return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return null}}function analyzeJSTaint(e,t,n){const i=[],r=t.split("\n"),a=parseJS(t);if(!a)return i;const{taintedVars:s,origins:o}=collectTaintedVars(a,n),l=new Set;function c(t,n){const a=lineOf(t),s=`${a}:${n.id}`;if(l.has(s))return;l.add(s);const c=findTaintOrigin(t,o);c&&(n=Object.assign({},n,{severity:c.severity||"critical"}),c.crossFile&&(n=Object.assign({},n,{crossFile:!0,sourceFile:c.sourceFile}))),i.push(mkTaintIssue(e,a,r,n))}function p(e){const t=findTaintOrigin(e,o);if(t)return`${t.crossFile?`${t.sourceFile}: `:""}${t.desc} (line ${t.line}) -> sink (line ${lineOf(e)})`}return walk(a,e=>{if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&SQL_METHODS.has(e.callee.property.name)){const t=e.arguments[0];t&&(isDynamicTaintedBuild(t,s)||isTaintedExpr(t,s))&&c(e,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:`Tainted user input flows into .${e.callee.property.name}() without sanitization - SQL injection risk`,taintPath:p(e)})}if("AssignmentExpression"!==e.type||"MemberExpression"!==e.left.type||"Identifier"!==e.left.property.type||"innerHTML"!==e.left.property.name&&"outerHTML"!==e.left.property.name||(isTaintedExpr(e.right,s)||isDynamicTaintedBuild(e.right,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via "+e.left.property.name,message:`Tainted user input flows into ${e.left.property.name} without sanitization - XSS risk`,taintPath:p(e)}),"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"document"===e.callee.object.name&&"Identifier"===e.callee.property.type&&("write"===e.callee.property.name||"writeln"===e.callee.property.name)){const t=e.arguments[0];t&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Tainted XSS via document.${e.callee.property.name}()`,message:`Tainted user input flows into document.${e.callee.property.name}() without sanitization - XSS risk`,taintPath:p(e)})}if("JSXAttribute"===e.type&&e.name&&"dangerouslySetInnerHTML"===e.name.name&&e.value&&"JSXExpressionContainer"===e.value.type){const t=e.value.expression;if(t&&"ObjectExpression"===t.type){const n=t.properties.find(e=>e.key&&("__html"===e.key.name||"__html"===e.key.value));n&&(isTaintedExpr(n.value,s)||isDynamicTaintedBuild(n.value,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via dangerouslySetInnerHTML",message:"Tainted user input flows into dangerouslySetInnerHTML without sanitization - XSS risk",taintPath:p(e)})}}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&/^res(ponse)?$/.test(e.callee.object.name)&&"Identifier"===e.callee.property.type&&("send"===e.callee.property.name||"write"===e.callee.property.name)){const t=e.arguments[0];t&&"ObjectExpression"!==t.type&&"ArrayExpression"!==t.type&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Reflected XSS via ${e.callee.object.name}.${e.callee.property.name}()`,message:`Tainted user input is reflected via ${e.callee.object.name}.${e.callee.property.name}() without sanitization - reflected XSS risk`,taintPath:p(e)})}}),i}const PY_SOURCE_PATTERNS=[/\brequest\.args\b/,/\brequest\.form\b/,/\brequest\.json\b/,/\brequest\.data\b/,/\brequest\.GET\b/,/\brequest\.POST\b/,/\bsys\.argv\b/,/\bos\.environ\b/,/\binput\s*\(/],PY_SANITIZER_WRAP_RE=/^\s*(int|float|re\.escape|html\.escape|escape|markupsafe\.escape|bleach\.clean)\s*\(/;function describePySource(e){for(const t of PY_SOURCE_PATTERNS){const n=e.match(t);if(n)return n[0]}return"tainted source"}function isPySourceText(e){return PY_SOURCE_PATTERNS.some(t=>t.test(e))}function isDynamicPyText(e){return isDynamicPyString(e)}function collectPyTaintedVars(e){const t=new Set,n=new Map;let i=!0,r=0;const a=/^\s*([A-Za-z_]\w*)\s*=(?!=)\s*([\s\S]+?)\s*$/;for(;i&&r<10;){i=!1,r++;for(const r of e){const e=r.text.split("\n")[0].match(a)||r.text.match(a);if(!e)continue;const s=e[1];if(t.has(s))continue;const o=r.text.slice(r.text.indexOf("=")+1);if(PY_SANITIZER_WRAP_RE.test(o.trim()))continue;let l=!1,c=null;if(isPySourceText(o))l=!0,c=describePySource(o);else for(const e of t)if(new RegExp("\\b"+e+"\\b").test(o)){l=!0,c=n.has(e)?n.get(e).desc:e;break}l&&(t.add(s),n.set(s,{desc:c,line:r.startLine}),i=!0)}}return{taintedVars:t,origins:n}}function textReferencesTaint(e,t){if(isPySourceText(e))return!0;for(const n of t)if(new RegExp("\\b"+n+"\\b").test(e))return!0;return!1}function analyzePythonTaint(e,t){const n=[],i=t.split("\n"),r=joinLogicalStatements(t),{taintedVars:a,origins:s}=collectPyTaintedVars(r),o=new Set;function l(t,r){const a=`${t}:${r.id}`;o.has(a)||(o.add(a),n.push(mkTaintIssue(e,t,i,r)))}function c(e,t){for(const n of a)if(new RegExp("\\b"+n+"\\b").test(e)&&s.has(n)){const e=s.get(n);return`${e.desc} (line ${e.line}) -> sink (line ${t})`}if(isPySourceText(e))return`${describePySource(e)} -> sink (line ${t})`}for(const e of r){const t=e.text,n=e.startLine,i=t.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(i){const e=t.indexOf("(",i.index),r=extractBalanced(t,e),s=splitTopLevelArgs(r)[0]||"";isDynamicPyText(s)&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:"Tainted user input flows into .execute() without sanitization - SQL injection risk",taintPath:c(s,n)})}const r=t.match(/\.(raw|extra)\s*\(|\bRawSQL\s*\(/);if(r){const e=t.indexOf("(",r.index),i=extractBalanced(t,e),s=splitTopLevelArgs(i)[0]||"";(isDynamicPyText(s)||isPySourceText(s))&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (Django)",message:"Tainted user input flows into raw SQL construct without sanitization - SQL injection risk",taintPath:c(s,n)})}const s=t.match(/\btext\s*\(/);if(s){const e=t.indexOf("(",s.index),i=extractBalanced(t,e);isDynamicPyText(i)&&textReferencesTaint(i,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (SQLAlchemy text())",message:"Tainted user input flows into SQLAlchemy text() without sanitization - SQL injection risk",taintPath:c(i,n)})}const o=t.match(/\bmark_safe\s*\(/);if(o){const e=t.indexOf("(",o.index),i=extractBalanced(t,e);textReferencesTaint(i,a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via mark_safe()",message:"Tainted user input flows into mark_safe() without sanitization - XSS risk",taintPath:c(i,n)})}const p=t.match(/\{\{\s*([A-Za-z_][\w.]*)[^}]*\|\s*safe\b/);p&&textReferencesTaint(p[1],a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via Jinja2 |safe filter",message:"Tainted user input is rendered with the |safe filter without sanitization - XSS risk",taintPath:c(p[1],n)})}return n}const JS_RESOLVE_EXTS=["",".js",".jsx",".ts",".tsx",".mjs",".cjs","/index.js","/index.ts","/index.jsx","/index.tsx"];function resolveImportPath(e,t,n){if(!t||!t.startsWith(".")&&!t.startsWith("/"))return null;const i=path.dirname(e),r=t.startsWith("/")?t:path.resolve(i,t);for(const e of JS_RESOLVE_EXTS){const t=r+e;if(n(t))return t}return null}function defaultFileExists(e){try{return fs.statSync(e).isFile()}catch(e){return!1}}function extractImportBindings(e){const t=[];return walk(e,e=>{if("ImportDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[])if("ImportDefaultSpecifier"===i.type)t.push({localName:i.local.name,importedName:"default",source:n});else if("ImportNamespaceSpecifier"===i.type)t.push({localName:i.local.name,importedName:"*",source:n});else if("ImportSpecifier"===i.type){const e=i.imported&&(i.imported.name||i.imported.value);t.push({localName:i.local.name,importedName:e||i.local.name,source:n})}}if("VariableDeclarator"===e.type&&e.init){const n=unwrapRequireCall(e.init);if(n)if("Identifier"===e.id.type)t.push({localName:e.id.name,importedName:"*",source:n});else if("ObjectPattern"===e.id.type)for(const i of e.id.properties)if("ObjectProperty"===i.type||"Property"===i.type){const e=i.key&&(i.key.name||i.key.value),r=i.value&&"Identifier"===i.value.type?i.value.name:e;e&&r&&t.push({localName:r,importedName:e,source:n})}}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type){const n=unwrapRequireCall(e.right);n&&t.push({localName:e.left.name,importedName:"*",source:n})}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&"then"===e.callee.property.name&&"ImportExpression"===e.callee.object.type&&e.callee.object.source&&"string"==typeof e.callee.object.source.value){const n=e.callee.object.source.value,i=e.arguments[0];i&&("ArrowFunctionExpression"===i.type||"FunctionExpression"===i.type)&&i.params[0]&&"Identifier"===i.params[0].type&&t.push({localName:i.params[0].name,importedName:"*",source:n})}}),t}function unwrapRequireCall(e){if(!e||"CallExpression"!==e.type)return null;if("Identifier"!==e.callee.type||"require"!==e.callee.name)return null;const t=e.arguments[0];return t&&"string"==typeof t.value?t.value:null}function extractReExports(e){const t=[];return walk(e,e=>{if("ExportNamedDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[]){const e=i.local&&(i.local.name||i.local.value),r=i.exported&&(i.exported.name||i.exported.value);e&&r&&t.push({importedName:e,exportedName:r,source:n})}}if("ExportAllDeclaration"===e.type&&e.source&&"string"==typeof e.source.value&&t.push({importedName:"*",exportedName:"*",source:e.source.value}),"AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)){const n=unwrapRequireCall(e.right);n&&t.push({importedName:"*",exportedName:"*",source:n})}}),t}function extractLocalExports(e){const t=[];return walk(e,e=>{if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&!e.left.computed&&"Identifier"===e.left.property.type){const n=memberChainToPath(e.left.object);"module.exports"!==n&&"exports"!==n||t.push({exportedName:e.left.property.name,localName:null,inlineNode:e.right})}if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)&&"ObjectExpression"===e.right.type)for(const n of e.right.properties){if("ObjectProperty"!==n.type&&"Property"!==n.type)continue;const e=n.key&&(n.key.name||n.key.value);e&&(n.value&&"Identifier"===n.value.type?t.push({exportedName:e,localName:n.value.name,inlineNode:null}):t.push({exportedName:e,localName:null,inlineNode:n.value}))}if("ExportNamedDeclaration"===e.type&&e.declaration){const n=e.declaration;if("VariableDeclaration"===n.type)for(const e of n.declarations)"Identifier"===e.id.type&&t.push({exportedName:e.id.name,localName:e.id.name,inlineNode:e.init||null});else"FunctionDeclaration"!==n.type&&"ClassDeclaration"!==n.type||!n.id||t.push({exportedName:n.id.name,localName:n.id.name,inlineNode:null})}if("ExportNamedDeclaration"===e.type&&!e.source)for(const n of e.specifiers||[]){const e=n.local&&(n.local.name||n.local.value),i=n.exported&&(n.exported.name||n.exported.value);e&&i&&t.push({exportedName:i,localName:e,inlineNode:null})}if("ExportDefaultDeclaration"===e.type){const n=e.declaration;n&&("FunctionDeclaration"===n.type||"ClassDeclaration"===n.type)&&n.id?t.push({exportedName:"default",localName:n.id.name,inlineNode:null}):n&&"Identifier"===n.type?t.push({exportedName:"default",localName:n.name,inlineNode:null}):t.push({exportedName:"default",localName:null,inlineNode:n})}}),t}function buildExportTaintMap(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=t.parsedCache||null,a=new Map;for(const t of e){const e=path.extname(t).toLowerCase();if(!JS_EXTS.has(e))continue;let i;try{i=n(t)}catch(e){continue}if(r&&r.set(t,{content:i,ast:null}),i.length>2e4)continue;const s=parseJS(i);r&&(r.get(t).ast=s),s&&a.set(t,{ast:s,content:i})}const s=new Map;for(const e of a.keys())s.set(e,new Map);const o=Math.max(10,a.size+1);let l=!0,c=0;for(;l&&c<o;){l=!1,c++;for(const[e,{ast:t}]of a){const n=s.get(e),r=extractImportBindings(t),a=new Map;for(const t of r){const n=resolveImportPath(e,t.source,i);if(!n||!s.has(n))continue;const r=s.get(n);if("*"===t.importedName)for(const[,e]of r)a.has(t.localName)||a.set(t.localName,e);else r.has(t.importedName)&&a.set(t.localName,r.get(t.importedName))}const{taintedVars:o,origins:c}=collectTaintedVars(t,a),p=extractLocalExports(t);for(const t of p){if(n.has(t.exportedName))continue;let i=null;if(t.localName&&o.has(t.localName)?i=c.get(t.localName)||{desc:t.localName,line:0}:t.inlineNode&&(isTaintedExpr(t.inlineNode,o)||isDynamicTaintedBuild(t.inlineNode,o))&&(i=findTaintOrigin(t.inlineNode,c)||{desc:describeJSSource(t.inlineNode),line:lineOf(t.inlineNode)}),i){const r=i.crossFile?i.sourceFile:e;n.set(t.exportedName,{desc:i.desc,line:i.line,sourceFile:r,crossFile:!0}),l=!0}}const u=extractReExports(t);for(const t of u){const r=resolveImportPath(e,t.source,i);if(!r||!s.has(r))continue;const a=s.get(r);if("*"===t.importedName)for(const[e,t]of a)n.has(e)||(n.set(e,t),l=!0);else a.has(t.importedName)&&!n.has(t.exportedName)&&(n.set(t.exportedName,a.get(t.importedName)),l=!0)}}}return s}function buildImportSeeds(e,t,n,i){const r=new Map,a=extractImportBindings(t);for(const t of a){const a=resolveImportPath(e,t.source,i);if(!a||!n.has(a))continue;const s=n.get(a);if("*"===t.importedName)for(const[,e]of s)r.has(t.localName)||r.set(t.localName,e);else s.has(t.importedName)&&r.set(t.localName,s.get(t.importedName))}return r}function analyzeProject(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=new Map,a=buildExportTaintMap(e,{readFile:n,fileExists:i,parsedCache:r}),s=[];for(const t of e){const e=path.extname(t).toLowerCase(),o=r.get(t);let l;if(o)l=o.content;else try{l=n(t)}catch(e){continue}if(l.length>2e4)(JS_EXTS.has(e)||PY_EXTS.has(e))&&s.push(fileTooLargeIssue(t,l.length));else try{if(JS_EXTS.has(e)){const e=o?o.ast:parseJS(l);if(!e)continue;const n=buildImportSeeds(t,e,a,i);s.push(...analyzeJSTaint(t,l,n.size?n:void 0))}else PY_EXTS.has(e)&&s.push(...analyzePythonTaint(t,l))}catch(e){}}return s}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]);function analyze(e,t){if(t.length>2e4)return[fileTooLargeIssue(e,t.length)];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSTaint(e,t);if(PY_EXTS.has(n))return analyzePythonTaint(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeProject:analyzeProject,analyzeJSTaint:analyzeJSTaint,analyzePythonTaint:analyzePythonTaint,buildExportTaintMap:buildExportTaintMap,buildImportSeeds:buildImportSeeds,resolveImportPath:resolveImportPath,_internal:{walk:walk,isTaintedExpr:isTaintedExpr,isJSSourceExpr:isJSSourceExpr,isSanitizerCall:isSanitizerCall,collectTaintedVars:collectTaintedVars,memberChainToPath:memberChainToPath,isPySourceText:isPySourceText,isDynamicPyText:isDynamicPyText,collectPyTaintedVars:collectPyTaintedVars,resolveImportPath:resolveImportPath,extractImportBindings:extractImportBindings,extractReExports:extractReExports,extractLocalExports:extractLocalExports,parseJS:parseJS}};
1
+ "use strict";const parser=require("@babel/parser"),path=require("path"),fs=require("fs"),{joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_TAINT_FILE_SIZE=2e4,MAX_FIXPOINT_ITERATIONS=10;function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const i=e[n];if(Array.isArray(i))for(const e of i)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else i&&"object"==typeof i&&"string"==typeof i.type&&walk(i,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkTaintIssue(e,t,n,i){const r={file:e,line:t,category:"security",type:i.type,severity:i.severity||"critical",id:i.id,name:i.name,message:i.message,code:snippet(n,t),source:"taint",taintPath:i.taintPath};return i.crossFile&&(r.crossFile=!0,r.sourceFile=i.sourceFile),r}function fileTooLargeIssue(e,t){return{file:e,line:0,category:"quality",type:"file_too_large",severity:"info",id:"TAINT003",name:"Taint Analysis Skipped",message:`File too large (${t} chars) - taint analysis skipped (cap: 20000 chars)`,code:"",source:"taint"}}const SANITIZER_FUNCS=new Set(["parseInt","parseFloat","Number","Boolean","encodeURIComponent","encodeURI","escape","sanitize","sanitizeHtml","stripTags"]),SANITIZER_MEMBER=new Set(["sanitize","escape","escapeHtml","stripTags","clean"]),SANITIZER_RECEIVER_NAMES=new Set(["dompurify","sanitizehtml","sanitize-html","html","xss","validator","bleach","markupsafe","purify","sqlstring","sanitizer","striptags","xssfilters","insane"]);function hasKnownSanitizerReceiver(e){let t=e,n=0;for(;t&&n++<20;){if("Identifier"===t.type)return SANITIZER_RECEIVER_NAMES.has(t.name.toLowerCase());if("MemberExpression"!==t.type)return!1;t=t.object}return!1}const JS_SOURCE_PATH_PATTERNS=[{re:/^req\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^request\.(params|query|body|headers)(\.|$)/,severity:"critical"},{re:/^process\.argv/,severity:"critical"},{re:/^process\.env/,severity:"warning"},{re:/^document\.location/,severity:"critical"},{re:/^window\.location/,severity:"critical"},{re:/^location\.(search|hash|href)/,severity:"critical"}];function memberChainToPath(e){const t=[];let n=e,i=0;for(;n&&i++<20;){if("Identifier"===n.type){t.unshift(n.name);break}if("ThisExpression"===n.type){t.unshift("this");break}if("MemberExpression"!==n.type)return null;n.computed||"Identifier"!==n.property.type?t.unshift("*"):t.unshift(n.property.name),n=n.object}return t.length?t.join("."):null}function jsSourceSeverity(e){if(!e)return null;if("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name)return"critical";if("NewExpression"===e.type&&"Identifier"===e.callee.type&&("URLSearchParams"===e.callee.name||"FormData"===e.callee.name))return"critical";const t=memberChainToPath(e);if(t)for(const e of JS_SOURCE_PATH_PATTERNS)if(e.re.test(t))return e.severity;return null}function isJSSourceExpr(e){return null!==jsSourceSeverity(e)}function describeJSSource(e){return memberChainToPath(e)||("CallExpression"===e.type&&"Identifier"===e.callee.type&&"prompt"===e.callee.name?"prompt()":"NewExpression"===e.type&&"Identifier"===e.callee.type?`new ${e.callee.name}()`:"tainted source")}function isSanitizerCall(e){return!(!e||"CallExpression"!==e.type)&&(!("Identifier"!==e.callee.type||!SANITIZER_FUNCS.has(e.callee.name))||!("MemberExpression"!==e.callee.type||"Identifier"!==e.callee.property.type||!SANITIZER_MEMBER.has(e.callee.property.name))&&hasKnownSanitizerReceiver(e.callee.object))}function isTaintedExpr(e,t,n){if(n=n||0,!e||n>12)return!1;if("Identifier"===e.type)return t.has(e.name);if("CallExpression"===e.type){if(isSanitizerCall(e))return!1;if(isJSSourceExpr(e))return!0;if("MemberExpression"===e.callee.type&&isTaintedExpr(e.callee.object,t,n+1))return!0;for(const i of e.arguments)if(isTaintedExpr(i,t,n+1))return!0;return!1}return"MemberExpression"===e.type?!!isJSSourceExpr(e)||isTaintedExpr(e.object,t,n+1):"NewExpression"===e.type?isJSSourceExpr(e):"BinaryExpression"===e.type&&"+"===e.operator?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):"TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t,n+1)):"AssignmentExpression"===e.type?isTaintedExpr(e.right,t,n+1):"ConditionalExpression"===e.type?isTaintedExpr(e.consequent,t,n+1)||isTaintedExpr(e.alternate,t,n+1):"LogicalExpression"===e.type?isTaintedExpr(e.left,t,n+1)||isTaintedExpr(e.right,t,n+1):!("SequenceExpression"!==e.type||!e.expressions.length)&&isTaintedExpr(e.expressions[e.expressions.length-1],t,n+1)}function isDynamicTaintedBuild(e,t){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).some(e=>isTaintedExpr(e,t)):"BinaryExpression"===e.type&&"+"===e.operator&&(isTaintedExpr(e.left,t)||isTaintedExpr(e.right,t)))}function collectFunctionReturnNodes(e){const t=[];return e.body&&"BlockStatement"===e.body.type?function n(i){if(i&&"object"==typeof i&&"string"==typeof i.type&&(i===e||"FunctionDeclaration"!==i.type&&"FunctionExpression"!==i.type&&"ArrowFunctionExpression"!==i.type)){"ReturnStatement"===i.type&&i.argument&&t.push(i.argument);for(const e in i){if("loc"===e||"range"===e||"start"===e||"end"===e)continue;const t=i[e];if(Array.isArray(t))for(const e of t)e&&"object"==typeof e&&"string"==typeof e.type&&n(e);else t&&"object"==typeof t&&"string"==typeof t.type&&n(t)}}}(e.body):e.body&&t.push(e.body),t}function findTaintOrigin(e,t){let n=null;return walk(e,e=>{if(!n)if("Identifier"===e.type&&t.has(e.name))n=t.get(e.name);else{const t=jsSourceSeverity(e);t&&(n={desc:describeJSSource(e),line:lineOf(e),severity:t})}}),n}function collectTaintedVars(e,t){const n=new Set,i=new Map,r=new Set,a=new Map;if(t)for(const[e,r]of t)n.add(e),i.set(e,r);let s=!0,o=0;function l(e){return!(!e||"CallExpression"!==e.type||"Identifier"!==e.callee.type||!r.has(e.callee.name))}for(;s&&o<10;)s=!1,o++,walk(e,e=>{if("FunctionDeclaration"!==e.type&&"FunctionExpression"!==e.type&&"ArrowFunctionExpression"!==e.type)return;const t=e.id&&e.id.name;if(t&&!r.has(t))for(const o of collectFunctionReturnNodes(e))if(isTaintedExpr(o,n)||l(o)){r.add(t),a.set(t,findTaintOrigin(o,i)||(l(o)?a.get(o.callee.name):null)||{desc:describeJSSource(o),line:lineOf(o),severity:jsSourceSeverity(o)||"critical"}),s=!0;break}}),walk(e,e=>{let t=null,r=null;if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.id.type?(t=e.id.name,r=e.init):"AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&(t=e.left.name,r=e.right),!t||n.has(t))return;const o=l(r);if(isTaintedExpr(r,n)||o){n.add(t),s=!0;const l=findTaintOrigin(r,i)||(o?a.get(r.callee.name):null)||{desc:describeJSSource(r),line:lineOf(e),severity:jsSourceSeverity(r)||"critical"};i.set(t,l)}});return{taintedVars:n,origins:i}}const SQL_METHODS=new Set(["query","execute","raw","where"]);function parseJS(e){try{return parser.parse(e,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return null}}function analyzeJSTaint(e,t,n){const i=[],r=t.split("\n"),a=parseJS(t);if(!a)return i;const{taintedVars:s,origins:o}=collectTaintedVars(a,n),l=new Set;function c(t,n){const a=lineOf(t),s=`${a}:${n.id}`;if(l.has(s))return;l.add(s);const c=findTaintOrigin(t,o);c&&(n=Object.assign({},n,{severity:c.severity||"critical"}),c.crossFile&&(n=Object.assign({},n,{crossFile:!0,sourceFile:c.sourceFile}))),i.push(mkTaintIssue(e,a,r,n))}function p(e){const t=findTaintOrigin(e,o);if(t)return`${t.crossFile?`${t.sourceFile}: `:""}${t.desc} (line ${t.line}) -> sink (line ${lineOf(e)})`}return walk(a,e=>{if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&SQL_METHODS.has(e.callee.property.name)){const t=e.arguments[0];t&&(isDynamicTaintedBuild(t,s)||isTaintedExpr(t,s))&&c(e,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:`Tainted user input flows into .${e.callee.property.name}() without sanitization - SQL injection risk`,taintPath:p(e)})}if("AssignmentExpression"!==e.type||"MemberExpression"!==e.left.type||"Identifier"!==e.left.property.type||"innerHTML"!==e.left.property.name&&"outerHTML"!==e.left.property.name||(isTaintedExpr(e.right,s)||isDynamicTaintedBuild(e.right,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via "+e.left.property.name,message:`Tainted user input flows into ${e.left.property.name} without sanitization - XSS risk`,taintPath:p(e)}),"CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&"document"===e.callee.object.name&&"Identifier"===e.callee.property.type&&("write"===e.callee.property.name||"writeln"===e.callee.property.name)){const t=e.arguments[0];t&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Tainted XSS via document.${e.callee.property.name}()`,message:`Tainted user input flows into document.${e.callee.property.name}() without sanitization - XSS risk`,taintPath:p(e)})}if("JSXAttribute"===e.type&&e.name&&"dangerouslySetInnerHTML"===e.name.name&&e.value&&"JSXExpressionContainer"===e.value.type){const t=e.value.expression;if(t&&"ObjectExpression"===t.type){const n=t.properties.find(e=>e.key&&("__html"===e.key.name||"__html"===e.key.value));n&&(isTaintedExpr(n.value,s)||isDynamicTaintedBuild(n.value,s))&&c(e,{type:"xss",id:"TAINT002",name:"Tainted XSS via dangerouslySetInnerHTML",message:"Tainted user input flows into dangerouslySetInnerHTML without sanitization - XSS risk",taintPath:p(e)})}}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.object.type&&/^res(ponse)?$/.test(e.callee.object.name)&&"Identifier"===e.callee.property.type&&("send"===e.callee.property.name||"write"===e.callee.property.name)){const t=e.arguments[0];t&&"ObjectExpression"!==t.type&&"ArrayExpression"!==t.type&&(isTaintedExpr(t,s)||isDynamicTaintedBuild(t,s))&&c(e,{type:"xss",id:"TAINT002",name:`Reflected XSS via ${e.callee.object.name}.${e.callee.property.name}()`,message:`Tainted user input is reflected via ${e.callee.object.name}.${e.callee.property.name}() without sanitization - reflected XSS risk`,taintPath:p(e)})}}),i}const PY_SOURCE_PATTERNS=[/\brequest\.args\b/,/\brequest\.form\b/,/\brequest\.json\b/,/\brequest\.data\b/,/\brequest\.GET\b/,/\brequest\.POST\b/,/\bsys\.argv\b/,/\bos\.environ\b/,/\binput\s*\(/],PY_SANITIZER_WRAP_RE=/^\s*(int|float|re\.escape|html\.escape|escape|markupsafe\.escape|bleach\.clean)\s*\(/;function describePySource(e){for(const t of PY_SOURCE_PATTERNS){const n=e.match(t);if(n)return n[0]}return"tainted source"}function isPySourceText(e){return PY_SOURCE_PATTERNS.some(t=>t.test(e))}function isDynamicPyText(e){return isDynamicPyString(e)}function collectPyTaintedVars(e){const t=new Set,n=new Map;let i=!0,r=0;const a=/^\s*([A-Za-z_]\w*)\s*=(?!=)\s*([\s\S]+?)\s*$/;for(;i&&r<10;){i=!1,r++;for(const r of e){const e=r.text.split("\n")[0].match(a)||r.text.match(a);if(!e)continue;const s=e[1],o=r.text.slice(r.text.indexOf("=")+1);if(PY_SANITIZER_WRAP_RE.test(o.trim())){t.has(s)&&(t.delete(s),n.delete(s),i=!0);continue}if(t.has(s))continue;let l=!1,c=null;if(isPySourceText(o))l=!0,c=describePySource(o);else for(const e of t)if(new RegExp("\\b"+e+"\\b").test(o)){l=!0,c=n.has(e)?n.get(e).desc:e;break}l&&(t.add(s),n.set(s,{desc:c,line:r.startLine}),i=!0)}}return{taintedVars:t,origins:n}}function textReferencesTaint(e,t){if(isPySourceText(e))return!0;for(const n of t)if(new RegExp("\\b"+n+"\\b").test(e))return!0;return!1}function analyzePythonTaint(e,t){const n=[],i=t.split("\n"),r=joinLogicalStatements(t),{taintedVars:a,origins:s}=collectPyTaintedVars(r),o=new Set;function l(t,r){const a=`${t}:${r.id}`;o.has(a)||(o.add(a),n.push(mkTaintIssue(e,t,i,r)))}function c(e,t){for(const n of a)if(new RegExp("\\b"+n+"\\b").test(e)&&s.has(n)){const e=s.get(n);return`${e.desc} (line ${e.line}) -> sink (line ${t})`}if(isPySourceText(e))return`${describePySource(e)} -> sink (line ${t})`}for(const e of r){const t=e.text,n=e.startLine,i=t.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(i){const e=t.indexOf("(",i.index),r=extractBalanced(t,e),s=splitTopLevelArgs(r)[0]||"";isDynamicPyText(s)&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection",message:"Tainted user input flows into .execute() without sanitization - SQL injection risk",taintPath:c(s,n)})}const r=t.match(/\.(raw|extra)\s*\(|\bRawSQL\s*\(/);if(r){const e=t.indexOf("(",r.index),i=extractBalanced(t,e),s=splitTopLevelArgs(i)[0]||"";(isDynamicPyText(s)||isPySourceText(s))&&textReferencesTaint(s,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (Django)",message:"Tainted user input flows into raw SQL construct without sanitization - SQL injection risk",taintPath:c(s,n)})}const s=t.match(/\btext\s*\(/);if(s){const e=t.indexOf("(",s.index),i=extractBalanced(t,e);isDynamicPyText(i)&&textReferencesTaint(i,a)&&l(n,{type:"sql_injection",id:"TAINT001",name:"Tainted SQL Injection (SQLAlchemy text())",message:"Tainted user input flows into SQLAlchemy text() without sanitization - SQL injection risk",taintPath:c(i,n)})}const o=t.match(/\bmark_safe\s*\(/);if(o){const e=t.indexOf("(",o.index),i=extractBalanced(t,e);textReferencesTaint(i,a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via mark_safe()",message:"Tainted user input flows into mark_safe() without sanitization - XSS risk",taintPath:c(i,n)})}const p=t.match(/\{\{\s*([A-Za-z_][\w.]*)[^}]*\|\s*safe\b/);p&&textReferencesTaint(p[1],a)&&l(n,{type:"xss",id:"TAINT002",name:"Tainted XSS via Jinja2 |safe filter",message:"Tainted user input is rendered with the |safe filter without sanitization - XSS risk",taintPath:c(p[1],n)})}return n}const JS_RESOLVE_EXTS=["",".js",".jsx",".ts",".tsx",".mjs",".cjs","/index.js","/index.ts","/index.jsx","/index.tsx"];function resolveImportPath(e,t,n){if(!t||!t.startsWith(".")&&!t.startsWith("/"))return null;const i=path.dirname(e),r=t.startsWith("/")?t:path.resolve(i,t);for(const e of JS_RESOLVE_EXTS){const t=r+e;if(n(t))return t}return null}function defaultFileExists(e){try{return fs.statSync(e).isFile()}catch(e){return!1}}function extractImportBindings(e){const t=[];return walk(e,e=>{if("ImportDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[])if("ImportDefaultSpecifier"===i.type)t.push({localName:i.local.name,importedName:"default",source:n});else if("ImportNamespaceSpecifier"===i.type)t.push({localName:i.local.name,importedName:"*",source:n});else if("ImportSpecifier"===i.type){const e=i.imported&&(i.imported.name||i.imported.value);t.push({localName:i.local.name,importedName:e||i.local.name,source:n})}}if("VariableDeclarator"===e.type&&e.init){const n=unwrapRequireCall(e.init);if(n)if("Identifier"===e.id.type)t.push({localName:e.id.name,importedName:"*",source:n});else if("ObjectPattern"===e.id.type)for(const i of e.id.properties)if("ObjectProperty"===i.type||"Property"===i.type){const e=i.key&&(i.key.name||i.key.value),r=i.value&&"Identifier"===i.value.type?i.value.name:e;e&&r&&t.push({localName:r,importedName:e,source:n})}}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type){const n=unwrapRequireCall(e.right);n&&t.push({localName:e.left.name,importedName:"*",source:n})}if("CallExpression"===e.type&&"MemberExpression"===e.callee.type&&"Identifier"===e.callee.property.type&&"then"===e.callee.property.name&&"ImportExpression"===e.callee.object.type&&e.callee.object.source&&"string"==typeof e.callee.object.source.value){const n=e.callee.object.source.value,i=e.arguments[0];i&&("ArrowFunctionExpression"===i.type||"FunctionExpression"===i.type)&&i.params[0]&&"Identifier"===i.params[0].type&&t.push({localName:i.params[0].name,importedName:"*",source:n})}}),t}function unwrapRequireCall(e){if(!e||"CallExpression"!==e.type)return null;if("Identifier"!==e.callee.type||"require"!==e.callee.name)return null;const t=e.arguments[0];return t&&"string"==typeof t.value?t.value:null}function extractReExports(e){const t=[];return walk(e,e=>{if("ExportNamedDeclaration"===e.type&&e.source&&"string"==typeof e.source.value){const n=e.source.value;for(const i of e.specifiers||[]){const e=i.local&&(i.local.name||i.local.value),r=i.exported&&(i.exported.name||i.exported.value);e&&r&&t.push({importedName:e,exportedName:r,source:n})}}if("ExportAllDeclaration"===e.type&&e.source&&"string"==typeof e.source.value&&t.push({importedName:"*",exportedName:"*",source:e.source.value}),"AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)){const n=unwrapRequireCall(e.right);n&&t.push({importedName:"*",exportedName:"*",source:n})}}),t}function extractLocalExports(e){const t=[];return walk(e,e=>{if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&!e.left.computed&&"Identifier"===e.left.property.type){const n=memberChainToPath(e.left.object);"module.exports"!==n&&"exports"!==n||t.push({exportedName:e.left.property.name,localName:null,inlineNode:e.right})}if("AssignmentExpression"===e.type&&"="===e.operator&&"MemberExpression"===e.left.type&&"module.exports"===memberChainToPath(e.left)&&"ObjectExpression"===e.right.type)for(const n of e.right.properties){if("ObjectProperty"!==n.type&&"Property"!==n.type)continue;const e=n.key&&(n.key.name||n.key.value);e&&(n.value&&"Identifier"===n.value.type?t.push({exportedName:e,localName:n.value.name,inlineNode:null}):t.push({exportedName:e,localName:null,inlineNode:n.value}))}if("ExportNamedDeclaration"===e.type&&e.declaration){const n=e.declaration;if("VariableDeclaration"===n.type)for(const e of n.declarations)"Identifier"===e.id.type&&t.push({exportedName:e.id.name,localName:e.id.name,inlineNode:e.init||null});else"FunctionDeclaration"!==n.type&&"ClassDeclaration"!==n.type||!n.id||t.push({exportedName:n.id.name,localName:n.id.name,inlineNode:null})}if("ExportNamedDeclaration"===e.type&&!e.source)for(const n of e.specifiers||[]){const e=n.local&&(n.local.name||n.local.value),i=n.exported&&(n.exported.name||n.exported.value);e&&i&&t.push({exportedName:i,localName:e,inlineNode:null})}if("ExportDefaultDeclaration"===e.type){const n=e.declaration;n&&("FunctionDeclaration"===n.type||"ClassDeclaration"===n.type)&&n.id?t.push({exportedName:"default",localName:n.id.name,inlineNode:null}):n&&"Identifier"===n.type?t.push({exportedName:"default",localName:n.name,inlineNode:null}):t.push({exportedName:"default",localName:null,inlineNode:n})}}),t}function buildExportTaintMap(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=t.parsedCache||null,a=new Map;for(const t of e){const e=path.extname(t).toLowerCase();if(!JS_EXTS.has(e))continue;let i;try{i=n(t)}catch(e){continue}if(r&&r.set(t,{content:i,ast:null}),i.length>2e4)continue;const s=parseJS(i);r&&(r.get(t).ast=s),s&&a.set(t,{ast:s,content:i})}const s=new Map;for(const e of a.keys())s.set(e,new Map);const o=Math.max(10,a.size+1);let l=!0,c=0;for(;l&&c<o;){l=!1,c++;for(const[e,{ast:t}]of a){const n=s.get(e),r=extractImportBindings(t),a=new Map;for(const t of r){const n=resolveImportPath(e,t.source,i);if(!n||!s.has(n))continue;const r=s.get(n);if("*"===t.importedName)for(const[,e]of r)a.has(t.localName)||a.set(t.localName,e);else r.has(t.importedName)&&a.set(t.localName,r.get(t.importedName))}const{taintedVars:o,origins:c}=collectTaintedVars(t,a),p=extractLocalExports(t);for(const t of p){if(n.has(t.exportedName))continue;let i=null;if(t.localName&&o.has(t.localName)?i=c.get(t.localName)||{desc:t.localName,line:0}:t.inlineNode&&(isTaintedExpr(t.inlineNode,o)||isDynamicTaintedBuild(t.inlineNode,o))&&(i=findTaintOrigin(t.inlineNode,c)||{desc:describeJSSource(t.inlineNode),line:lineOf(t.inlineNode)}),i){const r=i.crossFile?i.sourceFile:e;n.set(t.exportedName,{desc:i.desc,line:i.line,sourceFile:r,crossFile:!0}),l=!0}}const u=extractReExports(t);for(const t of u){const r=resolveImportPath(e,t.source,i);if(!r||!s.has(r))continue;const a=s.get(r);if("*"===t.importedName)for(const[e,t]of a)n.has(e)||(n.set(e,t),l=!0);else a.has(t.importedName)&&!n.has(t.exportedName)&&(n.set(t.exportedName,a.get(t.importedName)),l=!0)}}}return s}function buildImportSeeds(e,t,n,i){const r=new Map,a=extractImportBindings(t);for(const t of a){const a=resolveImportPath(e,t.source,i);if(!a||!n.has(a))continue;const s=n.get(a);if("*"===t.importedName)for(const[,e]of s)r.has(t.localName)||r.set(t.localName,e);else s.has(t.importedName)&&r.set(t.localName,s.get(t.importedName))}return r}function analyzeProject(e,t){const n=(t=t||{}).readFile||(e=>fs.readFileSync(e,"utf8")),i=t.fileExists||defaultFileExists,r=new Map,a=buildExportTaintMap(e,{readFile:n,fileExists:i,parsedCache:r}),s=[];for(const t of e){const e=path.extname(t).toLowerCase(),o=r.get(t);let l;if(o)l=o.content;else try{l=n(t)}catch(e){continue}if(l.length>2e4)(JS_EXTS.has(e)||PY_EXTS.has(e))&&s.push(fileTooLargeIssue(t,l.length));else try{if(JS_EXTS.has(e)){const e=o?o.ast:parseJS(l);if(!e)continue;const n=buildImportSeeds(t,e,a,i);s.push(...analyzeJSTaint(t,l,n.size?n:void 0))}else PY_EXTS.has(e)&&s.push(...analyzePythonTaint(t,l))}catch(e){}}return s}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]);function analyze(e,t){if(t.length>2e4)return[fileTooLargeIssue(e,t.length)];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSTaint(e,t);if(PY_EXTS.has(n))return analyzePythonTaint(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeProject:analyzeProject,analyzeJSTaint:analyzeJSTaint,analyzePythonTaint:analyzePythonTaint,buildExportTaintMap:buildExportTaintMap,buildImportSeeds:buildImportSeeds,resolveImportPath:resolveImportPath,_internal:{walk:walk,isTaintedExpr:isTaintedExpr,isJSSourceExpr:isJSSourceExpr,isSanitizerCall:isSanitizerCall,collectTaintedVars:collectTaintedVars,memberChainToPath:memberChainToPath,isPySourceText:isPySourceText,isDynamicPyText:isDynamicPyText,collectPyTaintedVars:collectPyTaintedVars,resolveImportPath:resolveImportPath,extractImportBindings:extractImportBindings,extractReExports:extractReExports,extractLocalExports:extractLocalExports,parseJS:parseJS}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thuban",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "The safety layer for AI-coded software. Detect hallucinated APIs, ghost code, tech debt, and architecture drift. One command. Minimal dependencies.",
5
5
  "bin": {
6
6
  "thuban": "./dist/cli.js"
@@ -48,8 +48,10 @@
48
48
  "dist/packages/crucible/*.js",
49
49
  "dist/packages/crucible/seeds/**",
50
50
  "dist/packages/crucible/.golden/",
51
+ "dist/packages/crucible/rules/**",
51
52
  "dist/README.md",
52
- "dist/LICENSE"
53
+ "dist/LICENSE",
54
+ "dist/package.json"
53
55
  ],
54
56
  "dependencies": {
55
57
  "@babel/parser": "^8.0.0",