wormclaude 1.0.182 → 1.0.184

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/pentest.js CHANGED
@@ -41,6 +41,106 @@ function hostOf(u) {
41
41
  return '';
42
42
  }
43
43
  }
44
+ // ── FAZ 4c: LOGIN OTOMASYONU (cred → session cookie; tamamen istemci-tarafı, sır sunucuya gitmez) ──
45
+ function _setCookies(res) {
46
+ // Node 18.14+ getSetCookie() tüm Set-Cookie satırlarını verir; yoksa tek başlığa düş.
47
+ try {
48
+ const f = res.headers.getSetCookie;
49
+ if (typeof f === 'function')
50
+ return f.call(res.headers) || [];
51
+ }
52
+ catch { }
53
+ const one = res.headers.get('set-cookie');
54
+ return one ? [one] : [];
55
+ }
56
+ function _mergeJar(jar, setCookies) {
57
+ for (const sc of setCookies) {
58
+ const kv = sc.split(';', 1)[0];
59
+ const eq = kv.indexOf('=');
60
+ if (eq > 0)
61
+ jar[kv.slice(0, eq).trim()] = kv.slice(eq + 1).trim();
62
+ }
63
+ return jar;
64
+ }
65
+ function _cookieHeader(jar) {
66
+ return Object.entries(jar).map(([k, v]) => `${k}=${v}`).join('; ');
67
+ }
68
+ function _attrOf(s, key) {
69
+ const m = new RegExp(key + '\\s*=\\s*["\']([^"\']*)', 'i').exec(s);
70
+ return m ? m[1] : '';
71
+ }
72
+ // Login formunu bul: type=password içeren <form>. userField = ilk görünür text/email input; passField =
73
+ // password input; hidden/csrf alanları gerçek değeriyle korunur.
74
+ function _findLoginForm(html, baseUrl) {
75
+ for (const m of html.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/gi)) {
76
+ const attrs = m[1], inner = m[2];
77
+ if (!/<input\b[^>]*type\s*=\s*["']?password/i.test(inner))
78
+ continue;
79
+ let passField = '', userField = '', hidden = {};
80
+ for (const t of inner.matchAll(/<(?:input|select|textarea)\b([^>]*)>/gi)) {
81
+ const ia = t[1];
82
+ const name = _attrOf(ia, 'name');
83
+ if (!name)
84
+ continue;
85
+ const typ = (_attrOf(ia, 'type') || 'text').toLowerCase();
86
+ if (typ === 'password')
87
+ passField = name;
88
+ else if (typ === 'hidden')
89
+ hidden[name] = _attrOf(ia, 'value');
90
+ else if (!userField && ['text', 'email', 'tel', 'username'].includes(typ))
91
+ userField = name;
92
+ }
93
+ if (!passField)
94
+ continue;
95
+ if (!userField)
96
+ userField = 'username';
97
+ let action = baseUrl;
98
+ try {
99
+ action = new URL(_attrOf(attrs, 'action') || baseUrl, baseUrl).toString();
100
+ }
101
+ catch { }
102
+ const method = (_attrOf(attrs, 'method') || 'post').toLowerCase();
103
+ return { action, method, userField, passField, hidden };
104
+ }
105
+ return null;
106
+ }
107
+ /** Otomatik login: login sayfasını çek → formu bul (CSRF korunur) → cred ile POST → Set-Cookie yakala
108
+ * → session cookie string döndür. Trafik kullanıcı IP'sinden; cookie/cred SUNUCUYA GİTMEZ. */
109
+ export async function autoLogin(loginUrl, username, password, extra) {
110
+ let jar = {};
111
+ try {
112
+ const r0 = await fetch(loginUrl, { headers: { 'User-Agent': 'Mozilla/5.0' }, redirect: 'follow', signal: AbortSignal.timeout(20000) });
113
+ _mergeJar(jar, _setCookies(r0));
114
+ const html = await r0.text();
115
+ const form = _findLoginForm(html, r0.url || loginUrl);
116
+ if (!form)
117
+ return { ok: false, cookie: '', note: 'Login formu bulunamadı (password alanı yok). login_url doğru mu?' };
118
+ const data = { ...form.hidden, [form.userField]: username, [form.passField]: password, ...(extra || {}) };
119
+ const r1 = await fetch(form.action, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0', ...(Object.keys(jar).length ? { Cookie: _cookieHeader(jar) } : {}) },
122
+ body: new URLSearchParams(data).toString(),
123
+ redirect: 'manual', signal: AbortSignal.timeout(20000),
124
+ });
125
+ _mergeJar(jar, _setCookies(r1));
126
+ // login sonrası 30x yönlendirme genelde asıl session cookie'sini set eder → bir adım takip et
127
+ const loc = r1.headers.get('location');
128
+ if (loc && [301, 302, 303, 307, 308].includes(r1.status)) {
129
+ try {
130
+ const nu = new URL(loc, form.action).toString();
131
+ const r2 = await fetch(nu, { headers: { 'User-Agent': 'Mozilla/5.0', ...(Object.keys(jar).length ? { Cookie: _cookieHeader(jar) } : {}) }, redirect: 'manual', signal: AbortSignal.timeout(15000) });
132
+ _mergeJar(jar, _setCookies(r2));
133
+ }
134
+ catch { }
135
+ }
136
+ if (!Object.keys(jar).length)
137
+ return { ok: false, cookie: '', note: 'Login denendi ama Set-Cookie gelmedi (kimlik hatalı ya da JS/token tabanlı login olabilir).' };
138
+ return { ok: true, cookie: _cookieHeader(jar), note: `Login OK — ${Object.keys(jar).length} cookie alındı (oturumlu tarama).` };
139
+ }
140
+ catch (e) {
141
+ return { ok: false, cookie: '', note: 'Login hatası: ' + (e?.name === 'TimeoutError' ? 'zaman aşımı' : (e?.message || e)) };
142
+ }
143
+ }
44
144
  // İstek başlıklarına kullanıcı oturumunu (auth) birleştir. Sunucunun spec'i temel; auth ÜSTE biner
45
145
  // (Cookie + Authorization), böylece her istek login-arkası koşar. Sunucu zaten Cookie/Authorization
46
146
  // koyduysa kullanıcınınki kazanır (gerçek oturum). allowed-host SSRF guard'ı değişmez.
package/dist/theme.js CHANGED
@@ -16,4 +16,4 @@ export const theme = {
16
16
  synType: '#a78bfa', // tip/sınıf adları, sabitler
17
17
  synProp: '#e0e0e0', // özellik/anahtar adları
18
18
  };
19
- export const VERSION = '1.0.182';
19
+ export const VERSION = '1.0.184';
package/dist/tools.js CHANGED
@@ -234,6 +234,9 @@ export const toolSchemas = [
234
234
  target: { type: 'string', description: 'Domain for recon/scan/dirscan (example.com) OR parameterized URL for xss/sqli (https://example.com/p?id=1)' },
235
235
  cookie: { type: 'string', description: 'Optional. Session cookie for AUTHENTICATED / behind-login scanning, e.g. "sessionid=abc123; csrftoken=xyz". The user copies it from their logged-in browser (DevTools → Application → Cookies, or the Cookie request header). Sent only to the target from the user IP — never stored on the server. Provide it whenever the user wants to scan pages behind a login.' },
236
236
  auth_header: { type: 'string', description: 'Optional. Authorization header value for token-based authenticated scanning, e.g. "Bearer eyJhbGci..." or "Basic dXNlcjpwYXNz". Use instead of / together with cookie when the site authenticates via a token header.' },
237
+ login_url: { type: 'string', description: 'Optional. URL of the login PAGE for AUTO-LOGIN. When the user gives credentials instead of a cookie, set this + username + password; the scanner fetches the login form, submits the credentials (preserving CSRF), captures the session cookie, and scans logged-in. Login happens from the user IP; credentials/cookie never reach the server.' },
238
+ username: { type: 'string', description: 'Optional. Username/email for auto-login (use with login_url + password).' },
239
+ password: { type: 'string', description: 'Optional. Password for auto-login (use with login_url + username).' },
237
240
  },
238
241
  required: ['tool', 'target'],
239
242
  },
@@ -742,8 +745,26 @@ async function execOne(call, hooks) {
742
745
  // AUTHENTICATED / login-arkası: kullanıcı session cookie / auth header verdiyse her isteğe enjekte
743
746
  // edilir (sır SUNUCUYA gitmez, sadece hedefe kullanıcı IP'sinden). Cache key'e auth-parmakizi ekle ki
744
747
  // anonim tarama sonucu authenticated tarama için (veya tersi) yeniden kullanılmasın.
745
- const _cookie = String(args?.cookie || '').trim();
748
+ let _cookie = String(args?.cookie || '').trim();
746
749
  const _authHeader = String(args?.auth_header || '').trim();
750
+ // FAZ 4c: login_url + username + password verildiyse OTOMATİK login ol → session cookie'yi al
751
+ // (manuel cookie kopyalamaya gerek yok). İstemci-tarafı; cred/cookie sunucuya gitmez.
752
+ const _loginUrl = String(args?.login_url || '').trim();
753
+ const _username = String(args?.username || '').trim();
754
+ const _password = String(args?.password || '');
755
+ let _loginNote = '';
756
+ if (_loginUrl && _username && _password && !_cookie) {
757
+ try {
758
+ const { autoLogin } = await import('./pentest.js');
759
+ const lr = await autoLogin(_loginUrl, _username, _password);
760
+ _loginNote = `\n[login] ${lr.note}`;
761
+ if (lr.ok && lr.cookie)
762
+ _cookie = lr.cookie;
763
+ }
764
+ catch (e) {
765
+ _loginNote = `\n[login] hata: ${e?.message || e}`;
766
+ }
767
+ }
747
768
  const _auth = (_cookie || _authHeader) ? { cookie: _cookie || undefined, authHeader: _authHeader || undefined } : undefined;
748
769
  const _authTag = _auth ? '|auth' : '';
749
770
  // FULL: tam kapsamlı tarama TEK ÇAĞRIDA — model orkestrasyona girmesin (döngü olur). Kod
@@ -755,10 +776,22 @@ async function execOne(call, hooks) {
755
776
  return { ok: false, args, output: `[ALREADY-SCANNED] full scan "${target}" already ran — findings are above. Do NOT call SecurityScan again; summarize the results in text.` };
756
777
  }
757
778
  try {
758
- const { runPentest } = await import('./pentest.js');
779
+ const { runPentest, fetchPtTools } = await import('./pentest.js');
759
780
  const all = [];
760
- let rpt = `Tam kapsamlı tarama — ${target}${_auth ? ' (authenticated / login-arkası oturum enjekte edildi)' : ''}\n`;
761
- for (const _t of ['recon', 'xss', 'sqli']) {
781
+ // PROFESYONEL KAPSAM: çekirdek (recon+dirscan+xss+sqli) + sunucudaki TÜM şablon tarayıcıları
782
+ // (SSRF/XXE/SSTI/LFI/LDAP/open-redirect/CRLF/graphql/exposed-files/waf-detect …). /fullscan ile aynı.
783
+ const _steps = ['recon', 'dirscan', 'xss', 'sqli'];
784
+ try {
785
+ const _pt = await fetchPtTools(cfg());
786
+ for (const tm of ((_pt && _pt.templates) || [])) {
787
+ if (tm && tm.id && !_steps.includes(String(tm.id)))
788
+ _steps.push(String(tm.id));
789
+ }
790
+ }
791
+ catch { }
792
+ let rpt = `Tam kapsamlı tarama — ${target}${_auth ? ' (authenticated / login-arkası — oturum enjekte edildi)' : ''}${_loginNote}\n${_steps.length} tarayıcı koşuluyor (recon+dirscan+xss+sqli + şablonlar).\n`;
793
+ let _wafSeen = false;
794
+ for (const _t of _steps) {
762
795
  if (hooks?.signal?.aborted) {
763
796
  rpt += `\n[STOPPED] kullanıcı durdurdu (Esc) — kalan taramalar atlandı.`;
764
797
  break;
@@ -768,29 +801,43 @@ async function execOne(call, hooks) {
768
801
  if (o?.ok) {
769
802
  const ff = o.report?.findings || [];
770
803
  all.push(...ff.map((x) => ({ ...x, scan: _t })));
771
- rpt += `\n[${_t}] ${ff.length ? ff.length + ' bulgu' : 'temiz'}` + (o.report?.target ? ` (${o.report.target})` : '');
804
+ if (ff.some((x) => /waf|firewall|cloudflare|akamai|imperva/i.test(JSON.stringify(x))))
805
+ _wafSeen = true;
806
+ rpt += `\n[${_t}] ${ff.length ? ff.length + ' bulgu' : 'temiz'}`;
772
807
  }
773
808
  else {
774
809
  const rsn = o?.reason || o?.plan?.reason || 'error';
775
- rpt += `\n[${_t}] çalışmadı: ${rsn}`;
776
810
  if (rsn === 'trust_required') {
777
- rpt += ' — Doğrulanmış Araştırmacı (seviye 3+) gerekir.';
811
+ rpt += `\n[${_t}] çalışmadı: ${rsn} — Doğrulanmış Araştırmacı (seviye 3+) gerekir.`;
778
812
  break;
779
813
  }
814
+ rpt += `\n[${_t}] ${rsn === 'invalid_target' ? 'atlandı (param yok)' : 'çalışmadı: ' + rsn}`;
780
815
  }
781
816
  }
782
817
  catch (e) {
783
818
  rpt += `\n[${_t}] hata: ${e?.message || e}`;
784
819
  }
785
820
  }
786
- rpt += `\n\nToplam ${all.length} bulgu.`;
821
+ // no_params spam'ini tekille (her inject tarayıcı bir tane ekliyor)
822
+ const _np = all.filter((f) => f.note === 'no_params');
823
+ for (let i = all.length - 1; i >= 0; i--)
824
+ if (all[i].note === 'no_params')
825
+ all.splice(i, 1);
826
+ if (_np.length)
827
+ all.push({ type: 'info', severity: 'info', url: target, note: 'no_params',
828
+ evidence: `${_np.length} enjeksiyon tarayıcısı test edilebilir GET parametresi bulamadı — girdiler POST form/login-arkası/JS-üretimli olabilir.` });
829
+ const _sev = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
830
+ all.sort((a, b) => (_sev[a.severity] ?? 9) - (_sev[b.severity] ?? 9));
831
+ if (_wafSeen || /cloudflare|akamai|imperva/i.test(rpt)) {
832
+ rpt += `\n\n⚠️ WAF/CDN tespit edildi — payload'lar filtrelenmiş olabilir; "temiz" sonuçlar KESİN değil. Kesin sonuç için origin sunucuya (CDN arkası) doğrudan tara.`;
833
+ }
834
+ rpt += `\n\nToplam ${all.length} bulgu (${_steps.length} tarayıcı).`;
787
835
  if (all.length)
788
- rpt += '\n' + all.slice(0, 60).map((x) => ' ' + JSON.stringify(x)).join('\n');
789
- rpt += `\n[full-done] recon+xss+sqli all ran. Do NOT call SecurityScan again — report the findings to the user in text (summary + prioritized fixes), in their language.`;
836
+ rpt += '\n' + all.slice(0, 80).map((x) => ' ' + JSON.stringify(x)).join('\n');
837
+ rpt += `\n[full-done] comprehensive scan (${_steps.length} scanners) ran. Do NOT call SecurityScan again — report the findings to the user in text (severity-ordered summary + prioritized fixes), in their language.`;
790
838
  const _now = Date.now();
791
839
  _scanCache.set(_fk, { out: rpt, t: _now });
792
- // Alt tarama tiplerini de cache'le → full sonrası model tek tek recon/xss/sqli çağıramaz (gereksiz tekrar).
793
- for (const _t of ['recon', 'xss', 'sqli'])
840
+ for (const _t of ['recon', 'xss', 'sqli', 'dirscan'])
794
841
  _scanCache.set(_t + '|' + target.toLowerCase() + _authTag, { out: rpt, t: _now });
795
842
  return { ok: true, output: rpt, args };
796
843
  }
@@ -816,7 +863,7 @@ async function execOne(call, hooks) {
816
863
  }
817
864
  const rep = out.report || {};
818
865
  const f = rep.findings || [];
819
- let txt = `Tarama tamam (${tool}) — ${rep.target || target}\n`;
866
+ let txt = `Tarama tamam (${tool}) — ${rep.target || target}${_loginNote}\n`;
820
867
  if (!f.length)
821
868
  txt += 'Zafiyet bulunamadı (hedef temiz ya da yanıt vermedi).';
822
869
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.182",
3
+ "version": "1.0.184",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {