wormclaude 1.0.181 → 1.0.183

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,8 +41,127 @@ 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
+ }
144
+ // İstek başlıklarına kullanıcı oturumunu (auth) birleştir. Sunucunun spec'i temel; auth ÜSTE biner
145
+ // (Cookie + Authorization), böylece her istek login-arkası koşar. Sunucu zaten Cookie/Authorization
146
+ // koyduysa kullanıcınınki kazanır (gerçek oturum). allowed-host SSRF guard'ı değişmez.
147
+ export function _mergeAuth(reqHeaders, auth) {
148
+ if (!auth || (!auth.cookie && !auth.authHeader && !auth.headers))
149
+ return reqHeaders;
150
+ const h = { ...(reqHeaders || {}) };
151
+ if (auth.cookie) {
152
+ const existing = h['Cookie'] || h['cookie'];
153
+ h['Cookie'] = existing ? `${existing}; ${auth.cookie}` : auth.cookie;
154
+ delete h['cookie'];
155
+ }
156
+ if (auth.authHeader)
157
+ h['Authorization'] = auth.authHeader;
158
+ if (auth.headers)
159
+ for (const [k, v] of Object.entries(auth.headers))
160
+ h[k] = v;
161
+ return h;
162
+ }
44
163
  // Tek bir istek-spec'ini yerelde çalıştır. allowed dışı host → atlanır (SSRF koruması).
45
- async function execReq(req, allowed) {
164
+ async function execReq(req, allowed, auth) {
46
165
  const t0 = Date.now();
47
166
  const h = hostOf(req.url);
48
167
  if (!allowed.has(h)) {
@@ -51,7 +170,7 @@ async function execReq(req, allowed) {
51
170
  try {
52
171
  const r = await fetch(req.url, {
53
172
  method: req.method || 'GET',
54
- headers: req.headers,
173
+ headers: _mergeAuth(req.headers, auth),
55
174
  body: req.body,
56
175
  redirect: req.follow === false ? 'manual' : 'follow', // open-redirect vb. için Location'ı görmek
57
176
  signal: AbortSignal.timeout(Math.max(3, req.timeout || 20) * 1000),
@@ -71,8 +190,10 @@ async function execReq(req, allowed) {
71
190
  }
72
191
  }
73
192
  // Tam akış: plan al → her round'da istekleri yerelde at → /exec ile analiz ettir → done'a dek döngü.
74
- export async function runPentest(config, tool, target, scopeAck, log) {
75
- const plan = await postJson(config, '/pentest/plan', { tool, target, scope_ack: scopeAck }, 15000);
193
+ export async function runPentest(config, tool, target, scopeAck, log, auth) {
194
+ const _authed = !!(auth && (auth.cookie || auth.authHeader || auth.headers));
195
+ // authenticated: SUNUCUYA SADECE BAYRAK gider (login-arkası crawl/POST planlasın diye) — sır/cookie DEĞİL.
196
+ const plan = await postJson(config, '/pentest/plan', { tool, target, scope_ack: scopeAck, authenticated: _authed }, 15000);
76
197
  if (!plan.ok)
77
198
  return { ok: false, reason: plan.reason, plan };
78
199
  const allowed = new Set((plan.allowed_hosts || [plan.host || '']).map((x) => x.toLowerCase()));
@@ -86,7 +207,7 @@ export async function runPentest(config, tool, target, scopeAck, log) {
86
207
  const CONC = 6;
87
208
  for (let i = 0; i < requests.length; i += CONC) {
88
209
  const batch = requests.slice(i, i + CONC);
89
- const rs = await Promise.all(batch.map((req) => execReq(req, allowed)));
210
+ const rs = await Promise.all(batch.map((req) => execReq(req, allowed, auth)));
90
211
  responses.push(...rs);
91
212
  }
92
213
  report = await postJson(config, '/pentest/exec', { job_id: plan.job_id, responses }, 30000);
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.181';
19
+ export const VERSION = '1.0.183';
package/dist/tools.js CHANGED
@@ -226,12 +226,17 @@ export const toolSchemas = [
226
226
  type: 'function',
227
227
  function: {
228
228
  name: 'SecurityScan',
229
- description: "Run an AUTHORIZED security scan with WormClaude's own server-side engine (no external tools, no manual payloads). Use THIS — never shell/curl/Invoke-WebRequest — whenever the user asks to scan/test a target (\"tara\", \"scan\", \"xss tara\", \"sqli\", \"recon\", \"açık bul\", \"zafiyet tara\", \"tam kapsamlı tara\"). For a FULL/comprehensive scan use tool='full' ONCE — it deterministically runs recon+XSS+SQLi in code and returns ONE consolidated report (do NOT chain recon/xss/sqli yourself, that loops). For a single check use recon/xss/sqli. For xss/sqli give a PARAMETERIZED url; for recon/full give a domain. Requires authorized-researcher level (3+); traffic exits the user's IP — only authorized targets.",
229
+ description: "Run an AUTHORIZED security scan with WormClaude's own server-side engine (no external tools, no manual payloads). Use THIS — never shell/curl/Invoke-WebRequest — whenever the user asks to scan/test a target (\"tara\", \"scan\", \"xss tara\", \"sqli\", \"recon\", \"açık bul\", \"zafiyet tara\", \"tam kapsamlı tara\"). For a FULL/comprehensive scan use tool='full' ONCE — it deterministically runs recon+XSS+SQLi in code and returns ONE consolidated report (do NOT chain recon/xss/sqli yourself, that loops). For a single check use recon/xss/sqli. For xss/sqli give a PARAMETERIZED url; for recon/full give a domain. For AUTHENTICATED / behind-login scanning, pass the user's session via `cookie` (and/or `auth_header`) — every request is then sent logged-in, so pages and parameters behind the login are reached. Requires authorized-researcher level (3+); traffic exits the user's IP — only authorized targets.",
230
230
  parameters: {
231
231
  type: 'object',
232
232
  properties: {
233
233
  tool: { type: 'string', enum: ['full', 'recon', 'scan', 'xss', 'sqli', 'dirscan'], description: 'full → tam kapsamlı (recon+xss+sqli tek seferde, kapsamlı tarama için BUNU kullan); recon/scan/dirscan → domain; xss/sqli → parameterized URL' },
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
+ 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
+ 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).' },
235
240
  },
236
241
  required: ['tool', 'target'],
237
242
  },
@@ -737,10 +742,35 @@ async function execOne(call, hooks) {
737
742
  }
738
743
  if (tool === 'scan')
739
744
  tool = 'recon'; // "scan" genel tarama → motorun bildiği "recon" (slash ile aynı)
745
+ // AUTHENTICATED / login-arkası: kullanıcı session cookie / auth header verdiyse her isteğe enjekte
746
+ // edilir (sır SUNUCUYA gitmez, sadece hedefe kullanıcı IP'sinden). Cache key'e auth-parmakizi ekle ki
747
+ // anonim tarama sonucu authenticated tarama için (veya tersi) yeniden kullanılmasın.
748
+ let _cookie = String(args?.cookie || '').trim();
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
+ }
768
+ const _auth = (_cookie || _authHeader) ? { cookie: _cookie || undefined, authHeader: _authHeader || undefined } : undefined;
769
+ const _authTag = _auth ? '|auth' : '';
740
770
  // FULL: tam kapsamlı tarama TEK ÇAĞRIDA — model orkestrasyona girmesin (döngü olur). Kod
741
771
  // deterministik olarak recon → xss → sqli koşar, TEK konsolide rapor döner.
742
772
  if (tool === 'full' || tool === 'fullscan') {
743
- const _fk = 'full|' + target.toLowerCase();
773
+ const _fk = 'full|' + target.toLowerCase() + _authTag;
744
774
  const _fc = _scanCache.get(_fk);
745
775
  if (_fc && Date.now() - _fc.t < 120000) {
746
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.` };
@@ -748,14 +778,14 @@ async function execOne(call, hooks) {
748
778
  try {
749
779
  const { runPentest } = await import('./pentest.js');
750
780
  const all = [];
751
- let rpt = `Tam kapsamlı tarama — ${target}\n`;
781
+ let rpt = `Tam kapsamlı tarama — ${target}${_auth ? ' (authenticated / login-arkası — oturum enjekte edildi)' : ''}${_loginNote}\n`;
752
782
  for (const _t of ['recon', 'xss', 'sqli']) {
753
783
  if (hooks?.signal?.aborted) {
754
784
  rpt += `\n[STOPPED] kullanıcı durdurdu (Esc) — kalan taramalar atlandı.`;
755
785
  break;
756
786
  }
757
787
  try {
758
- const o = await runPentest(cfg(), _t, target, true, () => { });
788
+ const o = await runPentest(cfg(), _t, target, true, () => { }, _auth);
759
789
  if (o?.ok) {
760
790
  const ff = o.report?.findings || [];
761
791
  all.push(...ff.map((x) => ({ ...x, scan: _t })));
@@ -782,7 +812,7 @@ async function execOne(call, hooks) {
782
812
  _scanCache.set(_fk, { out: rpt, t: _now });
783
813
  // Alt tarama tiplerini de cache'le → full sonrası model tek tek recon/xss/sqli çağıramaz (gereksiz tekrar).
784
814
  for (const _t of ['recon', 'xss', 'sqli'])
785
- _scanCache.set(_t + '|' + target.toLowerCase(), { out: rpt, t: _now });
815
+ _scanCache.set(_t + '|' + target.toLowerCase() + _authTag, { out: rpt, t: _now });
786
816
  return { ok: true, output: rpt, args };
787
817
  }
788
818
  catch (e) {
@@ -790,7 +820,7 @@ async function execOne(call, hooks) {
790
820
  }
791
821
  }
792
822
  // DÖNGÜ-KIRICI: aynı tarama 2 dk içinde tekrar → yeniden koşma, önceki sonuç + "yeniden tarama" notu.
793
- const _ck = tool + '|' + target.toLowerCase();
823
+ const _ck = tool + '|' + target.toLowerCase() + _authTag;
794
824
  const _cached = _scanCache.get(_ck);
795
825
  if (_cached && Date.now() - _cached.t < 120000) {
796
826
  // SADECE kısa DUR notu — bulguları TEKRAR verme (yoksa model onları yeniden yazıp tekrar çağırır).
@@ -798,7 +828,7 @@ async function execOne(call, hooks) {
798
828
  }
799
829
  try {
800
830
  const { runPentest } = await import('./pentest.js');
801
- const out = await runPentest(cfg(), tool, target, true, () => { });
831
+ const out = await runPentest(cfg(), tool, target, true, () => { }, _auth);
802
832
  if (!out?.ok) {
803
833
  const reason = out?.reason || out?.plan?.reason || 'error';
804
834
  const tip = reason === 'trust_required' ? ' (Doğrulanmış Araştırmacı / seviye 3+ gerekir: /dogrula)'
@@ -807,7 +837,7 @@ async function execOne(call, hooks) {
807
837
  }
808
838
  const rep = out.report || {};
809
839
  const f = rep.findings || [];
810
- let txt = `Tarama tamam (${tool}) — ${rep.target || target}\n`;
840
+ let txt = `Tarama tamam (${tool}) — ${rep.target || target}${_loginNote}\n`;
811
841
  if (!f.length)
812
842
  txt += 'Zafiyet bulunamadı (hedef temiz ya da yanıt vermedi).';
813
843
  else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.181",
3
+ "version": "1.0.183",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {