xploitscan 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- __require,
4
3
  checkUsage,
5
4
  clearToken,
6
5
  getStoredToken,
@@ -9,7 +8,7 @@ import {
9
8
  storeToken,
10
9
  syncUser,
11
10
  uploadScanResults
12
- } from "./chunk-47X3TUVR.js";
11
+ } from "./chunk-CBDFSACC.js";
13
12
 
14
13
  // src/index.ts
15
14
  import { Command } from "commander";
@@ -3166,7 +3165,42 @@ var complianceMap = {
3166
3165
  VC093: { owasp: "A01:2021", cwe: "CWE-22" },
3167
3166
  VC094: { owasp: "A03:2021", cwe: "CWE-78" },
3168
3167
  VC095: { owasp: "A05:2021", cwe: "CWE-942" },
3169
- VC096: { owasp: "A02:2021", cwe: "CWE-319" }
3168
+ VC096: { owasp: "A02:2021", cwe: "CWE-319" },
3169
+ VC097: { owasp: "A09:2021", cwe: "CWE-532" },
3170
+ VC098: { owasp: "A04:2021", cwe: "CWE-400" },
3171
+ VC099: { owasp: "A04:2021", cwe: "CWE-401" },
3172
+ VC100: { owasp: "A04:2021", cwe: "CWE-400" },
3173
+ VC101: { owasp: "A04:2021", cwe: "CWE-400" },
3174
+ VC102: { owasp: "A04:2021", cwe: "CWE-400" },
3175
+ VC103: { owasp: "A04:2021", cwe: "CWE-710" },
3176
+ VC104: { owasp: "A04:2021", cwe: "CWE-390" },
3177
+ VC105: { owasp: "A04:2021", cwe: "CWE-710" },
3178
+ VC106: { owasp: "A04:2021", cwe: "CWE-710" },
3179
+ VC107: { owasp: "A02:2021", cwe: "CWE-311" },
3180
+ VC108: { owasp: "A01:2021", cwe: "CWE-284" },
3181
+ VC109: { owasp: "A01:2021", cwe: "CWE-284" },
3182
+ VC110: { owasp: "A09:2021", cwe: "CWE-778" },
3183
+ VC111: { owasp: "A05:2021", cwe: "CWE-16" },
3184
+ VC112: { owasp: "A06:2021", cwe: "CWE-1104" },
3185
+ VC113: { owasp: "A05:2021", cwe: "CWE-200" },
3186
+ VC114: { owasp: "A05:2021", cwe: "CWE-16" },
3187
+ VC115: { owasp: "A02:2021", cwe: "CWE-311" },
3188
+ VC116: { owasp: "A05:2021", cwe: "CWE-770" },
3189
+ VC117: { owasp: "A03:2021", cwe: "CWE-22" },
3190
+ VC118: { owasp: "A09:2021", cwe: "CWE-532" },
3191
+ VC119: { owasp: "A07:2021", cwe: "CWE-798" },
3192
+ VC120: { owasp: "A07:2021", cwe: "CWE-352" },
3193
+ VC121: { owasp: "A06:2021", cwe: "CWE-1104" },
3194
+ VC122: { owasp: "A02:2021", cwe: "CWE-326" },
3195
+ VC123: { owasp: "A02:2021", cwe: "CWE-326" },
3196
+ VC124: { owasp: "A02:2021", cwe: "CWE-327" },
3197
+ VC125: { owasp: "A07:2021", cwe: "CWE-640" },
3198
+ VC126: { owasp: "A05:2021", cwe: "CWE-200" },
3199
+ VC127: { owasp: "A01:2021", cwe: "CWE-862" },
3200
+ VC128: { owasp: "A05:2021", cwe: "CWE-444" },
3201
+ VC129: { owasp: "A02:2021", cwe: "CWE-311" },
3202
+ VC130: { owasp: "A07:2021", cwe: "CWE-307" },
3203
+ VC131: { owasp: "A06:2021", cwe: "CWE-1104" }
3170
3204
  };
3171
3205
  var consoleLogProduction = {
3172
3206
  id: "VC097",
@@ -3646,6 +3680,526 @@ var k8sNoResourceLimits = {
3646
3680
  }];
3647
3681
  }
3648
3682
  };
3683
+ var pathTraversal = {
3684
+ id: "VC117",
3685
+ title: "Path Traversal Vulnerability",
3686
+ severity: "critical",
3687
+ category: "Injection",
3688
+ description: "User input is used to construct file paths without sanitization, allowing attackers to read/write arbitrary files (e.g., ../../etc/passwd).",
3689
+ check(content, filePath) {
3690
+ if (isTestFile(filePath)) return [];
3691
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|php|java)$/)) return [];
3692
+ const findings = [];
3693
+ const patterns = [
3694
+ /(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|appendFile|unlink|unlinkSync|access|stat|statSync|existsSync)\s*\(\s*(?:req\.|request\.|ctx\.|params\.|query\.)/gi,
3695
+ /(?:path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|ctx\.|params\.|query\.)[^)]*\)/gi,
3696
+ /(?:res\.sendFile|res\.download)\s*\([^)]*(?:req\.|request\.)/gi,
3697
+ /open\s*\(\s*(?:request\.|params\[|args\.)/gi
3698
+ ];
3699
+ for (const pat of patterns) {
3700
+ let m;
3701
+ while ((m = pat.exec(content)) !== null) {
3702
+ if (isCommentLine(content, m.index)) continue;
3703
+ const surrounding = content.substring(Math.max(0, m.index - 200), m.index + 200);
3704
+ if (/path\.normalize|sanitize|\.replace\(\s*['"]\.\./i.test(surrounding)) continue;
3705
+ const lineNum = content.substring(0, m.index).split("\n").length;
3706
+ findings.push({
3707
+ rule: "VC117",
3708
+ title: pathTraversal.title,
3709
+ severity: "critical",
3710
+ category: "Injection",
3711
+ file: filePath,
3712
+ line: lineNum,
3713
+ snippet: getSnippet(content, lineNum),
3714
+ fix: "Sanitize file paths: use path.normalize(), reject paths containing '..', and validate against an allowed base directory."
3715
+ });
3716
+ }
3717
+ }
3718
+ return findings;
3719
+ }
3720
+ };
3721
+ var piiInLogs = {
3722
+ id: "VC118",
3723
+ title: "Personally Identifiable Information in Logs",
3724
+ severity: "high",
3725
+ category: "Information Leakage",
3726
+ description: "Logging statements that include email addresses, passwords, SSNs, credit card numbers, or other PII can leak sensitive data to log aggregation systems.",
3727
+ check(content, filePath) {
3728
+ if (isTestFile(filePath)) return [];
3729
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
3730
+ const findings = [];
3731
+ const logPattern = /(?:console\.(?:log|info|warn|error|debug)|logger\.(?:info|warn|error|debug|log)|log\.(?:info|warn|error|debug)|logging\.(?:info|warn|error|debug))\s*\([^)]*(?:password|passwd|secret|ssn|social.?security|credit.?card|cardNumber|cvv|token|bearer|authorization)\b/gi;
3732
+ let m;
3733
+ while ((m = logPattern.exec(content)) !== null) {
3734
+ if (isCommentLine(content, m.index)) continue;
3735
+ if (isInsideFixMessage(content, m.index)) continue;
3736
+ const lineNum = content.substring(0, m.index).split("\n").length;
3737
+ findings.push({
3738
+ rule: "VC118",
3739
+ title: piiInLogs.title,
3740
+ severity: "high",
3741
+ category: "Information Leakage",
3742
+ file: filePath,
3743
+ line: lineNum,
3744
+ snippet: getSnippet(content, lineNum),
3745
+ fix: "Never log sensitive data. Redact or mask PII before logging: log('user login', { email: maskEmail(email) })."
3746
+ });
3747
+ }
3748
+ return findings;
3749
+ }
3750
+ };
3751
+ var hardcodedOAuthSecret = {
3752
+ id: "VC119",
3753
+ title: "Hardcoded OAuth Client Secret",
3754
+ severity: "critical",
3755
+ category: "Secrets",
3756
+ description: "OAuth client secrets hardcoded in source code can be extracted and used to impersonate your application.",
3757
+ check(content, filePath) {
3758
+ if (isTestFile(filePath)) return [];
3759
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php|env|json|yaml|yml)$/)) return [];
3760
+ if (/package-lock|yarn\.lock|pnpm-lock|composer\.lock/i.test(filePath)) return [];
3761
+ const findings = [];
3762
+ const patterns = [
3763
+ /client[_-]?secret\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}["']/gi,
3764
+ /GOOGLE_CLIENT_SECRET\s*[:=]\s*["'][^"']{10,}["']/gi,
3765
+ /GITHUB_CLIENT_SECRET\s*[:=]\s*["'][^"']{10,}["']/gi,
3766
+ /FACEBOOK_APP_SECRET\s*[:=]\s*["'][^"']{10,}["']/gi,
3767
+ /AUTH0_CLIENT_SECRET\s*[:=]\s*["'][^"']{10,}["']/gi
3768
+ ];
3769
+ for (const pat of patterns) {
3770
+ let m;
3771
+ while ((m = pat.exec(content)) !== null) {
3772
+ if (isCommentLine(content, m.index)) continue;
3773
+ if (/process\.env|os\.environ|ENV\[|getenv|\$\{|<your|CHANGE_ME|xxx|placeholder/i.test(m[0])) continue;
3774
+ const lineNum = content.substring(0, m.index).split("\n").length;
3775
+ findings.push({
3776
+ rule: "VC119",
3777
+ title: hardcodedOAuthSecret.title,
3778
+ severity: "critical",
3779
+ category: "Secrets",
3780
+ file: filePath,
3781
+ line: lineNum,
3782
+ snippet: getSnippet(content, lineNum),
3783
+ fix: "Move OAuth client secrets to environment variables. Never commit secrets to source control."
3784
+ });
3785
+ }
3786
+ }
3787
+ return findings;
3788
+ }
3789
+ };
3790
+ var missingOAuthState = {
3791
+ id: "VC120",
3792
+ title: "OAuth Flow Missing State Parameter",
3793
+ severity: "high",
3794
+ category: "Authentication",
3795
+ description: "OAuth authorization requests without a state parameter are vulnerable to CSRF attacks, allowing attackers to link their account to a victim's session.",
3796
+ check(content, filePath) {
3797
+ if (isTestFile(filePath)) return [];
3798
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
3799
+ const findings = [];
3800
+ const oauthUrlPattern = /(?:authorize\?|\/oauth\/authorize|\/auth\?|authorization_endpoint)[^}]*(?:client_id|response_type)/gi;
3801
+ let m;
3802
+ while ((m = oauthUrlPattern.exec(content)) !== null) {
3803
+ if (isCommentLine(content, m.index)) continue;
3804
+ const surrounding = content.substring(m.index, Math.min(content.length, m.index + 500));
3805
+ if (/state\s*[=:]/i.test(surrounding)) continue;
3806
+ const lineNum = content.substring(0, m.index).split("\n").length;
3807
+ findings.push({
3808
+ rule: "VC120",
3809
+ title: missingOAuthState.title,
3810
+ severity: "high",
3811
+ category: "Authentication",
3812
+ file: filePath,
3813
+ line: lineNum,
3814
+ snippet: getSnippet(content, lineNum),
3815
+ fix: "Always include a cryptographically random 'state' parameter in OAuth authorization requests and validate it on callback."
3816
+ });
3817
+ }
3818
+ return findings;
3819
+ }
3820
+ };
3821
+ var unpinnedGitHubAction = {
3822
+ id: "VC121",
3823
+ title: "Unpinned GitHub Actions Version",
3824
+ severity: "high",
3825
+ category: "Supply Chain",
3826
+ description: "GitHub Actions using branch references (@main, @master) instead of commit SHAs can be compromised via supply-chain attacks.",
3827
+ check(content, filePath) {
3828
+ if (!filePath.match(/\.github\/workflows\/.*\.(yml|yaml)$/)) return [];
3829
+ const findings = [];
3830
+ const usesPattern = /uses\s*:\s*[\w\-]+\/[\w\-]+@(main|master|dev|develop|latest)\b/gi;
3831
+ let m;
3832
+ while ((m = usesPattern.exec(content)) !== null) {
3833
+ if (isCommentLine(content, m.index)) continue;
3834
+ const lineNum = content.substring(0, m.index).split("\n").length;
3835
+ findings.push({
3836
+ rule: "VC121",
3837
+ title: unpinnedGitHubAction.title,
3838
+ severity: "high",
3839
+ category: "Supply Chain",
3840
+ file: filePath,
3841
+ line: lineNum,
3842
+ snippet: getSnippet(content, lineNum),
3843
+ fix: "Pin GitHub Actions to a specific commit SHA instead of a branch name: uses: owner/action@<full-sha>"
3844
+ });
3845
+ }
3846
+ return findings;
3847
+ }
3848
+ };
3849
+ var deprecatedTLS = {
3850
+ id: "VC122",
3851
+ title: "Deprecated TLS Version Configured",
3852
+ severity: "high",
3853
+ category: "Cryptography",
3854
+ description: "TLS 1.0 and 1.1 are deprecated and have known vulnerabilities. Only TLS 1.2+ should be used.",
3855
+ check(content, filePath) {
3856
+ if (isTestFile(filePath)) return [];
3857
+ if (!filePath.match(/\.(js|ts|py|rb|go|java|yaml|yml|json|conf|cfg|xml)$/)) return [];
3858
+ const findings = [];
3859
+ const patterns = [
3860
+ /(?:TLSv1_METHOD|TLSv1\.0|TLSv1\.1|ssl\.PROTOCOL_TLSv1|SSLv3|SSLv2|TLS_1_0|TLS_1_1|minVersion\s*[:=]\s*["']TLSv1(?:\.1)?["'])/gi,
3861
+ /(?:tls\.DEFAULT_MIN_VERSION|secureProtocol)\s*[:=]\s*["'](?:TLSv1_method|TLSv1_1_method|SSLv3_method)["']/gi,
3862
+ /ssl_protocols\s+.*(?:TLSv1(?:\.1)?(?:\s|;))/gi
3863
+ ];
3864
+ for (const pat of patterns) {
3865
+ let m;
3866
+ while ((m = pat.exec(content)) !== null) {
3867
+ if (isCommentLine(content, m.index)) continue;
3868
+ const lineNum = content.substring(0, m.index).split("\n").length;
3869
+ findings.push({
3870
+ rule: "VC122",
3871
+ title: deprecatedTLS.title,
3872
+ severity: "high",
3873
+ category: "Cryptography",
3874
+ file: filePath,
3875
+ line: lineNum,
3876
+ snippet: getSnippet(content, lineNum),
3877
+ fix: "Use TLS 1.2 or higher. Set minVersion to 'TLSv1.2' and remove support for TLS 1.0/1.1."
3878
+ });
3879
+ }
3880
+ }
3881
+ return findings;
3882
+ }
3883
+ };
3884
+ var weakRSAKeySize = {
3885
+ id: "VC123",
3886
+ title: "Weak RSA Key Size",
3887
+ severity: "high",
3888
+ category: "Cryptography",
3889
+ description: "RSA keys smaller than 2048 bits are considered insecure and can be factored with modern hardware.",
3890
+ check(content, filePath) {
3891
+ if (isTestFile(filePath)) return [];
3892
+ if (!filePath.match(/\.(js|ts|py|rb|go|java|php)$/)) return [];
3893
+ const findings = [];
3894
+ const patterns = [
3895
+ /generateKeyPair\s*\(\s*["']rsa["']\s*,\s*\{[^}]*modulusLength\s*:\s*(512|768|1024)\b/gi,
3896
+ /RSA\.generate\s*\(\s*(512|768|1024)\b/gi,
3897
+ /rsa\.GenerateKey\s*\([^,]*,\s*(512|768|1024)\b/gi,
3898
+ /KeyPairGenerator.*initialize\s*\(\s*(512|768|1024)\b/gi
3899
+ ];
3900
+ for (const pat of patterns) {
3901
+ let m;
3902
+ while ((m = pat.exec(content)) !== null) {
3903
+ if (isCommentLine(content, m.index)) continue;
3904
+ const lineNum = content.substring(0, m.index).split("\n").length;
3905
+ findings.push({
3906
+ rule: "VC123",
3907
+ title: weakRSAKeySize.title,
3908
+ severity: "high",
3909
+ category: "Cryptography",
3910
+ file: filePath,
3911
+ line: lineNum,
3912
+ snippet: getSnippet(content, lineNum),
3913
+ fix: "Use RSA key sizes of at least 2048 bits. 4096 bits is recommended for long-term security."
3914
+ });
3915
+ }
3916
+ }
3917
+ return findings;
3918
+ }
3919
+ };
3920
+ var ecbModeEncryption = {
3921
+ id: "VC124",
3922
+ title: "Insecure ECB Mode Encryption",
3923
+ severity: "high",
3924
+ category: "Cryptography",
3925
+ description: "ECB (Electronic Codebook) mode encrypts identical plaintext blocks to identical ciphertext, leaking patterns in the data.",
3926
+ check(content, filePath) {
3927
+ if (isTestFile(filePath)) return [];
3928
+ if (!filePath.match(/\.(js|ts|py|rb|go|java|php)$/)) return [];
3929
+ const findings = [];
3930
+ const patterns = [
3931
+ /createCipher(?:iv)?\s*\(\s*["'](?:aes-(?:128|192|256)-ecb|des-ecb)["']/gi,
3932
+ /AES\.(?:new|MODE_ECB)|MODE_ECB/gi,
3933
+ /Cipher\.getInstance\s*\(\s*["']AES\/ECB/gi,
3934
+ /aes\.NewCipher|cipher\.NewECBEncrypter/gi
3935
+ ];
3936
+ for (const pat of patterns) {
3937
+ let m;
3938
+ while ((m = pat.exec(content)) !== null) {
3939
+ if (isCommentLine(content, m.index)) continue;
3940
+ const lineNum = content.substring(0, m.index).split("\n").length;
3941
+ findings.push({
3942
+ rule: "VC124",
3943
+ title: ecbModeEncryption.title,
3944
+ severity: "high",
3945
+ category: "Cryptography",
3946
+ file: filePath,
3947
+ line: lineNum,
3948
+ snippet: getSnippet(content, lineNum),
3949
+ fix: "Use AES-GCM or AES-CBC with HMAC instead of ECB mode. GCM provides both encryption and authentication."
3950
+ });
3951
+ }
3952
+ }
3953
+ return findings;
3954
+ }
3955
+ };
3956
+ var insecurePasswordReset = {
3957
+ id: "VC125",
3958
+ title: "Insecure Password Reset Implementation",
3959
+ severity: "high",
3960
+ category: "Authentication",
3961
+ description: "Password reset using predictable tokens, no expiration, or user-enumeration leaks can be exploited to take over accounts.",
3962
+ check(content, filePath) {
3963
+ if (isTestFile(filePath)) return [];
3964
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
3965
+ if (!/(?:reset|forgot).*(?:password|passwd)/i.test(content)) return [];
3966
+ const findings = [];
3967
+ const weakTokens = /(?:reset|forgot).*(?:token|code)\s*[:=].*(?:Date\.now|Math\.random|uuid\(\)|nanoid\(\))/gi;
3968
+ let m;
3969
+ while ((m = weakTokens.exec(content)) !== null) {
3970
+ if (isCommentLine(content, m.index)) continue;
3971
+ const lineNum = content.substring(0, m.index).split("\n").length;
3972
+ findings.push({
3973
+ rule: "VC125",
3974
+ title: insecurePasswordReset.title,
3975
+ severity: "high",
3976
+ category: "Authentication",
3977
+ file: filePath,
3978
+ line: lineNum,
3979
+ snippet: getSnippet(content, lineNum),
3980
+ fix: "Use crypto.randomBytes(32).toString('hex') for reset tokens. Set expiration (15-60 minutes) and single-use enforcement."
3981
+ });
3982
+ }
3983
+ const enumeration = /(?:user|email|account)\s*(?:not\s*found|does\s*not\s*exist|invalid)/gi;
3984
+ while ((m = enumeration.exec(content)) !== null) {
3985
+ if (isCommentLine(content, m.index)) continue;
3986
+ const surrounding = content.substring(Math.max(0, m.index - 500), m.index);
3987
+ if (!/(?:reset|forgot).*password/i.test(surrounding)) continue;
3988
+ const lineNum = content.substring(0, m.index).split("\n").length;
3989
+ findings.push({
3990
+ rule: "VC125",
3991
+ title: insecurePasswordReset.title,
3992
+ severity: "high",
3993
+ category: "Authentication",
3994
+ file: filePath,
3995
+ line: lineNum,
3996
+ snippet: getSnippet(content, lineNum),
3997
+ fix: "Always return the same response regardless of whether the email exists. Say 'If an account exists, a reset link was sent.'"
3998
+ });
3999
+ }
4000
+ return findings;
4001
+ }
4002
+ };
4003
+ var terraformStateExposed = {
4004
+ id: "VC126",
4005
+ title: "Terraform State File Committed",
4006
+ severity: "critical",
4007
+ category: "Secrets",
4008
+ description: "Terraform state files contain sensitive infrastructure details, secrets, and access credentials in plaintext. They must never be committed to version control.",
4009
+ check(content, filePath) {
4010
+ const findings = [];
4011
+ if (/terraform\.tfstate(\.backup)?$/.test(filePath)) {
4012
+ findings.push({
4013
+ rule: "VC126",
4014
+ title: terraformStateExposed.title,
4015
+ severity: "critical",
4016
+ category: "Secrets",
4017
+ file: filePath,
4018
+ line: 1,
4019
+ snippet: getSnippet(content, 1),
4020
+ fix: "Add '*.tfstate' and '*.tfstate.backup' to .gitignore. Use remote state backends (S3, GCS, Terraform Cloud) instead."
4021
+ });
4022
+ }
4023
+ if (filePath.endsWith(".gitignore") && !content.includes("tfstate")) {
4024
+ if (/\.tf$/.test(filePath) || content.includes(".tf")) {
4025
+ }
4026
+ }
4027
+ return findings;
4028
+ }
4029
+ };
4030
+ var insecureHTTPMethods = {
4031
+ id: "VC127",
4032
+ title: "Dangerous HTTP Methods Without Auth",
4033
+ severity: "high",
4034
+ category: "Authorization",
4035
+ description: "DELETE, PUT, and PATCH endpoints without authentication checks allow unauthorized data modification or deletion.",
4036
+ check(content, filePath) {
4037
+ if (isTestFile(filePath)) return [];
4038
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
4039
+ const findings = [];
4040
+ const routePatterns = [
4041
+ /(?:app|router|server)\.(delete|put|patch)\s*\(\s*["']/gi
4042
+ ];
4043
+ for (const pat of routePatterns) {
4044
+ let m;
4045
+ while ((m = pat.exec(content)) !== null) {
4046
+ if (isCommentLine(content, m.index)) continue;
4047
+ const handlerBlock = content.substring(m.index, Math.min(content.length, m.index + 500));
4048
+ if (/auth|authenticate|authorize|requireAuth|isAuthenticated|protect|guard|middleware|session|jwt|verify|clerk|getAuth/i.test(handlerBlock)) continue;
4049
+ const lineNum = content.substring(0, m.index).split("\n").length;
4050
+ findings.push({
4051
+ rule: "VC127",
4052
+ title: insecureHTTPMethods.title,
4053
+ severity: "high",
4054
+ category: "Authorization",
4055
+ file: filePath,
4056
+ line: lineNum,
4057
+ snippet: getSnippet(content, lineNum),
4058
+ fix: "Add authentication middleware to DELETE, PUT, and PATCH routes. Verify the user has permission to modify the resource."
4059
+ });
4060
+ }
4061
+ }
4062
+ return findings;
4063
+ }
4064
+ };
4065
+ var httpRequestSmuggling = {
4066
+ id: "VC128",
4067
+ title: "HTTP Request Smuggling Risk",
4068
+ severity: "high",
4069
+ category: "Configuration",
4070
+ description: "Manually parsing Content-Length or Transfer-Encoding headers can lead to request smuggling when behind a reverse proxy.",
4071
+ check(content, filePath) {
4072
+ if (isTestFile(filePath)) return [];
4073
+ if (!filePath.match(/\.(js|ts|py|rb|go|java|php)$/)) return [];
4074
+ const findings = [];
4075
+ const patterns = [
4076
+ /(?:headers?\[?|getHeader\s*\(\s*)["'](?:content-length|transfer-encoding)["']\s*\]?\s*\)/gi,
4077
+ /parseInt\s*\(\s*(?:req|request)\.headers?\[?\s*["']content-length["']/gi
4078
+ ];
4079
+ for (const pat of patterns) {
4080
+ let m;
4081
+ while ((m = pat.exec(content)) !== null) {
4082
+ if (isCommentLine(content, m.index)) continue;
4083
+ const lineNum = content.substring(0, m.index).split("\n").length;
4084
+ findings.push({
4085
+ rule: "VC128",
4086
+ title: httpRequestSmuggling.title,
4087
+ severity: "high",
4088
+ category: "Configuration",
4089
+ file: filePath,
4090
+ line: lineNum,
4091
+ snippet: getSnippet(content, lineNum),
4092
+ fix: "Let your framework handle Content-Length and Transfer-Encoding headers. Do not manually parse or trust these values."
4093
+ });
4094
+ }
4095
+ }
4096
+ return findings;
4097
+ }
4098
+ };
4099
+ var unencryptedPII = {
4100
+ id: "VC129",
4101
+ title: "Sensitive Data Stored Without Encryption",
4102
+ severity: "high",
4103
+ category: "Information Leakage",
4104
+ description: "Database schemas storing PII (SSN, credit cards, health records) in plaintext violate compliance requirements and expose data in breaches.",
4105
+ check(content, filePath) {
4106
+ if (isTestFile(filePath)) return [];
4107
+ if (!filePath.match(/\.(sql|prisma|py|ts|js|rb)$/)) return [];
4108
+ const findings = [];
4109
+ const schemaPatterns = [
4110
+ /(?:column|field|Column|Field)\s*\(\s*["'](?:ssn|social_security|tax_id|credit_card|card_number|cvv|passport|driver_license|bank_account|routing_number)["']\s*,\s*(?:String|TEXT|VARCHAR|Text|text)/gi,
4111
+ /(?:ssn|social_security|tax_id|credit_card|card_number|cvv|passport_number|driver_license|bank_account|routing_number)\s+(?:TEXT|VARCHAR|String|text|character varying)/gi
4112
+ ];
4113
+ for (const pat of schemaPatterns) {
4114
+ let m;
4115
+ while ((m = pat.exec(content)) !== null) {
4116
+ if (isCommentLine(content, m.index)) continue;
4117
+ const lineNum = content.substring(0, m.index).split("\n").length;
4118
+ findings.push({
4119
+ rule: "VC129",
4120
+ title: unencryptedPII.title,
4121
+ severity: "high",
4122
+ category: "Information Leakage",
4123
+ file: filePath,
4124
+ line: lineNum,
4125
+ snippet: getSnippet(content, lineNum),
4126
+ fix: "Encrypt sensitive fields at the application level before storing. Use field-level encryption with a KMS for SSN, credit cards, and health data."
4127
+ });
4128
+ }
4129
+ }
4130
+ return findings;
4131
+ }
4132
+ };
4133
+ var missingAuthRateLimit = {
4134
+ id: "VC130",
4135
+ title: "Authentication Endpoint Without Rate Limiting",
4136
+ severity: "high",
4137
+ category: "Authentication",
4138
+ description: "Login, registration, and password reset endpoints without rate limiting are vulnerable to credential stuffing and brute-force attacks.",
4139
+ check(content, filePath) {
4140
+ if (isTestFile(filePath)) return [];
4141
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
4142
+ const findings = [];
4143
+ const authRoutePattern = /(?:app|router|server)\.(?:post|put)\s*\(\s*["'](?:\/(?:api\/)?(?:auth\/)?(?:login|signin|sign-in|register|signup|sign-up|forgot-password|reset-password))["']/gi;
4144
+ let m;
4145
+ while ((m = authRoutePattern.exec(content)) !== null) {
4146
+ if (isCommentLine(content, m.index)) continue;
4147
+ const surrounding = content.substring(Math.max(0, m.index - 300), Math.min(content.length, m.index + 300));
4148
+ if (/rateLimit|rateLimiter|throttle|slowDown|express-rate-limit|rate_limit|RateLimiter|limiter/i.test(surrounding)) continue;
4149
+ const lineNum = content.substring(0, m.index).split("\n").length;
4150
+ findings.push({
4151
+ rule: "VC130",
4152
+ title: missingAuthRateLimit.title,
4153
+ severity: "high",
4154
+ category: "Authentication",
4155
+ file: filePath,
4156
+ line: lineNum,
4157
+ snippet: getSnippet(content, lineNum),
4158
+ fix: "Add rate limiting to authentication endpoints. Limit to 5-10 attempts per minute per IP. Use express-rate-limit or similar."
4159
+ });
4160
+ }
4161
+ return findings;
4162
+ }
4163
+ };
4164
+ var vulnerableDependencies = {
4165
+ id: "VC131",
4166
+ title: "Potentially Vulnerable Dependency",
4167
+ severity: "high",
4168
+ category: "Supply Chain",
4169
+ description: "Dependencies with known security issues that are commonly found in AI-generated codebases.",
4170
+ check(content, filePath) {
4171
+ if (!filePath.endsWith("package.json")) return [];
4172
+ if (/node_modules|\.next|dist|build/.test(filePath)) return [];
4173
+ const findings = [];
4174
+ const vulnerablePackages = [
4175
+ [/"jsonwebtoken"\s*:\s*"[\^~]?[0-7]\./g, "jsonwebtoken < 8.x has signature bypass vulnerabilities"],
4176
+ [/"lodash"\s*:\s*"[\^~]?[0-3]\./g, "lodash < 4.x has prototype pollution vulnerabilities"],
4177
+ [/"minimist"\s*:\s*"[\^~]?[01]\.[01]\./g, "minimist < 1.2.6 has prototype pollution"],
4178
+ [/"node-fetch"\s*:\s*"[\^~]?[12]\./g, "node-fetch < 3.x has data exposure issues"],
4179
+ [/"express"\s*:\s*"[\^~]?[0-3]\./g, "express < 4.x has multiple known vulnerabilities"],
4180
+ [/"axios"\s*:\s*"[\^~]?0\.[0-9]\./g, "axios < 0.21 has SSRF vulnerabilities"],
4181
+ [/"tar"\s*:\s*"[\^~]?[0-5]\./g, "tar < 6.x has path traversal vulnerabilities"],
4182
+ [/"glob-parent"\s*:\s*"[\^~]?[0-4]\./g, "glob-parent < 5.1.2 has ReDoS vulnerability"]
4183
+ ];
4184
+ for (const [pat, message] of vulnerablePackages) {
4185
+ let m;
4186
+ while ((m = pat.exec(content)) !== null) {
4187
+ const lineNum = content.substring(0, m.index).split("\n").length;
4188
+ findings.push({
4189
+ rule: "VC131",
4190
+ title: vulnerableDependencies.title,
4191
+ severity: "high",
4192
+ category: "Supply Chain",
4193
+ file: filePath,
4194
+ line: lineNum,
4195
+ snippet: getSnippet(content, lineNum),
4196
+ fix: `${message}. Update to the latest version and run 'npm audit' regularly.`
4197
+ });
4198
+ }
4199
+ }
4200
+ return findings;
4201
+ }
4202
+ };
3649
4203
  var freeRules = [
3650
4204
  hardcodedSecrets,
3651
4205
  // VC001
@@ -3824,7 +4378,37 @@ var proRules = [
3824
4378
  dockerCopySensitive,
3825
4379
  dockerTooManyPorts,
3826
4380
  k8sSecretNotEncrypted,
3827
- k8sNoResourceLimits
4381
+ k8sNoResourceLimits,
4382
+ pathTraversal,
4383
+ // VC117
4384
+ piiInLogs,
4385
+ // VC118
4386
+ hardcodedOAuthSecret,
4387
+ // VC119
4388
+ missingOAuthState,
4389
+ // VC120
4390
+ unpinnedGitHubAction,
4391
+ // VC121
4392
+ deprecatedTLS,
4393
+ // VC122
4394
+ weakRSAKeySize,
4395
+ // VC123
4396
+ ecbModeEncryption,
4397
+ // VC124
4398
+ insecurePasswordReset,
4399
+ // VC125
4400
+ terraformStateExposed,
4401
+ // VC126
4402
+ insecureHTTPMethods,
4403
+ // VC127
4404
+ httpRequestSmuggling,
4405
+ // VC128
4406
+ unencryptedPII,
4407
+ // VC129
4408
+ missingAuthRateLimit,
4409
+ // VC130
4410
+ vulnerableDependencies
4411
+ // VC131
3828
4412
  ];
3829
4413
  function runCustomRules(content, filePath, disabledRules = [], tier = "free") {
3830
4414
  const findings = [];
@@ -5833,7 +6417,7 @@ Open this URL in your browser to log in:`));
5833
6417
  var program = new Command();
5834
6418
  program.name("xploitscan").description(
5835
6419
  "AI security scanner for vibe-coded apps. Find vulnerabilities before attackers do."
5836
- ).version("0.3.0");
6420
+ ).version("0.7.0");
5837
6421
  program.command("scan").description("Scan a directory for security vulnerabilities").argument("[directory]", "Directory to scan", ".").option("--no-ai", "Skip AI-powered analysis").option("-f, --format <format>", "Output format: terminal, json, sarif", "terminal").option("-v, --verbose", "Show detailed output", false).option("--diff [base]", "Scan only files changed vs base branch (default: main)").option("-w, --watch", "Watch for file changes and re-scan automatically", false).action(async (directory, opts) => {
5838
6422
  await scanCommand(directory, {
5839
6423
  directory,
@@ -5849,7 +6433,7 @@ auth.command("login").description("Log in to your XploitScan account").action(lo
5849
6433
  auth.command("logout").description("Log out of your XploitScan account").action(logoutCommand);
5850
6434
  auth.command("whoami").description("Show current logged-in user").action(whoamiCommand);
5851
6435
  program.command("upgrade").description("Upgrade to XploitScan Pro for unlimited scans").action(async () => {
5852
- const { getStoredToken: getStoredToken2, getCheckoutUrl } = await import("./api-QTNXZY4O.js");
6436
+ const { getStoredToken: getStoredToken2, getCheckoutUrl } = await import("./api-Z7VNGPT2.js");
5853
6437
  const chalk4 = (await import("chalk")).default;
5854
6438
  const token = getStoredToken2();
5855
6439
  if (!token) {
@@ -5862,7 +6446,7 @@ program.command("upgrade").description("Upgrade to XploitScan Pro for unlimited
5862
6446
  console.log(chalk4.green(`
5863
6447
  Open this URL to upgrade:`));
5864
6448
  console.log(chalk4.bold.underline(url));
5865
- const { execFile: execFile4 } = __require("child_process");
6449
+ const { execFile: execFile4 } = await import("child_process");
5866
6450
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
5867
6451
  execFile4(openCmd, [url], () => {
5868
6452
  });