wormclaude 1.0.144 → 1.0.146

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/cmdsec.js CHANGED
@@ -167,6 +167,23 @@ const DANGER = [
167
167
  { re: /\bmv\s+[^\n]*\s+\/dev\/null\b/, reason: 'Veriyi /dev/null\'a taşıma' },
168
168
  { re: />\s*\/dev\/null\s+2>&1\s*;\s*rm/, reason: 'Gizli silme' },
169
169
  ];
170
+ // ── Windows tehlikeli komutlar (HARD DENY) — kullanıcı Windows'ta ──────────
171
+ // Yalnız SİSTEM / sürücü-kökü hedefleri engellenir; proje altı (C:\Users\...\build)
172
+ // silmeleri meşrudur → onay akışına düşer, hard-deny EDİLMEZ.
173
+ const WIN_SYS = '(?:[a-zA-Z]:\\\\?(?:windows\\b|program\\s?files(?:\\s?\\(x86\\))?\\b|programdata\\b)' + // C:\Windows, C:\Program Files, C:\ProgramData
174
+ '|[a-zA-Z]:\\\\?(?:["\'\\s]|$)' + // C:\ (sürücü kökü)
175
+ '|system32\\b|%(?:systemroot|windir|systemdrive)%|\\$env:(?:windir|systemroot|systemdrive))';
176
+ const WIN_DANGER = [
177
+ // del / erase / rd / rmdir /s → sistem ya da sürücü kökü
178
+ { re: new RegExp('\\b(?:del|erase|rd|rmdir)\\b(?=[\\s\\S]*\\s/s\\b)[\\s\\S]*?' + WIN_SYS, 'i'),
179
+ reason: 'Windows sistem/sürücü kökünde özyinelemeli silme (del/rd /s)' },
180
+ // Remove-Item (alias ri/rm/del) -Recurse [-Force] → sistem ya da sürücü kökü
181
+ { re: new RegExp('\\b(?:remove-item|ri|rm|rmdir|rd|del)\\b(?=[\\s\\S]*-(?:recurse|r)\\b)[\\s\\S]*?' + WIN_SYS, 'i'),
182
+ reason: 'Windows kök/sistem yolunda Remove-Item -Recurse' },
183
+ { re: /\bformat\b\s+[a-zA-Z]:/i, reason: 'Disk biçimlendirme (format)' },
184
+ { re: /\bFormat-Volume\b/i, reason: 'Disk biçimlendirme (Format-Volume)' },
185
+ { re: /\b(?:Stop-Computer|Restart-Computer|shutdown(?:\.exe)?\b|Clear-Disk|Remove-Partition)\b/i, reason: 'Sistemi kapatma/disk temizleme' },
186
+ ];
170
187
  // ── Read-only komut tespiti (shellReadOnlyChecker.js'ten — sağlam) ──────────
171
188
  const READONLY_ROOTS = new Set([
172
189
  'awk', 'basename', 'cat', 'cd', 'column', 'cut', 'df', 'dirname', 'du', 'echo', 'env', 'find',
@@ -278,15 +295,17 @@ export function isShellCommandReadOnly(command) {
278
295
  return segs.length > 0 && segs.every(cmdIsReadOnly);
279
296
  }
280
297
  // ── Asıl güvenlik motoru ───────────────────────────────────────────────────
281
- export function checkCommand(rawCommand) {
298
+ export function checkCommand(rawCommand, opts) {
282
299
  const command = stripShellWrapper(String(rawCommand || ''));
283
300
  const roots = getCommandRoots(command);
284
- // 1) Command substitution -> HARD DENY
285
- if (detectCommandSubstitution(command)) {
301
+ const shell = opts?.shell;
302
+ // 1) Command substitution -> HARD DENY (yalnız bash/sh için; PowerShell'de `$()` ve backtick
303
+ // NORMAL sözdizimidir — alt-ifade / escape — bash komut-ikamesi değil → atla).
304
+ if (shell !== 'powershell' && detectCommandSubstitution(command)) {
286
305
  return { decision: 'deny', reason: 'Komut ikamesi ($(), <(), backtick) güvenlik nedeniyle engellendi', roots };
287
306
  }
288
- // 2) Tehlikeli blocklist -> HARD DENY
289
- for (const d of DANGER) {
307
+ // 2) Tehlikeli blocklist (POSIX + Windows) -> HARD DENY
308
+ for (const d of [...DANGER, ...WIN_DANGER]) {
290
309
  try {
291
310
  if (d.re instanceof RegExp && d.re.test(command))
292
311
  return { decision: 'deny', reason: d.reason, roots };
@@ -128,6 +128,72 @@ function toToolCall(obj, i) {
128
128
  function looksLikeCall(o) {
129
129
  return !!o && typeof o === 'object' && (typeof o.name === 'string' || typeof o.tool === 'string' || (o.function && typeof o.function === 'object'));
130
130
  }
131
+ // Sarmasız (wrapper'sız) üst-düzey JSON için SIKI kontrol: yalnız adı BİLİNEN bir araç olan
132
+ // veya `function`/`arguments` yapısı taşıyan nesne çağrıdır. Aksi halde `package.json` gibi
133
+ // `"name"` içeren GERÇEK dosya içerikleri yanlışlıkla araç çağrısı sanılırdı (latent bug).
134
+ const _KNOWN_TOOL_RE = /^(?:Bash|PowerShell|Read|Write|Edit|Glob|Grep|WebFetch|WebSearch|Sleep|TaskOutput|AskUserQuestion)$/;
135
+ function looksLikeKnownCall(o) {
136
+ if (!o || typeof o !== 'object')
137
+ return false;
138
+ if (o.function && typeof o.function === 'object')
139
+ return true;
140
+ const nm = o.name || o.tool || o.tool_name;
141
+ if (typeof nm !== 'string' || !_KNOWN_TOOL_RE.test(nm))
142
+ return false;
143
+ // Bilinen ad + bir argüman anahtarı (çıplak `{"name":"Read"}` da geçerli olabilir → ad yeter).
144
+ return true;
145
+ }
146
+ // ---- DOSYA-BLOĞU KURTARMA (build paste-vs-write) -----------------------------
147
+ // Model bir build görevinde Write çağırmak yerine ```kod bloğu YAPIŞTIRABİLİR ve
148
+ // dosya hiç oluşmaz. Bunu kurtarıyoruz — AMA yalnız blok AÇIKÇA bir dosya adıyla
149
+ // etiketliyse (`index.html`, **app.py**, `# server.py`, `Dosya: x.css`…). Etiket
150
+ // yoksa (saf "kod göster" / örnek) DOKUNMAYIZ. Recovered Write yine onay ister →
151
+ // kullanıcı sadece görmek istediyse reddeder (güvenli).
152
+ const _CODE_EXT = 'py|js|jsx|ts|tsx|html?|htm|css|scss|sass|less|json|jsonc|sh|bash|zsh|go|rs|java|kt|c|cc|cpp|cxx|h|hpp|rb|php|sql|ya?ml|toml|ini|env|xml|svg|vue|svelte|astro|md|markdown|txt|dockerfile|makefile|cs|swift|dart|lua|pl|r|m|mjs|cjs|tf|gradle|bat|ps1';
153
+ const _ECHO_FILE_LANGS = /^(plaintext|plain|text|txt|http|https|console|output|log|raw|diff|patch)$/i;
154
+ // Dosya-adı token'ı: `path/to/file.ext` veya `file.ext` (gerçek kod uzantısı şart).
155
+ const _FNAME = '(?:[\\w./\\\\-]+\\.(?:' + _CODE_EXT + ')|Dockerfile|Makefile|\\.(?:env|gitignore|babelrc)[\\w.-]*)';
156
+ // Etiket biçimleri — fence'ten HEMEN önce (≤2 satır), aynı paragraf:
157
+ // `file.ext` | **file.ext** | ## file.ext | File:/Dosya:/Filename: file.ext | file.ext:
158
+ const _LABEL_RE = new RegExp('(?:^|\\n)[ \\t>*#`\\-]*' +
159
+ '(?:(?:file|dosya|filename|dosya adı|path|yol)\\s*[:=]\\s*)?' +
160
+ '["`*\\[(]*\\s*(' + _FNAME + ')\\s*["`*\\])]*\\s*:?[ \\t]*' + // **bold**/`tick`/[bracket] çoklu sarmal
161
+ '(?:\\([^)\\n]*\\))?[ \\t]*' + // "index.html (ana sayfa)" gibi parantez-not
162
+ '\\r?\\n(?:[ \\t]*\\r?\\n)?' + // araya en çok bir boş satır
163
+ '```([a-zA-Z0-9_+-]*)[ \\t]*\\r?\\n([\\s\\S]*?)```', 'gi');
164
+ /** Metindeki dosya-adı etiketli kod bloklarını bulur (span'larıyla). Konservatif:
165
+ * gerçek kod uzantısı + echo-olmayan dil + ≥2 satır/≥40 karakter gövde şart. */
166
+ export function findFileBlocks(text) {
167
+ const t = text || '';
168
+ const out = [];
169
+ const seen = new Set();
170
+ let m;
171
+ _LABEL_RE.lastIndex = 0;
172
+ while ((m = _LABEL_RE.exec(t)) !== null) {
173
+ const file = m[1].trim();
174
+ const lang = (m[2] || '').trim();
175
+ const body = m[3] || '';
176
+ if (_ECHO_FILE_LANGS.test(lang))
177
+ continue; // komut/çıktı bloğu — dosya değil
178
+ const nonEmpty = body.split('\n').filter((l) => l.trim()).length;
179
+ if (body.trim().length < 15 && nonEmpty < 2)
180
+ continue; // önemsiz snippet (`a=1` gibi) — etiket asıl sinyal
181
+ const key = file + '|' + body.length;
182
+ if (seen.has(key))
183
+ continue;
184
+ seen.add(key);
185
+ out.push({ file, content: body.replace(/\s+$/, '') + '\n', start: m.index, end: m.index + m[0].length });
186
+ }
187
+ return out;
188
+ }
189
+ /** Dosya-bloklarını Write çağrılarına çevirir (yapısal/inline tool-call YOKKEN son çare). */
190
+ export function recoverFileBlocks(text) {
191
+ return findFileBlocks(text).map((b, i) => ({
192
+ id: `fileblock_${i}`,
193
+ name: 'Write',
194
+ args: JSON.stringify({ file_path: b.file, content: b.content }),
195
+ }));
196
+ }
131
197
  /** Metindeki üst-düzey {…} JSON nesnelerini (string/escape duyarlı) çıkarır.
132
198
  * Model çağrıları sarmasız ve BİRDEN FAZLA nesne olarak dökebilir: {…}{…} veya {…}\n{…}
133
199
  * (abliterated coder davranışı). Her üst-düzey nesneyi ayrı parça olarak döndürür. */
@@ -169,7 +235,8 @@ function extractTopLevelJsonObjects(text) {
169
235
  export function recoverInlineToolCalls(text) {
170
236
  const t = (text || '').trim();
171
237
  // JSON/prose/ToolName{…}/ToolName(…) çağrısı yoksa erken çık.
172
- if (!t || (!t.includes('"name"') && !t.includes('"tool"') && !t.includes('"function"') && !/AskUserQuestion/i.test(t) && !TOOL_PREFIX_RE.test(t) && !TOOL_PAREN_RE.test(t)))
238
+ // (Kod fence'i varsa çıkma dosya-adı etiketli blok olabilir, adım 7 karar verir.)
239
+ if (!t || (!t.includes('"name"') && !t.includes('"tool"') && !t.includes('"function"') && !/AskUserQuestion/i.test(t) && !TOOL_PREFIX_RE.test(t) && !TOOL_PAREN_RE.test(t) && !t.includes('```')))
173
240
  return [];
174
241
  const out = [];
175
242
  const seen = new Set();
@@ -193,7 +260,9 @@ export function recoverInlineToolCalls(text) {
193
260
  if (!out.length) {
194
261
  for (const m of t.matchAll(/```(?:json|tool_call|tool|function)?\s*([\s\S]*?)```/gi)) {
195
262
  const o = safeJsonParse(m[1].trim(), null);
196
- if (looksLikeCall(o))
263
+ // SIKI: ```json içine konmuş GERÇEK dosya (`{"name":"app",...}`) tool-call sanılmasın;
264
+ // sarmalı tool-call'lar zaten bilinen araç adı (Write/Bash…) kullanır → step 7 dosyayı alır.
265
+ if (looksLikeKnownCall(o))
197
266
  push(o);
198
267
  }
199
268
  }
@@ -202,8 +271,8 @@ export function recoverInlineToolCalls(text) {
202
271
  if (!out.length) {
203
272
  for (const frag of extractTopLevelJsonObjects(t)) {
204
273
  const o = safeJsonParse(frag, null);
205
- if (looksLikeCall(o))
206
- push(o);
274
+ if (looksLikeKnownCall(o))
275
+ push(o); // SIKI: package.json gibi dosyaları çağrı sanma
207
276
  }
208
277
  }
209
278
  // 4) AskUserQuestion'ı JSON yerine DÜZ-METİN (prose) yazan model (abliterated):
@@ -265,6 +334,18 @@ export function recoverInlineToolCalls(text) {
265
334
  }
266
335
  }
267
336
  }
337
+ // 7) SON ÇARE: hiç çağrı kurtarılamadıysa, dosya-adı etiketli ```kod bloklarını
338
+ // Write'a çevir (model build'de Write yerine kod yapıştırınca dosya kaybolmasın).
339
+ // Adım 7, gerçek tool-call YOKKEN çalışır → "kod göster"i bozmaz (etiket gerekir).
340
+ if (!out.length) {
341
+ for (const tc of recoverFileBlocks(t)) {
342
+ const k = tc.name + '|' + tc.args;
343
+ if (!seen.has(k)) {
344
+ seen.add(k);
345
+ out.push(tc);
346
+ }
347
+ }
348
+ }
268
349
  return out;
269
350
  }
270
351
  /** Modelin komut/çıktıyı TEKRAR yazdığı kod-bloklarını siler (```plaintext/bash/http/console…).
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.144';
19
+ export const VERSION = '1.0.146';
package/dist/tools.js CHANGED
@@ -694,7 +694,7 @@ async function execOne(call, hooks) {
694
694
  }
695
695
  // 3.5) Komut güvenliği (Bash/PowerShell) — cmdsec: deny→blokla, allow→izinsiz, confirm→izin akışı
696
696
  if ((call.name === 'Bash' || call.name === 'PowerShell') && args && args.command) {
697
- const chk = checkCommand(String(args.command));
697
+ const chk = checkCommand(String(args.command), { shell: call.name === 'PowerShell' ? 'powershell' : 'bash' });
698
698
  if (chk.decision === 'deny') {
699
699
  return { ok: false, output: `⛔ Güvenlik: komut engellendi — ${chk.reason || 'tehlikeli komut'}`, args };
700
700
  }
package/dist/tui.js CHANGED
@@ -445,7 +445,10 @@ export async function runTui() {
445
445
  if (allowAll.has(c.name))
446
446
  return resolve('allow');
447
447
  // Claude tarzı: Write/Edit'te YAZMADAN ÖNCE dosya içeriğini göster, sonra onay iste.
448
- if ((c.name === 'Write' && args?.content != null) || (c.name === 'Edit' && args?.new_string != null)) {
448
+ // İSTİSNA: kurtarılan dosya-bloğu (id `fileblock_`) içeriği zaten akışta prose olarak
449
+ // gösterildi → fileprev'i ATLA, yoksa aynı kod iki kez basılır.
450
+ const _recoveredBlock = typeof c.id === 'string' && c.id.startsWith('fileblock_');
451
+ if (!_recoveredBlock && ((c.name === 'Write' && args?.content != null) || (c.name === 'Edit' && args?.new_string != null))) {
449
452
  const _content = String(c.name === 'Write' ? args.content : args.new_string);
450
453
  printItem({ kind: 'fileprev', file: relWs(args.file_path), lines: _content.split('\n') });
451
454
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wormclaude",
3
- "version": "1.0.144",
3
+ "version": "1.0.146",
4
4
  "description": "WormClaude CLI - uncensored security+code assistant (ink TUI, Claude-style)",
5
5
  "type": "module",
6
6
  "bin": {