From c19062246cfea61323d1f01f4dba127dc44c370b Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 28 Mar 2026 18:32:25 +0200 Subject: [PATCH 01/35] feat(malware): add WASM-based YARA signature engine Replace hardcoded regex patterns with a YARA scanning engine powered by libyara-wasm. Rules are fetched from the Kudu cloud API, cached to disk, and reloaded automatically. The existing regex/hash patterns remain as a fallback when YARA is unavailable (no rules cached or WASM fails to load). - Add YaraEngine service wrapping libyara-wasm (WASM, zero native deps) - Add YaraRulesStore for cloud fetch, SHA-256 integrity validation, and disk caching with periodic 6-hour update checks - Integrate YARA into malware scanner Phase 3 with graceful fallback - Add update-yara-rules cloud agent command for push-based rule updates - Include cloud API contract doc for building the rules management backend Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/cloud-yara-rules-prompt.md | 352 +++++++++++++++++++++ package-lock.json | 7 + package.json | 1 + src/main/ipc/malware-scanner.ipc.ts | 272 ++++++++++------ src/main/services/cloud-agent-types.ts | 1 + src/main/services/cloud-agent.ts | 65 +++- src/main/services/yara-engine.test.ts | 175 ++++++++++ src/main/services/yara-engine.ts | 206 ++++++++++++ src/main/services/yara-rules-store.test.ts | 299 +++++++++++++++++ src/main/services/yara-rules-store.ts | 281 ++++++++++++++++ 10 files changed, 1568 insertions(+), 91 deletions(-) create mode 100644 docs/cloud-yara-rules-prompt.md create mode 100644 src/main/services/yara-engine.test.ts create mode 100644 src/main/services/yara-engine.ts create mode 100644 src/main/services/yara-rules-store.test.ts create mode 100644 src/main/services/yara-rules-store.ts diff --git a/docs/cloud-yara-rules-prompt.md b/docs/cloud-yara-rules-prompt.md new file mode 100644 index 00000000..dde48824 --- /dev/null +++ b/docs/cloud-yara-rules-prompt.md @@ -0,0 +1,352 @@ +# Prompt: Build the YARA Rules API + Rule Management System + +## Context + +Kudu is a desktop system cleaner (Electron) with a malware scanner. The desktop client now has a WASM-based YARA engine (`libyara-wasm`) that scans files against YARA rules. **Rules are NOT shipped with the app** — they come exclusively from the Kudu cloud backend. The client caches rules to disk and periodically pulls updates. + +Your job is to build: + +1. **The API endpoint** (`GET /api/yara-rules`) that serves YARA rule bundles to clients +2. **The YARA rule files** themselves — translating the existing detection patterns (listed below) into proper YARA rules, organized by category +3. **A rule management system** — database schema, admin tooling, and a workflow for adding/updating/versioning rules +4. **A push mechanism** — so you can trigger immediate rule updates to connected clients via the existing Pusher WebSocket infrastructure + +--- + +## Part 1: API Endpoint + +### `GET /api/yara-rules` + +**Request headers the client sends:** +``` +Accept: application/json +X-Kudu-Rules-Version: 1.0.0 (the version the client currently has cached; omitted on first fetch) +``` + +**Response when client is up to date (version matches):** +``` +HTTP/1.1 304 Not Modified +``` + +**Response when update is available (200 OK):** +```json +{ + "version": "1.2.0", + "updatedAt": "2026-03-28T12:00:00Z", + "sha256": "", + "rules": [ + { "filename": "miners.yar", "content": "rule CoinMiner_XMRig { ... }" }, + { "filename": "rats.yar", "content": "rule RAT_DarkComet { ... }" }, + { "filename": "ransomware.yar", "content": "..." } + ] +} +``` + +**Integrity contract — the client verifies this exactly:** +``` +sha256 = SHA-256( concatenation of all content fields, sorted alphabetically by filename ) +``` + +In other words: +1. Sort the `rules` array by `filename` (lexicographic, ascending) +2. Concatenate all `content` strings (no separator) +3. SHA-256 hash that concatenation (lowercase hex) + +If the hash doesn't match, the client rejects the entire bundle. + +**Client-side limits (your API should stay within these):** +- Max total response body: **50 MB** +- Max individual `content` field: **1 MB** +- Max number of rules entries: **10,000** +- Each `filename` must end in `.yar` +- Each `filename` must NOT contain `/`, `\`, or `..` (path traversal protection) +- Timeout: **60 seconds** + +### Push-based updates (via existing Pusher infrastructure) + +When you publish new rules, also send a command to connected clients: +```json +{ "type": "update-yara-rules", "requestId": "", "url": "https://cloud.usekudu.com/api/yara-rules" } +``` + +This triggers an immediate fetch+cache on the client. The client also pulls on its own every 6 hours regardless. + +--- + +## Part 2: YARA Rules to Generate + +### How a YARA rule must be structured for Kudu + +Every rule MUST have these `meta` fields: +- `detectionName` (string) — the detection label shown to the user, e.g. `"CoinMiner.XMRig"` +- `severity` (string) — one of: `"critical"`, `"high"`, `"medium"`, `"low"` +- `details` (string) — human-readable description of the threat + +Optional meta field: +- `filenameOnly` = `"true"` — tells the scanner this rule should only match files *outside* system directories on Windows (used for system process masquerade detection like fake svchost.exe) + +Example rule: +```yara +rule CoinMiner_XMRig +{ + meta: + detectionName = "CoinMiner.XMRig" + severity = "critical" + details = "XMRig cryptocurrency miner — uses CPU/GPU to mine Monero without consent" + + strings: + $s1 = "xmrig" nocase + + condition: + $s1 +} +``` + +Example hash-based rule (the YARA `hash` module is available): +```yara +import "hash" + +rule EICAR_TestFile +{ + meta: + detectionName = "EICAR.TestFile" + severity = "low" + details = "EICAR antivirus test file — not actual malware, used to verify AV detection" + + condition: + hash.sha256(0, filesize) == "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f" +} +``` + +Example filenameOnly rule: +```yara +rule Suspicious_FakeSvchost +{ + meta: + detectionName = "Suspicious.FakeSvchost" + severity = "high" + details = "svchost.exe found outside System32 — likely malware disguised as system process" + filenameOnly = "true" + + strings: + $s1 = "svchost" nocase + + condition: + $s1 +} +``` + +### Organize rules into these files: + +| File | Category | +|------|----------| +| `miners.yar` | Crypto miners | +| `adware.yar` | Adware and PUPs | +| `rats.yar` | Remote access trojans | +| `trojans.yar` | Banking trojans, keyloggers, general trojans | +| `stealers.yar` | Info stealers | +| `ransomware.yar` | Ransomware families and ransom notes | +| `loaders.yar` | Loaders, droppers, initial access tools | +| `hacktools.yar` | Red team / offensive tools | +| `osx_malware.yar` | macOS-specific malware | +| `linux_malware.yar` | Linux-specific malware | +| `suspicious.yar` | System process masquerades (all need `filenameOnly = "true"`) | +| `hashes.yar` | Hash-based detections (needs `import "hash"` at top) | + +### Existing detections to translate + +These are the hardcoded patterns currently in the Kudu client. Convert every single one into a YARA rule. Keep the exact same `detectionName`, `severity`, and `details` values: + +**Crypto miners (miners.yar):** +- `/xmrig/i` → CoinMiner.XMRig, critical +- `/cpuminer/i` → CoinMiner.CPUMiner, critical +- `/\bminerd\b/i` → CoinMiner.Minerd, critical +- `/nicehashminer/i` → CoinMiner.NiceHash, high +- `/coinhive/i` → CoinMiner.CoinHive, high + +**Adware/PUPs (adware.yar):** +- `/bonzi\s*buddy/i` → Adware.BonziBuddy, medium +- `/ask\s*toolbar/i` → PUP.AskToolbar, medium +- `/conduit[\s_-]?(toolbar|search|engine)/i` → PUP.Conduit, medium +- `/babylontoolbar/i` → PUP.BabylonToolbar, medium +- `/mywebsearch/i` → PUP.MyWebSearch, medium +- `/incredibar/i` → PUP.IncrediBar, medium +- `/sweetim/i` → PUP.SweetIM, medium +- `/opencandy/i` → PUP.OpenCandy, medium +- `/installcore/i` → PUP.InstallCore, medium +- `/softpulse/i` → PUP.SoftPulse, medium +- `/browsefox/i` → Adware.BrowseFox, medium +- `/crossrider/i` → Adware.CrossRider, medium +- `/wajam/i` → Adware.Wajam, high +- `/superfish/i` → Adware.Superfish, critical + +**RATs (rats.yar):** +- `/darkcomet/i` → RAT.DarkComet, critical +- `/njrat/i` → RAT.njRAT, critical +- `/nanocore/i` → RAT.NanoCore, critical +- `/quasar\.?rat/i` → RAT.Quasar, critical +- `/asyncrat/i` → RAT.AsyncRAT, critical +- `/poisonivy/i` → RAT.PoisonIvy, critical +- `/\bremcos\b/i` → RAT.Remcos, critical +- `/warzone[\s_-]?rat|ave[\s_-]?maria/i` → RAT.WarzoneRAT, critical +- `/xworm/i` → RAT.XWorm, critical +- `/dcrat/i` → RAT.DCRat, critical +- `/\bnetwire\b/i` → RAT.NetWire, critical + +**Trojans & Keyloggers (trojans.yar):** +- `/hawkeye[\s_.-]?(keylog|reborn|stealer|rat)/i` → Keylogger.HawkEye, critical +- `/ardamax/i` → Keylogger.Ardamax, high +- `/emotet/i` → Trojan.Emotet, critical +- `/trickbot/i` → Trojan.TrickBot, critical +- `/lokibot/i` → Trojan.LokiBot, critical +- `/formbook/i` → Trojan.FormBook, critical +- `/agenttesla/i` → Trojan.AgentTesla, critical + +**Stealers (stealers.yar):** +- `/redline\s*stealer/i` → Trojan.RedLine, critical +- `/raccoon\s*stealer/i` → Trojan.Raccoon, critical +- `/vidar[\s_.-]?(stealer|malware|trojan|loader)/i` → Trojan.Vidar, critical +- `/lumma[\s_-]?stealer/i` → Trojan.LummaStealer, critical +- `/\bstealc\b/i` → Trojan.StealC, critical +- `/\brisepro\b/i` → Trojan.RisePro, critical +- `/mystic[\s_-]?stealer/i` → Trojan.MysticStealer, critical + +**Ransomware (ransomware.yar):** +- `/wannacry/i` → Ransom.WannaCry, critical +- `/readme_for_decrypt/i` → Ransom.Generic, critical +- `/decrypt_instructions/i` → Ransom.Generic, critical +- `/your_files_are_encrypted/i` → Ransom.Generic, critical +- `/^how[\s_-]?to[\s_-]?decrypt\.(txt|html|hta)$/i` → Ransom.Generic, critical +- `/^restore[\s_-]?my[\s_-]?files\.(txt|html|hta)$/i` → Ransom.Generic, critical +- `/\blockbit\b/i` → Ransom.LockBit, critical +- `/blackcat[\s_-]?(ransomware|ransom|malware|locker)|\balphv\b/i` → Ransom.BlackCat, critical +- `/conti[\s_-]?(ransomware|ransom|locker|malware)/i` → Ransom.Conti, critical +- `/\brevil\b[\s_-](ransomware|ransom|locker|malware)|sodinokibi/i` → Ransom.REvil, critical +- `/ryuk[\s_-]?(ransomware|ransom|locker|malware)/i` → Ransom.Ryuk, critical +- `/blackbasta/i` → Ransom.BlackBasta, critical +- `/\bakira\b[\s_-]?(ransom|decrypt|locked)/i` → Ransom.Akira, critical +- `/royal[\s_-]?ransom/i` → Ransom.Royal, critical +- `/play[\s_-]?ransom|play[\s_-]?crypt/i` → Ransom.Play, critical + +**Loaders & Hack Tools (loaders.yar + hacktools.yar):** +- `/gootloader/i` → Trojan.Gootloader, critical +- `/icedid|bokbot/i` → Trojan.IcedID, critical +- `/bumblebee[\s_-]?(loader|malware|trojan)/i` → Trojan.Bumblebee, critical +- `/pikabot/i` → Trojan.Pikabot, critical +- `/qakbot|\bqbot\b/i` → Trojan.QakBot, critical +- `/cobalt[\s_-]?strike/i` → HackTool.CobaltStrike, critical +- `/meterpreter/i` → HackTool.Meterpreter, critical +- `/sliver[\s_-]?(implant|beacon|c2)/i` → HackTool.Sliver, critical + +**macOS malware (osx_malware.yar):** +- `/shlayer/i` → OSX.Shlayer, critical +- `/pirrit/i` → Adware.OSX.Pirrit, high +- `/bundlore/i` → Adware.OSX.Bundlore, medium +- `/adload/i` → Adware.OSX.Adload, high +- `/genieo/i` → Adware.OSX.Genieo, medium +- `/mackeeper/i` → PUP.OSX.MacKeeper, medium +- `/xcsset/i` → OSX.XCSSET, critical +- `/silver[\s_-]?sparrow/i` → OSX.SilverSparrow, critical +- `/atomic[\s_-]?stealer|amos[\s_-]?stealer/i` → OSX.AtomicStealer, critical +- `/\brealst\b/i` → OSX.Realst, critical +- `/cuckoo[\s_-]?stealer/i` → OSX.CuckooStealer, critical +- `/banshee[\s_-]?stealer/i` → OSX.BansheeStealer, critical +- `/cthulhu[\s_-]?stealer/i` → OSX.CthulhuStealer, critical +- `/metastealer/i` → OSX.MetaStealer, critical +- `/poseidon[\s_-]?stealer/i` → OSX.Poseidon, critical +- `/kandykorn/i` → OSX.KandyKorn, critical + +**Linux malware (linux_malware.yar):** +- `/\bxorddos\b/i` → Linux.XorDDoS, critical +- `/\bperfctl\b/i` → Linux.Perfctl, critical + +**Suspicious filenames (suspicious.yar) — all need `filenameOnly = "true"`:** +- svchost.exe → Suspicious.FakeSvchost, high +- csrss.exe → Suspicious.FakeCsrss, high +- lsass.exe → Suspicious.FakeLsass, high +- winlogon.exe → Suspicious.FakeWinlogon, high +- services.exe → Suspicious.FakeServices, high +- explorer.exe → Suspicious.FakeExplorer, high +- taskmgr.exe → Suspicious.FakeTaskmgr, high +- rundll32.exe → Suspicious.FakeRundll, high +- spoolsv.exe → Suspicious.FakeSpoolsv, high +- conhost.exe → Suspicious.FakeConhost, high +- dwm.exe → Suspicious.FakeDwm, high +- smss.exe → Suspicious.FakeSmss, high +- wininit.exe → Suspicious.FakeWininit, high +- dllhost.exe → Suspicious.FakeDllhost, high +- taskhost.exe → Suspicious.FakeTaskhost, high + +**Hashes (hashes.yar) — needs `import "hash"` at the top:** +- EICAR test file: SHA-256 `275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f` → EICAR.TestFile, low + +--- + +## Part 3: Rule Management System + +Build a system for managing YARA rules with: + +### Database schema +- **`yara_rule_files`** table — stores each `.yar` file: + - `id`, `filename` (unique), `content` (text), `created_at`, `updated_at` +- **`yara_rule_versions`** table — tracks published bundles: + - `id`, `version` (semver string, unique), `sha256`, `rules_count`, `published_at`, `published_by` +- **`yara_rule_version_files`** join table — links a version to its rule files (snapshot): + - `version_id`, `rule_file_id`, `content_snapshot` (the content at time of publish) + +### Admin API +- `GET /admin/yara-rules` — list all rule files with their current content +- `PUT /admin/yara-rules/:filename` — create or update a rule file +- `DELETE /admin/yara-rules/:filename` — delete a rule file +- `POST /admin/yara-rules/publish` — snapshot current rules into a new version, compute sha256, and optionally push to all connected clients +- `GET /admin/yara-rules/versions` — list all published versions +- `GET /admin/yara-rules/versions/:version` — get a specific version's full bundle + +### Version workflow +1. Edit rule files via the admin API (or a UI) +2. When ready, call `POST /admin/yara-rules/publish` with `{ "version": "1.1.0", "push": true }` +3. This snapshots all current rule files, computes the sha256, stores the version +4. If `push: true`, sends the `update-yara-rules` command to all connected clients via Pusher + +### Seeding +On first deploy, seed the database with all the rules listed above (translated from the regex patterns). Set the initial version to `1.0.0`. + +--- + +## Part 4: Updating Rules Going Forward + +### Adding a new threat +1. Determine the category (miner, RAT, stealer, etc.) and which `.yar` file it belongs to +2. Write a YARA rule with the required meta fields (`detectionName`, `severity`, `details`) +3. Add it to the appropriate file via the admin API +4. Publish a new version with `push: true` + +### Improving detection (reducing false positives) +1. Tighten the rule's `strings` or `condition` — e.g., require multiple indicators instead of a single string match +2. Use YARA features like `filesize`, `uint16(0) == 0x5A4D` (PE check), hex patterns, regex anchors +3. Test against benign samples before publishing + +### Adding hash-based detections +1. Add new hash entries to `hashes.yar` using the `hash.sha256(0, filesize)` condition +2. These provide zero false-positive detection for known-bad files +3. Good sources: VirusTotal, MalwareBazaar, abuse.ch + +### Rule quality checklist +- Every rule has `detectionName`, `severity`, `details` in meta +- Rule names use `Category_ThreatName` format (e.g., `CoinMiner_XMRig`) +- Strings use `nocase` for case-insensitive matching +- Regex patterns in YARA use `/pattern/i` syntax +- `filenameOnly = "true"` is set on rules that should only apply to files outside system directories +- Rules with multiple alternatives use `any of them` in the condition +- Hash rules import `"hash"` at the top of the file (once per file, not per rule) + +--- + +## Summary of what to build + +1. **`GET /api/yara-rules`** endpoint — serves the bundle, supports `X-Kudu-Rules-Version` for 304, computes and includes `sha256` +2. **Database tables** — `yara_rule_files`, `yara_rule_versions`, `yara_rule_version_files` +3. **Admin CRUD API** — for managing rule files and publishing versions +4. **Seed data** — all rules translated from the patterns above, version 1.0.0 +5. **Push integration** — on publish, optionally send `update-yara-rules` command to clients via Pusher +6. **The actual `.yar` rule content** — proper YARA syntax, organized by the file categories listed above diff --git a/package-lock.json b/package-lock.json index c577f181..1e7075be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "electron-updater": "^6.8.3", "framer-motion": "^12.38.0", "i18next": "^25.9.0", + "libyara-wasm": "^1.2.1", "lucide-react": "^0.577.0", "pusher-js": "^8.4.0", "react-i18next": "^16.5.8", @@ -6882,6 +6883,12 @@ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "license": "MIT" }, + "node_modules/libyara-wasm": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-1.2.1.tgz", + "integrity": "sha512-PNqUNWnwjZLe55iA8Rv6vLQRjSdO2OnVg24aRE8v+ytR8CRB8agIG6pS9h2VQejuJP1A/uR4pwcBggUxoNC7DA==", + "license": "ISC" + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", diff --git a/package.json b/package.json index 19bb9459..f9bd0ee9 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "electron-updater": "^6.8.3", "framer-motion": "^12.38.0", "i18next": "^25.9.0", + "libyara-wasm": "^1.2.1", "lucide-react": "^0.577.0", "pusher-js": "^8.4.0", "react-i18next": "^16.5.8", diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index c4ae2f1f..7ed59465 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -18,6 +18,8 @@ import { validateStringArray } from '../services/ipc-validation' import { psUtf8, execNativeUtf8 } from '../services/exec-utf8' import { getSettings } from '../services/settings-store' import { isExcluded } from '../services/file-utils' +import { createYaraEngine, yaraMatchToThreatFields, type YaraEngine } from '../services/yara-engine' +import { getCachedRulePaths, startPeriodicRuleChecks } from '../services/yara-rules-store' const execFileAsync = promisify(execFile) @@ -225,6 +227,43 @@ const TRUSTED_PATHS = [ 'logitech', 'razer', 'corsair', 'steelseries', ] +// ─── YARA engine (lazy-initialized singleton) ──────────────── + +let _yaraEngine: YaraEngine | null = null +let _yaraInitAttempted = false + +async function getYaraEngine(): Promise { + if (_yaraInitAttempted) return _yaraEngine + _yaraInitAttempted = true + try { + _yaraEngine = createYaraEngine() + await _yaraEngine.initialize() + const ruleFiles = getCachedRulePaths() + const result = _yaraEngine.loadRules(ruleFiles) + console.log(`[yara] Loaded ${result.loaded} rules from ${ruleFiles.length} files`) + if (result.errors.length > 0) { + console.warn('[yara] Rule load warnings:', result.errors) + } + if (result.loaded === 0) { + console.warn('[yara] No rules loaded — falling back to regex patterns') + _yaraEngine.dispose() + _yaraEngine = null + } + return _yaraEngine + } catch (err) { + console.warn('[yara] Init failed, falling back to regex patterns:', err) + _yaraEngine = null + return null + } +} + +/** Reset engine so it re-initializes on next scan (e.g. after rule update). */ +export function resetYaraEngine(): void { + if (_yaraEngine) _yaraEngine.dispose() + _yaraEngine = null + _yaraInitAttempted = false +} + // ─── Heuristic checks ───────────────────────────────────────── function calculateEntropy(buffer: Buffer): number { @@ -1107,6 +1146,15 @@ export async function scanMalware( const scannableExts = getScannableExtensions() const systemDirs = getSystemDirs() + // YARA engine: try to initialize once per app lifetime + const yaraEngine = await getYaraEngine() + if (yaraEngine) { + // Replace 'Known Signatures' with 'YARA Rules Engine' in the engines list + const sigIdx = engines.indexOf('Known Signatures') + if (sigIdx !== -1) engines[sigIdx] = 'YARA Rules Engine' + else engines.unshift('YARA Rules Engine') + } + // Native AV: ClamAV on Linux, codesign on macOS (skip Defender on Windows — real-time protection already covers it) const hasNativeAv = !!platform.malware.scanWithNativeAv && process.platform !== 'win32' if (hasNativeAv) { @@ -1182,14 +1230,15 @@ export async function scanMalware( const MAX_READ_SIZE = 50 * 1024 * 1024 // ── Phase 3: Signature + heuristic scan ── - updateCategory('signatures', { status: 'running', totalItems: totalFiles }) + const sigEngineName = yaraEngine ? 'YARA Rules Engine' : 'Signature Database' + updateCategory('signatures', { status: 'running', totalItems: totalFiles, label: sigEngineName }) updateCategory('heuristics', { status: 'running', totalItems: totalFiles }) sendProgress({ step: 'signatures', - stepLabel: 'Checking known malware signatures & patterns...', + stepLabel: yaraEngine ? 'Scanning with YARA rules engine...' : 'Checking known malware signatures & patterns...', progress: 5, totalFiles, - engine: 'Signature Database' + engine: sigEngineName }) const SIG_CONCURRENCY = 16 @@ -1204,51 +1253,107 @@ export async function scanMalware( if (!fileStat) return const fileSize = fileStat.size - // ── Check 1: Known malware filename patterns ── - const dirLower = filePath.toLowerCase().replace(/\\/g, '/') - const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) + // Read file buffer (needed for YARA content scanning and heuristic analysis) + let fileBuffer: Buffer | null = null + if (fileSize <= MAX_READ_SIZE) { + fileBuffer = await readFile(filePath).catch(() => null) + } - if (!isInSystemDir && process.platform === 'win32') { - for (const sus of SUSPICIOUS_FILENAMES) { - if (sus.pattern.test(fileName)) { - threats.push({ - id: randomUUID(), - path: filePath, - fileName, - size: fileSize, - detectionName: sus.name, - severity: sus.severity, - source: 'signature', - details: sus.details, - selected: true - }) - sigThreatsCount++ + // ── Signature matching: YARA or regex fallback ── + if (yaraEngine && fileBuffer) { + // YARA scans file content against all compiled rules (replaces regex + hash checks) + const matches = yaraEngine.scanBuffer(fileBuffer) + for (const match of matches) { + if (threats.some(t => t.path === filePath)) break + + // Skip filename-only rules (filenameOnly metadata) for files in system directories + if (match.metadata.filenameOnly === 'true') { + const dirLower = filePath.toLowerCase().replace(/\\/g, '/') + const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) + if (isInSystemDir || process.platform !== 'win32') continue + } + + const fields = yaraMatchToThreatFields(match) + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: fields.detectionName, + severity: fields.severity, + source: 'signature', + details: fields.details, + selected: true, + }) + sigThreatsCount++ + } + } else { + // Fallback: regex pattern matching + hash lookup (used when YARA is unavailable) + const dirLower = filePath.toLowerCase().replace(/\\/g, '/') + const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) + + if (!isInSystemDir && process.platform === 'win32') { + for (const sus of SUSPICIOUS_FILENAMES) { + if (sus.pattern.test(fileName)) { + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: sus.name, + severity: sus.severity, + source: 'signature', + details: sus.details, + selected: true + }) + sigThreatsCount++ + break + } + } + } + + for (const mal of KNOWN_MALWARE_PATTERNS) { + if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { + if (!threats.some(t => t.path === filePath)) { + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: mal.name, + severity: mal.severity, + source: 'signature', + details: mal.details, + selected: true + }) + sigThreatsCount++ + } break } } - } - for (const mal of KNOWN_MALWARE_PATTERNS) { - if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { - if (!threats.some(t => t.path === filePath)) { + // Hash lookup (fallback only — YARA handles hashes via the hash module) + if (fileBuffer && !threats.some(t => t.path === filePath)) { + const hash = createHash('sha256').update(fileBuffer).digest('hex') + const knownMalware = KNOWN_MALWARE_HASHES[hash] + if (knownMalware) { threats.push({ id: randomUUID(), path: filePath, fileName, size: fileSize, - detectionName: mal.name, - severity: mal.severity, + detectionName: knownMalware.name, + severity: knownMalware.severity, source: 'signature', - details: mal.details, + details: knownMalware.details, selected: true }) sigThreatsCount++ } - break } } - // ── Check 2: Double extension detection ── + // ── Double extension detection (heuristic — always runs) ── if (hasDoubleExtension(fileName) && !threats.some(t => t.path === filePath)) { threats.push({ id: randomUUID(), @@ -1264,78 +1369,58 @@ export async function scanMalware( heuristicThreatsCount++ } - // ── Check 3 & 4: File hash + PE analysis ── - if (fileSize <= MAX_READ_SIZE) { - const fileBuffer = await readFile(filePath).catch(() => null) - if (fileBuffer && !threats.some(t => t.path === filePath)) { - const hash = createHash('sha256').update(fileBuffer).digest('hex') - const knownMalware = KNOWN_MALWARE_HASHES[hash] - if (knownMalware) { - threats.push({ - id: randomUUID(), - path: filePath, - fileName, - size: fileSize, - detectionName: knownMalware.name, - severity: knownMalware.severity, - source: 'signature', - details: knownMalware.details, - selected: true - }) - sigThreatsCount++ - } - - // PE heuristic analysis - const pathLower = filePath.toLowerCase() - const isTrustedPath = TRUSTED_PATHS.some(tp => pathLower.includes(tp)) - - if (shouldAnalyzePE && isPEFile(fileBuffer) && !isTrustedPath && !threats.some(t => t.path === filePath)) { - const pe = analyzePE(fileBuffer) - if (pe) { - const issues: string[] = [] - if (pe.isPacked) issues.push('packed/encrypted sections') - if (pe.hasWritableExecutable) issues.push('writable+executable sections') - if (pe.hasSuspiciousImports.length >= 4) { - issues.push(`suspicious API imports: ${pe.hasSuspiciousImports.join(', ')}`) - } - if (pe.entropy > 7.4) issues.push(`very high entropy (${pe.entropy.toFixed(2)})`) - - if (issues.length >= 3) { - threats.push({ - id: randomUUID(), - path: filePath, - fileName, - size: fileSize, - detectionName: 'Heuristic.Suspicious.PE', - severity: 'high', - source: 'heuristic', - details: `PE analysis flags: ${issues.join('; ')}`, - selected: false - }) - heuristicThreatsCount++ - } + // ── PE + LNK heuristic analysis (always runs) ── + if (fileBuffer && !threats.some(t => t.path === filePath)) { + // PE heuristic analysis + const pathLower = filePath.toLowerCase() + const isTrustedPath = TRUSTED_PATHS.some(tp => pathLower.includes(tp)) + + if (shouldAnalyzePE && isPEFile(fileBuffer) && !isTrustedPath) { + const pe = analyzePE(fileBuffer) + if (pe) { + const issues: string[] = [] + if (pe.isPacked) issues.push('packed/encrypted sections') + if (pe.hasWritableExecutable) issues.push('writable+executable sections') + if (pe.hasSuspiciousImports.length >= 4) { + issues.push(`suspicious API imports: ${pe.hasSuspiciousImports.join(', ')}`) } - } + if (pe.entropy > 7.4) issues.push(`very high entropy (${pe.entropy.toFixed(2)})`) - // ── LNK analysis (Windows) ── - if (process.platform === 'win32' && ext === '.lnk' && !threats.some(t => t.path === filePath)) { - const lnkIssues = analyzeLnkContent(fileBuffer) - if (lnkIssues.length > 0) { + if (issues.length >= 3) { threats.push({ id: randomUUID(), path: filePath, fileName, size: fileSize, - detectionName: 'Heuristic.Suspicious.LNK', + detectionName: 'Heuristic.Suspicious.PE', severity: 'high', source: 'heuristic', - details: `Weaponized shortcut: ${lnkIssues.join('; ')}`, - selected: true + details: `PE analysis flags: ${issues.join('; ')}`, + selected: false }) heuristicThreatsCount++ } } } + + // ── LNK analysis (Windows) ── + if (process.platform === 'win32' && ext === '.lnk' && !threats.some(t => t.path === filePath)) { + const lnkIssues = analyzeLnkContent(fileBuffer) + if (lnkIssues.length > 0) { + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: 'Heuristic.Suspicious.LNK', + severity: 'high', + source: 'heuristic', + details: `Weaponized shortcut: ${lnkIssues.join('; ')}`, + selected: true + }) + heuristicThreatsCount++ + } + } } // ── Check 5: macOS LaunchAgent/Daemon plist persistence analysis ── @@ -1372,12 +1457,12 @@ export async function scanMalware( sendProgress({ step: currentStep as MalwareScanProgress['step'], stepLabel: currentStep === 'signatures' - ? 'Matching filenames, hashes & known malware patterns...' + ? (yaraEngine ? 'Scanning with YARA rules engine...' : 'Matching filenames, hashes & known malware patterns...') : 'Analyzing PE headers, entropy & suspicious API imports...', currentPath: chunk[0], progress: 5 + (batch / totalFiles) * 35, totalFiles, - engine: currentStep === 'signatures' ? 'Signature Database' : 'Heuristic Engine' + engine: currentStep === 'signatures' ? sigEngineName : 'Heuristic Engine' }) } @@ -1843,4 +1928,11 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { }) ipcMain.handle(IPC.MALWARE_QUARANTINE_LIST, () => listQuarantinedFiles()) + + // Start periodic YARA rule updates from the cloud + const settings = getSettings() + if (settings.cloud.apiKey) { + const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' + startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) + } } diff --git a/src/main/services/cloud-agent-types.ts b/src/main/services/cloud-agent-types.ts index 42f384a5..93dbf378 100644 --- a/src/main/services/cloud-agent-types.ts +++ b/src/main/services/cloud-agent-types.ts @@ -79,6 +79,7 @@ export type CloudCommand = | { type: 'registry-fix'; requestId: string; entryIds?: string[] } // Phase 4: Threat monitoring | { type: 'update-threat-blacklist'; requestId: string; url: string } + | { type: 'update-yara-rules'; requestId: string; url: string } | { type: 'get-threat-status'; requestId: string } // Phase 5: CVE scanning | { type: 'cve-scan'; requestId: string } diff --git a/src/main/services/cloud-agent.ts b/src/main/services/cloud-agent.ts index 0f81ea96..8061dc7b 100644 --- a/src/main/services/cloud-agent.ts +++ b/src/main/services/cloud-agent.ts @@ -44,6 +44,8 @@ import type { import type { ScanResult, CloudActionEntry, StartupSafetyResult } from '../../shared/types' import { addCloudHistoryEntry } from './cloud-history-store' import { downloadAndUpdateBlacklist, loadBlacklist } from './threat-blacklist-store' +import { fetchAndCacheRules } from './yara-rules-store' +import { resetYaraEngine } from '../ipc/malware-scanner.ipc' import { threatMonitor } from './threat-monitor' import { isLikelyFalsePositive, deduplicateCves } from './cve-filter' @@ -1519,7 +1521,7 @@ class CloudAgentService { 'privacy-scan', 'privacy-apply', 'debloater-scan', 'debloater-remove', 'service-scan', 'service-apply', 'malware-quarantine', 'malware-delete', 'registry-scan', 'registry-fix', - 'update-threat-blacklist', 'get-threat-status', + 'update-threat-blacklist', 'update-yara-rules', 'get-threat-status', 'cve-scan', ]) @@ -1714,6 +1716,9 @@ class CloudAgentService { case 'update-threat-blacklist': await this.handleUpdateThreatBlacklist(cmd.requestId, cmd.url) break + case 'update-yara-rules': + await this.handleUpdateYaraRules(cmd.requestId, cmd.url) + break case 'get-threat-status': await this.handleGetThreatStatus(cmd.requestId) break @@ -1798,6 +1803,7 @@ class CloudAgentService { case 'registry-scan': return 'Registry scan' case 'registry-fix': return `Fix ${cmd.entryIds?.length ?? 0} registry entries` case 'update-threat-blacklist': return 'Update threat blacklist' + case 'update-yara-rules': return 'Update YARA rules' case 'get-threat-status': return 'Get threat status' case 'cve-scan': return 'CVE vulnerability scan' default: return (cmd as CloudCommand).type @@ -3075,6 +3081,63 @@ class CloudAgentService { } } + // ─── YARA Rules ─────────────────────────────────────── + + private async handleUpdateYaraRules(requestId: string, url: string): Promise { + if (typeof url !== 'string' || url.length === 0 || url.length > 2000) { + await this.postCommandResult(requestId, false, undefined, 'Invalid URL') + return + } + + // SSRF validation: reuse the same private/loopback checks + try { + const parsed = new URL(url) + if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + await this.postCommandResult(requestId, false, undefined, 'Only HTTP(S) URLs allowed') + return + } + if (app.isPackaged) { + const host = parsed.hostname.toLowerCase() + if (host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1' || host === '0.0.0.0') { + await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') + return + } + if (host.startsWith('10.') || host.startsWith('192.168.') || host.startsWith('169.254.')) { + await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') + return + } + if (/^172\.(1[6-9]|2\d|3[01])\./.test(host)) { + await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') + return + } + } + } catch { + await this.postCommandResult(requestId, false, undefined, 'Invalid URL format') + return + } + + try { + await assertPublicResolution(url) + } catch (err) { + await this.postCommandResult(requestId, false, undefined, err instanceof Error ? err.message : 'DNS rebinding check failed') + return + } + + cloudLog('INFO', `Updating YARA rules from ${url}`) + const result = await fetchAndCacheRules(url) + + if (result.success) { + // Reset engine so it re-initializes with new rules on next scan + resetYaraEngine() + await this.postCommandResult(requestId, true, result.stats ? { + rulesCount: result.stats.rulesCount, + version: result.stats.version, + } : { message: 'Already up to date' }) + } else { + await this.postCommandResult(requestId, false, undefined, result.error!.slice(0, 200)) + } + } + private async handleGetThreatStatus(requestId: string): Promise { const snapshot = threatMonitor.getThreatSnapshot() if (!snapshot) { diff --git a/src/main/services/yara-engine.test.ts b/src/main/services/yara-engine.test.ts new file mode 100644 index 00000000..013edaab --- /dev/null +++ b/src/main/services/yara-engine.test.ts @@ -0,0 +1,175 @@ +import { describe, it, expect } from 'vitest' + +// ─── Test pure conversion logic (replicated to avoid Electron imports) ─── + +interface YaraMatch { + ruleName: string + metadata: { + detectionName?: string + severity?: 'critical' | 'high' | 'medium' | 'low' + details?: string + filenameOnly?: string + } + matchedStrings: string[] +} + +function yaraMatchToThreatFields(match: YaraMatch): { + detectionName: string + severity: 'critical' | 'high' | 'medium' | 'low' + details: string +} { + return { + detectionName: match.metadata.detectionName || match.ruleName.replace(/_/g, '.'), + severity: match.metadata.severity || 'high', + details: match.metadata.details || `YARA rule match: ${match.ruleName}`, + } +} + +// ─── yaraMatchToThreatFields ───────────────────────────────────── + +describe('yaraMatchToThreatFields', () => { + it('uses metadata fields when available', () => { + const match: YaraMatch = { + ruleName: 'CoinMiner_XMRig', + metadata: { + detectionName: 'CoinMiner.XMRig', + severity: 'critical', + details: 'XMRig cryptocurrency miner', + }, + matchedStrings: ['xmrig'], + } + const result = yaraMatchToThreatFields(match) + expect(result.detectionName).toBe('CoinMiner.XMRig') + expect(result.severity).toBe('critical') + expect(result.details).toBe('XMRig cryptocurrency miner') + }) + + it('falls back to rule name for detectionName when metadata missing', () => { + const match: YaraMatch = { + ruleName: 'CoinMiner_XMRig', + metadata: {}, + matchedStrings: [], + } + const result = yaraMatchToThreatFields(match) + expect(result.detectionName).toBe('CoinMiner.XMRig') + }) + + it('converts underscores to dots in rule name fallback', () => { + const match: YaraMatch = { + ruleName: 'Trojan_AgentTesla_Variant', + metadata: {}, + matchedStrings: [], + } + const result = yaraMatchToThreatFields(match) + expect(result.detectionName).toBe('Trojan.AgentTesla.Variant') + }) + + it('defaults severity to high when metadata missing', () => { + const match: YaraMatch = { + ruleName: 'Test', + metadata: {}, + matchedStrings: [], + } + const result = yaraMatchToThreatFields(match) + expect(result.severity).toBe('high') + }) + + it('defaults details to YARA rule match message', () => { + const match: YaraMatch = { + ruleName: 'RAT_DarkComet', + metadata: {}, + matchedStrings: [], + } + const result = yaraMatchToThreatFields(match) + expect(result.details).toBe('YARA rule match: RAT_DarkComet') + }) + + it('handles all severity levels', () => { + for (const sev of ['critical', 'high', 'medium', 'low'] as const) { + const match: YaraMatch = { + ruleName: 'Test', + metadata: { severity: sev }, + matchedStrings: [], + } + expect(yaraMatchToThreatFields(match).severity).toBe(sev) + } + }) +}) + +// ─── YARA rule parsing (integration-style, tests the WASM module) ── + +describe('libyara-wasm integration', () => { + it('matches a simple string rule', async () => { + const initYara = require('libyara-wasm') + const yara = await initYara() + + const rules = ` +rule Test_Simple { + meta: + detectionName = "Test.Simple" + severity = "medium" + details = "Test detection" + strings: + $a = "malware_test" nocase + condition: + $a +}` + const result = yara.run('this contains MALWARE_TEST data', rules) + expect(result.matchedRules.size()).toBe(1) + expect(result.matchedRules.get(0).ruleName).toBe('Test_Simple') + + const meta = result.matchedRules.get(0).metadata + const metaMap: Record = {} + for (let i = 0; i < meta.size(); i++) { + const m = meta.get(i) + metaMap[m.identifier] = m.data + } + expect(metaMap.detectionName).toBe('Test.Simple') + expect(metaMap.severity).toBe('medium') + }) + + it('reports compile errors for invalid rules', async () => { + const initYara = require('libyara-wasm') + const yara = await initYara() + + const result = yara.run('data', 'rule bad { invalid syntax here }') + expect(result.compileErrors.size()).toBeGreaterThan(0) + }) + + it('returns no matches for clean data', async () => { + const initYara = require('libyara-wasm') + const yara = await initYara() + + const rules = ` +rule Test_NoMatch { + strings: + $a = "this_will_not_match_anything" + condition: + $a +}` + const result = yara.run('clean file content', rules) + expect(result.matchedRules.size()).toBe(0) + }) + + it('supports hash module for SHA-256 matching', async () => { + const initYara = require('libyara-wasm') + const crypto = require('crypto') + const yara = await initYara() + + const testData = 'EICAR-STANDARD-ANTIVIRUS-TEST-FILE' + const hash = crypto.createHash('sha256').update(testData).digest('hex') + + const rules = ` +import "hash" +rule Hash_Test { + meta: + detectionName = "Test.Hash" + severity = "low" + condition: + hash.sha256(0, filesize) == "${hash}" +}` + const result = yara.run(testData, rules) + expect(result.matchedRules.size()).toBe(1) + expect(result.matchedRules.get(0).ruleName).toBe('Hash_Test') + }) +}) diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts new file mode 100644 index 00000000..6e5e98be --- /dev/null +++ b/src/main/services/yara-engine.ts @@ -0,0 +1,206 @@ +import { readFileSync } from 'fs' +import { basename } from 'path' + +// ─── Types ─────────────────────────────────────────────────── + +export interface YaraMatch { + ruleName: string + metadata: { + detectionName?: string + severity?: 'critical' | 'high' | 'medium' | 'low' + details?: string + filenameOnly?: string + } + matchedStrings: string[] +} + +/** Embind vector — iterable via .size() and .get(i) */ +interface EmbindVector { + size(): number + get(i: number): T +} + +interface LibYaraResult { + matchedRules: EmbindVector<{ + ruleName: string + metadata: EmbindVector<{ identifier: string; data: string }> + resolvedMatches: EmbindVector<{ location: number; matchLength: number; data: string }> + }> + compileErrors: EmbindVector<{ message: string; lineNumber: number; warning: boolean }> + consoleLogs: EmbindVector +} + +interface LibYaraModule { + run(data: string, rules: string): LibYaraResult +} + +// ─── Engine ────────────────────────────────────────────────── + +export class YaraEngine { + private _module: LibYaraModule | null = null + private _compiledRules: string = '' + private _ready = false + private _rulesLoaded = 0 + + /** Load the WASM module. Call once before scanning. */ + async initialize(): Promise { + try { + // libyara-wasm exports a factory that returns a ready promise + // eslint-disable-next-line @typescript-eslint/no-var-requires + const initYara = require('libyara-wasm') + this._module = await initYara() + this._ready = true + } catch (err) { + console.warn('[yara] WASM initialization failed:', err) + this._ready = false + throw err + } + } + + isReady(): boolean { + return this._ready && this._module !== null + } + + /** + * Load YARA rules from file paths and/or raw source strings. + * Returns the number of rules that compiled successfully and any errors. + */ + loadRules(ruleFilePaths: string[], extraSources: string[] = []): { loaded: number; errors: string[] } { + if (!this._module) { + return { loaded: 0, errors: ['YARA engine not initialized'] } + } + + const errors: string[] = [] + const ruleSources: string[] = [] + + for (const filePath of ruleFilePaths) { + try { + const source = readFileSync(filePath, 'utf-8') + ruleSources.push(`// File: ${basename(filePath)}\n${source}`) + } catch (err) { + errors.push(`Failed to read ${basename(filePath)}: ${err instanceof Error ? err.message : String(err)}`) + } + } + + // Append cloud / in-memory rule sources + for (const source of extraSources) { + ruleSources.push(source) + } + + if (ruleSources.length === 0) { + this._compiledRules = '' + this._rulesLoaded = 0 + return { loaded: 0, errors } + } + + // Concatenate all rules and test-compile them to check for errors + const combined = ruleSources.join('\n\n') + const testResult = this._module.run('', combined) + const compileErrors = testResult.compileErrors + + for (let i = 0; i < compileErrors.size(); i++) { + const e = compileErrors.get(i) + if (!e.warning) { + errors.push(`Compile error (line ${e.lineNumber}): ${e.message}`) + } + } + + // Even with errors, YARA may have partially compiled. + // Store the rules — the engine will use what it can. + this._compiledRules = combined + + // Count rule definitions (approximate) + const ruleCount = (combined.match(/^\s*rule\s+\w+/gm) || []).length + this._rulesLoaded = ruleCount + + return { loaded: ruleCount, errors } + } + + get rulesLoaded(): number { + return this._rulesLoaded + } + + /** + * Scan a buffer against loaded YARA rules. + * Returns matched rules with metadata. + */ + scanBuffer(buffer: Buffer): YaraMatch[] { + if (!this._module || !this._compiledRules) return [] + + try { + // libyara-wasm expects string input — convert buffer to binary string + const dataStr = buffer.toString('binary') + const result = this._module.run(dataStr, this._compiledRules) + return this._parseMatches(result) + } catch (err) { + console.warn('[yara] Scan error:', err) + return [] + } + } + + private _parseMatches(result: LibYaraResult): YaraMatch[] { + const matches: YaraMatch[] = [] + const matchedRules = result.matchedRules + + for (let i = 0; i < matchedRules.size(); i++) { + const rule = matchedRules.get(i) + + // Parse metadata into a typed map + const metadata: YaraMatch['metadata'] = {} + const meta = rule.metadata + for (let j = 0; j < meta.size(); j++) { + const m = meta.get(j) + switch (m.identifier) { + case 'detectionName': metadata.detectionName = m.data; break + case 'severity': metadata.severity = m.data as YaraMatch['metadata']['severity']; break + case 'details': metadata.details = m.data; break + case 'filenameOnly': metadata.filenameOnly = m.data; break + } + } + + // Collect matched strings + const matchedStrings: string[] = [] + const resolved = rule.resolvedMatches + for (let j = 0; j < resolved.size(); j++) { + matchedStrings.push(resolved.get(j).data) + } + + matches.push({ + ruleName: rule.ruleName, + metadata, + matchedStrings, + }) + } + + return matches + } + + dispose(): void { + this._module = null + this._compiledRules = '' + this._ready = false + this._rulesLoaded = 0 + } +} + +// ─── Factory ───────────────────────────────────────────────── + +export function createYaraEngine(): YaraEngine { + return new YaraEngine() +} + +/** + * Convert a YaraMatch to the metadata fields used by MalwareThreat. + * Pure function — safe for testing without Electron. + */ +export function yaraMatchToThreatFields(match: YaraMatch): { + detectionName: string + severity: 'critical' | 'high' | 'medium' | 'low' + details: string +} { + return { + detectionName: match.metadata.detectionName || match.ruleName.replace(/_/g, '.'), + severity: match.metadata.severity || 'high', + details: match.metadata.details || `YARA rule match: ${match.ruleName}`, + } +} diff --git a/src/main/services/yara-rules-store.test.ts b/src/main/services/yara-rules-store.test.ts new file mode 100644 index 00000000..13975ad6 --- /dev/null +++ b/src/main/services/yara-rules-store.test.ts @@ -0,0 +1,299 @@ +import { describe, it, expect } from 'vitest' +import { createHash } from 'crypto' + +// ─── Replicate pure validation logic to avoid Electron imports ─── + +const MAX_RULE_CONTENT_BYTES = 1 * 1024 * 1024 +const MAX_RULE_COUNT = 10_000 + +interface YaraRuleFile { + filename: string + content: string +} + +interface YaraRuleBundle { + version: string + updatedAt: string + sha256: string + rules: YaraRuleFile[] +} + +function validateRuleBundle(raw: unknown): YaraRuleBundle | null { + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return null + + const obj = raw as Record + if (typeof obj.version !== 'string' || obj.version.length === 0 || obj.version.length > 100) return null + if (typeof obj.updatedAt !== 'string' || obj.updatedAt.length === 0 || obj.updatedAt.length > 100) return null + if (typeof obj.sha256 !== 'string' || obj.sha256.length === 0 || obj.sha256.length > 128) return null + + if (!Array.isArray(obj.rules) || obj.rules.length === 0 || obj.rules.length > MAX_RULE_COUNT) return null + + const rules: YaraRuleFile[] = [] + for (const item of obj.rules) { + if (typeof item !== 'object' || item === null) return null + const entry = item as Record + if (typeof entry.filename !== 'string' || !entry.filename.endsWith('.yar')) return null + if (typeof entry.content !== 'string' || entry.content.length === 0) return null + if (entry.content.length > MAX_RULE_CONTENT_BYTES) return null + if (entry.filename.includes('/') || entry.filename.includes('\\') || entry.filename.includes('..')) return null + rules.push({ filename: entry.filename, content: entry.content }) + } + + return { + version: obj.version, + updatedAt: obj.updatedAt, + sha256: obj.sha256, + rules, + } +} + +function computeBundleHash(rules: YaraRuleFile[]): string { + const sorted = [...rules].sort((a, b) => a.filename.localeCompare(b.filename)) + const combined = sorted.map(r => r.content).join('') + return createHash('sha256').update(combined).digest('hex') +} + +// ─── validateRuleBundle ────────────────────────────────────── + +describe('validateRuleBundle', () => { + const validBundle = { + version: '1.0.0', + updatedAt: '2026-03-28T12:00:00Z', + sha256: 'abc123', + rules: [ + { filename: 'miners.yar', content: 'rule Test { condition: true }' }, + ], + } + + it('accepts a valid bundle', () => { + expect(validateRuleBundle(validBundle)).not.toBeNull() + }) + + it('returns correct fields', () => { + const result = validateRuleBundle(validBundle)! + expect(result.version).toBe('1.0.0') + expect(result.updatedAt).toBe('2026-03-28T12:00:00Z') + expect(result.sha256).toBe('abc123') + expect(result.rules).toHaveLength(1) + expect(result.rules[0].filename).toBe('miners.yar') + }) + + it('rejects null', () => { + expect(validateRuleBundle(null)).toBeNull() + }) + + it('rejects arrays', () => { + expect(validateRuleBundle([1, 2, 3])).toBeNull() + }) + + it('rejects non-objects', () => { + expect(validateRuleBundle('string')).toBeNull() + expect(validateRuleBundle(42)).toBeNull() + }) + + it('rejects missing version', () => { + const { version, ...rest } = validBundle + expect(validateRuleBundle(rest)).toBeNull() + }) + + it('rejects empty version', () => { + expect(validateRuleBundle({ ...validBundle, version: '' })).toBeNull() + }) + + it('rejects missing updatedAt', () => { + const { updatedAt, ...rest } = validBundle + expect(validateRuleBundle(rest)).toBeNull() + }) + + it('rejects missing sha256', () => { + const { sha256, ...rest } = validBundle + expect(validateRuleBundle(rest)).toBeNull() + }) + + it('rejects empty rules array', () => { + expect(validateRuleBundle({ ...validBundle, rules: [] })).toBeNull() + }) + + it('rejects non-array rules', () => { + expect(validateRuleBundle({ ...validBundle, rules: 'not an array' })).toBeNull() + }) + + it('rejects rules without .yar extension', () => { + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'test.txt', content: 'rule Test { condition: true }' }], + })).toBeNull() + }) + + it('rejects rules with empty content', () => { + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'test.yar', content: '' }], + })).toBeNull() + }) + + it('rejects path traversal in filename', () => { + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: '../evil.yar', content: 'rule X { condition: true }' }], + })).toBeNull() + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'sub/test.yar', content: 'rule X { condition: true }' }], + })).toBeNull() + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'sub\\test.yar', content: 'rule X { condition: true }' }], + })).toBeNull() + }) + + it('rejects rules exceeding content size limit', () => { + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'big.yar', content: 'x'.repeat(MAX_RULE_CONTENT_BYTES + 1) }], + })).toBeNull() + }) + + it('accepts rules at the content size limit', () => { + expect(validateRuleBundle({ + ...validBundle, + rules: [{ filename: 'big.yar', content: 'x'.repeat(MAX_RULE_CONTENT_BYTES) }], + })).not.toBeNull() + }) +}) + +// ─── computeBundleHash ─────────────────────────────────────── + +describe('computeBundleHash', () => { + it('produces consistent hashes for the same content', () => { + const rules: YaraRuleFile[] = [ + { filename: 'a.yar', content: 'rule A { condition: true }' }, + { filename: 'b.yar', content: 'rule B { condition: true }' }, + ] + expect(computeBundleHash(rules)).toBe(computeBundleHash(rules)) + }) + + it('sorts by filename before hashing (order-independent)', () => { + const rules1: YaraRuleFile[] = [ + { filename: 'b.yar', content: 'rule B { condition: true }' }, + { filename: 'a.yar', content: 'rule A { condition: true }' }, + ] + const rules2: YaraRuleFile[] = [ + { filename: 'a.yar', content: 'rule A { condition: true }' }, + { filename: 'b.yar', content: 'rule B { condition: true }' }, + ] + expect(computeBundleHash(rules1)).toBe(computeBundleHash(rules2)) + }) + + it('produces different hashes for different content', () => { + const rules1: YaraRuleFile[] = [ + { filename: 'a.yar', content: 'rule A { condition: true }' }, + ] + const rules2: YaraRuleFile[] = [ + { filename: 'a.yar', content: 'rule B { condition: false }' }, + ] + expect(computeBundleHash(rules1)).not.toBe(computeBundleHash(rules2)) + }) + + it('returns a valid SHA-256 hex string', () => { + const rules: YaraRuleFile[] = [ + { filename: 'test.yar', content: 'rule Test { condition: true }' }, + ] + const hash = computeBundleHash(rules) + expect(hash).toMatch(/^[0-9a-f]{64}$/) + }) +}) + +// ─── Integrity verification ────────────────────────────────── + +describe('bundle integrity verification', () => { + it('validates correctly when sha256 matches computed hash', () => { + const rules: YaraRuleFile[] = [ + { filename: 'test.yar', content: 'rule Test { condition: true }' }, + ] + const sha256 = computeBundleHash(rules) + const bundle = validateRuleBundle({ + version: '1.0.0', + updatedAt: '2026-03-28T12:00:00Z', + sha256, + rules, + }) + expect(bundle).not.toBeNull() + expect(computeBundleHash(bundle!.rules)).toBe(sha256) + }) + + it('detects tampered content via hash mismatch', () => { + const rules: YaraRuleFile[] = [ + { filename: 'test.yar', content: 'rule Test { condition: true }' }, + ] + const sha256 = computeBundleHash(rules) + // Tamper with the content + const tamperedRules: YaraRuleFile[] = [ + { filename: 'test.yar', content: 'rule Malicious { condition: true }' }, + ] + expect(computeBundleHash(tamperedRules)).not.toBe(sha256) + }) +}) + +// ─── Metadata validation ───────────────────────────────────── + +describe('metadata validation', () => { + function validateMetadata(raw: unknown): boolean { + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return false + const obj = raw as Record + return ( + typeof obj.version === 'string' && obj.version.length > 0 && obj.version.length <= 100 && + typeof obj.updatedAt === 'string' && obj.updatedAt.length > 0 && obj.updatedAt.length <= 100 && + typeof obj.rulesCount === 'number' && obj.rulesCount >= 0 && + typeof obj.sha256 === 'string' && obj.sha256.length > 0 && obj.sha256.length <= 128 + ) + } + + it('accepts valid metadata', () => { + expect(validateMetadata({ + version: '1.0.0', + updatedAt: '2026-03-28T12:00:00Z', + rulesCount: 50, + sha256: 'abc123', + })).toBe(true) + }) + + it('rejects null', () => { + expect(validateMetadata(null)).toBe(false) + }) + + it('rejects missing version', () => { + expect(validateMetadata({ + updatedAt: '2026-03-28T12:00:00Z', + rulesCount: 50, + sha256: 'abc123', + })).toBe(false) + }) + + it('rejects empty version', () => { + expect(validateMetadata({ + version: '', + updatedAt: '2026-03-28T12:00:00Z', + rulesCount: 50, + sha256: 'abc123', + })).toBe(false) + }) + + it('rejects negative rulesCount', () => { + expect(validateMetadata({ + version: '1.0.0', + updatedAt: '2026-03-28T12:00:00Z', + rulesCount: -1, + sha256: 'abc123', + })).toBe(false) + }) + + it('rejects non-number rulesCount', () => { + expect(validateMetadata({ + version: '1.0.0', + updatedAt: '2026-03-28T12:00:00Z', + rulesCount: '50', + sha256: 'abc123', + })).toBe(false) + }) +}) diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts new file mode 100644 index 00000000..292dc3a8 --- /dev/null +++ b/src/main/services/yara-rules-store.ts @@ -0,0 +1,281 @@ +import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, readdirSync } from 'fs' +import { join, basename } from 'path' +import { createHash } from 'crypto' +import { app } from 'electron' + +// ─── Constants ─────────────────────────────────────────────── + +const MAX_DOWNLOAD_BYTES = 50 * 1024 * 1024 // 50 MB +const MAX_RULE_CONTENT_BYTES = 1 * 1024 * 1024 // 1 MB per rule file +const MAX_RULE_COUNT = 10_000 +const DOWNLOAD_TIMEOUT_MS = 60_000 +const DEFAULT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000 // 6 hours +const RULES_ENDPOINT = '/api/yara-rules' + +// ─── Types ─────────────────────────────────────────────────── + +export interface YaraRuleFile { + filename: string + content: string +} + +export interface YaraRuleBundle { + version: string + updatedAt: string + sha256: string + rules: YaraRuleFile[] +} + +interface YaraRulesMetadata { + version: string + updatedAt: string + rulesCount: number + sha256: string +} + +// ─── Paths ─────────────────────────────────────────────────── + +let _dataDir: string | null = null + +function getDataDir(): string { + if (!_dataDir) { + _dataDir = app.isPackaged + ? app.getPath('userData') + : join(app.getPath('userData'), 'Kudu-Dev') + } + return _dataDir +} + +function getCachedRulesDir(): string { + return join(getDataDir(), 'yara-rules') +} + +function getMetadataPath(): string { + return join(getCachedRulesDir(), 'metadata.json') +} + +// ─── Cached rule files (downloaded from cloud, persisted to disk) ── + +/** Get paths to cached YARA rule files. */ +export function getCachedRulePaths(): string[] { + try { + const dir = getCachedRulesDir() + if (!existsSync(dir)) return [] + return readdirSync(dir) + .filter(f => f.endsWith('.yar')) + .sort() + .map(f => join(dir, f)) + } catch { + return [] + } +} + +export function getRulesMetadata(): YaraRulesMetadata | null { + try { + const path = getMetadataPath() + if (!existsSync(path)) return null + const raw = readFileSync(path, 'utf-8') + const parsed = JSON.parse(raw) + if (!validateMetadata(parsed)) return null + return parsed as YaraRulesMetadata + } catch { + return null + } +} + +function validateMetadata(raw: unknown): boolean { + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return false + const obj = raw as Record + return ( + typeof obj.version === 'string' && obj.version.length > 0 && obj.version.length <= 100 && + typeof obj.updatedAt === 'string' && obj.updatedAt.length > 0 && obj.updatedAt.length <= 100 && + typeof obj.rulesCount === 'number' && obj.rulesCount >= 0 && + typeof obj.sha256 === 'string' && obj.sha256.length > 0 && obj.sha256.length <= 128 + ) +} + +function saveMetadata(meta: YaraRulesMetadata): void { + const dir = getCachedRulesDir() + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) + + const path = getMetadataPath() + const tmpPath = path + '.tmp' + writeFileSync(tmpPath, JSON.stringify(meta, null, 2), 'utf-8') + renameSync(tmpPath, path) +} + +// ─── Bundle validation ─────────────────────────────────────── + +export function validateRuleBundle(raw: unknown): YaraRuleBundle | null { + if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return null + + const obj = raw as Record + if (typeof obj.version !== 'string' || obj.version.length === 0 || obj.version.length > 100) return null + if (typeof obj.updatedAt !== 'string' || obj.updatedAt.length === 0 || obj.updatedAt.length > 100) return null + if (typeof obj.sha256 !== 'string' || obj.sha256.length === 0 || obj.sha256.length > 128) return null + + if (!Array.isArray(obj.rules) || obj.rules.length === 0 || obj.rules.length > MAX_RULE_COUNT) return null + + const rules: YaraRuleFile[] = [] + for (const item of obj.rules) { + if (typeof item !== 'object' || item === null) return null + const entry = item as Record + if (typeof entry.filename !== 'string' || !entry.filename.endsWith('.yar')) return null + if (typeof entry.content !== 'string' || entry.content.length === 0) return null + if (entry.content.length > MAX_RULE_CONTENT_BYTES) return null + if (entry.filename.includes('/') || entry.filename.includes('\\') || entry.filename.includes('..')) return null + rules.push({ filename: entry.filename, content: entry.content }) + } + + return { + version: obj.version, + updatedAt: obj.updatedAt, + sha256: obj.sha256, + rules, + } +} + +/** + * Compute the expected SHA-256 hash for a rule bundle. + * Hash is over concatenated content fields, sorted by filename. + */ +export function computeBundleHash(rules: YaraRuleFile[]): string { + const sorted = [...rules].sort((a, b) => a.filename.localeCompare(b.filename)) + const combined = sorted.map(r => r.content).join('') + return createHash('sha256').update(combined).digest('hex') +} + +// ─── Cloud fetch + disk caching ────────────────────────────── + +/** + * Fetch YARA rules from a URL, validate integrity, and cache to disk. + * Sends X-Kudu-Rules-Version header so the server can return 304 if current. + */ +export async function fetchAndCacheRules(url: string): Promise<{ + success: boolean + error?: string + stats?: { rulesCount: number; version: string } +}> { + try { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS) + + let response: Response + try { + const meta = getRulesMetadata() + const headers: Record = { 'Accept': 'application/json' } + if (meta) headers['X-Kudu-Rules-Version'] = meta.version + + response = await fetch(url, { signal: controller.signal, headers }) + } finally { + clearTimeout(timeout) + } + + // 304 = already up to date + if (response.status === 304) { + return { success: true } + } + + if (!response.ok) { + return { success: false, error: `Download failed: HTTP ${response.status}` } + } + + const contentLength = response.headers.get('content-length') + if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) { + return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } + } + + const text = await response.text() + if (text.length > MAX_DOWNLOAD_BYTES) { + return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } + } + + let parsed: unknown + try { + parsed = JSON.parse(text) + } catch { + return { success: false, error: 'Invalid rules bundle: JSON parse error' } + } + + const bundle = validateRuleBundle(parsed) + if (!bundle) { + return { success: false, error: 'Invalid rules bundle: validation failed' } + } + + // Verify integrity + const computedHash = computeBundleHash(bundle.rules) + if (computedHash !== bundle.sha256) { + return { success: false, error: 'Integrity check failed: SHA-256 mismatch' } + } + + // Write rules to disk cache + const dir = getCachedRulesDir() + if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) + + for (const rule of bundle.rules) { + const filePath = join(dir, rule.filename) + const tmpPath = filePath + '.tmp' + writeFileSync(tmpPath, rule.content, 'utf-8') + renameSync(tmpPath, filePath) + } + + saveMetadata({ + version: bundle.version, + updatedAt: bundle.updatedAt, + rulesCount: bundle.rules.length, + sha256: bundle.sha256, + }) + + return { + success: true, + stats: { rulesCount: bundle.rules.length, version: bundle.version }, + } + } catch (err) { + const msg = err instanceof Error ? err.message : 'Unknown error' + return { success: false, error: `Download failed: ${msg}`.slice(0, 200) } + } +} + +// ─── Periodic rule updates ─────────────────────────────────── + +let _checkInterval: ReturnType | null = null +let _onRulesUpdated: (() => void) | null = null + +/** + * Start periodic checks for new YARA rules from the cloud. + * @param serverUrl Base URL of the Kudu cloud server + * @param onUpdated Callback fired when new rules are downloaded (so the engine can reload) + * @param intervalMs How often to check (default: 6 hours) + */ +export function startPeriodicRuleChecks( + serverUrl: string, + onUpdated: () => void, + intervalMs: number = DEFAULT_CHECK_INTERVAL_MS, +): void { + stopPeriodicRuleChecks() + _onRulesUpdated = onUpdated + + const check = async () => { + try { + const result = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) + if (result.success && result.stats) { + console.log(`[yara] Updated rules to v${result.stats.version} (${result.stats.rulesCount} rules)`) + _onRulesUpdated?.() + } + } catch (err) { + console.warn('[yara] Periodic rule check failed:', err) + } + } + + // Run first check after a short delay (let app finish initializing) + setTimeout(check, 30_000) + _checkInterval = setInterval(check, intervalMs) +} + +export function stopPeriodicRuleChecks(): void { + if (_checkInterval) { + clearInterval(_checkInterval) + _checkInterval = null + } + _onRulesUpdated = null +} From c0bcdd29424129174c8cabd72262165a35d28f57 Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 28 Mar 2026 19:55:04 +0200 Subject: [PATCH 02/35] =?UTF-8?q?fix(malware):=20address=20codex=20review?= =?UTF-8?q?=20=E2=80=94=20filename=20checks,=20compile=20validation,=20sta?= =?UTF-8?q?le=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Always run SUSPICIOUS_FILENAMES checks regardless of YARA availability, so masquerading files (e.g. svchost.exe outside System32) are caught even when YARA is active - Detect when all YARA rules fail to compile and report loaded=0 so the regex fallback activates instead of scanning with an empty ruleset - Remove stale .yar files from disk cache when a new bundle arrives, so rules deleted server-side stop being enforced locally Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 91 +++++++++++++-------------- src/main/services/yara-engine.ts | 18 ++++-- src/main/services/yara-rules-store.ts | 12 +++- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 7ed59465..b8226ff7 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1259,59 +1259,58 @@ export async function scanMalware( fileBuffer = await readFile(filePath).catch(() => null) } + // ── Suspicious filename check (always runs, regardless of YARA) ── + const dirLower = filePath.toLowerCase().replace(/\\/g, '/') + const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) + + if (!isInSystemDir && process.platform === 'win32') { + for (const sus of SUSPICIOUS_FILENAMES) { + if (sus.pattern.test(fileName)) { + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: sus.name, + severity: sus.severity, + source: 'signature', + details: sus.details, + selected: true + }) + sigThreatsCount++ + break + } + } + } + // ── Signature matching: YARA or regex fallback ── if (yaraEngine && fileBuffer) { // YARA scans file content against all compiled rules (replaces regex + hash checks) - const matches = yaraEngine.scanBuffer(fileBuffer) - for (const match of matches) { - if (threats.some(t => t.path === filePath)) break - - // Skip filename-only rules (filenameOnly metadata) for files in system directories - if (match.metadata.filenameOnly === 'true') { - const dirLower = filePath.toLowerCase().replace(/\\/g, '/') - const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) - if (isInSystemDir || process.platform !== 'win32') continue - } + if (!threats.some(t => t.path === filePath)) { + const matches = yaraEngine.scanBuffer(fileBuffer) + for (const match of matches) { + if (threats.some(t => t.path === filePath)) break - const fields = yaraMatchToThreatFields(match) - threats.push({ - id: randomUUID(), - path: filePath, - fileName, - size: fileSize, - detectionName: fields.detectionName, - severity: fields.severity, - source: 'signature', - details: fields.details, - selected: true, - }) - sigThreatsCount++ - } - } else { - // Fallback: regex pattern matching + hash lookup (used when YARA is unavailable) - const dirLower = filePath.toLowerCase().replace(/\\/g, '/') - const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) + // Skip filename-only rules — already handled by SUSPICIOUS_FILENAMES above + if (match.metadata.filenameOnly === 'true') continue - if (!isInSystemDir && process.platform === 'win32') { - for (const sus of SUSPICIOUS_FILENAMES) { - if (sus.pattern.test(fileName)) { - threats.push({ - id: randomUUID(), - path: filePath, - fileName, - size: fileSize, - detectionName: sus.name, - severity: sus.severity, - source: 'signature', - details: sus.details, - selected: true - }) - sigThreatsCount++ - break - } + const fields = yaraMatchToThreatFields(match) + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: fields.detectionName, + severity: fields.severity, + source: 'signature', + details: fields.details, + selected: true, + }) + sigThreatsCount++ } } - + } else if (!threats.some(t => t.path === filePath)) { + // Fallback: regex pattern matching + hash lookup (used when YARA is unavailable) for (const mal of KNOWN_MALWARE_PATTERNS) { if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { if (!threats.some(t => t.path === filePath)) { diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 6e5e98be..736e926c 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -98,19 +98,27 @@ export class YaraEngine { const testResult = this._module.run('', combined) const compileErrors = testResult.compileErrors + let nonWarningErrors = 0 for (let i = 0; i < compileErrors.size(); i++) { const e = compileErrors.get(i) if (!e.warning) { errors.push(`Compile error (line ${e.lineNumber}): ${e.message}`) + nonWarningErrors++ } } - // Even with errors, YARA may have partially compiled. - // Store the rules — the engine will use what it can. - this._compiledRules = combined - - // Count rule definitions (approximate) + // Count rule declarations in source text const ruleCount = (combined.match(/^\s*rule\s+\w+/gm) || []).length + + // If every rule had a compile error, nothing actually compiled — report 0 + if (nonWarningErrors > 0 && nonWarningErrors >= ruleCount) { + this._compiledRules = '' + this._rulesLoaded = 0 + return { loaded: 0, errors } + } + + // Even with partial errors, YARA uses what it can. + this._compiledRules = combined this._rulesLoaded = ruleCount return { loaded: ruleCount, errors } diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 292dc3a8..067f24c5 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync, readdirSync } from 'fs' +import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync, readdirSync } from 'fs' import { join, basename } from 'path' import { createHash } from 'crypto' import { app } from 'electron' @@ -212,6 +212,16 @@ export async function fetchAndCacheRules(url: string): Promise<{ const dir = getCachedRulesDir() if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) + // Remove stale .yar files not present in the new bundle + const newFilenames = new Set(bundle.rules.map(r => r.filename)) + try { + for (const existing of readdirSync(dir)) { + if (existing.endsWith('.yar') && !newFilenames.has(existing)) { + try { unlinkSync(join(dir, existing)) } catch { /* best effort */ } + } + } + } catch { /* directory read failed — not critical */ } + for (const rule of bundle.rules) { const filePath = join(dir, rule.filename) const tmpPath = filePath + '.tmp' From 14bd22c0a8f4612344423c6816becdb71521fb4e Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 28 Mar 2026 20:00:06 +0200 Subject: [PATCH 03/35] feat(build): fetch and bundle YARA rules at build time for offline installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a CI step that fetches the latest YARA rules from the cloud API during release builds and bundles them as extraResources. Users who install without internet access get the rules that were current at build time. Cloud-cached rules still override bundled ones at runtime. - Add scripts/fetch-yara-rules.js — fetches rules, validates SHA-256, writes to resources/yara-rules/ (gracefully skips if API unreachable) - Add "Fetch latest YARA rules" step to release.yml before build - Re-add bundled rule support: getBundledRulePaths() + getAllRulePaths() with cached-overrides-bundled precedence - Add resources/yara-rules/ to .gitignore (CI-generated, not committed) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/release.yml | 5 ++ .gitignore | 1 + electron-builder.yml | 4 + scripts/fetch-yara-rules.js | 100 +++++++++++++++++++++ src/main/ipc/malware-scanner.ipc.ts | 4 +- src/main/services/yara-rules-store.test.ts | 48 ++++++++++ src/main/services/yara-rules-store.ts | 42 ++++++++- 7 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 scripts/fetch-yara-rules.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a03132f0..77912a93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,6 +86,11 @@ jobs: creds: ${{ secrets.AZURE_CREDENTIALS }} allow-no-subscriptions: true + - name: Fetch latest YARA rules + run: node scripts/fetch-yara-rules.js + env: + KUDU_RULES_URL: ${{ secrets.KUDU_RULES_URL || 'https://cloud.usekudu.com/api/yara-rules' }} + - name: Build, package, and upload shell: bash env: diff --git a/.gitignore b/.gitignore index e1b40a2c..b519224a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Thumbs.db cloud/ CLOUD-COMMANDS.md manifests/ +resources/yara-rules/ diff --git a/electron-builder.yml b/electron-builder.yml index 46ce540a..43cc6fec 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -22,6 +22,10 @@ extraResources: to: icons filter: - "**/*" + - from: resources/yara-rules + to: yara-rules + filter: + - "**/*.yar" win: target: diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js new file mode 100644 index 00000000..e99fb4f3 --- /dev/null +++ b/scripts/fetch-yara-rules.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node + +/** + * Fetch the latest YARA rules from the Kudu cloud API and write them + * to resources/yara-rules/ so they get bundled with the installer. + * + * Usage: + * node scripts/fetch-yara-rules.js # uses default URL + * KUDU_RULES_URL=https://... node scripts/fetch-yara-rules.js + * + * If the API is unreachable, the script exits with code 0 and a warning — + * the build will succeed without bundled rules (the app still works via + * the regex fallback and will fetch rules from the cloud at runtime). + */ + +const { writeFileSync, mkdirSync, existsSync } = require('fs') +const { join } = require('path') +const { createHash } = require('crypto') + +const RULES_URL = process.env.KUDU_RULES_URL || 'https://cloud.usekudu.com/api/yara-rules' +const OUT_DIR = join(__dirname, '..', 'resources', 'yara-rules') +const TIMEOUT_MS = 30_000 + +async function main() { + console.log(`[fetch-yara-rules] Fetching from ${RULES_URL}`) + + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS) + + let response + try { + response = await fetch(RULES_URL, { + signal: controller.signal, + headers: { 'Accept': 'application/json' }, + }) + } catch (err) { + console.warn(`[fetch-yara-rules] Could not reach API — skipping bundled rules (${err.message})`) + ensureEmptyDir() + return + } finally { + clearTimeout(timeout) + } + + if (!response.ok) { + console.warn(`[fetch-yara-rules] API returned ${response.status} — skipping bundled rules`) + ensureEmptyDir() + return + } + + let body + try { + body = await response.json() + } catch { + console.warn('[fetch-yara-rules] Invalid JSON response — skipping bundled rules') + ensureEmptyDir() + return + } + + if (!body.rules || !Array.isArray(body.rules) || body.rules.length === 0) { + console.warn('[fetch-yara-rules] No rules in response — skipping bundled rules') + ensureEmptyDir() + return + } + + // Verify integrity if sha256 is present + if (body.sha256) { + const sorted = [...body.rules].sort((a, b) => a.filename.localeCompare(b.filename)) + const combined = sorted.map(r => r.content).join('') + const computed = createHash('sha256').update(combined).digest('hex') + if (computed !== body.sha256) { + console.error(`[fetch-yara-rules] SHA-256 mismatch — expected ${body.sha256}, got ${computed}`) + process.exit(1) + } + } + + // Write rules + if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true }) + + let count = 0 + for (const rule of body.rules) { + if (!rule.filename || !rule.content) continue + if (!rule.filename.endsWith('.yar')) continue + if (rule.filename.includes('/') || rule.filename.includes('\\') || rule.filename.includes('..')) continue + + writeFileSync(join(OUT_DIR, rule.filename), rule.content, 'utf-8') + count++ + } + + console.log(`[fetch-yara-rules] Wrote ${count} rule files to resources/yara-rules/ (v${body.version || 'unknown'})`) +} + +function ensureEmptyDir() { + // Create the directory so electron-builder doesn't fail on missing extraResources + if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true }) +} + +main().catch(err => { + console.warn(`[fetch-yara-rules] Unexpected error — skipping bundled rules (${err.message})`) + ensureEmptyDir() +}) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index b8226ff7..7c7eb6ea 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -19,7 +19,7 @@ import { psUtf8, execNativeUtf8 } from '../services/exec-utf8' import { getSettings } from '../services/settings-store' import { isExcluded } from '../services/file-utils' import { createYaraEngine, yaraMatchToThreatFields, type YaraEngine } from '../services/yara-engine' -import { getCachedRulePaths, startPeriodicRuleChecks } from '../services/yara-rules-store' +import { getAllRulePaths, startPeriodicRuleChecks } from '../services/yara-rules-store' const execFileAsync = promisify(execFile) @@ -238,7 +238,7 @@ async function getYaraEngine(): Promise { try { _yaraEngine = createYaraEngine() await _yaraEngine.initialize() - const ruleFiles = getCachedRulePaths() + const ruleFiles = getAllRulePaths() const result = _yaraEngine.loadRules(ruleFiles) console.log(`[yara] Loaded ${result.loaded} rules from ${ruleFiles.length} files`) if (result.errors.length > 0) { diff --git a/src/main/services/yara-rules-store.test.ts b/src/main/services/yara-rules-store.test.ts index 13975ad6..17cce971 100644 --- a/src/main/services/yara-rules-store.test.ts +++ b/src/main/services/yara-rules-store.test.ts @@ -235,6 +235,54 @@ describe('bundle integrity verification', () => { }) }) +// ─── Rule file precedence (bundled vs cached) ─────────────── + +describe('rule file precedence', () => { + // Replicate the merging logic from getAllRulePaths + function mergeRulePaths(bundled: string[], cached: string[]): string[] { + if (cached.length === 0) return bundled + if (bundled.length === 0) return cached + const cachedNames = new Set(cached.map(p => { + const parts = p.replace(/\\/g, '/').split('/') + return parts[parts.length - 1] + })) + const merged = bundled.filter(p => { + const parts = p.replace(/\\/g, '/').split('/') + return !cachedNames.has(parts[parts.length - 1]) + }) + return [...merged, ...cached] + } + + it('returns bundled when no cached rules exist', () => { + const bundled = ['/app/resources/miners.yar', '/app/resources/rats.yar'] + expect(mergeRulePaths(bundled, [])).toEqual(bundled) + }) + + it('returns cached when no bundled rules exist', () => { + const cached = ['/data/miners.yar', '/data/rats.yar'] + expect(mergeRulePaths([], cached)).toEqual(cached) + }) + + it('cached rules override bundled with same filename', () => { + const bundled = ['/app/resources/miners.yar', '/app/resources/rats.yar'] + const cached = ['/data/miners.yar'] + const result = mergeRulePaths(bundled, cached) + expect(result).toContain('/app/resources/rats.yar') + expect(result).toContain('/data/miners.yar') + expect(result).not.toContain('/app/resources/miners.yar') + expect(result).toHaveLength(2) + }) + + it('includes new cached rules not in bundled', () => { + const bundled = ['/app/resources/miners.yar'] + const cached = ['/data/custom.yar'] + const result = mergeRulePaths(bundled, cached) + expect(result).toContain('/app/resources/miners.yar') + expect(result).toContain('/data/custom.yar') + expect(result).toHaveLength(2) + }) +}) + // ─── Metadata validation ───────────────────────────────────── describe('metadata validation', () => { diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 067f24c5..7db37f3c 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -54,12 +54,17 @@ function getMetadataPath(): string { return join(getCachedRulesDir(), 'metadata.json') } -// ─── Cached rule files (downloaded from cloud, persisted to disk) ── +// ─── Bundled rules (fetched at build time, shipped with the installer) ── -/** Get paths to cached YARA rule files. */ -export function getCachedRulePaths(): string[] { +function getBundledRulesDir(): string { + return app.isPackaged + ? join(process.resourcesPath, 'yara-rules') + : join(__dirname, '../../resources/yara-rules') +} + +/** List .yar files in a directory. */ +function listYarFiles(dir: string): string[] { try { - const dir = getCachedRulesDir() if (!existsSync(dir)) return [] return readdirSync(dir) .filter(f => f.endsWith('.yar')) @@ -70,6 +75,35 @@ export function getCachedRulePaths(): string[] { } } +/** Get paths to bundled YARA rule files (shipped with the app). */ +export function getBundledRulePaths(): string[] { + return listYarFiles(getBundledRulesDir()) +} + +// ─── Cached rule files (downloaded from cloud, persisted to disk) ── + +/** Get paths to cached YARA rule files. */ +export function getCachedRulePaths(): string[] { + return listYarFiles(getCachedRulesDir()) +} + +/** + * Get all YARA rule file paths. + * Cached (cloud-downloaded) rules override bundled ones by filename, + * so cloud updates supersede the version that shipped with the installer. + */ +export function getAllRulePaths(): string[] { + const bundled = getBundledRulePaths() + const cached = getCachedRulePaths() + + if (cached.length === 0) return bundled + if (bundled.length === 0) return cached + + const cachedNames = new Set(cached.map(p => basename(p))) + const merged = bundled.filter(p => !cachedNames.has(basename(p))) + return [...merged, ...cached] +} + export function getRulesMetadata(): YaraRulesMetadata | null { try { const path = getMetadataPath() From 90bb71e699f4e45337cd4b959e75e4fa55816644 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 02:22:53 +0200 Subject: [PATCH 04/35] =?UTF-8?q?fix(malware):=20address=20second=20codex?= =?UTF-8?q?=20review=20=E2=80=94=20init=20race,=20severity=20validation,?= =?UTF-8?q?=20stale=20bundled=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use a shared promise for YARA engine initialization so concurrent callers await the same init rather than getting a half-initialized singleton - Validate YARA severity metadata against the allowed set (critical|high|medium|low), clamping unknown values to 'high' - Clear stale .yar files in fetch-yara-rules.js before writing new bundle so removed rules don't persist across builds Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/fetch-yara-rules.js | 9 +++++++-- src/main/ipc/malware-scanner.ipc.ts | 26 +++++++++++++++----------- src/main/services/yara-engine.test.ts | 15 ++++++++++++++- src/main/services/yara-engine.ts | 6 +++++- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js index e99fb4f3..b888b2c9 100644 --- a/scripts/fetch-yara-rules.js +++ b/scripts/fetch-yara-rules.js @@ -13,7 +13,7 @@ * the regex fallback and will fetch rules from the cloud at runtime). */ -const { writeFileSync, mkdirSync, existsSync } = require('fs') +const { writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } = require('fs') const { join } = require('path') const { createHash } = require('crypto') @@ -73,8 +73,13 @@ async function main() { } } - // Write rules + // Write rules — clear stale .yar files first so removed rules don't persist if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true }) + for (const existing of readdirSync(OUT_DIR)) { + if (existing.endsWith('.yar')) { + try { unlinkSync(join(OUT_DIR, existing)) } catch { /* best effort */ } + } + } let count = 0 for (const rule of body.rules) { diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 7c7eb6ea..7f531c74 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -230,29 +230,33 @@ const TRUSTED_PATHS = [ // ─── YARA engine (lazy-initialized singleton) ──────────────── let _yaraEngine: YaraEngine | null = null -let _yaraInitAttempted = false +let _yaraInitPromise: Promise | null = null async function getYaraEngine(): Promise { - if (_yaraInitAttempted) return _yaraEngine - _yaraInitAttempted = true + if (_yaraInitPromise) return _yaraInitPromise + _yaraInitPromise = _initYaraEngine() + return _yaraInitPromise +} + +async function _initYaraEngine(): Promise { try { - _yaraEngine = createYaraEngine() - await _yaraEngine.initialize() + const engine = createYaraEngine() + await engine.initialize() const ruleFiles = getAllRulePaths() - const result = _yaraEngine.loadRules(ruleFiles) + const result = engine.loadRules(ruleFiles) console.log(`[yara] Loaded ${result.loaded} rules from ${ruleFiles.length} files`) if (result.errors.length > 0) { console.warn('[yara] Rule load warnings:', result.errors) } if (result.loaded === 0) { console.warn('[yara] No rules loaded — falling back to regex patterns') - _yaraEngine.dispose() - _yaraEngine = null + engine.dispose() + return null } - return _yaraEngine + _yaraEngine = engine + return engine } catch (err) { console.warn('[yara] Init failed, falling back to regex patterns:', err) - _yaraEngine = null return null } } @@ -261,7 +265,7 @@ async function getYaraEngine(): Promise { export function resetYaraEngine(): void { if (_yaraEngine) _yaraEngine.dispose() _yaraEngine = null - _yaraInitAttempted = false + _yaraInitPromise = null } // ─── Heuristic checks ───────────────────────────────────────── diff --git a/src/main/services/yara-engine.test.ts b/src/main/services/yara-engine.test.ts index 013edaab..1c18a2f8 100644 --- a/src/main/services/yara-engine.test.ts +++ b/src/main/services/yara-engine.test.ts @@ -13,14 +13,18 @@ interface YaraMatch { matchedStrings: string[] } +const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low'] as const) + function yaraMatchToThreatFields(match: YaraMatch): { detectionName: string severity: 'critical' | 'high' | 'medium' | 'low' details: string } { + const rawSeverity = match.metadata.severity + const severity = rawSeverity && VALID_SEVERITIES.has(rawSeverity) ? rawSeverity : 'high' return { detectionName: match.metadata.detectionName || match.ruleName.replace(/_/g, '.'), - severity: match.metadata.severity || 'high', + severity, details: match.metadata.details || `YARA rule match: ${match.ruleName}`, } } @@ -94,6 +98,15 @@ describe('yaraMatchToThreatFields', () => { expect(yaraMatchToThreatFields(match).severity).toBe(sev) } }) + + it('clamps invalid severity to high', () => { + const match: YaraMatch = { + ruleName: 'Test', + metadata: { severity: 'info' as any }, + matchedStrings: [], + } + expect(yaraMatchToThreatFields(match).severity).toBe('high') + }) }) // ─── YARA rule parsing (integration-style, tests the WASM module) ── diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 736e926c..81c44fc0 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -201,14 +201,18 @@ export function createYaraEngine(): YaraEngine { * Convert a YaraMatch to the metadata fields used by MalwareThreat. * Pure function — safe for testing without Electron. */ +const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low'] as const) + export function yaraMatchToThreatFields(match: YaraMatch): { detectionName: string severity: 'critical' | 'high' | 'medium' | 'low' details: string } { + const rawSeverity = match.metadata.severity + const severity = rawSeverity && VALID_SEVERITIES.has(rawSeverity) ? rawSeverity : 'high' return { detectionName: match.metadata.detectionName || match.ruleName.replace(/_/g, '.'), - severity: match.metadata.severity || 'high', + severity, details: match.metadata.details || `YARA rule match: ${match.ruleName}`, } } From 5d7041d88c4ddc2ecd755b7126f4f9bc82692050 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 02:43:59 +0200 Subject: [PATCH 05/35] =?UTF-8?q?fix(malware):=20address=20third=20codex?= =?UTF-8?q?=20review=20=E2=80=94=20fetch=20timeout,=20SSRF=20redirects,=20?= =?UTF-8?q?scan-safe=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Keep abort timer active through full response body read, not just headers, so a slow-drip server can't hold the connection indefinitely - Disable HTTP redirects in fetchAndCacheRules (redirect: 'error') to prevent SSRF bypass via 30x to loopback/private addresses - resetYaraEngine() no longer disposes the current engine instance — it clears the init promise so the next scan creates a fresh one, while any in-flight scan keeps using its captured engine safely Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 7 ++++-- src/main/services/yara-rules-store.ts | 35 +++++++++++++++------------ 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 7f531c74..906df50c 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -261,9 +261,12 @@ async function _initYaraEngine(): Promise { } } -/** Reset engine so it re-initializes on next scan (e.g. after rule update). */ +/** + * Mark the engine for re-initialization on the next scan. + * Does NOT dispose the current engine — an in-flight scan may still be + * using it. The old instance is released once no scan references it. + */ export function resetYaraEngine(): void { - if (_yaraEngine) _yaraEngine.dispose() _yaraEngine = null _yaraInitPromise = null } diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 7db37f3c..05ea5266 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -194,32 +194,35 @@ export async function fetchAndCacheRules(url: string): Promise<{ const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS) - let response: Response + let text: string try { const meta = getRulesMetadata() const headers: Record = { 'Accept': 'application/json' } if (meta) headers['X-Kudu-Rules-Version'] = meta.version - response = await fetch(url, { signal: controller.signal, headers }) - } finally { - clearTimeout(timeout) - } + // Disable redirects to prevent SSRF bypass (a public URL could 30x to loopback) + const response = await fetch(url, { signal: controller.signal, headers, redirect: 'error' }) - // 304 = already up to date - if (response.status === 304) { - return { success: true } - } + // 304 = already up to date + if (response.status === 304) { + return { success: true } + } - if (!response.ok) { - return { success: false, error: `Download failed: HTTP ${response.status}` } - } + if (!response.ok) { + return { success: false, error: `Download failed: HTTP ${response.status}` } + } - const contentLength = response.headers.get('content-length') - if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) { - return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } + const contentLength = response.headers.get('content-length') + if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) { + return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } + } + + // Keep timeout active through full body read so a slow-drip server can't hang us + text = await response.text() + } finally { + clearTimeout(timeout) } - const text = await response.text() if (text.length > MAX_DOWNLOAD_BYTES) { return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } } From 898f0a29589c308e6b5bc161f06eea64830838eb Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 02:54:08 +0200 Subject: [PATCH 06/35] fix(malware): clear stale rules on failed fetch, run periodic checks for all users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ensureEmptyDir() now deletes existing .yar files so revoked rules aren't accidentally packaged when a fetch fails in reused workspaces - Remove cloud API key gate from periodic YARA rule checks — rule downloads are public and should run for all users, not just cloud-linked ones Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/fetch-yara-rules.js | 13 +++++++++++-- src/main/ipc/malware-scanner.ipc.ts | 9 +++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js index b888b2c9..d85d9f3a 100644 --- a/scripts/fetch-yara-rules.js +++ b/scripts/fetch-yara-rules.js @@ -95,8 +95,17 @@ async function main() { } function ensureEmptyDir() { - // Create the directory so electron-builder doesn't fail on missing extraResources - if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true }) + // Create the directory so electron-builder doesn't fail on missing extraResources, + // and clear any stale .yar files so revoked rules aren't accidentally packaged. + if (!existsSync(OUT_DIR)) { + mkdirSync(OUT_DIR, { recursive: true }) + return + } + for (const f of readdirSync(OUT_DIR)) { + if (f.endsWith('.yar')) { + try { unlinkSync(join(OUT_DIR, f)) } catch { /* best effort */ } + } + } } main().catch(err => { diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 906df50c..0f407073 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1935,10 +1935,7 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { ipcMain.handle(IPC.MALWARE_QUARANTINE_LIST, () => listQuarantinedFiles()) - // Start periodic YARA rule updates from the cloud - const settings = getSettings() - if (settings.cloud.apiKey) { - const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' - startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) - } + // Start periodic YARA rule updates — runs for all users, not just cloud-linked + const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' + startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) } From caa5028e09a072f68a62bb19bf88c2a3cd8f4bb7 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:04:06 +0200 Subject: [PATCH 07/35] fix(malware): remove redundant post-read size check in fetchAndCacheRules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The text.length check after response.text() was pointless — by that point the full body is already in memory. The content-length pre-check plus the 60s abort timer are the real guards. Removed the dead check and clarified the comment. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/yara-rules-store.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 05ea5266..ca352395 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -217,16 +217,15 @@ export async function fetchAndCacheRules(url: string): Promise<{ return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } } - // Keep timeout active through full body read so a slow-drip server can't hang us + // The 60s abort timer stays active through the body read, so a slow + // server can't hold us indefinitely. That plus the content-length + // pre-check is sufficient — no need for a post-read size check since + // the memory is already allocated by that point. text = await response.text() } finally { clearTimeout(timeout) } - if (text.length > MAX_DOWNLOAD_BYTES) { - return { success: false, error: 'Rules bundle too large (exceeds 50 MB)' } - } - let parsed: unknown try { parsed = JSON.parse(text) From 6e9d2eb6b36ae77c43126847d57af798a90bd2bb Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:04:52 +0200 Subject: [PATCH 08/35] fix(build): run fetch-yara-rules before all package scripts All npm run package* scripts now run scripts/fetch-yara-rules.js first, which fetches rules if the API is reachable or creates the empty resources/yara-rules/ directory if not. This prevents electron-builder from failing on missing extraResources outside of release CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f9bd0ee9..76792573 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "dev": "electron-vite dev", "build": "electron-vite build", "preview": "electron-vite preview", - "package": "electron-vite build && electron-builder", - "package:win": "electron-vite build && electron-builder --win", - "package:mac": "electron-vite build && electron-builder --mac", - "package:linux": "electron-vite build && electron-builder --linux", - "package:all": "electron-vite build && electron-builder --win --mac --linux", + "prefetch:rules": "node scripts/fetch-yara-rules.js", + "package": "npm run prefetch:rules && electron-vite build && electron-builder", + "package:win": "npm run prefetch:rules && electron-vite build && electron-builder --win", + "package:mac": "npm run prefetch:rules && electron-vite build && electron-builder --mac", + "package:linux": "npm run prefetch:rules && electron-vite build && electron-builder --linux", + "package:all": "npm run prefetch:rules && electron-vite build && electron-builder --win --mac --linux", "test": "vitest run", "test:watch": "vitest", "validate:rules": "npx tsx src/main/rules/validate.ts", From cd8c99c58e2fb2cc653b25698e180d8beb4a2b9f Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:21:25 +0200 Subject: [PATCH 09/35] feat(ui): add Database tab to malware scanner page Show YARA signature database info in a new tab on the malware scanner page: engine status, rules loaded, signature version, last updated time, rule source (cloud/bundled), and rule file counts. Includes a "Check for Updates" button to manually trigger a cloud rule fetch. - Add YaraRulesInfo type and MALWARE_YARA_INFO/UPDATE IPC channels - Add getYaraRulesInfo() handler returning engine and rule metadata - Add malwareYaraInfo/malwareYaraUpdate preload methods - Add Database tab with 2x3 info grid and update button - Add English translations for all database tab strings Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 38 ++++- src/main/services/yara-rules-store.ts | 2 +- src/preload/index.ts | 4 + src/renderer/src/locales/en/malware.json | 22 +++ src/renderer/src/pages/MalwareScannerPage.tsx | 142 +++++++++++++++++- src/shared/channels.ts | 2 + src/shared/types.ts | 11 ++ 7 files changed, 215 insertions(+), 6 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 0f407073..2c2d5cb1 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -10,7 +10,8 @@ import type { MalwareScanResult, MalwareScanProgress, MalwareActionResult, - QuarantinedItem + QuarantinedItem, + YaraRulesInfo } from '../../shared/types' import type { WindowGetter } from './index' import { getPlatform } from '../platform' @@ -19,7 +20,7 @@ import { psUtf8, execNativeUtf8 } from '../services/exec-utf8' import { getSettings } from '../services/settings-store' import { isExcluded } from '../services/file-utils' import { createYaraEngine, yaraMatchToThreatFields, type YaraEngine } from '../services/yara-engine' -import { getAllRulePaths, startPeriodicRuleChecks } from '../services/yara-rules-store' +import { getAllRulePaths, getBundledRulePaths, getCachedRulePaths, getRulesMetadata, startPeriodicRuleChecks, fetchAndCacheRules, RULES_ENDPOINT } from '../services/yara-rules-store' const execFileAsync = promisify(execFile) @@ -271,6 +272,29 @@ export function resetYaraEngine(): void { _yaraInitPromise = null } +function getYaraRulesInfo(): YaraRulesInfo { + const meta = getRulesMetadata() + const bundled = getBundledRulePaths() + const cached = getCachedRulePaths() + const engine = _yaraEngine + const available = engine !== null && engine.isReady() + + let source: YaraRulesInfo['source'] = 'none' + if (cached.length > 0) source = 'cloud' + else if (bundled.length > 0) source = 'bundled' + + return { + available, + engine: available ? 'yara' : 'regex-fallback', + rulesLoaded: engine?.rulesLoaded ?? 0, + version: meta?.version ?? null, + updatedAt: meta?.updatedAt ?? null, + source, + bundledRules: bundled.length, + cachedRules: cached.length, + } +} + // ─── Heuristic checks ───────────────────────────────────────── function calculateEntropy(buffer: Buffer): number { @@ -1935,6 +1959,16 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { ipcMain.handle(IPC.MALWARE_QUARANTINE_LIST, () => listQuarantinedFiles()) + ipcMain.handle(IPC.MALWARE_YARA_INFO, () => getYaraRulesInfo()) + + ipcMain.handle(IPC.MALWARE_YARA_UPDATE, async () => { + const result = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) + if (result.success && result.stats) { + resetYaraEngine() + } + return result + }) + // Start periodic YARA rule updates — runs for all users, not just cloud-linked const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index ca352395..e667d12a 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -10,7 +10,7 @@ const MAX_RULE_CONTENT_BYTES = 1 * 1024 * 1024 // 1 MB per rule file const MAX_RULE_COUNT = 10_000 const DOWNLOAD_TIMEOUT_MS = 60_000 const DEFAULT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000 // 6 hours -const RULES_ENDPOINT = '/api/yara-rules' +export const RULES_ENDPOINT = '/api/yara-rules' // ─── Types ─────────────────────────────────────────────────── diff --git a/src/preload/index.ts b/src/preload/index.ts index f69260be..b743836c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -273,6 +273,10 @@ const api = { ipcRenderer.on(IPC.MALWARE_PROGRESS, handler) return () => { ipcRenderer.removeListener(IPC.MALWARE_PROGRESS, handler) } }, + malwareYaraInfo: (): Promise => + ipcRenderer.invoke(IPC.MALWARE_YARA_INFO), + malwareYaraUpdate: (): Promise<{ success: boolean; error?: string; stats?: { rulesCount: number; version: string } }> => + ipcRenderer.invoke(IPC.MALWARE_YARA_UPDATE), // Driver Manager driverScan: (): Promise => ipcRenderer.invoke(IPC.DRIVER_SCAN), diff --git a/src/renderer/src/locales/en/malware.json b/src/renderer/src/locales/en/malware.json index 33bce9a4..43e14d2f 100644 --- a/src/renderer/src/locales/en/malware.json +++ b/src/renderer/src/locales/en/malware.json @@ -62,6 +62,28 @@ "tabScanner": "Scanner", "tabQuarantine": "Quarantine", + "tabDatabase": "Database", + + "dbTitle": "Signature Database", + "dbDescription": "YARA malware detection rules used by the scanner", + "dbFetchLatest": "Check for Updates", + "dbUpdating": "Updating...", + "dbEngine": "Engine", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex Fallback", + "dbRulesLoaded": "Rules Loaded", + "dbVersion": "Signature Version", + "dbLastUpdated": "Last Updated", + "dbNever": "Never", + "dbSource": "Rule Source", + "dbSourceCloud": "Cloud (cached)", + "dbSourceBundled": "Bundled with app", + "dbSourceNone": "No rules loaded", + "dbRuleFiles": "Rule Files", + "dbRuleFilesCounts": "{{bundled}} bundled, {{cached}} from cloud", + "dbUpdateSuccess": "Updated to v{{version}} ({{count}} rules)", + "dbAlreadyCurrent": "Signatures are already up to date", + "dbUpdateFailed": "Failed to update signatures", "quarantineEmptyTitle": "No Quarantined Items", "quarantineEmptyDescription": "Files moved to quarantine will appear here. You can restore or permanently delete them.", "quarantineHeading": "Quarantined Files", diff --git a/src/renderer/src/pages/MalwareScannerPage.tsx b/src/renderer/src/pages/MalwareScannerPage.tsx index 4686f4f3..37b4986d 100644 --- a/src/renderer/src/pages/MalwareScannerPage.tsx +++ b/src/renderer/src/pages/MalwareScannerPage.tsx @@ -22,7 +22,10 @@ import { Clock, Activity, ChevronDown, - RotateCcw + RotateCcw, + Database, + RefreshCw, + Download } from 'lucide-react' import { toast } from 'sonner' import { PageHeader } from '@/components/layout/PageHeader' @@ -69,9 +72,11 @@ export function MalwareScannerPage() { const actionMode = useMalwareStore(s => s.actionMode) const expandedId = useMalwareStore(s => s.expandedId) - const [viewMode, setViewMode] = useState<'scanner' | 'quarantine'>('scanner') + const [viewMode, setViewMode] = useState<'scanner' | 'quarantine' | 'database'>('scanner') const [showConfirm, setShowConfirm] = useState(false) const [quarantineConfirmMode, setQuarantineConfirmMode] = useState<'restore' | 'delete' | null>(null) + const [yaraInfo, setYaraInfo] = useState(null) + const [yaraUpdating, setYaraUpdating] = useState(false) const quarantineItems = useMalwareStore(s => s.quarantineItems) const quarantineSelectedIds = useMalwareStore(s => s.quarantineSelectedIds) @@ -184,6 +189,32 @@ export function MalwareScannerPage() { } }, []) + const loadYaraInfo = useCallback(async () => { + try { + const info = await window.kudu.malwareYaraInfo() + setYaraInfo(info) + } catch { /* ignore */ } + }, []) + + const handleYaraUpdate = useCallback(async () => { + setYaraUpdating(true) + try { + const result = await window.kudu.malwareYaraUpdate() + if (result.success && result.stats) { + toast.success(t('dbUpdateSuccess', { version: result.stats.version, count: result.stats.rulesCount })) + } else if (result.success) { + toast.info(t('dbAlreadyCurrent')) + } else { + toast.error(result.error || t('dbUpdateFailed')) + } + await loadYaraInfo() + } catch { + toast.error(t('dbUpdateFailed')) + } finally { + setYaraUpdating(false) + } + }, [loadYaraInfo, t]) + const handleQuarantineRestore = useCallback(async () => { setQuarantineConfirmMode(null) const items = store.getState().quarantineItems @@ -242,7 +273,8 @@ export function MalwareScannerPage() { useEffect(() => { if (viewMode === 'quarantine') loadQuarantineItems() - }, [viewMode, loadQuarantineItems]) + if (viewMode === 'database') loadYaraInfo() + }, [viewMode, loadQuarantineItems, loadYaraInfo]) const isScanning = status === 'scanning' const isActing = status === 'acting' @@ -335,6 +367,12 @@ export function MalwareScannerPage() { )} + {/* ════════════════════ SCANNER TAB ════════════════════ */} @@ -1064,6 +1102,104 @@ export function MalwareScannerPage() { )} + {/* ════════════════════ DATABASE TAB ════════════════════ */} + {viewMode === 'database' && ( +
+
+ + {/* Header + update button */} +
+
+
+ +
+
+

{t('dbTitle')}

+

{t('dbDescription')}

+
+
+ +
+ + {/* Info grid */} + {yaraInfo ? ( +
+ {/* Engine status */} +
+

{t('dbEngine')}

+
+
+ + {yaraInfo.available ? t('dbEngineYara') : t('dbEngineRegex')} + +
+
+ + {/* Rules loaded */} +
+

{t('dbRulesLoaded')}

+ + {yaraInfo.rulesLoaded > 0 ? yaraInfo.rulesLoaded.toLocaleString() : '—'} + +
+ + {/* Signature version */} +
+

{t('dbVersion')}

+ + {yaraInfo.version || '—'} + +
+ + {/* Last updated */} +
+

{t('dbLastUpdated')}

+ + {yaraInfo.updatedAt ? new Date(yaraInfo.updatedAt).toLocaleString() : t('dbNever')} + +
+ + {/* Source */} +
+

{t('dbSource')}

+ + {yaraInfo.source === 'cloud' ? t('dbSourceCloud') : yaraInfo.source === 'bundled' ? t('dbSourceBundled') : t('dbSourceNone')} + +
+ + {/* Rule file counts */} +
+

{t('dbRuleFiles')}

+ + {t('dbRuleFilesCounts', { bundled: yaraInfo.bundledRules, cached: yaraInfo.cachedRules })} + +
+
+ ) : ( +
+ +
+ )} +
+
+ )} + {/* ════════════════════ CONFIRM DIALOGS ════════════════════ */} Date: Sun, 29 Mar 2026 01:22:55 +0000 Subject: [PATCH 10/35] chore: auto-update translations --- src/renderer/src/locales/.checksums.json | 58 +++++++-------- src/renderer/src/locales/ar/malware.json | 39 +++++++--- src/renderer/src/locales/cs/malware.json | 45 ++++++++---- src/renderer/src/locales/da/malware.json | 35 +++++++-- src/renderer/src/locales/de/malware.json | 25 ++++++- src/renderer/src/locales/el/malware.json | 45 ++++++++---- src/renderer/src/locales/es/malware.json | 31 ++++++-- src/renderer/src/locales/fi/malware.json | 47 ++++++++---- src/renderer/src/locales/fr/malware.json | 31 ++++++-- src/renderer/src/locales/he/malware.json | 37 +++++++--- src/renderer/src/locales/hi/malware.json | 73 ++++++++++++------- src/renderer/src/locales/hu/malware.json | 45 ++++++++---- src/renderer/src/locales/id/malware.json | 35 +++++++-- src/renderer/src/locales/it/malware.json | 45 ++++++++---- src/renderer/src/locales/ja/malware.json | 27 ++++++- src/renderer/src/locales/ko/malware.json | 33 +++++++-- src/renderer/src/locales/ms/malware.json | 27 ++++++- src/renderer/src/locales/nl/malware.json | 31 ++++++-- src/renderer/src/locales/no/malware.json | 39 +++++++--- src/renderer/src/locales/pl/malware.json | 41 ++++++++--- src/renderer/src/locales/pt/malware.json | 79 +++++++++++++-------- src/renderer/src/locales/ro/malware.json | 27 ++++++- src/renderer/src/locales/ru/malware.json | 43 ++++++++--- src/renderer/src/locales/sv/malware.json | 37 +++++++--- src/renderer/src/locales/th/malware.json | 49 +++++++++---- src/renderer/src/locales/tr/malware.json | 25 ++++++- src/renderer/src/locales/uk/malware.json | 53 +++++++++----- src/renderer/src/locales/vi/malware.json | 29 ++++++-- src/renderer/src/locales/zh-CN/malware.json | 49 +++++++++---- src/renderer/src/locales/zh-TW/malware.json | 35 +++++++-- 30 files changed, 912 insertions(+), 303 deletions(-) diff --git a/src/renderer/src/locales/.checksums.json b/src/renderer/src/locales/.checksums.json index 62d3b8f5..ba52b694 100644 --- a/src/renderer/src/locales/.checksums.json +++ b/src/renderer/src/locales/.checksums.json @@ -3,7 +3,7 @@ "es/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "es/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "es/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "es/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "es/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "es/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "es/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "es/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -22,7 +22,7 @@ "fr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fr/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "fr/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "fr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -41,7 +41,7 @@ "de/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "de/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "de/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "de/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "de/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "de/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "de/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "de/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -60,7 +60,7 @@ "pt/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pt/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pt/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pt/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "pt/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "pt/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pt/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pt/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -79,7 +79,7 @@ "it/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "it/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "it/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "it/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "it/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "it/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "it/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "it/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -98,7 +98,7 @@ "ja/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ja/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ja/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ja/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ja/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ja/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ja/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ja/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -117,7 +117,7 @@ "ko/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ko/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "ko/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "ko/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ko/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ko/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ko/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ko/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -136,7 +136,7 @@ "zh-CN/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-CN/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-CN/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-CN/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "zh-CN/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "zh-CN/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-CN/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "zh-CN/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -155,7 +155,7 @@ "zh-TW/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-TW/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-TW/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-TW/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "zh-TW/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "zh-TW/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-TW/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "zh-TW/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -174,7 +174,7 @@ "ru/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ru/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ru/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ru/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ru/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ru/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ru/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ru/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -193,7 +193,7 @@ "ar/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ar/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ar/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ar/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ar/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ar/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ar/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ar/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -212,7 +212,7 @@ "hi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hi/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "hi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "hi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -231,7 +231,7 @@ "tr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "tr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "tr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "tr/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "tr/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "tr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "tr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "tr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -250,7 +250,7 @@ "nl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "nl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "nl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "nl/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "nl/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "nl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "nl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "nl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -269,7 +269,7 @@ "pl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pl/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "pl/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "pl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -288,7 +288,7 @@ "sv/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "sv/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "sv/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "sv/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "sv/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "sv/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "sv/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "sv/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -307,7 +307,7 @@ "no/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "no/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "no/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "no/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "no/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "no/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "no/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "no/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -326,7 +326,7 @@ "da/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "da/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "da/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "da/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "da/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "da/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "da/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "da/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -345,7 +345,7 @@ "fi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fi/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "fi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "fi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -364,7 +364,7 @@ "cs/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "cs/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "cs/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "cs/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "cs/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "cs/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "cs/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "cs/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -383,7 +383,7 @@ "th/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "th/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "th/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "th/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "th/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "th/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "th/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "th/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -402,7 +402,7 @@ "vi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "vi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "vi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "vi/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "vi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "vi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "vi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "vi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -421,7 +421,7 @@ "id/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "id/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "id/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "id/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "id/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "id/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "id/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "id/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -440,7 +440,7 @@ "ms/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ms/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ms/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ms/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ms/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ms/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ms/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ms/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -459,7 +459,7 @@ "uk/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "uk/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "uk/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "uk/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "uk/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "uk/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "uk/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "uk/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -478,7 +478,7 @@ "ro/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ro/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ro/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ro/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "ro/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "ro/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ro/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "ro/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -497,7 +497,7 @@ "el/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "el/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "el/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "el/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "el/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "el/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "el/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "el/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -516,7 +516,7 @@ "he/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "he/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "he/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "he/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "he/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "he/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "he/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "he/hardening": "262c9c4b827b486f7b9f5bfdeb27eca2d63c3c0e439ae12de4f9ea9fb0319bec", @@ -535,7 +535,7 @@ "hu/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hu/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hu/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hu/malware": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "hu/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", "hu/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hu/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hu/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", diff --git a/src/renderer/src/locales/ar/malware.json b/src/renderer/src/locales/ar/malware.json index 8c9b30c6..52464801 100644 --- a/src/renderer/src/locales/ar/malware.json +++ b/src/renderer/src/locales/ar/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "فاحص البرامج الضارة", - "pageDescription": "اكتشاف التهديدات متعدد المحركات — التوقيعات، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", + "pageDescription": "اكتشاف التهديدات عبر عدة محركات — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", "scanButtonScanning": "جارٍ الفحص...", "scanButton": "فحص", "quarantineButton": "الحجر", @@ -20,9 +20,9 @@ "threatCountPlural": "{{count}} تهديدات", "threatFound": "تم العثور على {{count}}", "scanCategoryClean": "نظيف", - "scanCategoryNA": "غير متوفر", + "scanCategoryNA": "غير متاح", "scanSummarySystemClean": "النظام نظيف", - "scanSummaryThreatsDetected": "تم اكتشاف {{count}} من التهديدات", + "scanSummaryThreatsDetected": "تم اكتشاف {{count}} تهديد", "scanStatFiles": "الملفات", "scanStatDuration": "المدة", "scanStatEngines": "المحركات", @@ -39,7 +39,7 @@ "actionResultDeleted": "تم حذف {{count}}", "actionResultFailed": "فشل {{count}}", "noThreatsDetectedTitle": "لم يتم اكتشاف أي تهديدات", - "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملفًا عبر {{engineCount}} محركات خلال {{duration}} ثانية — نظامك نظيف.", + "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملف عبر {{engineCount}} محرك خلال {{duration}} ثانية — نظامك نظيف.", "emptyStateTitle": "فاحص البرامج الضارة", "emptyStateDescription": "انقر فوق \"فحص\" للتحقق من نظامك بحثًا عن البرامج الضارة والبرامج الإعلانية وعمال تعدين العملات المشفرة والملفات المشبوهة.", "detectedThreatsHeading": "التهديدات المكتشفة", @@ -50,19 +50,40 @@ "threatDetailSize": "الحجم", "threatDetailPath": "المسار", "confirmQuarantineTitle": "نقل التهديدات إلى الحجر", - "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا عند الحاجة.", + "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا إذا لزم الأمر.", "confirmQuarantineLabel": "نقل إلى الحجر الآن", "confirmDeleteTitle": "حذف التهديدات", "confirmDeleteDescription": "سيؤدي هذا إلى حذف {{count}} من التهديدات المكتشفة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", - "confirmDeleteLabel": "حذف نهائيًا", + "confirmDeleteLabel": "حذف نهائي", "toastScanFailed": "فشل فحص البرامج الضارة", "toastActionFailed": "فشل {{action}} التهديدات", "toastActionFailedDescription": "حاول التشغيل كمسؤول", "errorOperationFailed": "فشلت العملية — حاول التشغيل كمسؤول", "tabScanner": "الفاحص", "tabQuarantine": "الحجر", + "tabDatabase": "قاعدة البيانات", + "dbTitle": "قاعدة بيانات التواقيع", + "dbDescription": "قواعد YARA لاكتشاف البرامج الضارة المستخدمة بواسطة الفاحص", + "dbFetchLatest": "التحقق من وجود تحديثات", + "dbUpdating": "جارٍ التحديث...", + "dbEngine": "المحرك", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex احتياطي", + "dbRulesLoaded": "القواعد المحمّلة", + "dbVersion": "إصدار التواقيع", + "dbLastUpdated": "آخر تحديث", + "dbNever": "أبدًا", + "dbSource": "مصدر القواعد", + "dbSourceCloud": "السحابة (مخزنة مؤقتًا)", + "dbSourceBundled": "مضمّنة مع التطبيق", + "dbSourceNone": "لم يتم تحميل أي قواعد", + "dbRuleFiles": "ملفات القواعد", + "dbRuleFilesCounts": "{{bundled}} مضمّنة، {{cached}} من السحابة", + "dbUpdateSuccess": "تم التحديث إلى v{{version}} ({{count}} قاعدة)", + "dbAlreadyCurrent": "التواقيع محدّثة بالفعل", + "dbUpdateFailed": "فشل تحديث التواقيع", "quarantineEmptyTitle": "لا توجد عناصر في الحجر", - "quarantineEmptyDescription": "ستظهر هنا الملفات المنقولة إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", + "quarantineEmptyDescription": "ستظهر هنا الملفات التي تم نقلها إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", "quarantineHeading": "الملفات المعزولة", "quarantineCount": "{{count}} ملف", "quarantineCountPlural": "{{count}} ملفات", @@ -72,7 +93,7 @@ "quarantineColumnOriginal": "الموقع الأصلي", "quarantineOriginalUnknown": "غير معروف", "quarantineRestoreButton": "استعادة", - "quarantineDeleteButton": "حذف نهائيًا", + "quarantineDeleteButton": "حذف نهائي", "quarantineSelectAll": "تحديد الكل", "quarantineDeselectAll": "إلغاء تحديد الكل", "confirmRestoreTitle": "استعادة الملفات", @@ -80,7 +101,7 @@ "confirmRestoreLabel": "استعادة الآن", "confirmDeleteQuarantineTitle": "حذف نهائي", "confirmDeleteQuarantineDescription": "سيؤدي هذا إلى حذف {{count}} من الملفات المعزولة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", - "confirmDeleteQuarantineLabel": "حذف نهائيًا", + "confirmDeleteQuarantineLabel": "حذف نهائي", "quarantineLoading": "جارٍ تحميل الحجر...", "quarantineRestoring": "جارٍ استعادة الملفات...", "quarantineDeleting": "جارٍ حذف الملفات...", diff --git a/src/renderer/src/locales/cs/malware.json b/src/renderer/src/locales/cs/malware.json index 6b7cb638..1c245072 100644 --- a/src/renderer/src/locales/cs/malware.json +++ b/src/renderer/src/locales/cs/malware.json @@ -15,14 +15,14 @@ "sourceHeuristic": "Heuristická analýza", "sourceSignature": "Známá signatura", "initializingScanEngines": "Inicializace skenovacích enginů...", - "filesScanned": "Zkontrolováno {{count}} souborů", + "filesScanned": "Zkontrolováno souborů: {{count}}", "threatCount": "{{count}} hrozba", "threatCountPlural": "{{count}} hrozeb", - "threatFound": "Nalezeno {{count}}", + "threatFound": "Nalezeno: {{count}}", "scanCategoryClean": "čisté", "scanCategoryNA": "N/A", "scanSummarySystemClean": "Systém je čistý", - "scanSummaryThreatsDetected": "Zjištěno hrozeb: {{count}}", + "scanSummaryThreatsDetected": "Zjištěné hrozby: {{count}}", "scanStatFiles": "Soubory", "scanStatDuration": "Doba trvání", "scanStatEngines": "Enginy", @@ -30,18 +30,18 @@ "selectedHeading": "Vybráno", "selectedOfThreats": "z {{count}} hrozeb", "selectAll": "Vybrat vše", - "deselectAll": "Zrušit výběr všeho", + "deselectAll": "Zrušit výběr", "actingQuarantining": "Přesouvání vybraných hrozeb do karantény...", "actingDeleting": "Odstraňování vybraných hrozeb...", "actionCompleteQuarantine": "Přesun do karantény dokončen", "actionCompleteDeletion": "Odstranění dokončeno", - "actionResultQuarantined": "{{count}} přesunuto do karantény", - "actionResultDeleted": "{{count}} odstraněno", - "actionResultFailed": "{{count}} selhalo", + "actionResultQuarantined": "Přesunuto do karantény: {{count}}", + "actionResultDeleted": "Odstraněno: {{count}}", + "actionResultFailed": "Nezdařilo se: {{count}}", "noThreatsDetectedTitle": "Nebyly zjištěny žádné hrozby", "noThreatsDetectedDescription": "Bylo zkontrolováno {{filesScanned}} souborů pomocí {{engineCount}} enginů za {{duration}} s — váš systém je čistý.", "emptyStateTitle": "Skener malwaru", - "emptyStateDescription": "Kliknutím na „Kontrolovat“ zkontrolujete systém na malware, adware, kryptoměnové těžaře a podezřelé soubory.", + "emptyStateDescription": "Kliknutím na „Kontrolovat“ zkontrolujete systém na malware, adware, kryptotěžaře a podezřelé soubory.", "detectedThreatsHeading": "Zjištěné hrozby", "detectedThreatsCount": "{{count}} hrozba", "detectedThreatsCountPlural": "{{count}} hrozeb", @@ -56,11 +56,32 @@ "confirmDeleteDescription": "Tímto trvale odstraníte {{count}} zjištěných hrozeb. Tuto akci nelze vrátit zpět.", "confirmDeleteLabel": "Trvale odstranit", "toastScanFailed": "Kontrola malwaru se nezdařila", - "toastActionFailed": "Nepodařilo se {{action}} hrozby", + "toastActionFailed": "Akci {{action}} u hrozeb se nepodařilo provést", "toastActionFailedDescription": "Zkuste spustit jako správce", "errorOperationFailed": "Operace se nezdařila — zkuste spustit jako správce", "tabScanner": "Skener", "tabQuarantine": "Karanténa", + "tabDatabase": "Databáze", + "dbTitle": "Databáze signatur", + "dbDescription": "Pravidla YARA pro detekci malwaru používaná skenerem", + "dbFetchLatest": "Vyhledat aktualizace", + "dbUpdating": "Probíhá aktualizace...", + "dbEngine": "Engine", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Záložní Regex", + "dbRulesLoaded": "Načtená pravidla", + "dbVersion": "Verze signatur", + "dbLastUpdated": "Naposledy aktualizováno", + "dbNever": "Nikdy", + "dbSource": "Zdroj pravidel", + "dbSourceCloud": "Cloud (v mezipaměti)", + "dbSourceBundled": "Součást aplikace", + "dbSourceNone": "Nejsou načtena žádná pravidla", + "dbRuleFiles": "Soubory pravidel", + "dbRuleFilesCounts": "{{bundled}} součástí aplikace, {{cached}} z cloudu", + "dbUpdateSuccess": "Aktualizováno na v{{version}} ({{count}} pravidel)", + "dbAlreadyCurrent": "Signatury jsou již aktuální", + "dbUpdateFailed": "Aktualizace signatur se nezdařila", "quarantineEmptyTitle": "Žádné položky v karanténě", "quarantineEmptyDescription": "Zde se zobrazí soubory přesunuté do karantény. Můžete je obnovit nebo trvale odstranit.", "quarantineHeading": "Soubory v karanténě", @@ -74,7 +95,7 @@ "quarantineRestoreButton": "Obnovit", "quarantineDeleteButton": "Trvale odstranit", "quarantineSelectAll": "Vybrat vše", - "quarantineDeselectAll": "Zrušit výběr všeho", + "quarantineDeselectAll": "Zrušit výběr", "confirmRestoreTitle": "Obnovit soubory", "confirmRestoreDescription": "Tímto obnovíte {{count}} souborů do jejich původního umístění. Obnovujte pouze soubory, kterým důvěřujete.", "confirmRestoreLabel": "Obnovit nyní", @@ -84,8 +105,8 @@ "quarantineLoading": "Načítání karantény...", "quarantineRestoring": "Obnovování souborů...", "quarantineDeleting": "Odstraňování souborů...", - "toastRestoreSuccess": "Obnoveno {{count}} souborů", + "toastRestoreSuccess": "Obnoveno souborů: {{count}}", "toastRestoreFailed": "Některé soubory se nepodařilo obnovit", - "toastDeleteQuarantineSuccess": "Trvale odstraněno {{count}} souborů", + "toastDeleteQuarantineSuccess": "Trvale odstraněno souborů: {{count}}", "toastRestoreNoOriginal": "Nelze obnovit — původní umístění není známo" } diff --git a/src/renderer/src/locales/da/malware.json b/src/renderer/src/locales/da/malware.json index da629f59..99b23ec7 100644 --- a/src/renderer/src/locales/da/malware.json +++ b/src/renderer/src/locales/da/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malware-scanner", - "pageDescription": "Trusselsregistrering med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning af persistens", + "pageDescription": "Trusselsregistrering med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning for persistens", "scanButtonScanning": "Scanner...", "scanButton": "Scan", "quarantineButton": "Sæt i karantæne", @@ -39,7 +39,7 @@ "actionResultDeleted": "{{count}} slettet", "actionResultFailed": "{{count}} mislykkedes", "noThreatsDetectedTitle": "Ingen trusler registreret", - "noThreatsDetectedDescription": "Scannede {{filesScanned}} filer på tværs af {{engineCount}} motorer på {{duration}} sek. — dit system er rent.", + "noThreatsDetectedDescription": "Scannede {{filesScanned}} filer på tværs af {{engineCount}} motorer på {{duration}}s — dit system er rent.", "emptyStateTitle": "Malware-scanner", "emptyStateDescription": "Klik på \"Scan\" for at kontrollere dit system for malware, adware, cryptominere og mistænkelige filer.", "detectedThreatsHeading": "Registrerede trusler", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Dette flytter {{count}} registrerede trussel/trusler til karantæne. Filer kan gendannes senere, hvis det er nødvendigt.", "confirmQuarantineLabel": "Sæt i karantæne nu", "confirmDeleteTitle": "Slet trusler", - "confirmDeleteDescription": "Dette vil permanent slette {{count}} registrerede trussel/trusler. Denne handling kan ikke fortrydes.", + "confirmDeleteDescription": "Dette sletter permanent {{count}} registrerede trussel/trusler. Denne handling kan ikke fortrydes.", "confirmDeleteLabel": "Slet permanent", "toastScanFailed": "Malware-scanning mislykkedes", "toastActionFailed": "Kunne ikke {{action}} trusler", @@ -61,6 +61,27 @@ "errorOperationFailed": "Handlingen mislykkedes — prøv at køre som administrator", "tabScanner": "Scanner", "tabQuarantine": "Karantæne", + "tabDatabase": "Database", + "dbTitle": "Signaturdatabase", + "dbDescription": "YARA-regler til malware-registrering, som bruges af scanneren", + "dbFetchLatest": "Søg efter opdateringer", + "dbUpdating": "Opdaterer...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-reserve", + "dbRulesLoaded": "Regler indlæst", + "dbVersion": "Signaturversion", + "dbLastUpdated": "Senest opdateret", + "dbNever": "Aldrig", + "dbSource": "Regelkilde", + "dbSourceCloud": "Cloud (cachelagret)", + "dbSourceBundled": "Medfølger appen", + "dbSourceNone": "Ingen regler indlæst", + "dbRuleFiles": "Regelfiler", + "dbRuleFilesCounts": "{{bundled}} medfølgende, {{cached}} fra cloud", + "dbUpdateSuccess": "Opdateret til v{{version}} ({{count}} regler)", + "dbAlreadyCurrent": "Signaturerne er allerede opdaterede", + "dbUpdateFailed": "Kunne ikke opdatere signaturer", "quarantineEmptyTitle": "Ingen elementer i karantæne", "quarantineEmptyDescription": "Filer, der flyttes til karantæne, vises her. Du kan gendanne eller slette dem permanent.", "quarantineHeading": "Filer i karantæne", @@ -76,16 +97,16 @@ "quarantineSelectAll": "Vælg alle", "quarantineDeselectAll": "Fravælg alle", "confirmRestoreTitle": "Gendan filer", - "confirmRestoreDescription": "Dette vil gendanne {{count}} fil/filer til deres oprindelige placering. Gendan kun filer, du har tillid til.", + "confirmRestoreDescription": "Dette gendanner {{count}} fil(er) til deres oprindelige placering. Gendan kun filer, du har tillid til.", "confirmRestoreLabel": "Gendan nu", "confirmDeleteQuarantineTitle": "Slet permanent", - "confirmDeleteQuarantineDescription": "Dette vil permanent slette {{count}} fil/filer i karantæne. Denne handling kan ikke fortrydes.", + "confirmDeleteQuarantineDescription": "Dette sletter permanent {{count}} fil(er) i karantæne. Denne handling kan ikke fortrydes.", "confirmDeleteQuarantineLabel": "Slet permanent", "quarantineLoading": "Indlæser karantæne...", "quarantineRestoring": "Gendanner filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil/filer gendannet", + "toastRestoreSuccess": "{{count}} fil(er) gendannet", "toastRestoreFailed": "Kunne ikke gendanne nogle filer", - "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", "toastRestoreNoOriginal": "Kan ikke gendanne — oprindelig placering er ukendt" } diff --git a/src/renderer/src/locales/de/malware.json b/src/renderer/src/locales/de/malware.json index 02aee352..ce79c6d1 100644 --- a/src/renderer/src/locales/de/malware.json +++ b/src/renderer/src/locales/de/malware.json @@ -9,8 +9,8 @@ "severityHigh": "Hoch", "severityMedium": "Mittel", "severityLow": "Niedrig", - "sourceDefenderWindows": "Integrierter AV", - "sourceDefenderMac": "Code Signing", + "sourceDefenderWindows": "Integriertes AV", + "sourceDefenderMac": "Codesignierung", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristische Analyse", "sourceSignature": "Bekannte Signatur", @@ -61,6 +61,27 @@ "errorOperationFailed": "Vorgang fehlgeschlagen — versuchen Sie, das Programm als Administrator auszuführen", "tabScanner": "Scanner", "tabQuarantine": "Quarantäne", + "tabDatabase": "Datenbank", + "dbTitle": "Signaturdatenbank", + "dbDescription": "YARA-Malware-Erkennungsregeln, die vom Scanner verwendet werden", + "dbFetchLatest": "Nach Updates suchen", + "dbUpdating": "Wird aktualisiert...", + "dbEngine": "Engine", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-Fallback", + "dbRulesLoaded": "Geladene Regeln", + "dbVersion": "Signaturversion", + "dbLastUpdated": "Zuletzt aktualisiert", + "dbNever": "Nie", + "dbSource": "Regelquelle", + "dbSourceCloud": "Cloud (zwischengespeichert)", + "dbSourceBundled": "Mit der App gebündelt", + "dbSourceNone": "Keine Regeln geladen", + "dbRuleFiles": "Regeldateien", + "dbRuleFilesCounts": "{{bundled}} gebündelt, {{cached}} aus der Cloud", + "dbUpdateSuccess": "Auf v{{version}} aktualisiert ({{count}} Regeln)", + "dbAlreadyCurrent": "Signaturen sind bereits auf dem neuesten Stand", + "dbUpdateFailed": "Signaturen konnten nicht aktualisiert werden", "quarantineEmptyTitle": "Keine Elemente in Quarantäne", "quarantineEmptyDescription": "In Quarantäne verschobene Dateien werden hier angezeigt. Sie können sie wiederherstellen oder dauerhaft löschen.", "quarantineHeading": "Dateien in Quarantäne", diff --git a/src/renderer/src/locales/el/malware.json b/src/renderer/src/locales/el/malware.json index 85b72e9d..761f4f20 100644 --- a/src/renderer/src/locales/el/malware.json +++ b/src/renderer/src/locales/el/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Σαρωτής κακόβουλου λογισμικού", - "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση παραμονής", + "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση μονιμότητας", "scanButtonScanning": "Γίνεται σάρωση...", "scanButton": "Σάρωση", "quarantineButton": "Καραντίνα", @@ -30,9 +30,9 @@ "selectedHeading": "Επιλεγμένα", "selectedOfThreats": "από {{count}} απειλές", "selectAll": "Επιλογή όλων", - "deselectAll": "Αποεπιλογή όλων", + "deselectAll": "Κατάργηση επιλογής όλων", "actingQuarantining": "Τα επιλεγμένα στοιχεία μεταφέρονται σε καραντίνα...", - "actingDeleting": "Γίνεται διαγραφή των επιλεγμένων απειλών...", + "actingDeleting": "Τα επιλεγμένα στοιχεία διαγράφονται...", "actionCompleteQuarantine": "Η μεταφορά σε καραντίνα ολοκληρώθηκε", "actionCompleteDeletion": "Η διαγραφή ολοκληρώθηκε", "actionResultQuarantined": "{{count}} σε καραντίνα", @@ -50,10 +50,10 @@ "threatDetailSize": "Μέγεθος", "threatDetailPath": "Διαδρομή", "confirmQuarantineTitle": "Μεταφορά απειλών σε καραντίνα", - "confirmQuarantineDescription": "Αυτό θα μεταφέρει {{count}} εντοπισμένη/-ες απειλή/-ές σε καραντίνα. Τα αρχεία μπορούν να αποκατασταθούν αργότερα, αν χρειαστεί.", + "confirmQuarantineDescription": "Αυτό θα μεταφέρει {{count}} εντοπισμένη(-ες) απειλή(-ές) σε καραντίνα. Τα αρχεία μπορούν να αποκατασταθούν αργότερα, αν χρειαστεί.", "confirmQuarantineLabel": "Μεταφορά σε καραντίνα τώρα", "confirmDeleteTitle": "Διαγραφή απειλών", - "confirmDeleteDescription": "Αυτό θα διαγράψει οριστικά {{count}} εντοπισμένη/-ες απειλή/-ές. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "confirmDeleteDescription": "Αυτό θα διαγράψει οριστικά {{count}} εντοπισμένη(-ες) απειλή(-ές). Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmDeleteLabel": "Οριστική διαγραφή", "toastScanFailed": "Η σάρωση για κακόβουλο λογισμικό απέτυχε", "toastActionFailed": "Αποτυχία {{action}} απειλών", @@ -61,6 +61,27 @@ "errorOperationFailed": "Η λειτουργία απέτυχε — δοκιμάστε να εκτελέσετε ως διαχειριστής", "tabScanner": "Σαρωτής", "tabQuarantine": "Καραντίνα", + "tabDatabase": "Βάση δεδομένων", + "dbTitle": "Βάση δεδομένων υπογραφών", + "dbDescription": "Κανόνες ανίχνευσης κακόβουλου λογισμικού YARA που χρησιμοποιούνται από τον σαρωτή", + "dbFetchLatest": "Έλεγχος για ενημερώσεις", + "dbUpdating": "Γίνεται ενημέρωση...", + "dbEngine": "Μηχανή", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Εφεδρικό Regex", + "dbRulesLoaded": "Φορτωμένοι κανόνες", + "dbVersion": "Έκδοση υπογραφών", + "dbLastUpdated": "Τελευταία ενημέρωση", + "dbNever": "Ποτέ", + "dbSource": "Πηγή κανόνων", + "dbSourceCloud": "Cloud (στη μνήμη cache)", + "dbSourceBundled": "Περιλαμβάνεται στην εφαρμογή", + "dbSourceNone": "Δεν φορτώθηκαν κανόνες", + "dbRuleFiles": "Αρχεία κανόνων", + "dbRuleFilesCounts": "{{bundled}} ενσωματωμένα, {{cached}} από το cloud", + "dbUpdateSuccess": "Ενημερώθηκε σε v{{version}} ({{count}} κανόνες)", + "dbAlreadyCurrent": "Οι υπογραφές είναι ήδη ενημερωμένες", + "dbUpdateFailed": "Αποτυχία ενημέρωσης υπογραφών", "quarantineEmptyTitle": "Δεν υπάρχουν στοιχεία σε καραντίνα", "quarantineEmptyDescription": "Τα αρχεία που μεταφέρονται σε καραντίνα θα εμφανίζονται εδώ. Μπορείτε να τα αποκαταστήσετε ή να τα διαγράψετε οριστικά.", "quarantineHeading": "Αρχεία σε καραντίνα", @@ -69,23 +90,23 @@ "quarantineColumnFile": "Αρχείο", "quarantineColumnDate": "Ημερομηνία", "quarantineColumnSize": "Μέγεθος", - "quarantineColumnOriginal": "Αρχική τοποθεσία", + "quarantineColumnOriginal": "Αρχική θέση", "quarantineOriginalUnknown": "Άγνωστη", "quarantineRestoreButton": "Αποκατάσταση", "quarantineDeleteButton": "Οριστική διαγραφή", "quarantineSelectAll": "Επιλογή όλων", - "quarantineDeselectAll": "Αποεπιλογή όλων", + "quarantineDeselectAll": "Κατάργηση επιλογής όλων", "confirmRestoreTitle": "Αποκατάσταση αρχείων", - "confirmRestoreDescription": "Αυτό θα αποκαταστήσει {{count}} αρχείο/-α στην αρχική του/τους τοποθεσία. Να αποκαθιστάτε μόνο αρχεία που εμπιστεύεστε.", + "confirmRestoreDescription": "Αυτό θα αποκαταστήσει {{count}} αρχείο(-α) στην αρχική του θέση. Αποκαθιστάτε μόνο αρχεία που εμπιστεύεστε.", "confirmRestoreLabel": "Αποκατάσταση τώρα", "confirmDeleteQuarantineTitle": "Οριστική διαγραφή", - "confirmDeleteQuarantineDescription": "Αυτό θα διαγράψει οριστικά {{count}} αρχείο/-α σε καραντίνα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "confirmDeleteQuarantineDescription": "Αυτό θα διαγράψει οριστικά {{count}} αρχείο(-α) σε καραντίνα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmDeleteQuarantineLabel": "Οριστική διαγραφή", "quarantineLoading": "Φόρτωση καραντίνας...", "quarantineRestoring": "Γίνεται αποκατάσταση αρχείων...", "quarantineDeleting": "Γίνεται διαγραφή αρχείων...", - "toastRestoreSuccess": "Αποκαταστάθηκαν {{count}} αρχείο/-α", + "toastRestoreSuccess": "Αποκαταστάθηκαν {{count}} αρχείο(-α)", "toastRestoreFailed": "Αποτυχία αποκατάστασης ορισμένων αρχείων", - "toastDeleteQuarantineSuccess": "Διαγράφηκαν οριστικά {{count}} αρχείο/-α", - "toastRestoreNoOriginal": "Δεν είναι δυνατή η αποκατάσταση — η αρχική τοποθεσία είναι άγνωστη" + "toastDeleteQuarantineSuccess": "Διαγράφηκαν οριστικά {{count}} αρχείο(-α)", + "toastRestoreNoOriginal": "Δεν είναι δυνατή η αποκατάσταση — η αρχική θέση είναι άγνωστη" } diff --git a/src/renderer/src/locales/es/malware.json b/src/renderer/src/locales/es/malware.json index 079f51c9..4a6f9296 100644 --- a/src/renderer/src/locales/es/malware.json +++ b/src/renderer/src/locales/es/malware.json @@ -39,7 +39,7 @@ "actionResultDeleted": "{{count}} eliminadas", "actionResultFailed": "{{count}} con error", "noThreatsDetectedTitle": "No se detectaron amenazas", - "noThreatsDetectedDescription": "Se analizaron {{filesScanned}} archivos con {{engineCount}} motores en {{duration}} s; el sistema está limpio.", + "noThreatsDetectedDescription": "Se analizaron {{filesScanned}} archivos con {{engineCount}} motores en {{duration}} s: el sistema está limpio.", "emptyStateTitle": "Escáner de malware", "emptyStateDescription": "Haga clic en \"Analizar\" para comprobar si hay malware, adware, mineros de criptomonedas y archivos sospechosos en el sistema.", "detectedThreatsHeading": "Amenazas detectadas", @@ -50,17 +50,38 @@ "threatDetailSize": "Tamaño", "threatDetailPath": "Ruta", "confirmQuarantineTitle": "Poner amenazas en cuarentena", - "confirmQuarantineDescription": "Esto moverá {{count}} amenaza(s) detectada(s) a la cuarentena. Los archivos podrán restaurarse más adelante si es necesario.", + "confirmQuarantineDescription": "Esto moverá {{count}} amenaza(s) detectada(s) a la cuarentena. Los archivos se podrán restaurar más adelante si es necesario.", "confirmQuarantineLabel": "Poner en cuarentena ahora", "confirmDeleteTitle": "Eliminar amenazas", "confirmDeleteDescription": "Esto eliminará permanentemente {{count}} amenaza(s) detectada(s). Esta acción no se puede deshacer.", "confirmDeleteLabel": "Eliminar permanentemente", - "toastScanFailed": "Error al analizar malware", + "toastScanFailed": "Error al analizar en busca de malware", "toastActionFailed": "No se pudieron {{action}} las amenazas", "toastActionFailedDescription": "Intente ejecutar como administrador", "errorOperationFailed": "La operación falló; intente ejecutar como administrador", "tabScanner": "Escáner", "tabQuarantine": "Cuarentena", + "tabDatabase": "Base de datos", + "dbTitle": "Base de datos de firmas", + "dbDescription": "Reglas YARA de detección de malware utilizadas por el escáner", + "dbFetchLatest": "Buscar actualizaciones", + "dbUpdating": "Actualizando...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Alternativa Regex", + "dbRulesLoaded": "Reglas cargadas", + "dbVersion": "Versión de firmas", + "dbLastUpdated": "Última actualización", + "dbNever": "Nunca", + "dbSource": "Origen de las reglas", + "dbSourceCloud": "Nube (en caché)", + "dbSourceBundled": "Incluidas con la aplicación", + "dbSourceNone": "No hay reglas cargadas", + "dbRuleFiles": "Archivos de reglas", + "dbRuleFilesCounts": "{{bundled}} incluidas, {{cached}} desde la nube", + "dbUpdateSuccess": "Actualizado a la v{{version}} ({{count}} reglas)", + "dbAlreadyCurrent": "Las firmas ya están actualizadas", + "dbUpdateFailed": "No se pudieron actualizar las firmas", "quarantineEmptyTitle": "No hay elementos en cuarentena", "quarantineEmptyDescription": "Los archivos movidos a la cuarentena aparecerán aquí. Puede restaurarlos o eliminarlos permanentemente.", "quarantineHeading": "Archivos en cuarentena", @@ -76,7 +97,7 @@ "quarantineSelectAll": "Seleccionar todo", "quarantineDeselectAll": "Deseleccionar todo", "confirmRestoreTitle": "Restaurar archivos", - "confirmRestoreDescription": "Esto restaurará {{count}} archivo(s) a su ubicación original. Restaure solo los archivos en los que confíe.", + "confirmRestoreDescription": "Esto restaurará {{count}} archivo(s) a su ubicación original. Restaure solo archivos en los que confíe.", "confirmRestoreLabel": "Restaurar ahora", "confirmDeleteQuarantineTitle": "Eliminar permanentemente", "confirmDeleteQuarantineDescription": "Esto eliminará permanentemente {{count}} archivo(s) en cuarentena. Esta acción no se puede deshacer.", @@ -87,5 +108,5 @@ "toastRestoreSuccess": "{{count}} archivo(s) restaurado(s)", "toastRestoreFailed": "No se pudieron restaurar algunos archivos", "toastDeleteQuarantineSuccess": "{{count}} archivo(s) eliminado(s) permanentemente", - "toastRestoreNoOriginal": "No se puede restaurar; se desconoce la ubicación original" + "toastRestoreNoOriginal": "No se puede restaurar: ubicación original desconocida" } diff --git a/src/renderer/src/locales/fi/malware.json b/src/renderer/src/locales/fi/malware.json index 90fb6fc9..88bf0444 100644 --- a/src/renderer/src/locales/fi/malware.json +++ b/src/renderer/src/locales/fi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Haittaohjelmaskanneri", - "pageDescription": "Monimoottorinen uhkien tunnistus — allekirjoitukset, heuristiikka, komentosarja-analyysi, järjestelmän eheyden tarkistus ja pysyvyyden tarkistus", + "pageDescription": "Monimoottorinen uhkien tunnistus — tunnisteet, heuristiikka, skriptianalyysi, järjestelmän eheys ja pysyvyysskannaus", "scanButtonScanning": "Skannataan...", "scanButton": "Skannaa", "quarantineButton": "Siirrä karanteeniin", @@ -13,14 +13,14 @@ "sourceDefenderMac": "Koodin allekirjoitus", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristinen analyysi", - "sourceSignature": "Tunnettu allekirjoitus", + "sourceSignature": "Tunnettu tunniste", "initializingScanEngines": "Alustetaan skannausmoottoreita...", "filesScanned": "{{count}} tiedostoa skannattu", "threatCount": "{{count}} uhka", "threatCountPlural": "{{count}} uhkaa", "threatFound": "{{count}} löydetty", "scanCategoryClean": "puhdas", - "scanCategoryNA": "Ei saatavilla", + "scanCategoryNA": "Ei käytettävissä", "scanSummarySystemClean": "Järjestelmä puhdas", "scanSummaryThreatsDetected": "{{count}} uhkaa havaittu", "scanStatFiles": "Tiedostot", @@ -38,7 +38,7 @@ "actionResultQuarantined": "{{count}} siirretty karanteeniin", "actionResultDeleted": "{{count}} poistettu", "actionResultFailed": "{{count}} epäonnistui", - "noThreatsDetectedTitle": "Uhkia ei havaittu", + "noThreatsDetectedTitle": "Uhkiä ei havaittu", "noThreatsDetectedDescription": "Skannattiin {{filesScanned}} tiedostoa {{engineCount}} moottorilla {{duration}} sekunnissa — järjestelmäsi on puhdas.", "emptyStateTitle": "Haittaohjelmaskanneri", "emptyStateDescription": "Napsauta \"Skannaa\" tarkistaaksesi järjestelmäsi haittaohjelmien, mainosohjelmien, kryptolouhijoiden ja epäilyttävien tiedostojen varalta.", @@ -49,21 +49,42 @@ "threatDetailFile": "Tiedosto", "threatDetailSize": "Koko", "threatDetailPath": "Polku", - "confirmQuarantineTitle": "Siirrä uhat karanteeniin", + "confirmQuarantineTitle": "Siirrä uhkat karanteeniin", "confirmQuarantineDescription": "Tämä siirtää {{count}} havaittua uhkaa karanteeniin. Tiedostot voidaan tarvittaessa palauttaa myöhemmin.", "confirmQuarantineLabel": "Siirrä karanteeniin nyt", "confirmDeleteTitle": "Poista uhat", "confirmDeleteDescription": "Tämä poistaa pysyvästi {{count}} havaittua uhkaa. Tätä toimintoa ei voi kumota.", "confirmDeleteLabel": "Poista pysyvästi", "toastScanFailed": "Haittaohjelmaskannaus epäonnistui", - "toastActionFailed": "Uhkiin kohdistuva toiminto \"{{action}}\" epäonnistui", + "toastActionFailed": "Uhkiin kohdistuva toiminto {{action}} epäonnistui", "toastActionFailedDescription": "Yritä suorittaa järjestelmänvalvojana", "errorOperationFailed": "Toiminto epäonnistui — yritä suorittaa järjestelmänvalvojana", "tabScanner": "Skanneri", "tabQuarantine": "Karanteeni", - "quarantineEmptyTitle": "Ei karanteeniin siirrettyjä kohteita", + "tabDatabase": "Tietokanta", + "dbTitle": "Tunnistetietokanta", + "dbDescription": "Skannerin käyttämät YARA-haittaohjelmien tunnistussäännöt", + "dbFetchLatest": "Tarkista päivitykset", + "dbUpdating": "Päivitetään...", + "dbEngine": "Moottori", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-varajärjestelmä", + "dbRulesLoaded": "Sääntöjä ladattu", + "dbVersion": "Tunnisteversio", + "dbLastUpdated": "Viimeksi päivitetty", + "dbNever": "Ei koskaan", + "dbSource": "Sääntöjen lähde", + "dbSourceCloud": "Pilvi (välimuistissa)", + "dbSourceBundled": "Sisältyy sovellukseen", + "dbSourceNone": "Sääntöjä ei ladattu", + "dbRuleFiles": "Sääntötiedostot", + "dbRuleFilesCounts": "{{bundled}} sisältyy sovellukseen, {{cached}} pilvestä", + "dbUpdateSuccess": "Päivitetty versioon v{{version}} ({{count}} sääntöä)", + "dbAlreadyCurrent": "Tunnisteet ovat jo ajan tasalla", + "dbUpdateFailed": "Tunnisteiden päivitys epäonnistui", + "quarantineEmptyTitle": "Ei karanteenissa olevia kohteita", "quarantineEmptyDescription": "Karanteeniin siirretyt tiedostot näkyvät täällä. Voit palauttaa ne tai poistaa ne pysyvästi.", - "quarantineHeading": "Karanteeniin siirretyt tiedostot", + "quarantineHeading": "Karanteenissa olevat tiedostot", "quarantineCount": "{{count}} tiedosto", "quarantineCountPlural": "{{count}} tiedostoa", "quarantineColumnFile": "Tiedosto", @@ -79,13 +100,13 @@ "confirmRestoreDescription": "Tämä palauttaa {{count}} tiedostoa niiden alkuperäiseen sijaintiin. Palauta vain tiedostoja, joihin luotat.", "confirmRestoreLabel": "Palauta nyt", "confirmDeleteQuarantineTitle": "Poista pysyvästi", - "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteeniin siirrettyä tiedostoa. Tätä toimintoa ei voi kumota.", + "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteenissa olevaa tiedostoa. Tätä toimintoa ei voi kumota.", "confirmDeleteQuarantineLabel": "Poista pysyvästi", "quarantineLoading": "Ladataan karanteenia...", "quarantineRestoring": "Palautetaan tiedostoja...", "quarantineDeleting": "Poistetaan tiedostoja...", - "toastRestoreSuccess": "{{count}} tiedostoa palautettu", - "toastRestoreFailed": "Joidenkin tiedostojen palauttaminen epäonnistui", - "toastDeleteQuarantineSuccess": "{{count}} tiedostoa poistettu pysyvästi", - "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti ei ole tiedossa" + "toastRestoreSuccess": "{{count}} tiedosto(a) palautettu", + "toastRestoreFailed": "Joidenkin tiedostojen palautus epäonnistui", + "toastDeleteQuarantineSuccess": "{{count}} tiedosto(a) poistettu pysyvästi", + "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti on tuntematon" } diff --git a/src/renderer/src/locales/fr/malware.json b/src/renderer/src/locales/fr/malware.json index 89488980..02291250 100644 --- a/src/renderer/src/locales/fr/malware.json +++ b/src/renderer/src/locales/fr/malware.json @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Aucune menace détectée", "noThreatsDetectedDescription": "Analyse de {{filesScanned}} fichiers avec {{engineCount}} moteurs en {{duration}} s — votre système est propre.", "emptyStateTitle": "Analyseur de malware", - "emptyStateDescription": "Cliquez sur \"Analyser\" pour rechercher des malware, adwares, mineurs de cryptomonnaie et fichiers suspects sur votre système.", + "emptyStateDescription": "Cliquez sur \"Analyser\" pour vérifier la présence de malware, d’adware, de mineurs de cryptomonnaie et de fichiers suspects sur votre système.", "detectedThreatsHeading": "Menaces détectées", "detectedThreatsCount": "{{count}} menace", "detectedThreatsCountPlural": "{{count}} menaces", @@ -50,19 +50,40 @@ "threatDetailSize": "Taille", "threatDetailPath": "Chemin", "confirmQuarantineTitle": "Mettre les menaces en quarantaine", - "confirmQuarantineDescription": "Cette action placera {{count}} menace(s) détectée(s) en quarantaine. Les fichiers pourront être restaurés ultérieurement si nécessaire.", + "confirmQuarantineDescription": "Cette action déplacera {{count}} menace(s) détectée(s) vers la quarantaine. Les fichiers pourront être restaurés ultérieurement si nécessaire.", "confirmQuarantineLabel": "Mettre en quarantaine maintenant", "confirmDeleteTitle": "Supprimer les menaces", "confirmDeleteDescription": "Cette action supprimera définitivement {{count}} menace(s) détectée(s). Cette action est irréversible.", "confirmDeleteLabel": "Supprimer définitivement", - "toastScanFailed": "L’analyse anti-malware a échoué", + "toastScanFailed": "L’analyse des malware a échoué", "toastActionFailed": "Échec de l’action {{action}} sur les menaces", "toastActionFailedDescription": "Essayez d’exécuter en tant qu’administrateur", "errorOperationFailed": "L’opération a échoué — essayez d’exécuter en tant qu’administrateur", "tabScanner": "Analyseur", "tabQuarantine": "Quarantaine", + "tabDatabase": "Base de données", + "dbTitle": "Base de signatures", + "dbDescription": "Règles de détection de malware YARA utilisées par l’analyseur", + "dbFetchLatest": "Rechercher des mises à jour", + "dbUpdating": "Mise à jour en cours...", + "dbEngine": "Moteur", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Secours Regex", + "dbRulesLoaded": "Règles chargées", + "dbVersion": "Version des signatures", + "dbLastUpdated": "Dernière mise à jour", + "dbNever": "Jamais", + "dbSource": "Source des règles", + "dbSourceCloud": "Cloud (en cache)", + "dbSourceBundled": "Inclus avec l’application", + "dbSourceNone": "Aucune règle chargée", + "dbRuleFiles": "Fichiers de règles", + "dbRuleFilesCounts": "{{bundled}} inclus, {{cached}} depuis le cloud", + "dbUpdateSuccess": "Mise à jour vers v{{version}} ({{count}} règles)", + "dbAlreadyCurrent": "Les signatures sont déjà à jour", + "dbUpdateFailed": "Échec de la mise à jour des signatures", "quarantineEmptyTitle": "Aucun élément en quarantaine", - "quarantineEmptyDescription": "Les fichiers placés en quarantaine apparaîtront ici. Vous pourrez les restaurer ou les supprimer définitivement.", + "quarantineEmptyDescription": "Les fichiers déplacés en quarantaine apparaîtront ici. Vous pourrez les restaurer ou les supprimer définitivement.", "quarantineHeading": "Fichiers en quarantaine", "quarantineCount": "{{count}} fichier", "quarantineCountPlural": "{{count}} fichiers", @@ -87,5 +108,5 @@ "toastRestoreSuccess": "{{count}} fichier(s) restauré(s)", "toastRestoreFailed": "Échec de la restauration de certains fichiers", "toastDeleteQuarantineSuccess": "{{count}} fichier(s) supprimé(s) définitivement", - "toastRestoreNoOriginal": "Restauration impossible — emplacement d’origine inconnu" + "toastRestoreNoOriginal": "Impossible de restaurer — emplacement d’origine inconnu" } diff --git a/src/renderer/src/locales/he/malware.json b/src/renderer/src/locales/he/malware.json index 9e827add..2c1e52f1 100644 --- a/src/renderer/src/locales/he/malware.json +++ b/src/renderer/src/locales/he/malware.json @@ -5,7 +5,7 @@ "scanButton": "סרוק", "quarantineButton": "העבר להסגר", "deleteButton": "מחק", - "severityCritical": "קריטית", + "severityCritical": "קריטי", "severityHigh": "גבוהה", "severityMedium": "בינונית", "severityLow": "נמוכה", @@ -15,14 +15,14 @@ "sourceHeuristic": "ניתוח היוריסטי", "sourceSignature": "חתימה מוכרת", "initializingScanEngines": "מאתחל מנועי סריקה...", - "filesScanned": "{{count}} קבצים נסרקו", + "filesScanned": "נסרקו {{count}} קבצים", "threatCount": "{{count}} איום", "threatCountPlural": "{{count}} איומים", - "threatFound": "{{count}} נמצאו", + "threatFound": "נמצאו {{count}}", "scanCategoryClean": "נקי", "scanCategoryNA": "לא זמין", "scanSummarySystemClean": "המערכת נקייה", - "scanSummaryThreatsDetected": "{{count}} איומים זוהו", + "scanSummaryThreatsDetected": "זוהו {{count}} איומים", "scanStatFiles": "קבצים", "scanStatDuration": "משך", "scanStatEngines": "מנועים", @@ -50,19 +50,40 @@ "threatDetailSize": "גודל", "threatDetailPath": "נתיב", "confirmQuarantineTitle": "העבר איומים להסגר", - "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן יהיה לשחזר קבצים מאוחר יותר במידת הצורך.", + "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן יהיה לשחזר קבצים מאוחר יותר אם יהיה צורך.", "confirmQuarantineLabel": "העבר להסגר כעת", "confirmDeleteTitle": "מחק איומים", "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איום/איומים שזוהו. לא ניתן לבטל פעולה זו.", "confirmDeleteLabel": "מחק לצמיתות", "toastScanFailed": "סריקת הנוזקות נכשלה", - "toastActionFailed": "לא ניתן היה {{action}} את האיומים", + "toastActionFailed": "נכשל הניסיון לבצע {{action}} באיומים", "toastActionFailedDescription": "נסה להפעיל כמנהל מערכת", "errorOperationFailed": "הפעולה נכשלה — נסה להפעיל כמנהל מערכת", "tabScanner": "סורק", "tabQuarantine": "הסגר", + "tabDatabase": "מסד נתונים", + "dbTitle": "מסד נתוני חתימות", + "dbDescription": "כללי זיהוי נוזקות של YARA שבהם הסורק משתמש", + "dbFetchLatest": "בדוק אם קיימים עדכונים", + "dbUpdating": "מעדכן...", + "dbEngine": "מנוע", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "חלופת Regex", + "dbRulesLoaded": "כללים שנטענו", + "dbVersion": "גרסת חתימות", + "dbLastUpdated": "עודכן לאחרונה", + "dbNever": "מעולם לא", + "dbSource": "מקור הכללים", + "dbSourceCloud": "ענן (במטמון)", + "dbSourceBundled": "כלול ביישום", + "dbSourceNone": "לא נטענו כללים", + "dbRuleFiles": "קובצי כללים", + "dbRuleFilesCounts": "{{bundled}} כלולים, {{cached}} מהענן", + "dbUpdateSuccess": "עודכן ל-v{{version}} ({{count}} כללים)", + "dbAlreadyCurrent": "החתימות כבר מעודכנות", + "dbUpdateFailed": "עדכון החתימות נכשל", "quarantineEmptyTitle": "אין פריטים בהסגר", - "quarantineEmptyDescription": "קבצים שהועברו להסגר יופיעו כאן. ניתן לשחזר או למחוק אותם לצמיתות.", + "quarantineEmptyDescription": "קבצים שהועברו להסגר יופיעו כאן. ניתן לשחזר אותם או למחוק אותם לצמיתות.", "quarantineHeading": "קבצים בהסגר", "quarantineCount": "{{count}} קובץ", "quarantineCountPlural": "{{count}} קבצים", @@ -76,7 +97,7 @@ "quarantineSelectAll": "בחר הכול", "quarantineDeselectAll": "בטל בחירה של הכול", "confirmRestoreTitle": "שחזר קבצים", - "confirmRestoreDescription": "פעולה זו תשחזר {{count}} קובץ/קבצים למיקום המקורי שלהם. שחזר רק קבצים שאתה סומך עליהם.", + "confirmRestoreDescription": "פעולה זו תשחזר {{count}} קובץ/קבצים למיקומם המקורי. שחזר רק קבצים שאתה סומך עליהם.", "confirmRestoreLabel": "שחזר כעת", "confirmDeleteQuarantineTitle": "מחק לצמיתות", "confirmDeleteQuarantineDescription": "פעולה זו תמחק לצמיתות {{count}} קובץ/קבצים שבהסגר. לא ניתן לבטל פעולה זו.", diff --git a/src/renderer/src/locales/hi/malware.json b/src/renderer/src/locales/hi/malware.json index 8e1e5fce..9275cb03 100644 --- a/src/renderer/src/locales/hi/malware.json +++ b/src/renderer/src/locales/hi/malware.json @@ -1,19 +1,19 @@ { - "pageTitle": "Malware स्कैनर", - "pageDescription": "मल्टी-इंजन खतरा पहचान — signatures, heuristics, script analysis, system integrity और persistence scanning", + "pageTitle": "मैलवेयर स्कैनर", + "pageDescription": "मल्टी-इंजन खतरा पहचान — सिग्नेचर, ह्यूरिस्टिक्स, स्क्रिप्ट विश्लेषण, सिस्टम अखंडता और पर्सिस्टेंस स्कैनिंग", "scanButtonScanning": "स्कैन किया जा रहा है...", "scanButton": "स्कैन", - "quarantineButton": "Quarantine", + "quarantineButton": "क्वारंटीन", "deleteButton": "हटाएँ", "severityCritical": "गंभीर", "severityHigh": "उच्च", "severityMedium": "मध्यम", "severityLow": "निम्न", "sourceDefenderWindows": "मूल AV", - "sourceDefenderMac": "Code Signing", + "sourceDefenderMac": "कोड साइनिंग", "sourceDefenderLinux": "ClamAV", - "sourceHeuristic": "Heuristic Analysis", - "sourceSignature": "ज्ञात Signature", + "sourceHeuristic": "ह्यूरिस्टिक विश्लेषण", + "sourceSignature": "ज्ञात सिग्नेचर", "initializingScanEngines": "स्कैन इंजन प्रारंभ किए जा रहे हैं...", "filesScanned": "{{count}} फ़ाइलें स्कैन की गईं", "threatCount": "{{count}} खतरा", @@ -31,17 +31,17 @@ "selectedOfThreats": "{{count}} खतरों में से", "selectAll": "सभी चुनें", "deselectAll": "सभी का चयन हटाएँ", - "actingQuarantining": "चयनित खतरों को Quarantine में भेजा जा रहा है...", + "actingQuarantining": "चयनित खतरों को क्वारंटीन किया जा रहा है...", "actingDeleting": "चयनित खतरों को हटाया जा रहा है...", - "actionCompleteQuarantine": "Quarantine पूर्ण", + "actionCompleteQuarantine": "क्वारंटीन पूर्ण", "actionCompleteDeletion": "हटाना पूर्ण", - "actionResultQuarantined": "{{count}} Quarantine में भेजे गए", + "actionResultQuarantined": "{{count}} क्वारंटीन किए गए", "actionResultDeleted": "{{count}} हटाए गए", "actionResultFailed": "{{count}} विफल", "noThreatsDetectedTitle": "कोई खतरा नहीं मिला", "noThreatsDetectedDescription": "{{duration}}s में {{engineCount}} इंजनों पर {{filesScanned}} फ़ाइलें स्कैन की गईं — आपका सिस्टम साफ़ है।", - "emptyStateTitle": "Malware स्कैनर", - "emptyStateDescription": "\"स्कैन\" पर क्लिक करके अपने सिस्टम में malware, adware, crypto miners और संदिग्ध फ़ाइलों की जाँच करें।", + "emptyStateTitle": "मैलवेयर स्कैनर", + "emptyStateDescription": "\"स्कैन\" पर क्लिक करके अपने सिस्टम में मैलवेयर, एडवेयर, क्रिप्टो माइनर और संदिग्ध फ़ाइलों की जाँच करें।", "detectedThreatsHeading": "पाए गए खतरे", "detectedThreatsCount": "{{count}} खतरा", "detectedThreatsCountPlural": "{{count}} खतरे", @@ -49,21 +49,42 @@ "threatDetailFile": "फ़ाइल", "threatDetailSize": "आकार", "threatDetailPath": "पथ", - "confirmQuarantineTitle": "खतरों को Quarantine में भेजें", - "confirmQuarantineDescription": "इससे पाए गए {{count}} threat(s) को Quarantine में भेज दिया जाएगा। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", - "confirmQuarantineLabel": "अभी Quarantine में भेजें", + "confirmQuarantineTitle": "खतरों को क्वारंटीन करें", + "confirmQuarantineDescription": "इससे पाए गए {{count}} खतरे क्वारंटीन में भेज दिए जाएँगे। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", + "confirmQuarantineLabel": "अभी क्वारंटीन करें", "confirmDeleteTitle": "खतरों को हटाएँ", - "confirmDeleteDescription": "इससे पाए गए {{count}} threat(s) स्थायी रूप से हटा दिए जाएँगे। इस क्रिया को पूर्ववत नहीं किया जा सकता।", + "confirmDeleteDescription": "इससे पाए गए {{count}} खतरे स्थायी रूप से हटा दिए जाएँगे। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteLabel": "स्थायी रूप से हटाएँ", - "toastScanFailed": "Malware स्कैन विफल हुआ", + "toastScanFailed": "मैलवेयर स्कैन विफल हुआ", "toastActionFailed": "खतरों को {{action}} करने में विफल", "toastActionFailedDescription": "व्यवस्थापक के रूप में चलाने का प्रयास करें", - "errorOperationFailed": "क्रिया विफल हुई — व्यवस्थापक के रूप में चलाने का प्रयास करें", + "errorOperationFailed": "कार्रवाई विफल हुई — व्यवस्थापक के रूप में चलाने का प्रयास करें", "tabScanner": "स्कैनर", - "tabQuarantine": "Quarantine", - "quarantineEmptyTitle": "कोई Quarantine आइटम नहीं", - "quarantineEmptyDescription": "Quarantine में भेजी गई फ़ाइलें यहाँ दिखाई देंगी। आप उन्हें पुनर्स्थापित कर सकते हैं या स्थायी रूप से हटा सकते हैं।", - "quarantineHeading": "Quarantine की गई फ़ाइलें", + "tabQuarantine": "क्वारंटीन", + "tabDatabase": "डेटाबेस", + "dbTitle": "सिग्नेचर डेटाबेस", + "dbDescription": "स्कैनर द्वारा उपयोग किए जाने वाले YARA मैलवेयर पहचान नियम", + "dbFetchLatest": "अपडेट की जाँच करें", + "dbUpdating": "अपडेट किया जा रहा है...", + "dbEngine": "इंजन", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex फ़ॉलबैक", + "dbRulesLoaded": "लोड किए गए नियम", + "dbVersion": "सिग्नेचर संस्करण", + "dbLastUpdated": "अंतिम अपडेट", + "dbNever": "कभी नहीं", + "dbSource": "नियम स्रोत", + "dbSourceCloud": "क्लाउड (कैश्ड)", + "dbSourceBundled": "ऐप के साथ शामिल", + "dbSourceNone": "कोई नियम लोड नहीं किए गए", + "dbRuleFiles": "नियम फ़ाइलें", + "dbRuleFilesCounts": "{{bundled}} शामिल, {{cached}} क्लाउड से", + "dbUpdateSuccess": "v{{version}} में अपडेट किया गया ({{count}} नियम)", + "dbAlreadyCurrent": "सिग्नेचर पहले से अद्यतित हैं", + "dbUpdateFailed": "सिग्नेचर अपडेट करने में विफल", + "quarantineEmptyTitle": "कोई क्वारंटीन आइटम नहीं", + "quarantineEmptyDescription": "क्वारंटीन में भेजी गई फ़ाइलें यहाँ दिखाई देंगी। आप उन्हें पुनर्स्थापित कर सकते हैं या स्थायी रूप से हटा सकते हैं।", + "quarantineHeading": "क्वारंटीन की गई फ़ाइलें", "quarantineCount": "{{count}} फ़ाइल", "quarantineCountPlural": "{{count}} फ़ाइलें", "quarantineColumnFile": "फ़ाइल", @@ -76,16 +97,16 @@ "quarantineSelectAll": "सभी चुनें", "quarantineDeselectAll": "सभी का चयन हटाएँ", "confirmRestoreTitle": "फ़ाइलें पुनर्स्थापित करें", - "confirmRestoreDescription": "इससे {{count}} file(s) को उनके मूल स्थान पर पुनर्स्थापित किया जाएगा। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", + "confirmRestoreDescription": "इससे {{count}} फ़ाइलें उनके मूल स्थान पर पुनर्स्थापित कर दी जाएँगी। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", "confirmRestoreLabel": "अभी पुनर्स्थापित करें", "confirmDeleteQuarantineTitle": "स्थायी रूप से हटाएँ", - "confirmDeleteQuarantineDescription": "इससे {{count}} quarantined file(s) स्थायी रूप से हटा दी जाएँगी। इस क्रिया को पूर्ववत नहीं किया जा सकता।", + "confirmDeleteQuarantineDescription": "इससे {{count}} क्वारंटीन की गई फ़ाइलें स्थायी रूप से हटा दी जाएँगी। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteQuarantineLabel": "स्थायी रूप से हटाएँ", - "quarantineLoading": "Quarantine लोड किया जा रहा है...", + "quarantineLoading": "क्वारंटीन लोड किया जा रहा है...", "quarantineRestoring": "फ़ाइलें पुनर्स्थापित की जा रही हैं...", "quarantineDeleting": "फ़ाइलें हटाई जा रही हैं...", - "toastRestoreSuccess": "{{count}} file(s) पुनर्स्थापित की गईं", + "toastRestoreSuccess": "{{count}} फ़ाइलें पुनर्स्थापित की गईं", "toastRestoreFailed": "कुछ फ़ाइलों को पुनर्स्थापित करने में विफल", - "toastDeleteQuarantineSuccess": "{{count}} file(s) स्थायी रूप से हटाई गईं", + "toastDeleteQuarantineSuccess": "{{count}} फ़ाइलें स्थायी रूप से हटाई गईं", "toastRestoreNoOriginal": "पुनर्स्थापित नहीं किया जा सकता — मूल स्थान अज्ञात है" } diff --git a/src/renderer/src/locales/hu/malware.json b/src/renderer/src/locales/hu/malware.json index 2b504dca..ea0d9603 100644 --- a/src/renderer/src/locales/hu/malware.json +++ b/src/renderer/src/locales/hu/malware.json @@ -1,5 +1,5 @@ { - "pageTitle": "Malware-ellenőrző", + "pageTitle": "Kártevőkereső", "pageDescription": "Többmotoros fenyegetésészlelés — szignatúrák, heurisztika, szkriptelemzés, rendszerintegritás és perzisztencia vizsgálata", "scanButtonScanning": "Vizsgálat folyamatban...", "scanButton": "Vizsgálat", @@ -9,7 +9,7 @@ "severityHigh": "Magas", "severityMedium": "Közepes", "severityLow": "Alacsony", - "sourceDefenderWindows": "Natív AV", + "sourceDefenderWindows": "Beépített AV", "sourceDefenderMac": "Kódaláírás", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heurisztikus elemzés", @@ -40,8 +40,8 @@ "actionResultFailed": "{{count}} sikertelen", "noThreatsDetectedTitle": "Nem észlelhető fenyegetés", "noThreatsDetectedDescription": "{{filesScanned}} fájl vizsgálva {{engineCount}} motorral {{duration}} mp alatt — a rendszer tiszta.", - "emptyStateTitle": "Malware-ellenőrző", - "emptyStateDescription": "Kattintson a „Vizsgálat” gombra a rendszer malware, adware, kriptobányászok és gyanús fájlok ellenőrzéséhez.", + "emptyStateTitle": "Kártevőkereső", + "emptyStateDescription": "Kattintson a „Vizsgálat” gombra, hogy ellenőrizze rendszerét kártevők, reklámprogramok, kriptobányászok és gyanús fájlok után.", "detectedThreatsHeading": "Észlelt fenyegetések", "detectedThreatsCount": "{{count}} fenyegetés", "detectedThreatsCountPlural": "{{count}} fenyegetés", @@ -53,16 +53,37 @@ "confirmQuarantineDescription": "Ez a művelet {{count}} észlelt fenyegetés(eke)t helyez karanténba. A fájlok szükség esetén később visszaállíthatók.", "confirmQuarantineLabel": "Karanténba helyezés most", "confirmDeleteTitle": "Fenyegetések törlése", - "confirmDeleteDescription": "Ez a művelet véglegesen törli a(z) {{count}} észlelt fenyegetés(eke)t. Ez a művelet nem vonható vissza.", + "confirmDeleteDescription": "Ez a művelet véglegesen törli a(z) {{count}} észlelt fenyegetés(eke)t. A művelet nem vonható vissza.", "confirmDeleteLabel": "Végleges törlés", - "toastScanFailed": "A malware-vizsgálat sikertelen volt", - "toastActionFailed": "A fenyegetések {{action}} művelete sikertelen volt", + "toastScanFailed": "A kártevővizsgálat sikertelen", + "toastActionFailed": "Nem sikerült a fenyegetések {{action}} művelete", "toastActionFailedDescription": "Próbálja rendszergazdaként futtatni", - "errorOperationFailed": "A művelet sikertelen volt — próbálja rendszergazdaként futtatni", - "tabScanner": "Ellenőrző", + "errorOperationFailed": "A művelet sikertelen — próbálja rendszergazdaként futtatni", + "tabScanner": "Kereső", "tabQuarantine": "Karantén", + "tabDatabase": "Adatbázis", + "dbTitle": "Szignatúra-adatbázis", + "dbDescription": "A kereső által használt YARA kártevőészlelési szabályok", + "dbFetchLatest": "Frissítések keresése", + "dbUpdating": "Frissítés...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex tartalék mód", + "dbRulesLoaded": "Betöltött szabályok", + "dbVersion": "Szignatúra verziója", + "dbLastUpdated": "Utoljára frissítve", + "dbNever": "Soha", + "dbSource": "Szabályforrás", + "dbSourceCloud": "Felhő (gyorsítótárazva)", + "dbSourceBundled": "Az alkalmazással együtt", + "dbSourceNone": "Nincs betöltött szabály", + "dbRuleFiles": "Szabályfájlok", + "dbRuleFilesCounts": "{{bundled}} csomagban, {{cached}} a felhőből", + "dbUpdateSuccess": "Frissítve erre: v{{version}} ({{count}} szabály)", + "dbAlreadyCurrent": "A szignatúrák már naprakészek", + "dbUpdateFailed": "Nem sikerült frissíteni a szignatúrákat", "quarantineEmptyTitle": "Nincsenek karanténba helyezett elemek", - "quarantineEmptyDescription": "Az ide áthelyezett fájlok itt jelennek meg. Visszaállíthatja vagy véglegesen törölheti őket.", + "quarantineEmptyDescription": "Az ide karanténba helyezett fájlok itt jelennek meg. Visszaállíthatja vagy véglegesen törölheti őket.", "quarantineHeading": "Karanténba helyezett fájlok", "quarantineCount": "{{count}} fájl", "quarantineCountPlural": "{{count}} fájl", @@ -79,13 +100,13 @@ "confirmRestoreDescription": "Ez a művelet {{count}} fájl(oka)t állít vissza az eredeti helyére. Csak megbízható fájlokat állítson vissza.", "confirmRestoreLabel": "Visszaállítás most", "confirmDeleteQuarantineTitle": "Végleges törlés", - "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli a(z) {{count}} karanténba helyezett fájl(oka)t. Ez a művelet nem vonható vissza.", + "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli {{count}} karanténba helyezett fájl(oka)t. A művelet nem vonható vissza.", "confirmDeleteQuarantineLabel": "Végleges törlés", "quarantineLoading": "Karantén betöltése...", "quarantineRestoring": "Fájlok visszaállítása...", "quarantineDeleting": "Fájlok törlése...", "toastRestoreSuccess": "{{count}} fájl visszaállítva", - "toastRestoreFailed": "Néhány fájl visszaállítása sikertelen volt", + "toastRestoreFailed": "Néhány fájl visszaállítása nem sikerült", "toastDeleteQuarantineSuccess": "{{count}} fájl véglegesen törölve", "toastRestoreNoOriginal": "Nem állítható vissza — az eredeti hely ismeretlen" } diff --git a/src/renderer/src/locales/id/malware.json b/src/renderer/src/locales/id/malware.json index c09add59..ec683206 100644 --- a/src/renderer/src/locales/id/malware.json +++ b/src/renderer/src/locales/id/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Pemindai Malware", - "pageDescription": "Deteksi ancaman multi-engine — signature, heuristik, analisis skrip, integritas sistem, dan pemindaian persistensi", + "pageDescription": "Deteksi ancaman multi-mesin — signature, heuristik, analisis skrip, integritas sistem, dan pemindaian persistensi", "scanButtonScanning": "Memindai...", "scanButton": "Pindai", "quarantineButton": "Karantina", @@ -14,7 +14,7 @@ "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analisis Heuristik", "sourceSignature": "Signature Dikenal", - "initializingScanEngines": "Menginisialisasi engine pemindaian...", + "initializingScanEngines": "Menginisialisasi mesin pemindaian...", "filesScanned": "{{count}} file dipindai", "threatCount": "{{count}} ancaman", "threatCountPlural": "{{count}} ancaman", @@ -25,7 +25,7 @@ "scanSummaryThreatsDetected": "{{count}} Ancaman Terdeteksi", "scanStatFiles": "File", "scanStatDuration": "Durasi", - "scanStatEngines": "Engine", + "scanStatEngines": "Mesin", "severityHeading": "Tingkat Keparahan", "selectedHeading": "Dipilih", "selectedOfThreats": "dari {{count}} ancaman", @@ -39,7 +39,7 @@ "actionResultDeleted": "{{count}} dihapus", "actionResultFailed": "{{count}} gagal", "noThreatsDetectedTitle": "Tidak Ada Ancaman Terdeteksi", - "noThreatsDetectedDescription": "Memindai {{filesScanned}} file di {{engineCount}} engine dalam {{duration}} dtk — sistem Anda bersih.", + "noThreatsDetectedDescription": "Memindai {{filesScanned}} file di {{engineCount}} mesin dalam {{duration}} dtk — sistem Anda bersih.", "emptyStateTitle": "Pemindai Malware", "emptyStateDescription": "Klik \"Pindai\" untuk memeriksa sistem Anda dari malware, adware, penambang kripto, dan file mencurigakan.", "detectedThreatsHeading": "Ancaman Terdeteksi", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Ini akan memindahkan {{count}} ancaman yang terdeteksi ke karantina. File dapat dipulihkan nanti jika diperlukan.", "confirmQuarantineLabel": "Karantina Sekarang", "confirmDeleteTitle": "Hapus Ancaman", - "confirmDeleteDescription": "Ini akan menghapus secara permanen {{count}} ancaman yang terdeteksi. Tindakan ini tidak dapat dibatalkan.", + "confirmDeleteDescription": "Ini akan menghapus permanen {{count}} ancaman yang terdeteksi. Tindakan ini tidak dapat dibatalkan.", "confirmDeleteLabel": "Hapus Permanen", "toastScanFailed": "Pemindaian malware gagal", "toastActionFailed": "Gagal {{action}} ancaman", @@ -61,6 +61,27 @@ "errorOperationFailed": "Operasi gagal — coba jalankan sebagai administrator", "tabScanner": "Pemindai", "tabQuarantine": "Karantina", + "tabDatabase": "Database", + "dbTitle": "Database Signature", + "dbDescription": "Aturan deteksi malware YARA yang digunakan oleh pemindai", + "dbFetchLatest": "Periksa Pembaruan", + "dbUpdating": "Memperbarui...", + "dbEngine": "Mesin", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Cadangan Regex", + "dbRulesLoaded": "Aturan Dimuat", + "dbVersion": "Versi Signature", + "dbLastUpdated": "Terakhir Diperbarui", + "dbNever": "Tidak Pernah", + "dbSource": "Sumber Aturan", + "dbSourceCloud": "Cloud (di-cache)", + "dbSourceBundled": "Disertakan dengan aplikasi", + "dbSourceNone": "Tidak ada aturan yang dimuat", + "dbRuleFiles": "File Aturan", + "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari cloud", + "dbUpdateSuccess": "Diperbarui ke v{{version}} ({{count}} aturan)", + "dbAlreadyCurrent": "Signature sudah versi terbaru", + "dbUpdateFailed": "Gagal memperbarui signature", "quarantineEmptyTitle": "Tidak Ada Item yang Dikarantina", "quarantineEmptyDescription": "File yang dipindahkan ke karantina akan muncul di sini. Anda dapat memulihkan atau menghapusnya secara permanen.", "quarantineHeading": "File yang Dikarantina", @@ -79,13 +100,13 @@ "confirmRestoreDescription": "Ini akan memulihkan {{count}} file ke lokasi aslinya. Hanya pulihkan file yang Anda percayai.", "confirmRestoreLabel": "Pulihkan Sekarang", "confirmDeleteQuarantineTitle": "Hapus Permanen", - "confirmDeleteQuarantineDescription": "Ini akan menghapus secara permanen {{count}} file yang dikarantina. Tindakan ini tidak dapat dibatalkan.", + "confirmDeleteQuarantineDescription": "Ini akan menghapus permanen {{count}} file yang dikarantina. Tindakan ini tidak dapat dibatalkan.", "confirmDeleteQuarantineLabel": "Hapus Permanen", "quarantineLoading": "Memuat karantina...", "quarantineRestoring": "Memulihkan file...", "quarantineDeleting": "Menghapus file...", "toastRestoreSuccess": "{{count}} file dipulihkan", "toastRestoreFailed": "Gagal memulihkan beberapa file", - "toastDeleteQuarantineSuccess": "{{count}} file dihapus secara permanen", + "toastDeleteQuarantineSuccess": "{{count}} file dihapus permanen", "toastRestoreNoOriginal": "Tidak dapat memulihkan — lokasi asli tidak diketahui" } diff --git a/src/renderer/src/locales/it/malware.json b/src/renderer/src/locales/it/malware.json index 65f614c7..351b56f7 100644 --- a/src/renderer/src/locales/it/malware.json +++ b/src/renderer/src/locales/it/malware.json @@ -27,21 +27,21 @@ "scanStatDuration": "Durata", "scanStatEngines": "Motori", "severityHeading": "Gravità", - "selectedHeading": "Selezionate", + "selectedHeading": "Selezionati", "selectedOfThreats": "di {{count}} minacce", "selectAll": "Seleziona tutto", "deselectAll": "Deseleziona tutto", - "actingQuarantining": "Spostamento in quarantena delle minacce selezionate...", + "actingQuarantining": "Messa in quarantena delle minacce selezionate...", "actingDeleting": "Eliminazione delle minacce selezionate...", "actionCompleteQuarantine": "Quarantena completata", "actionCompleteDeletion": "Eliminazione completata", "actionResultQuarantined": "{{count}} in quarantena", - "actionResultDeleted": "{{count}} eliminate", - "actionResultFailed": "{{count}} non riuscite", + "actionResultDeleted": "{{count}} eliminati", + "actionResultFailed": "{{count}} non riusciti", "noThreatsDetectedTitle": "Nessuna minaccia rilevata", "noThreatsDetectedDescription": "Analizzati {{filesScanned}} file con {{engineCount}} motori in {{duration}} s — il sistema è pulito.", "emptyStateTitle": "Scanner malware", - "emptyStateDescription": "Fare clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, crypto miner e file sospetti.", + "emptyStateDescription": "Fai clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, crypto miner e file sospetti.", "detectedThreatsHeading": "Minacce rilevate", "detectedThreatsCount": "{{count}} minaccia", "detectedThreatsCountPlural": "{{count}} minacce", @@ -50,19 +50,40 @@ "threatDetailSize": "Dimensione", "threatDetailPath": "Percorso", "confirmQuarantineTitle": "Metti in quarantena le minacce", - "confirmQuarantineDescription": "Questa operazione sposterà in quarantena {{count}} minaccia/e rilevata/e. Se necessario, i file potranno essere ripristinati in seguito.", + "confirmQuarantineDescription": "Questa operazione sposterà {{count}} minaccia/e rilevata/e in quarantena. Se necessario, i file potranno essere ripristinati in seguito.", "confirmQuarantineLabel": "Metti ora in quarantena", "confirmDeleteTitle": "Elimina le minacce", "confirmDeleteDescription": "Questa operazione eliminerà definitivamente {{count}} minaccia/e rilevata/e. L'azione non può essere annullata.", "confirmDeleteLabel": "Elimina definitivamente", "toastScanFailed": "Scansione malware non riuscita", "toastActionFailed": "Impossibile {{action}} le minacce", - "toastActionFailedDescription": "Provare a eseguire come amministratore", - "errorOperationFailed": "Operazione non riuscita — provare a eseguire come amministratore", + "toastActionFailedDescription": "Prova a eseguire come amministratore", + "errorOperationFailed": "Operazione non riuscita — prova a eseguire come amministratore", "tabScanner": "Scanner", "tabQuarantine": "Quarantena", + "tabDatabase": "Database", + "dbTitle": "Database delle firme", + "dbDescription": "Regole di rilevamento malware YARA utilizzate dallo scanner", + "dbFetchLatest": "Controlla aggiornamenti", + "dbUpdating": "Aggiornamento in corso...", + "dbEngine": "Motore", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Fallback Regex", + "dbRulesLoaded": "Regole caricate", + "dbVersion": "Versione delle firme", + "dbLastUpdated": "Ultimo aggiornamento", + "dbNever": "Mai", + "dbSource": "Origine delle regole", + "dbSourceCloud": "Cloud (in cache)", + "dbSourceBundled": "Incluso nell'app", + "dbSourceNone": "Nessuna regola caricata", + "dbRuleFiles": "File delle regole", + "dbRuleFilesCounts": "{{bundled}} inclusi, {{cached}} dal cloud", + "dbUpdateSuccess": "Aggiornato alla v{{version}} ({{count}} regole)", + "dbAlreadyCurrent": "Le firme sono già aggiornate", + "dbUpdateFailed": "Aggiornamento delle firme non riuscito", "quarantineEmptyTitle": "Nessun elemento in quarantena", - "quarantineEmptyDescription": "I file spostati in quarantena verranno visualizzati qui. È possibile ripristinarli o eliminarli definitivamente.", + "quarantineEmptyDescription": "I file spostati in quarantena verranno visualizzati qui. Puoi ripristinarli o eliminarli definitivamente.", "quarantineHeading": "File in quarantena", "quarantineCount": "{{count}} file", "quarantineCountPlural": "{{count}} file", @@ -76,14 +97,14 @@ "quarantineSelectAll": "Seleziona tutto", "quarantineDeselectAll": "Deseleziona tutto", "confirmRestoreTitle": "Ripristina file", - "confirmRestoreDescription": "Questa operazione ripristinerà {{count}} file nella posizione originale. Ripristinare solo i file considerati attendibili.", + "confirmRestoreDescription": "Questa operazione ripristinerà {{count}} file nella posizione originale. Ripristina solo i file di cui ti fidi.", "confirmRestoreLabel": "Ripristina ora", "confirmDeleteQuarantineTitle": "Elimina definitivamente", "confirmDeleteQuarantineDescription": "Questa operazione eliminerà definitivamente {{count}} file in quarantena. L'azione non può essere annullata.", "confirmDeleteQuarantineLabel": "Elimina definitivamente", "quarantineLoading": "Caricamento della quarantena...", - "quarantineRestoring": "Ripristino dei file...", - "quarantineDeleting": "Eliminazione dei file...", + "quarantineRestoring": "Ripristino dei file in corso...", + "quarantineDeleting": "Eliminazione dei file in corso...", "toastRestoreSuccess": "{{count}} file ripristinati", "toastRestoreFailed": "Impossibile ripristinare alcuni file", "toastDeleteQuarantineSuccess": "{{count}} file eliminati definitivamente", diff --git a/src/renderer/src/locales/ja/malware.json b/src/renderer/src/locales/ja/malware.json index b669dc7c..9ad7f22a 100644 --- a/src/renderer/src/locales/ja/malware.json +++ b/src/renderer/src/locales/ja/malware.json @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "脅威は検出されませんでした", "noThreatsDetectedDescription": "{{engineCount}} 個のエンジンで {{filesScanned}} 個のファイルを {{duration}} 秒間スキャンしました — システムはクリーンです。", "emptyStateTitle": "マルウェア スキャナー", - "emptyStateDescription": "\"スキャン\" をクリックして、システム内のマルウェア、アドウェア、暗号通貨マイナー、不審なファイルを確認します。", + "emptyStateDescription": "\"スキャン\" をクリックして、システム内のマルウェア、アドウェア、暗号資産マイナー、不審なファイルを確認します。", "detectedThreatsHeading": "検出された脅威", "detectedThreatsCount": "{{count}} 件の脅威", "detectedThreatsCountPlural": "{{count}} 件の脅威", @@ -50,7 +50,7 @@ "threatDetailSize": "サイズ", "threatDetailPath": "パス", "confirmQuarantineTitle": "脅威を隔離", - "confirmQuarantineDescription": "検出された脅威 {{count}} 件を隔離に移動します。必要に応じて後でファイルを復元できます。", + "confirmQuarantineDescription": "検出された脅威 {{count}} 件を隔離に移動します。必要に応じて、後でファイルを復元できます。", "confirmQuarantineLabel": "今すぐ隔離", "confirmDeleteTitle": "脅威を削除", "confirmDeleteDescription": "検出された脅威 {{count}} 件を完全に削除します。この操作は元に戻せません。", @@ -61,8 +61,29 @@ "errorOperationFailed": "操作に失敗しました — 管理者として実行してみてください", "tabScanner": "スキャナー", "tabQuarantine": "隔離", + "tabDatabase": "データベース", + "dbTitle": "シグネチャ データベース", + "dbDescription": "スキャナーで使用される YARA マルウェア検出ルール", + "dbFetchLatest": "更新プログラムの確認", + "dbUpdating": "更新しています...", + "dbEngine": "エンジン", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex フォールバック", + "dbRulesLoaded": "読み込まれたルール", + "dbVersion": "シグネチャ バージョン", + "dbLastUpdated": "最終更新", + "dbNever": "なし", + "dbSource": "ルール ソース", + "dbSourceCloud": "クラウド(キャッシュ済み)", + "dbSourceBundled": "アプリに同梱", + "dbSourceNone": "ルールが読み込まれていません", + "dbRuleFiles": "ルール ファイル", + "dbRuleFilesCounts": "{{bundled}} 個同梱、{{cached}} 個をクラウドから取得", + "dbUpdateSuccess": "v{{version}} に更新しました({{count}} ルール)", + "dbAlreadyCurrent": "シグネチャはすでに最新です", + "dbUpdateFailed": "シグネチャの更新に失敗しました", "quarantineEmptyTitle": "隔離された項目はありません", - "quarantineEmptyDescription": "隔離に移動したファイルはここに表示されます。復元または完全に削除できます。", + "quarantineEmptyDescription": "隔離に移動されたファイルはここに表示されます。復元または完全に削除できます。", "quarantineHeading": "隔離されたファイル", "quarantineCount": "{{count}} 個のファイル", "quarantineCountPlural": "{{count}} 個のファイル", diff --git a/src/renderer/src/locales/ko/malware.json b/src/renderer/src/locales/ko/malware.json index 2b0a6d8c..7a1313da 100644 --- a/src/renderer/src/locales/ko/malware.json +++ b/src/renderer/src/locales/ko/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "악성코드 검사기", - "pageDescription": "멀티 엔진 위협 탐지 — 시그니처, 휴리스틱, 스크립트 분석, 시스템 무결성 및 지속성 검사", + "pageDescription": "다중 엔진 위협 탐지 — 시그니처, 휴리스틱, 스크립트 분석, 시스템 무결성 및 지속성 검사", "scanButtonScanning": "검사 중...", "scanButton": "검사", "quarantineButton": "격리", @@ -9,7 +9,7 @@ "severityHigh": "높음", "severityMedium": "보통", "severityLow": "낮음", - "sourceDefenderWindows": "기본 제공 AV", + "sourceDefenderWindows": "기본 AV", "sourceDefenderMac": "코드 서명", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "휴리스틱 분석", @@ -38,7 +38,7 @@ "actionResultQuarantined": "{{count}}개 격리됨", "actionResultDeleted": "{{count}}개 삭제됨", "actionResultFailed": "{{count}}개 실패", - "noThreatsDetectedTitle": "탐지된 위협 없음", + "noThreatsDetectedTitle": "위협이 탐지되지 않았습니다", "noThreatsDetectedDescription": "{{duration}}초 동안 {{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 검사했습니다 — 시스템이 정상입니다.", "emptyStateTitle": "악성코드 검사기", "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 악성코드, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", @@ -61,8 +61,29 @@ "errorOperationFailed": "작업에 실패했습니다 — 관리자 권한으로 실행해 보세요", "tabScanner": "검사기", "tabQuarantine": "격리", - "quarantineEmptyTitle": "격리된 항목 없음", - "quarantineEmptyDescription": "격리로 이동한 파일이 여기에 표시됩니다. 파일을 복원하거나 영구적으로 삭제할 수 있습니다.", + "tabDatabase": "데이터베이스", + "dbTitle": "시그니처 데이터베이스", + "dbDescription": "검사기에서 사용하는 YARA 악성코드 탐지 규칙", + "dbFetchLatest": "업데이트 확인", + "dbUpdating": "업데이트 중...", + "dbEngine": "엔진", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex 대체", + "dbRulesLoaded": "로드된 규칙", + "dbVersion": "시그니처 버전", + "dbLastUpdated": "마지막 업데이트", + "dbNever": "없음", + "dbSource": "규칙 소스", + "dbSourceCloud": "클라우드(캐시됨)", + "dbSourceBundled": "앱에 포함됨", + "dbSourceNone": "로드된 규칙 없음", + "dbRuleFiles": "규칙 파일", + "dbRuleFilesCounts": "앱 포함 {{bundled}}개, 클라우드 {{cached}}개", + "dbUpdateSuccess": "v{{version}}로 업데이트됨 (규칙 {{count}}개)", + "dbAlreadyCurrent": "시그니처가 이미 최신 상태입니다", + "dbUpdateFailed": "시그니처 업데이트에 실패했습니다", + "quarantineEmptyTitle": "격리된 항목이 없습니다", + "quarantineEmptyDescription": "격리로 이동된 파일이 여기에 표시됩니다. 파일을 복원하거나 영구적으로 삭제할 수 있습니다.", "quarantineHeading": "격리된 파일", "quarantineCount": "{{count}}개 파일", "quarantineCountPlural": "{{count}}개 파일", @@ -85,7 +106,7 @@ "quarantineRestoring": "파일 복원 중...", "quarantineDeleting": "파일 삭제 중...", "toastRestoreSuccess": "{{count}}개 파일 복원됨", - "toastRestoreFailed": "일부 파일을 복원하지 못했습니다", + "toastRestoreFailed": "일부 파일 복원에 실패했습니다", "toastDeleteQuarantineSuccess": "{{count}}개 파일이 영구적으로 삭제됨", "toastRestoreNoOriginal": "복원할 수 없습니다 — 원래 위치를 알 수 없습니다" } diff --git a/src/renderer/src/locales/ms/malware.json b/src/renderer/src/locales/ms/malware.json index 47ef1e7f..11b55619 100644 --- a/src/renderer/src/locales/ms/malware.json +++ b/src/renderer/src/locales/ms/malware.json @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} ancaman", "threatFound": "{{count}} ditemui", "scanCategoryClean": "bersih", - "scanCategoryNA": "T/A", + "scanCategoryNA": "N/A", "scanSummarySystemClean": "Sistem Bersih", "scanSummaryThreatsDetected": "{{count}} Ancaman Dikesan", "scanStatFiles": "Fail", @@ -35,7 +35,7 @@ "actingDeleting": "Memadam ancaman yang dipilih...", "actionCompleteQuarantine": "Kuarantin selesai", "actionCompleteDeletion": "Pemadaman selesai", - "actionResultQuarantined": "{{count}} dikuarantinkan", + "actionResultQuarantined": "{{count}} dikuarantin", "actionResultDeleted": "{{count}} dipadam", "actionResultFailed": "{{count}} gagal", "noThreatsDetectedTitle": "Tiada Ancaman Dikesan", @@ -61,8 +61,29 @@ "errorOperationFailed": "Operasi gagal — cuba jalankan sebagai pentadbir", "tabScanner": "Pengimbas", "tabQuarantine": "Kuarantin", + "tabDatabase": "Pangkalan Data", + "dbTitle": "Pangkalan Data Tandatangan", + "dbDescription": "Peraturan pengesanan malware YARA yang digunakan oleh pengimbas", + "dbFetchLatest": "Semak Kemas Kini", + "dbUpdating": "Mengemas kini...", + "dbEngine": "Enjin", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Sandaran Regex", + "dbRulesLoaded": "Peraturan Dimuatkan", + "dbVersion": "Versi Tandatangan", + "dbLastUpdated": "Terakhir Dikemas Kini", + "dbNever": "Tidak Pernah", + "dbSource": "Sumber Peraturan", + "dbSourceCloud": "Awan (cache)", + "dbSourceBundled": "Disertakan dengan aplikasi", + "dbSourceNone": "Tiada peraturan dimuatkan", + "dbRuleFiles": "Fail Peraturan", + "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari awan", + "dbUpdateSuccess": "Dikemas kini ke v{{version}} ({{count}} peraturan)", + "dbAlreadyCurrent": "Tandatangan sudah pun terkini", + "dbUpdateFailed": "Gagal mengemas kini tandatangan", "quarantineEmptyTitle": "Tiada Item Dikuarantin", - "quarantineEmptyDescription": "Fail yang dipindahkan ke kuarantin akan dipaparkan di sini. Anda boleh memulihkan atau memadamkannya secara kekal.", + "quarantineEmptyDescription": "Fail yang dipindahkan ke kuarantin akan dipaparkan di sini. Anda boleh memulihkan atau memadamnya secara kekal.", "quarantineHeading": "Fail Dikuarantin", "quarantineCount": "{{count}} fail", "quarantineCountPlural": "{{count}} fail", diff --git a/src/renderer/src/locales/nl/malware.json b/src/renderer/src/locales/nl/malware.json index 5be4a48b..1435a6c0 100644 --- a/src/renderer/src/locales/nl/malware.json +++ b/src/renderer/src/locales/nl/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malwarescanner", - "pageDescription": "Detectie van bedreigingen met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en scan op persistentie", + "pageDescription": "Detectie van bedreigingen met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en persistentiescans", "scanButtonScanning": "Scannen...", "scanButton": "Scannen", "quarantineButton": "In quarantaine plaatsen", @@ -9,7 +9,7 @@ "severityHigh": "Hoog", "severityMedium": "Gemiddeld", "severityLow": "Laag", - "sourceDefenderWindows": "Ingebouwde AV", + "sourceDefenderWindows": "Native AV", "sourceDefenderMac": "Code Signing", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristische analyse", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} bedreigingen", "threatFound": "{{count}} gevonden", "scanCategoryClean": "schoon", - "scanCategoryNA": "N.v.t.", + "scanCategoryNA": "n.v.t.", "scanSummarySystemClean": "Systeem schoon", "scanSummaryThreatsDetected": "{{count}} bedreigingen gedetecteerd", "scanStatFiles": "Bestanden", @@ -50,7 +50,7 @@ "threatDetailSize": "Grootte", "threatDetailPath": "Pad", "confirmQuarantineTitle": "Bedreigingen in quarantaine plaatsen", - "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) naar de quarantaine verplaatst. Bestanden kunnen later indien nodig worden hersteld.", + "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) in quarantaine geplaatst. Bestanden kunnen later indien nodig worden hersteld.", "confirmQuarantineLabel": "Nu in quarantaine plaatsen", "confirmDeleteTitle": "Bedreigingen verwijderen", "confirmDeleteDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.", @@ -61,8 +61,29 @@ "errorOperationFailed": "Bewerking mislukt — probeer uit te voeren als administrator", "tabScanner": "Scanner", "tabQuarantine": "Quarantaine", + "tabDatabase": "Database", + "dbTitle": "Handtekeningendatabase", + "dbDescription": "YARA-malwaredetectieregels die door de scanner worden gebruikt", + "dbFetchLatest": "Controleren op updates", + "dbUpdating": "Bijwerken...", + "dbEngine": "Engine", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-terugvaloptie", + "dbRulesLoaded": "Regels geladen", + "dbVersion": "Handtekeningenversie", + "dbLastUpdated": "Laatst bijgewerkt", + "dbNever": "Nooit", + "dbSource": "Bron van regels", + "dbSourceCloud": "Cloud (in cache)", + "dbSourceBundled": "Meegeleverd met app", + "dbSourceNone": "Geen regels geladen", + "dbRuleFiles": "Regelbestanden", + "dbRuleFilesCounts": "{{bundled}} meegeleverd, {{cached}} uit cloud", + "dbUpdateSuccess": "Bijgewerkt naar v{{version}} ({{count}} regels)", + "dbAlreadyCurrent": "Handtekeningen zijn al up-to-date", + "dbUpdateFailed": "Bijwerken van handtekeningen mislukt", "quarantineEmptyTitle": "Geen items in quarantaine", - "quarantineEmptyDescription": "Bestanden die naar de quarantaine zijn verplaatst, worden hier weergegeven. U kunt ze herstellen of permanent verwijderen.", + "quarantineEmptyDescription": "Bestanden die in quarantaine zijn geplaatst, worden hier weergegeven. U kunt ze herstellen of permanent verwijderen.", "quarantineHeading": "Bestanden in quarantaine", "quarantineCount": "{{count}} bestand", "quarantineCountPlural": "{{count}} bestanden", diff --git a/src/renderer/src/locales/no/malware.json b/src/renderer/src/locales/no/malware.json index 254c1e5c..65764a17 100644 --- a/src/renderer/src/locales/no/malware.json +++ b/src/renderer/src/locales/no/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Skanner for skadelig programvare", - "pageDescription": "Trusseldeteksjon med flere motorer — signaturer, heuristikk, skriptanalyse, systemintegritet og skanning av persistens", + "pageDescription": "Trusseldeteksjon med flere motorer — signaturer, heuristikk, skriptanalyse, systemintegritet og skanning av vedvarende trusler", "scanButtonScanning": "Skanner...", "scanButton": "Skann", "quarantineButton": "Sett i karantene", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} trusler", "threatFound": "{{count}} funnet", "scanCategoryClean": "ren", - "scanCategoryNA": "I/T", + "scanCategoryNA": "Ikke tilgjengelig", "scanSummarySystemClean": "Systemet er rent", "scanSummaryThreatsDetected": "{{count}} trusler oppdaget", "scanStatFiles": "Filer", @@ -30,7 +30,7 @@ "selectedHeading": "Valgt", "selectedOfThreats": "av {{count}} trusler", "selectAll": "Velg alle", - "deselectAll": "Fjern alle valg", + "deselectAll": "Fjern markering", "actingQuarantining": "Setter valgte trusler i karantene...", "actingDeleting": "Sletter valgte trusler...", "actionCompleteQuarantine": "Karantene fullført", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Ingen trusler oppdaget", "noThreatsDetectedDescription": "Skannet {{filesScanned}} filer med {{engineCount}} motorer på {{duration}} s — systemet ditt er rent.", "emptyStateTitle": "Skanner for skadelig programvare", - "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet ditt for skadelig programvare, reklameprogrammer, kryptogravere og mistenkelige filer.", + "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet ditt for skadelig programvare, reklameprogrammer, kryptoutvinnere og mistenkelige filer.", "detectedThreatsHeading": "Oppdagede trusler", "detectedThreatsCount": "{{count}} trussel", "detectedThreatsCountPlural": "{{count}} trusler", @@ -61,6 +61,27 @@ "errorOperationFailed": "Operasjonen mislyktes — prøv å kjøre som administrator", "tabScanner": "Skanner", "tabQuarantine": "Karantene", + "tabDatabase": "Database", + "dbTitle": "Signaturdatabase", + "dbDescription": "YARA-regler for oppdagelse av skadelig programvare som brukes av skanneren", + "dbFetchLatest": "Se etter oppdateringer", + "dbUpdating": "Oppdaterer...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-reserveløsning", + "dbRulesLoaded": "Regler lastet inn", + "dbVersion": "Signaturversjon", + "dbLastUpdated": "Sist oppdatert", + "dbNever": "Aldri", + "dbSource": "Regelkilde", + "dbSourceCloud": "Sky (bufret)", + "dbSourceBundled": "Levert med appen", + "dbSourceNone": "Ingen regler lastet inn", + "dbRuleFiles": "Regelfiler", + "dbRuleFilesCounts": "{{bundled}} medfølgende, {{cached}} fra skyen", + "dbUpdateSuccess": "Oppdatert til v{{version}} ({{count}} regler)", + "dbAlreadyCurrent": "Signaturene er allerede oppdatert", + "dbUpdateFailed": "Kunne ikke oppdatere signaturene", "quarantineEmptyTitle": "Ingen elementer i karantene", "quarantineEmptyDescription": "Filer som flyttes til karantene, vises her. Du kan gjenopprette eller slette dem permanent.", "quarantineHeading": "Filer i karantene", @@ -74,18 +95,18 @@ "quarantineRestoreButton": "Gjenopprett", "quarantineDeleteButton": "Slett permanent", "quarantineSelectAll": "Velg alle", - "quarantineDeselectAll": "Fjern alle valg", + "quarantineDeselectAll": "Fjern markering", "confirmRestoreTitle": "Gjenopprett filer", - "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil/filer til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", + "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil(er) til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", "confirmRestoreLabel": "Gjenopprett nå", "confirmDeleteQuarantineTitle": "Slett permanent", - "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} fil/filer i karantene permanent. Denne handlingen kan ikke angres.", + "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} filer i karantene permanent. Denne handlingen kan ikke angres.", "confirmDeleteQuarantineLabel": "Slett permanent", "quarantineLoading": "Laster inn karantene...", "quarantineRestoring": "Gjenoppretter filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil/filer gjenopprettet", + "toastRestoreSuccess": "{{count}} fil(er) gjenopprettet", "toastRestoreFailed": "Kunne ikke gjenopprette noen filer", - "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", "toastRestoreNoOriginal": "Kan ikke gjenopprette — opprinnelig plassering er ukjent" } diff --git a/src/renderer/src/locales/pl/malware.json b/src/renderer/src/locales/pl/malware.json index f3d13b67..9bf1e6d6 100644 --- a/src/renderer/src/locales/pl/malware.json +++ b/src/renderer/src/locales/pl/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Skaner malware", - "pageDescription": "Wielosilnikowe wykrywanie zagrożeń — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie mechanizmów utrwalania", + "pageDescription": "Wielosilnikowe wykrywanie zagrożeń — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie trwałości", "scanButtonScanning": "Skanowanie...", "scanButton": "Skanuj", "quarantineButton": "Kwarantanna", @@ -14,11 +14,11 @@ "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analiza heurystyczna", "sourceSignature": "Znana sygnatura", - "initializingScanEngines": "Inicjowanie silników skanowania...", + "initializingScanEngines": "Inicjalizowanie silników skanowania...", "filesScanned": "Przeskanowano {{count}} plików", "threatCount": "{{count}} zagrożenie", "threatCountPlural": "{{count}} zagrożenia", - "threatFound": "Znaleziono: {{count}}", + "threatFound": "Znaleziono {{count}}", "scanCategoryClean": "czysty", "scanCategoryNA": "N/D", "scanSummarySystemClean": "System czysty", @@ -27,17 +27,17 @@ "scanStatDuration": "Czas trwania", "scanStatEngines": "Silniki", "severityHeading": "Poziom zagrożenia", - "selectedHeading": "Zaznaczone", + "selectedHeading": "Wybrane", "selectedOfThreats": "z {{count}} zagrożeń", "selectAll": "Zaznacz wszystko", "deselectAll": "Odznacz wszystko", - "actingQuarantining": "Przenoszenie zaznaczonych zagrożeń do kwarantanny...", - "actingDeleting": "Usuwanie zaznaczonych zagrożeń...", + "actingQuarantining": "Przenoszenie wybranych zagrożeń do kwarantanny...", + "actingDeleting": "Usuwanie wybranych zagrożeń...", "actionCompleteQuarantine": "Przenoszenie do kwarantanny zakończone", "actionCompleteDeletion": "Usuwanie zakończone", "actionResultQuarantined": "Przeniesiono do kwarantanny: {{count}}", "actionResultDeleted": "Usunięto: {{count}}", - "actionResultFailed": "Niepowodzenie: {{count}}", + "actionResultFailed": "Niepowodzenia: {{count}}", "noThreatsDetectedTitle": "Nie wykryto zagrożeń", "noThreatsDetectedDescription": "Przeskanowano {{filesScanned}} plików przy użyciu {{engineCount}} silników w {{duration}} s — system jest czysty.", "emptyStateTitle": "Skaner malware", @@ -56,11 +56,32 @@ "confirmDeleteDescription": "Spowoduje to trwałe usunięcie {{count}} wykrytych zagrożeń. Tej operacji nie można cofnąć.", "confirmDeleteLabel": "Usuń trwale", "toastScanFailed": "Skanowanie malware nie powiodło się", - "toastActionFailed": "Nie udało się wykonać działania „{{action}}” dla zagrożeń", + "toastActionFailed": "Nie udało się wykonać działania „{{action}}” na zagrożeniach", "toastActionFailedDescription": "Spróbuj uruchomić jako administrator", "errorOperationFailed": "Operacja nie powiodła się — spróbuj uruchomić jako administrator", "tabScanner": "Skaner", "tabQuarantine": "Kwarantanna", + "tabDatabase": "Baza danych", + "dbTitle": "Baza sygnatur", + "dbDescription": "Reguły wykrywania malware YARA używane przez skaner", + "dbFetchLatest": "Sprawdź aktualizacje", + "dbUpdating": "Aktualizowanie...", + "dbEngine": "Silnik", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Awaryjny Regex", + "dbRulesLoaded": "Załadowane reguły", + "dbVersion": "Wersja sygnatur", + "dbLastUpdated": "Ostatnia aktualizacja", + "dbNever": "Nigdy", + "dbSource": "Źródło reguł", + "dbSourceCloud": "Chmura (z pamięci podręcznej)", + "dbSourceBundled": "Dołączone do aplikacji", + "dbSourceNone": "Nie załadowano reguł", + "dbRuleFiles": "Pliki reguł", + "dbRuleFilesCounts": "{{bundled}} dołączonych, {{cached}} z chmury", + "dbUpdateSuccess": "Zaktualizowano do v{{version}} (reguł: {{count}})", + "dbAlreadyCurrent": "Sygnatury są już aktualne", + "dbUpdateFailed": "Nie udało się zaktualizować sygnatur", "quarantineEmptyTitle": "Brak elementów w kwarantannie", "quarantineEmptyDescription": "Pliki przeniesione do kwarantanny pojawią się tutaj. Możesz je przywrócić lub trwale usunąć.", "quarantineHeading": "Pliki w kwarantannie", @@ -84,8 +105,8 @@ "quarantineLoading": "Ładowanie kwarantanny...", "quarantineRestoring": "Przywracanie plików...", "quarantineDeleting": "Usuwanie plików...", - "toastRestoreSuccess": "Przywrócono pliki: {{count}}", + "toastRestoreSuccess": "Przywrócono plików: {{count}}", "toastRestoreFailed": "Nie udało się przywrócić niektórych plików", - "toastDeleteQuarantineSuccess": "Trwale usunięto pliki: {{count}}", + "toastDeleteQuarantineSuccess": "Trwale usunięto plików: {{count}}", "toastRestoreNoOriginal": "Nie można przywrócić — oryginalna lokalizacja jest nieznana" } diff --git a/src/renderer/src/locales/pt/malware.json b/src/renderer/src/locales/pt/malware.json index a0d260b0..0d6e83d4 100644 --- a/src/renderer/src/locales/pt/malware.json +++ b/src/renderer/src/locales/pt/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Scanner de Malware", - "pageDescription": "Deteção de ameaças com vários motores — assinaturas, heurísticas, análise de scripts, integridade do sistema e verificação de persistência", + "pageDescription": "Deteção de ameaças com vários motores — assinaturas, heurística, análise de scripts, integridade do sistema e análise de persistência", "scanButtonScanning": "A analisar...", "scanButton": "Analisar", "quarantineButton": "Colocar em quarentena", @@ -10,78 +10,99 @@ "severityMedium": "Média", "severityLow": "Baixa", "sourceDefenderWindows": "AV nativo", - "sourceDefenderMac": "Assinatura de Código", + "sourceDefenderMac": "Assinatura de código", "sourceDefenderLinux": "ClamAV", - "sourceHeuristic": "Análise Heurística", - "sourceSignature": "Assinatura Conhecida", - "initializingScanEngines": "A inicializar os motores de análise...", + "sourceHeuristic": "Análise heurística", + "sourceSignature": "Assinatura conhecida", + "initializingScanEngines": "A inicializar motores de análise...", "filesScanned": "{{count}} ficheiros analisados", "threatCount": "{{count}} ameaça", "threatCountPlural": "{{count}} ameaças", "threatFound": "{{count}} encontrada(s)", "scanCategoryClean": "limpo", "scanCategoryNA": "N/D", - "scanSummarySystemClean": "Sistema Limpo", - "scanSummaryThreatsDetected": "{{count}} Ameaças Detetadas", + "scanSummarySystemClean": "Sistema limpo", + "scanSummaryThreatsDetected": "{{count}} ameaças detetadas", "scanStatFiles": "Ficheiros", "scanStatDuration": "Duração", "scanStatEngines": "Motores", "severityHeading": "Gravidade", "selectedHeading": "Selecionadas", "selectedOfThreats": "de {{count}} ameaças", - "selectAll": "Selecionar Tudo", - "deselectAll": "Desmarcar Tudo", - "actingQuarantining": "A colocar as ameaças selecionadas em quarentena...", - "actingDeleting": "A eliminar as ameaças selecionadas...", + "selectAll": "Selecionar tudo", + "deselectAll": "Desmarcar tudo", + "actingQuarantining": "A colocar ameaças selecionadas em quarentena...", + "actingDeleting": "A eliminar ameaças selecionadas...", "actionCompleteQuarantine": "Quarentena concluída", "actionCompleteDeletion": "Eliminação concluída", "actionResultQuarantined": "{{count}} em quarentena", "actionResultDeleted": "{{count}} eliminadas", "actionResultFailed": "{{count}} falharam", - "noThreatsDetectedTitle": "Nenhuma Ameaça Detetada", + "noThreatsDetectedTitle": "Nenhuma ameaça detetada", "noThreatsDetectedDescription": "Foram analisados {{filesScanned}} ficheiros em {{engineCount}} motores em {{duration}}s — o seu sistema está limpo.", "emptyStateTitle": "Scanner de Malware", "emptyStateDescription": "Clique em \"Analisar\" para verificar o seu sistema quanto a malware, adware, mineradores de criptomoedas e ficheiros suspeitos.", - "detectedThreatsHeading": "Ameaças Detetadas", + "detectedThreatsHeading": "Ameaças detetadas", "detectedThreatsCount": "{{count}} ameaça", "detectedThreatsCountPlural": "{{count}} ameaças", "threatDetailDetails": "Detalhes", "threatDetailFile": "Ficheiro", "threatDetailSize": "Tamanho", "threatDetailPath": "Caminho", - "confirmQuarantineTitle": "Colocar Ameaças em Quarentena", + "confirmQuarantineTitle": "Colocar ameaças em quarentena", "confirmQuarantineDescription": "Isto moverá {{count}} ameaça(s) detetada(s) para a quarentena. Os ficheiros podem ser restaurados mais tarde, se necessário.", - "confirmQuarantineLabel": "Colocar em Quarentena Agora", - "confirmDeleteTitle": "Eliminar Ameaças", + "confirmQuarantineLabel": "Colocar em quarentena agora", + "confirmDeleteTitle": "Eliminar ameaças", "confirmDeleteDescription": "Isto eliminará permanentemente {{count}} ameaça(s) detetada(s). Esta ação não pode ser anulada.", - "confirmDeleteLabel": "Eliminar Permanentemente", + "confirmDeleteLabel": "Eliminar permanentemente", "toastScanFailed": "A análise de malware falhou", "toastActionFailed": "Falha ao {{action}} ameaças", "toastActionFailedDescription": "Tente executar como administrador", "errorOperationFailed": "A operação falhou — tente executar como administrador", "tabScanner": "Scanner", "tabQuarantine": "Quarentena", - "quarantineEmptyTitle": "Nenhum Item em Quarentena", + "tabDatabase": "Base de dados", + "dbTitle": "Base de dados de assinaturas", + "dbDescription": "Regras YARA de deteção de malware utilizadas pelo scanner", + "dbFetchLatest": "Procurar atualizações", + "dbUpdating": "A atualizar...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Fallback Regex", + "dbRulesLoaded": "Regras carregadas", + "dbVersion": "Versão das assinaturas", + "dbLastUpdated": "Última atualização", + "dbNever": "Nunca", + "dbSource": "Origem das regras", + "dbSourceCloud": "Cloud (em cache)", + "dbSourceBundled": "Incluídas na aplicação", + "dbSourceNone": "Nenhuma regra carregada", + "dbRuleFiles": "Ficheiros de regras", + "dbRuleFilesCounts": "{{bundled}} incluídas, {{cached}} da cloud", + "dbUpdateSuccess": "Atualizado para v{{version}} ({{count}} regras)", + "dbAlreadyCurrent": "As assinaturas já estão atualizadas", + "dbUpdateFailed": "Falha ao atualizar assinaturas", + "quarantineEmptyTitle": "Nenhum item em quarentena", "quarantineEmptyDescription": "Os ficheiros movidos para a quarentena aparecerão aqui. Pode restaurá-los ou eliminá-los permanentemente.", - "quarantineHeading": "Ficheiros em Quarentena", + "quarantineHeading": "Ficheiros em quarentena", "quarantineCount": "{{count}} ficheiro", "quarantineCountPlural": "{{count}} ficheiros", "quarantineColumnFile": "Ficheiro", "quarantineColumnDate": "Data", "quarantineColumnSize": "Tamanho", - "quarantineColumnOriginal": "Localização Original", + "quarantineColumnOriginal": "Localização original", "quarantineOriginalUnknown": "Desconhecida", "quarantineRestoreButton": "Restaurar", - "quarantineDeleteButton": "Eliminar Permanentemente", - "quarantineSelectAll": "Selecionar Tudo", - "quarantineDeselectAll": "Desmarcar Tudo", - "confirmRestoreTitle": "Restaurar Ficheiros", - "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a sua localização original. Restaure apenas ficheiros em que confia.", - "confirmRestoreLabel": "Restaurar Agora", - "confirmDeleteQuarantineTitle": "Eliminar Permanentemente", + "quarantineDeleteButton": "Eliminar permanentemente", + "quarantineSelectAll": "Selecionar tudo", + "quarantineDeselectAll": "Desmarcar tudo", + "confirmRestoreTitle": "Restaurar ficheiros", + "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a respetiva localização original. Restaure apenas ficheiros em que confia.", + "confirmRestoreLabel": "Restaurar agora", + "confirmDeleteQuarantineTitle": "Eliminar permanentemente", "confirmDeleteQuarantineDescription": "Isto eliminará permanentemente {{count}} ficheiro(s) em quarentena. Esta ação não pode ser anulada.", - "confirmDeleteQuarantineLabel": "Eliminar Permanentemente", - "quarantineLoading": "A carregar a quarentena...", + "confirmDeleteQuarantineLabel": "Eliminar permanentemente", + "quarantineLoading": "A carregar quarentena...", "quarantineRestoring": "A restaurar ficheiros...", "quarantineDeleting": "A eliminar ficheiros...", "toastRestoreSuccess": "{{count}} ficheiro(s) restaurado(s)", diff --git a/src/renderer/src/locales/ro/malware.json b/src/renderer/src/locales/ro/malware.json index e149ddb5..5f570440 100644 --- a/src/renderer/src/locales/ro/malware.json +++ b/src/renderer/src/locales/ro/malware.json @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Nu au fost detectate amenințări", "noThreatsDetectedDescription": "Au fost scanate {{filesScanned}} fișiere cu {{engineCount}} motoare în {{duration}}s — sistemul dvs. este curat.", "emptyStateTitle": "Scanner malware", - "emptyStateDescription": "Faceți clic pe „Scanează” pentru a verifica sistemul dvs. pentru malware, adware, mineri crypto și fișiere suspecte.", + "emptyStateDescription": "Faceți clic pe „Scanează” pentru a verifica sistemul pentru malware, adware, mineri crypto și fișiere suspecte.", "detectedThreatsHeading": "Amenințări detectate", "detectedThreatsCount": "{{count}} amenințare", "detectedThreatsCountPlural": "{{count}} amenințări", @@ -56,12 +56,33 @@ "confirmDeleteDescription": "Aceasta va șterge definitiv {{count}} amenințare(ări) detectată(e). Această acțiune nu poate fi anulată.", "confirmDeleteLabel": "Șterge definitiv", "toastScanFailed": "Scanarea malware a eșuat", - "toastActionFailed": "Nu s-a reușit {{action}} amenințărilor", + "toastActionFailed": "Nu s-a reușit acțiunea {{action}} asupra amenințărilor", "toastActionFailedDescription": "Încercați să rulați ca administrator", "errorOperationFailed": "Operațiunea a eșuat — încercați să rulați ca administrator", "tabScanner": "Scanner", "tabQuarantine": "Carantină", - "quarantineEmptyTitle": "Nu există elemente în carantină", + "tabDatabase": "Bază de date", + "dbTitle": "Bază de date cu semnături", + "dbDescription": "Reguli YARA de detectare malware utilizate de scanner", + "dbFetchLatest": "Verifică actualizările", + "dbUpdating": "Se actualizează...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Fallback Regex", + "dbRulesLoaded": "Reguli încărcate", + "dbVersion": "Versiune semnături", + "dbLastUpdated": "Ultima actualizare", + "dbNever": "Niciodată", + "dbSource": "Sursă reguli", + "dbSourceCloud": "Cloud (în cache)", + "dbSourceBundled": "Inclusă în aplicație", + "dbSourceNone": "Nu sunt încărcate reguli", + "dbRuleFiles": "Fișiere de reguli", + "dbRuleFilesCounts": "{{bundled}} incluse, {{cached}} din cloud", + "dbUpdateSuccess": "Actualizat la v{{version}} ({{count}} reguli)", + "dbAlreadyCurrent": "Semnăturile sunt deja actualizate", + "dbUpdateFailed": "Actualizarea semnăturilor a eșuat", + "quarantineEmptyTitle": "Niciun element în carantină", "quarantineEmptyDescription": "Fișierele mutate în carantină vor apărea aici. Le puteți restaura sau șterge definitiv.", "quarantineHeading": "Fișiere în carantină", "quarantineCount": "{{count}} fișier", diff --git a/src/renderer/src/locales/ru/malware.json b/src/renderer/src/locales/ru/malware.json index 98e6120a..09d8d690 100644 --- a/src/renderer/src/locales/ru/malware.json +++ b/src/renderer/src/locales/ru/malware.json @@ -1,14 +1,14 @@ { "pageTitle": "Сканер вредоносных программ", - "pageDescription": "Многоуровневое обнаружение угроз — сигнатуры, эвристика, анализ сценариев, проверка целостности системы и сканирование механизмов закрепления", + "pageDescription": "Многоуровневое обнаружение угроз — сигнатуры, эвристика, анализ сценариев, проверка целостности системы и сканирование на закрепление", "scanButtonScanning": "Сканирование...", "scanButton": "Сканировать", "quarantineButton": "Поместить в карантин", "deleteButton": "Удалить", - "severityCritical": "Критическая", - "severityHigh": "Высокая", - "severityMedium": "Средняя", - "severityLow": "Низкая", + "severityCritical": "Критический", + "severityHigh": "Высокий", + "severityMedium": "Средний", + "severityLow": "Низкий", "sourceDefenderWindows": "Встроенный антивирус", "sourceDefenderMac": "Подпись кода", "sourceDefenderLinux": "ClamAV", @@ -39,7 +39,7 @@ "actionResultDeleted": "Удалено: {{count}}", "actionResultFailed": "Не удалось: {{count}}", "noThreatsDetectedTitle": "Угроз не обнаружено", - "noThreatsDetectedDescription": "Проверено файлов: {{filesScanned}}, модулей: {{engineCount}}, за {{duration}} с — система чиста.", + "noThreatsDetectedDescription": "Проверено файлов: {{filesScanned}}, модулей: {{engineCount}}, за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер вредоносных программ", "emptyStateDescription": "Нажмите «Сканировать», чтобы проверить систему на вредоносные программы, рекламное ПО, криптомайнеры и подозрительные файлы.", "detectedThreatsHeading": "Обнаруженные угрозы", @@ -50,10 +50,10 @@ "threatDetailSize": "Размер", "threatDetailPath": "Путь", "confirmQuarantineTitle": "Поместить угрозы в карантин", - "confirmQuarantineDescription": "Это действие переместит {{count}} обнаруженных угроз(и) в карантин. При необходимости файлы можно будет восстановить позже.", + "confirmQuarantineDescription": "Это действие переместит {{count}} обнаруженных угроз(ы) в карантин. При необходимости файлы можно будет восстановить позже.", "confirmQuarantineLabel": "Поместить в карантин", "confirmDeleteTitle": "Удалить угрозы", - "confirmDeleteDescription": "Это действие безвозвратно удалит {{count}} обнаруженных угроз(и). Его нельзя отменить.", + "confirmDeleteDescription": "Это действие безвозвратно удалит {{count}} обнаруженных угроз(ы). Отменить это действие невозможно.", "confirmDeleteLabel": "Удалить безвозвратно", "toastScanFailed": "Не удалось выполнить сканирование на вредоносные программы", "toastActionFailed": "Не удалось выполнить действие «{{action}}» для угроз", @@ -61,7 +61,28 @@ "errorOperationFailed": "Не удалось выполнить операцию — попробуйте запустить от имени администратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", - "quarantineEmptyTitle": "Нет объектов в карантине", + "tabDatabase": "База данных", + "dbTitle": "База сигнатур", + "dbDescription": "Правила обнаружения вредоносных программ YARA, используемые сканером", + "dbFetchLatest": "Проверить обновления", + "dbUpdating": "Обновление...", + "dbEngine": "Модуль", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Резервный Regex", + "dbRulesLoaded": "Загружено правил", + "dbVersion": "Версия сигнатур", + "dbLastUpdated": "Последнее обновление", + "dbNever": "Никогда", + "dbSource": "Источник правил", + "dbSourceCloud": "Облако (кэшировано)", + "dbSourceBundled": "Встроено в приложение", + "dbSourceNone": "Правила не загружены", + "dbRuleFiles": "Файлы правил", + "dbRuleFilesCounts": "{{bundled}} встроено, {{cached}} из облака", + "dbUpdateSuccess": "Обновлено до v{{version}} (правил: {{count}})", + "dbAlreadyCurrent": "Сигнатуры уже актуальны", + "dbUpdateFailed": "Не удалось обновить сигнатуры", + "quarantineEmptyTitle": "Нет элементов в карантине", "quarantineEmptyDescription": "Файлы, перемещённые в карантин, будут отображаться здесь. Вы можете восстановить их или удалить безвозвратно.", "quarantineHeading": "Файлы в карантине", "quarantineCount": "{{count}} файл", @@ -79,7 +100,7 @@ "confirmRestoreDescription": "Это действие восстановит {{count}} файл(ов) в исходное расположение. Восстанавливайте только те файлы, которым доверяете.", "confirmRestoreLabel": "Восстановить", "confirmDeleteQuarantineTitle": "Удалить безвозвратно", - "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Его нельзя отменить.", + "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Отменить это действие невозможно.", "confirmDeleteQuarantineLabel": "Удалить безвозвратно", "quarantineLoading": "Загрузка карантина...", "quarantineRestoring": "Восстановление файлов...", @@ -87,5 +108,5 @@ "toastRestoreSuccess": "Восстановлено файлов: {{count}}", "toastRestoreFailed": "Не удалось восстановить некоторые файлы", "toastDeleteQuarantineSuccess": "Безвозвратно удалено файлов: {{count}}", - "toastRestoreNoOriginal": "Невозможно восстановить — исходное расположение неизвестно" + "toastRestoreNoOriginal": "Не удаётся восстановить — исходное расположение неизвестно" } diff --git a/src/renderer/src/locales/sv/malware.json b/src/renderer/src/locales/sv/malware.json index 20d625af..2c1ed3e4 100644 --- a/src/renderer/src/locales/sv/malware.json +++ b/src/renderer/src/locales/sv/malware.json @@ -39,13 +39,13 @@ "actionResultDeleted": "{{count}} borttagna", "actionResultFailed": "{{count}} misslyckades", "noThreatsDetectedTitle": "Inga hot identifierades", - "noThreatsDetectedDescription": "{{filesScanned}} filer skannades med {{engineCount}} motorer på {{duration}} s — ditt system är rent.", + "noThreatsDetectedDescription": "Skannade {{filesScanned}} filer med {{engineCount}} motorer på {{duration}}s — ditt system är rent.", "emptyStateTitle": "Skanner för skadlig kod", - "emptyStateDescription": "Klicka på \"Skanna\" för att kontrollera systemet efter skadlig kod, annonsprogram, kryptominers och misstänkta filer.", + "emptyStateDescription": "Klicka på \"Skanna\" för att kontrollera systemet efter skadlig kod, annonsprogram, kryptoutvinnare och misstänkta filer.", "detectedThreatsHeading": "Identifierade hot", "detectedThreatsCount": "{{count}} hot", "detectedThreatsCountPlural": "{{count}} hot", - "threatDetailDetails": "Information", + "threatDetailDetails": "Detaljer", "threatDetailFile": "Fil", "threatDetailSize": "Storlek", "threatDetailPath": "Sökväg", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Detta flyttar {{count}} identifierade hot till karantän. Filer kan återställas senare vid behov.", "confirmQuarantineLabel": "Sätt i karantän nu", "confirmDeleteTitle": "Ta bort hot", - "confirmDeleteDescription": "Detta tar permanent bort {{count}} identifierade hot. Den här åtgärden kan inte ångras.", + "confirmDeleteDescription": "Detta tar permanent bort {{count}} identifierade hot. Åtgärden kan inte ångras.", "confirmDeleteLabel": "Ta bort permanent", "toastScanFailed": "Skanning efter skadlig kod misslyckades", "toastActionFailed": "Det gick inte att {{action}} hot", @@ -61,6 +61,27 @@ "errorOperationFailed": "Åtgärden misslyckades — försök att köra som administratör", "tabScanner": "Skanner", "tabQuarantine": "Karantän", + "tabDatabase": "Databas", + "dbTitle": "Signaturdatabas", + "dbDescription": "YARA-regler för identifiering av skadlig kod som används av skannern", + "dbFetchLatest": "Sök efter uppdateringar", + "dbUpdating": "Uppdaterar...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex-reservlösning", + "dbRulesLoaded": "Regler inlästa", + "dbVersion": "Signaturversion", + "dbLastUpdated": "Senast uppdaterad", + "dbNever": "Aldrig", + "dbSource": "Regelkälla", + "dbSourceCloud": "Moln (cachelagrat)", + "dbSourceBundled": "Medföljer appen", + "dbSourceNone": "Inga regler inlästa", + "dbRuleFiles": "Regelfiler", + "dbRuleFilesCounts": "{{bundled}} medföljande, {{cached}} från molnet", + "dbUpdateSuccess": "Uppdaterad till v{{version}} ({{count}} regler)", + "dbAlreadyCurrent": "Signaturerna är redan uppdaterade", + "dbUpdateFailed": "Det gick inte att uppdatera signaturerna", "quarantineEmptyTitle": "Inga objekt i karantän", "quarantineEmptyDescription": "Filer som flyttas till karantän visas här. Du kan återställa eller ta bort dem permanent.", "quarantineHeading": "Filer i karantän", @@ -76,16 +97,16 @@ "quarantineSelectAll": "Markera alla", "quarantineDeselectAll": "Avmarkera alla", "confirmRestoreTitle": "Återställ filer", - "confirmRestoreDescription": "Detta återställer {{count}} filer till deras ursprungliga plats. Återställ endast filer som du litar på.", + "confirmRestoreDescription": "Detta återställer {{count}} fil(er) till deras ursprungliga plats. Återställ endast filer som du litar på.", "confirmRestoreLabel": "Återställ nu", "confirmDeleteQuarantineTitle": "Ta bort permanent", - "confirmDeleteQuarantineDescription": "Detta tar permanent bort {{count}} filer i karantän. Den här åtgärden kan inte ångras.", + "confirmDeleteQuarantineDescription": "Detta tar permanent bort {{count}} fil(er) i karantän. Åtgärden kan inte ångras.", "confirmDeleteQuarantineLabel": "Ta bort permanent", "quarantineLoading": "Läser in karantän...", "quarantineRestoring": "Återställer filer...", "quarantineDeleting": "Tar bort filer...", - "toastRestoreSuccess": "{{count}} filer återställda", + "toastRestoreSuccess": "{{count}} fil(er) återställda", "toastRestoreFailed": "Det gick inte att återställa vissa filer", - "toastDeleteQuarantineSuccess": "{{count}} filer permanent borttagna", + "toastDeleteQuarantineSuccess": "{{count}} fil(er) permanent borttagna", "toastRestoreNoOriginal": "Kan inte återställa — ursprunglig plats är okänd" } diff --git a/src/renderer/src/locales/th/malware.json b/src/renderer/src/locales/th/malware.json index 796138d4..ff7ffd9e 100644 --- a/src/renderer/src/locales/th/malware.json +++ b/src/renderer/src/locales/th/malware.json @@ -5,11 +5,11 @@ "scanButton": "สแกน", "quarantineButton": "กักกัน", "deleteButton": "ลบ", - "severityCritical": "วิกฤต", + "severityCritical": "ร้ายแรง", "severityHigh": "สูง", "severityMedium": "ปานกลาง", "severityLow": "ต่ำ", - "sourceDefenderWindows": "โปรแกรมป้องกันไวรัสในระบบ", + "sourceDefenderWindows": "โปรแกรมป้องกันไวรัสในตัว", "sourceDefenderMac": "การลงนามโค้ด", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "การวิเคราะห์เชิงฮิวริสติก", @@ -28,20 +28,20 @@ "scanStatEngines": "เอนจิน", "severityHeading": "ระดับความรุนแรง", "selectedHeading": "ที่เลือก", - "selectedOfThreats": "จาก {{count}} ภัยคุกคาม", + "selectedOfThreats": "จากภัยคุกคาม {{count}} รายการ", "selectAll": "เลือกทั้งหมด", "deselectAll": "ยกเลิกการเลือกทั้งหมด", "actingQuarantining": "กำลังกักกันภัยคุกคามที่เลือก...", "actingDeleting": "กำลังลบภัยคุกคามที่เลือก...", - "actionCompleteQuarantine": "กักกันเสร็จสิ้น", - "actionCompleteDeletion": "ลบเสร็จสิ้น", + "actionCompleteQuarantine": "กักกันเสร็จสมบูรณ์", + "actionCompleteDeletion": "ลบเสร็จสมบูรณ์", "actionResultQuarantined": "กักกันแล้ว {{count}} รายการ", "actionResultDeleted": "ลบแล้ว {{count}} รายการ", "actionResultFailed": "ล้มเหลว {{count}} รายการ", "noThreatsDetectedTitle": "ไม่พบภัยคุกคาม", - "noThreatsDetectedDescription": "สแกน {{filesScanned}} ไฟล์ด้วย {{engineCount}} เอนจิน ในเวลา {{duration}} วินาที — ระบบของคุณสะอาด", + "noThreatsDetectedDescription": "สแกน {{filesScanned}} ไฟล์ด้วย {{engineCount}} เอนจินใน {{duration}} วินาที — ระบบของคุณสะอาด", "emptyStateTitle": "ตัวสแกนมัลแวร์", - "emptyStateDescription": "คลิก \"สแกน\" เพื่อตรวจสอบระบบของคุณหามัลแวร์ แอดแวร์ ตัวขุดคริปโต และไฟล์ที่น่าสงสัย", + "emptyStateDescription": "คลิก \"สแกน\" เพื่อตรวจสอบระบบของคุณหามัลแวร์, แอดแวร์, ตัวขุดคริปโต และไฟล์ที่น่าสงสัย", "detectedThreatsHeading": "ภัยคุกคามที่ตรวจพบ", "detectedThreatsCount": "{{count}} ภัยคุกคาม", "detectedThreatsCountPlural": "{{count}} ภัยคุกคาม", @@ -50,10 +50,10 @@ "threatDetailSize": "ขนาด", "threatDetailPath": "เส้นทาง", "confirmQuarantineTitle": "กักกันภัยคุกคาม", - "confirmQuarantineDescription": "การดำเนินการนี้จะย้ายภัยคุกคามที่ตรวจพบ {{count}} รายการไปยังกักกัน คุณสามารถกู้คืนไฟล์ได้ภายหลังหากจำเป็น", + "confirmQuarantineDescription": "การดำเนินการนี้จะย้ายภัยคุกคามที่ตรวจพบ {{count}} รายการไปยังกักกัน สามารถกู้คืนไฟล์ได้ภายหลังหากจำเป็น", "confirmQuarantineLabel": "กักกันตอนนี้", "confirmDeleteTitle": "ลบภัยคุกคาม", - "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้", "confirmDeleteLabel": "ลบอย่างถาวร", "toastScanFailed": "การสแกนมัลแวร์ล้มเหลว", "toastActionFailed": "ไม่สามารถ{{action}}ภัยคุกคามได้", @@ -61,6 +61,27 @@ "errorOperationFailed": "การดำเนินการล้มเหลว — ลองเรียกใช้ในฐานะผู้ดูแลระบบ", "tabScanner": "ตัวสแกน", "tabQuarantine": "กักกัน", + "tabDatabase": "ฐานข้อมูล", + "dbTitle": "ฐานข้อมูลลายเซ็น", + "dbDescription": "กฎการตรวจจับมัลแวร์ YARA ที่ใช้โดยตัวสแกน", + "dbFetchLatest": "ตรวจหาการอัปเดต", + "dbUpdating": "กำลังอัปเดต...", + "dbEngine": "เอนจิน", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex สำรอง", + "dbRulesLoaded": "กฎที่โหลดแล้ว", + "dbVersion": "เวอร์ชันลายเซ็น", + "dbLastUpdated": "อัปเดตล่าสุด", + "dbNever": "ไม่เคย", + "dbSource": "แหล่งที่มาของกฎ", + "dbSourceCloud": "Cloud (แคชแล้ว)", + "dbSourceBundled": "มาพร้อมแอป", + "dbSourceNone": "ไม่ได้โหลดกฎ", + "dbRuleFiles": "ไฟล์กฎ", + "dbRuleFilesCounts": "มาพร้อมแอป {{bundled}} รายการ, จาก cloud {{cached}} รายการ", + "dbUpdateSuccess": "อัปเดตเป็น v{{version}} แล้ว ({{count}} กฎ)", + "dbAlreadyCurrent": "ลายเซ็นเป็นเวอร์ชันล่าสุดอยู่แล้ว", + "dbUpdateFailed": "ไม่สามารถอัปเดตลายเซ็นได้", "quarantineEmptyTitle": "ไม่มีรายการที่ถูกกักกัน", "quarantineEmptyDescription": "ไฟล์ที่ย้ายไปยังกักกันจะแสดงที่นี่ คุณสามารถกู้คืนหรือลบอย่างถาวรได้", "quarantineHeading": "ไฟล์ที่ถูกกักกัน", @@ -76,16 +97,16 @@ "quarantineSelectAll": "เลือกทั้งหมด", "quarantineDeselectAll": "ยกเลิกการเลือกทั้งหมด", "confirmRestoreTitle": "กู้คืนไฟล์", - "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} รายการไปยังตำแหน่งเดิม กู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", + "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} ไฟล์ไปยังตำแหน่งเดิม กู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", "confirmRestoreLabel": "กู้คืนตอนนี้", "confirmDeleteQuarantineTitle": "ลบอย่างถาวร", - "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} รายการอย่างถาวร การดำเนินการนี้ไม่สามารถย้อนกลับได้", + "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} ไฟล์อย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้", "confirmDeleteQuarantineLabel": "ลบอย่างถาวร", - "quarantineLoading": "กำลังโหลดรายการกักกัน...", + "quarantineLoading": "กำลังโหลดกักกัน...", "quarantineRestoring": "กำลังกู้คืนไฟล์...", "quarantineDeleting": "กำลังลบไฟล์...", - "toastRestoreSuccess": "กู้คืนไฟล์แล้ว {{count}} รายการ", + "toastRestoreSuccess": "กู้คืนไฟล์แล้ว {{count}} ไฟล์", "toastRestoreFailed": "ไม่สามารถกู้คืนไฟล์บางรายการได้", - "toastDeleteQuarantineSuccess": "ลบไฟล์อย่างถาวรแล้ว {{count}} รายการ", + "toastDeleteQuarantineSuccess": "ลบไฟล์อย่างถาวรแล้ว {{count}} ไฟล์", "toastRestoreNoOriginal": "ไม่สามารถกู้คืนได้ — ไม่ทราบตำแหน่งเดิม" } diff --git a/src/renderer/src/locales/tr/malware.json b/src/renderer/src/locales/tr/malware.json index 96f91c52..d9e35208 100644 --- a/src/renderer/src/locales/tr/malware.json +++ b/src/renderer/src/locales/tr/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Kötü Amaçlı Yazılım Tarayıcısı", - "pageDescription": "Çok motorlu tehdit algılama — imzalar, sezgisel analiz, betik analizi, sistem bütünlüğü ve kalıcılık taraması", + "pageDescription": "Çoklu motor tehdit algılama — imzalar, sezgisel analiz, betik analizi, sistem bütünlüğü ve kalıcılık taraması", "scanButtonScanning": "Taranıyor...", "scanButton": "Tara", "quarantineButton": "Karantinaya Al", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Tehdit Algılanmadı", "noThreatsDetectedDescription": "{{duration}} sn içinde {{engineCount}} motorla {{filesScanned}} dosya tarandı — sisteminiz temiz.", "emptyStateTitle": "Kötü Amaçlı Yazılım Tarayıcısı", - "emptyStateDescription": "Sisteminizi kötü amaçlı yazılım, reklam yazılımı, kripto madencileri ve şüpheli dosyalar için denetlemek üzere \"Tara\" seçeneğine tıklayın.", + "emptyStateDescription": "Sisteminizi kötü amaçlı yazılım, reklam yazılımı, kripto madencileri ve şüpheli dosyalar açısından denetlemek için \"Tara\" seçeneğine tıklayın.", "detectedThreatsHeading": "Algılanan Tehditler", "detectedThreatsCount": "{{count}} tehdit", "detectedThreatsCountPlural": "{{count}} tehdit", @@ -61,6 +61,27 @@ "errorOperationFailed": "İşlem başarısız oldu — yönetici olarak çalıştırmayı deneyin", "tabScanner": "Tarayıcı", "tabQuarantine": "Karantina", + "tabDatabase": "Veritabanı", + "dbTitle": "İmza Veritabanı", + "dbDescription": "Tarayıcı tarafından kullanılan YARA kötü amaçlı yazılım algılama kuralları", + "dbFetchLatest": "Güncellemeleri Denetle", + "dbUpdating": "Güncelleniyor...", + "dbEngine": "Motor", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex Geri Dönüşü", + "dbRulesLoaded": "Yüklenen Kurallar", + "dbVersion": "İmza Sürümü", + "dbLastUpdated": "Son Güncelleme", + "dbNever": "Hiçbir zaman", + "dbSource": "Kural Kaynağı", + "dbSourceCloud": "Bulut (önbelleğe alınmış)", + "dbSourceBundled": "Uygulamayla birlikte gelir", + "dbSourceNone": "Hiç kural yüklenmedi", + "dbRuleFiles": "Kural Dosyaları", + "dbRuleFilesCounts": "{{bundled}} paketlenmiş, {{cached}} buluttan", + "dbUpdateSuccess": "v{{version}} sürümüne güncellendi ({{count}} kural)", + "dbAlreadyCurrent": "İmzalar zaten güncel", + "dbUpdateFailed": "İmzalar güncellenemedi", "quarantineEmptyTitle": "Karantinaya Alınmış Öğe Yok", "quarantineEmptyDescription": "Karantinaya taşınan dosyalar burada görünür. Bunları geri yükleyebilir veya kalıcı olarak silebilirsiniz.", "quarantineHeading": "Karantinaya Alınmış Dosyalar", diff --git a/src/renderer/src/locales/uk/malware.json b/src/renderer/src/locales/uk/malware.json index a1a8a9b5..88a4b332 100644 --- a/src/renderer/src/locales/uk/malware.json +++ b/src/renderer/src/locales/uk/malware.json @@ -26,43 +26,64 @@ "scanStatFiles": "Файли", "scanStatDuration": "Тривалість", "scanStatEngines": "Рушії", - "severityHeading": "Рівень небезпеки", + "severityHeading": "Серйозність", "selectedHeading": "Вибрано", "selectedOfThreats": "з {{count}} загроз", "selectAll": "Вибрати все", - "deselectAll": "Зняти вибір з усього", + "deselectAll": "Скасувати вибір", "actingQuarantining": "Переміщення вибраних загроз до карантину...", "actingDeleting": "Видалення вибраних загроз...", "actionCompleteQuarantine": "Переміщення до карантину завершено", "actionCompleteDeletion": "Видалення завершено", - "actionResultQuarantined": "До карантину переміщено: {{count}}", + "actionResultQuarantined": "Переміщено до карантину: {{count}}", "actionResultDeleted": "Видалено: {{count}}", "actionResultFailed": "Не вдалося: {{count}}", "noThreatsDetectedTitle": "Загроз не виявлено", - "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів за допомогою {{engineCount}} рушіїв за {{duration}} с — ваша система чиста.", + "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів у {{engineCount}} рушіях за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер шкідливого ПЗ", "emptyStateDescription": "Натисніть \"Сканувати\", щоб перевірити систему на шкідливе ПЗ, рекламне ПЗ, криптомайнери та підозрілі файли.", "detectedThreatsHeading": "Виявлені загрози", "detectedThreatsCount": "{{count}} загроза", "detectedThreatsCountPlural": "{{count}} загроз", - "threatDetailDetails": "Докладно", + "threatDetailDetails": "Відомості", "threatDetailFile": "Файл", "threatDetailSize": "Розмір", "threatDetailPath": "Шлях", "confirmQuarantineTitle": "Перемістити загрози до карантину", - "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(у) до карантину. За потреби файли можна буде відновити пізніше.", + "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(и) до карантину. За потреби файли можна буде відновити пізніше.", "confirmQuarantineLabel": "Перемістити до карантину", "confirmDeleteTitle": "Видалити загрози", - "confirmDeleteDescription": "Буде назавжди видалено {{count}} виявлених загроз(у). Цю дію неможливо скасувати.", - "confirmDeleteLabel": "Видалити назавжди", + "confirmDeleteDescription": "Буде остаточно видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", + "confirmDeleteLabel": "Видалити остаточно", "toastScanFailed": "Не вдалося виконати сканування на шкідливе ПЗ", "toastActionFailed": "Не вдалося {{action}} загрози", "toastActionFailedDescription": "Спробуйте запустити від імені адміністратора", - "errorOperationFailed": "Операцію не виконано — спробуйте запустити від імені адміністратора", + "errorOperationFailed": "Операція не вдалася — спробуйте запустити від імені адміністратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", + "tabDatabase": "База даних", + "dbTitle": "База сигнатур", + "dbDescription": "Правила виявлення шкідливого ПЗ YARA, які використовує сканер", + "dbFetchLatest": "Перевірити наявність оновлень", + "dbUpdating": "Оновлення...", + "dbEngine": "Рушій", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Резервний Regex", + "dbRulesLoaded": "Завантажено правил", + "dbVersion": "Версія сигнатур", + "dbLastUpdated": "Останнє оновлення", + "dbNever": "Ніколи", + "dbSource": "Джерело правил", + "dbSourceCloud": "Хмара (кешовано)", + "dbSourceBundled": "У комплекті з програмою", + "dbSourceNone": "Правила не завантажено", + "dbRuleFiles": "Файли правил", + "dbRuleFilesCounts": "{{bundled}} у комплекті, {{cached}} із хмари", + "dbUpdateSuccess": "Оновлено до v{{version}} ({{count}} правил)", + "dbAlreadyCurrent": "Сигнатури вже актуальні", + "dbUpdateFailed": "Не вдалося оновити сигнатури", "quarantineEmptyTitle": "Елементів у карантині немає", - "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити назавжди.", + "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити остаточно.", "quarantineHeading": "Файли в карантині", "quarantineCount": "{{count}} файл", "quarantineCountPlural": "{{count}} файлів", @@ -72,20 +93,20 @@ "quarantineColumnOriginal": "Початкове розташування", "quarantineOriginalUnknown": "Невідомо", "quarantineRestoreButton": "Відновити", - "quarantineDeleteButton": "Видалити назавжди", + "quarantineDeleteButton": "Видалити остаточно", "quarantineSelectAll": "Вибрати все", - "quarantineDeselectAll": "Зняти вибір з усього", + "quarantineDeselectAll": "Скасувати вибір", "confirmRestoreTitle": "Відновити файли", "confirmRestoreDescription": "Буде відновлено {{count}} файл(ів) до початкового розташування. Відновлюйте лише ті файли, яким довіряєте.", "confirmRestoreLabel": "Відновити зараз", - "confirmDeleteQuarantineTitle": "Видалити назавжди", - "confirmDeleteQuarantineDescription": "Буде назавжди видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", - "confirmDeleteQuarantineLabel": "Видалити назавжди", + "confirmDeleteQuarantineTitle": "Видалити остаточно", + "confirmDeleteQuarantineDescription": "Буде остаточно видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", + "confirmDeleteQuarantineLabel": "Видалити остаточно", "quarantineLoading": "Завантаження карантину...", "quarantineRestoring": "Відновлення файлів...", "quarantineDeleting": "Видалення файлів...", "toastRestoreSuccess": "Відновлено файл(ів): {{count}}", "toastRestoreFailed": "Не вдалося відновити деякі файли", - "toastDeleteQuarantineSuccess": "Назавжди видалено файл(ів): {{count}}", + "toastDeleteQuarantineSuccess": "Остаточно видалено файл(ів): {{count}}", "toastRestoreNoOriginal": "Неможливо відновити — початкове розташування невідоме" } diff --git a/src/renderer/src/locales/vi/malware.json b/src/renderer/src/locales/vi/malware.json index ac41765e..f5e5f60c 100644 --- a/src/renderer/src/locales/vi/malware.json +++ b/src/renderer/src/locales/vi/malware.json @@ -26,7 +26,7 @@ "scanStatFiles": "Tệp", "scanStatDuration": "Thời lượng", "scanStatEngines": "Công cụ", - "severityHeading": "Mức độ", + "severityHeading": "Mức độ nghiêm trọng", "selectedHeading": "Đã chọn", "selectedOfThreats": "trong số {{count}} mối đe dọa", "selectAll": "Chọn tất cả", @@ -39,7 +39,7 @@ "actionResultDeleted": "Đã xóa {{count}}", "actionResultFailed": "{{count}} thất bại", "noThreatsDetectedTitle": "Không phát hiện mối đe dọa", - "noThreatsDetectedDescription": "Đã quét {{filesScanned}} tệp bằng {{engineCount}} công cụ trong {{duration}} giây — hệ thống của bạn sạch.", + "noThreatsDetectedDescription": "Đã quét {{filesScanned}} tệp trên {{engineCount}} công cụ trong {{duration}} giây — hệ thống của bạn sạch.", "emptyStateTitle": "Trình quét phần mềm độc hại", "emptyStateDescription": "Nhấp vào \"Quét\" để kiểm tra hệ thống của bạn tìm phần mềm độc hại, phần mềm quảng cáo, trình đào tiền mã hóa và các tệp đáng ngờ.", "detectedThreatsHeading": "Các mối đe dọa đã phát hiện", @@ -61,6 +61,27 @@ "errorOperationFailed": "Thao tác thất bại — hãy thử chạy với quyền quản trị viên", "tabScanner": "Trình quét", "tabQuarantine": "Cách ly", + "tabDatabase": "Cơ sở dữ liệu", + "dbTitle": "Cơ sở dữ liệu chữ ký", + "dbDescription": "Các quy tắc phát hiện phần mềm độc hại YARA được trình quét sử dụng", + "dbFetchLatest": "Kiểm tra bản cập nhật", + "dbUpdating": "Đang cập nhật...", + "dbEngine": "Công cụ", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex dự phòng", + "dbRulesLoaded": "Quy tắc đã tải", + "dbVersion": "Phiên bản chữ ký", + "dbLastUpdated": "Cập nhật lần cuối", + "dbNever": "Chưa bao giờ", + "dbSource": "Nguồn quy tắc", + "dbSourceCloud": "Đám mây (đã lưu đệm)", + "dbSourceBundled": "Đi kèm ứng dụng", + "dbSourceNone": "Chưa tải quy tắc nào", + "dbRuleFiles": "Tệp quy tắc", + "dbRuleFilesCounts": "{{bundled}} đi kèm, {{cached}} từ đám mây", + "dbUpdateSuccess": "Đã cập nhật lên v{{version}} ({{count}} quy tắc)", + "dbAlreadyCurrent": "Các chữ ký đã được cập nhật mới nhất", + "dbUpdateFailed": "Không thể cập nhật chữ ký", "quarantineEmptyTitle": "Không có mục nào trong vùng cách ly", "quarantineEmptyDescription": "Các tệp được chuyển vào vùng cách ly sẽ xuất hiện tại đây. Bạn có thể khôi phục hoặc xóa vĩnh viễn chúng.", "quarantineHeading": "Tệp đã cách ly", @@ -76,7 +97,7 @@ "quarantineSelectAll": "Chọn tất cả", "quarantineDeselectAll": "Bỏ chọn tất cả", "confirmRestoreTitle": "Khôi phục tệp", - "confirmRestoreDescription": "Thao tác này sẽ khôi phục {{count}} tệp về vị trí gốc. Chỉ khôi phục các tệp mà bạn tin cậy.", + "confirmRestoreDescription": "Thao tác này sẽ khôi phục {{count}} tệp về vị trí gốc của chúng. Chỉ khôi phục những tệp bạn tin cậy.", "confirmRestoreLabel": "Khôi phục ngay", "confirmDeleteQuarantineTitle": "Xóa vĩnh viễn", "confirmDeleteQuarantineDescription": "Thao tác này sẽ xóa vĩnh viễn {{count}} tệp đã cách ly. Không thể hoàn tác hành động này.", @@ -87,5 +108,5 @@ "toastRestoreSuccess": "Đã khôi phục {{count}} tệp", "toastRestoreFailed": "Không thể khôi phục một số tệp", "toastDeleteQuarantineSuccess": "Đã xóa vĩnh viễn {{count}} tệp", - "toastRestoreNoOriginal": "Không thể khôi phục — không xác định được vị trí gốc" + "toastRestoreNoOriginal": "Không thể khôi phục — không xác định vị trí gốc" } diff --git a/src/renderer/src/locales/zh-CN/malware.json b/src/renderer/src/locales/zh-CN/malware.json index 7b82386c..6c101032 100644 --- a/src/renderer/src/locales/zh-CN/malware.json +++ b/src/renderer/src/locales/zh-CN/malware.json @@ -24,11 +24,11 @@ "scanSummarySystemClean": "系统干净", "scanSummaryThreatsDetected": "检测到 {{count}} 个威胁", "scanStatFiles": "文件", - "scanStatDuration": "耗时", + "scanStatDuration": "时长", "scanStatEngines": "引擎", "severityHeading": "严重级别", "selectedHeading": "已选择", - "selectedOfThreats": "共 {{count}} 个威胁", + "selectedOfThreats": "/ 共 {{count}} 个威胁", "selectAll": "全选", "deselectAll": "取消全选", "actingQuarantining": "正在隔离所选威胁...", @@ -39,9 +39,9 @@ "actionResultDeleted": "已删除 {{count}} 个", "actionResultFailed": "{{count}} 个失败", "noThreatsDetectedTitle": "未检测到威胁", - "noThreatsDetectedDescription": "已在 {{duration}} 秒内使用 {{engineCount}} 个引擎扫描 {{filesScanned}} 个文件——您的系统是干净的。", + "noThreatsDetectedDescription": "已通过 {{engineCount}} 个引擎在 {{duration}} 秒内扫描 {{filesScanned}} 个文件——您的系统很干净。", "emptyStateTitle": "恶意软件扫描器", - "emptyStateDescription": "点击“扫描”以检查您的系统中是否存在恶意软件、广告软件、加密货币挖矿程序和可疑文件。", + "emptyStateDescription": "点击“扫描”以检查系统中的恶意软件、广告软件、加密货币挖矿程序和可疑文件。", "detectedThreatsHeading": "检测到的威胁", "detectedThreatsCount": "{{count}} 个威胁", "detectedThreatsCountPlural": "{{count}} 个威胁", @@ -50,7 +50,7 @@ "threatDetailSize": "大小", "threatDetailPath": "路径", "confirmQuarantineTitle": "隔离威胁", - "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以还原文件。", + "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以恢复这些文件。", "confirmQuarantineLabel": "立即隔离", "confirmDeleteTitle": "删除威胁", "confirmDeleteDescription": "这将永久删除检测到的 {{count}} 个威胁。此操作无法撤销。", @@ -61,8 +61,29 @@ "errorOperationFailed": "操作失败——请尝试以管理员身份运行", "tabScanner": "扫描器", "tabQuarantine": "隔离区", + "tabDatabase": "数据库", + "dbTitle": "签名数据库", + "dbDescription": "扫描器使用的 YARA 恶意软件检测规则", + "dbFetchLatest": "检查更新", + "dbUpdating": "正在更新...", + "dbEngine": "引擎", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex 回退", + "dbRulesLoaded": "已加载规则", + "dbVersion": "签名版本", + "dbLastUpdated": "上次更新", + "dbNever": "从未", + "dbSource": "规则来源", + "dbSourceCloud": "云端(已缓存)", + "dbSourceBundled": "随应用附带", + "dbSourceNone": "未加载规则", + "dbRuleFiles": "规则文件", + "dbRuleFilesCounts": "{{bundled}} 个随附,{{cached}} 个来自云端", + "dbUpdateSuccess": "已更新到 v{{version}}({{count}} 条规则)", + "dbAlreadyCurrent": "签名已是最新", + "dbUpdateFailed": "更新签名失败", "quarantineEmptyTitle": "没有已隔离的项目", - "quarantineEmptyDescription": "移至隔离区的文件将显示在这里。您可以还原或永久删除它们。", + "quarantineEmptyDescription": "移至隔离区的文件将显示在此处。您可以恢复或永久删除它们。", "quarantineHeading": "已隔离的文件", "quarantineCount": "{{count}} 个文件", "quarantineCountPlural": "{{count}} 个文件", @@ -71,21 +92,21 @@ "quarantineColumnSize": "大小", "quarantineColumnOriginal": "原始位置", "quarantineOriginalUnknown": "未知", - "quarantineRestoreButton": "还原", + "quarantineRestoreButton": "恢复", "quarantineDeleteButton": "永久删除", "quarantineSelectAll": "全选", "quarantineDeselectAll": "取消全选", - "confirmRestoreTitle": "还原文件", - "confirmRestoreDescription": "这将把 {{count}} 个文件还原到其原始位置。请仅还原您信任的文件。", - "confirmRestoreLabel": "立即还原", + "confirmRestoreTitle": "恢复文件", + "confirmRestoreDescription": "这将把 {{count}} 个文件恢复到其原始位置。请仅恢复您信任的文件。", + "confirmRestoreLabel": "立即恢复", "confirmDeleteQuarantineTitle": "永久删除", "confirmDeleteQuarantineDescription": "这将永久删除 {{count}} 个已隔离文件。此操作无法撤销。", "confirmDeleteQuarantineLabel": "永久删除", "quarantineLoading": "正在加载隔离区...", - "quarantineRestoring": "正在还原文件...", + "quarantineRestoring": "正在恢复文件...", "quarantineDeleting": "正在删除文件...", - "toastRestoreSuccess": "已还原 {{count}} 个文件", - "toastRestoreFailed": "无法还原部分文件", + "toastRestoreSuccess": "已恢复 {{count}} 个文件", + "toastRestoreFailed": "恢复部分文件失败", "toastDeleteQuarantineSuccess": "已永久删除 {{count}} 个文件", - "toastRestoreNoOriginal": "无法还原——原始位置未知" + "toastRestoreNoOriginal": "无法恢复——原始位置未知" } diff --git a/src/renderer/src/locales/zh-TW/malware.json b/src/renderer/src/locales/zh-TW/malware.json index c383eec5..594ccd43 100644 --- a/src/renderer/src/locales/zh-TW/malware.json +++ b/src/renderer/src/locales/zh-TW/malware.json @@ -5,7 +5,7 @@ "scanButton": "掃描", "quarantineButton": "隔離", "deleteButton": "刪除", - "severityCritical": "重大", + "severityCritical": "嚴重", "severityHigh": "高", "severityMedium": "中", "severityLow": "低", @@ -20,9 +20,9 @@ "threatCountPlural": "{{count}} 個威脅", "threatFound": "找到 {{count}} 個", "scanCategoryClean": "乾淨", - "scanCategoryNA": "N/A", + "scanCategoryNA": "不適用", "scanSummarySystemClean": "系統乾淨", - "scanSummaryThreatsDetected": "偵測到 {{count}} 個威脅", + "scanSummaryThreatsDetected": "已偵測到 {{count}} 個威脅", "scanStatFiles": "檔案", "scanStatDuration": "持續時間", "scanStatEngines": "引擎", @@ -39,10 +39,10 @@ "actionResultDeleted": "已刪除 {{count}} 個", "actionResultFailed": "{{count}} 個失敗", "noThreatsDetectedTitle": "未偵測到威脅", - "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統很乾淨。", + "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統是乾淨的。", "emptyStateTitle": "惡意軟體掃描器", "emptyStateDescription": "按一下「掃描」以檢查您的系統是否有惡意軟體、廣告軟體、加密貨幣挖礦程式與可疑檔案。", - "detectedThreatsHeading": "已偵測的威脅", + "detectedThreatsHeading": "已偵測到的威脅", "detectedThreatsCount": "{{count}} 個威脅", "detectedThreatsCountPlural": "{{count}} 個威脅", "threatDetailDetails": "詳細資料", @@ -61,8 +61,29 @@ "errorOperationFailed": "作業失敗 — 請嘗試以系統管理員身分執行", "tabScanner": "掃描器", "tabQuarantine": "隔離區", + "tabDatabase": "資料庫", + "dbTitle": "特徵碼資料庫", + "dbDescription": "掃描器使用的 YARA 惡意軟體偵測規則", + "dbFetchLatest": "檢查更新", + "dbUpdating": "更新中...", + "dbEngine": "引擎", + "dbEngineYara": "YARA (WASM)", + "dbEngineRegex": "Regex 備援", + "dbRulesLoaded": "已載入規則", + "dbVersion": "特徵碼版本", + "dbLastUpdated": "上次更新", + "dbNever": "從未", + "dbSource": "規則來源", + "dbSourceCloud": "雲端(已快取)", + "dbSourceBundled": "隨應用程式附帶", + "dbSourceNone": "未載入規則", + "dbRuleFiles": "規則檔案", + "dbRuleFilesCounts": "{{bundled}} 個隨附,{{cached}} 個來自雲端", + "dbUpdateSuccess": "已更新至 v{{version}}({{count}} 條規則)", + "dbAlreadyCurrent": "特徵碼已是最新版本", + "dbUpdateFailed": "更新特徵碼失敗", "quarantineEmptyTitle": "沒有已隔離的項目", - "quarantineEmptyDescription": "移至隔離區的檔案會顯示於此。您可以還原或永久刪除它們。", + "quarantineEmptyDescription": "移至隔離區的檔案會顯示在這裡。您可以還原或永久刪除它們。", "quarantineHeading": "已隔離的檔案", "quarantineCount": "{{count}} 個檔案", "quarantineCountPlural": "{{count}} 個檔案", @@ -76,7 +97,7 @@ "quarantineSelectAll": "全選", "quarantineDeselectAll": "取消全選", "confirmRestoreTitle": "還原檔案", - "confirmRestoreDescription": "這會將 {{count}} 個檔案還原至其原始位置。請僅還原您信任的檔案。", + "confirmRestoreDescription": "這會將 {{count}} 個檔案還原到其原始位置。請僅還原您信任的檔案。", "confirmRestoreLabel": "立即還原", "confirmDeleteQuarantineTitle": "永久刪除", "confirmDeleteQuarantineDescription": "這會永久刪除 {{count}} 個已隔離的檔案。此動作無法復原。", From d6c091bd1ae5cb4f8d55c9b47644100ae2729700 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:24:07 +0200 Subject: [PATCH 11/35] fix(malware): pass raw bytes to YARA via Uint8Array, fix serverUrl reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use Uint8Array instead of toString('binary') when calling libyara-wasm run(). The embind layer correctly preserves raw bytes from Uint8Array, while string marshaling corrupts bytes 0x80-0xFF via UTF-8 encoding, breaking hash rules and hex pattern matching on binary files. - Move serverUrl declaration before its use in MALWARE_YARA_UPDATE handler — const is not hoisted, so the previous ordering caused a ReferenceError when clicking "Check for Updates" in the Database tab. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 4 +++- src/main/services/yara-engine.ts | 9 +++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 2c2d5cb1..fe9eebbf 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1961,6 +1961,9 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { ipcMain.handle(IPC.MALWARE_YARA_INFO, () => getYaraRulesInfo()) + // Cloud URL for rule fetching — used by both manual update and periodic checks + const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' + ipcMain.handle(IPC.MALWARE_YARA_UPDATE, async () => { const result = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) if (result.success && result.stats) { @@ -1970,6 +1973,5 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { }) // Start periodic YARA rule updates — runs for all users, not just cloud-linked - const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 81c44fc0..e266ed69 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -31,7 +31,7 @@ interface LibYaraResult { } interface LibYaraModule { - run(data: string, rules: string): LibYaraResult + run(data: string | Uint8Array, rules: string): LibYaraResult } // ─── Engine ────────────────────────────────────────────────── @@ -136,9 +136,10 @@ export class YaraEngine { if (!this._module || !this._compiledRules) return [] try { - // libyara-wasm expects string input — convert buffer to binary string - const dataStr = buffer.toString('binary') - const result = this._module.run(dataStr, this._compiledRules) + // Pass raw bytes as Uint8Array — embind preserves them correctly. + // Using toString('binary') would corrupt high bytes (0x80-0xFF) through + // UTF-8 marshaling, breaking hash rules and hex pattern matching. + const result = this._module.run(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength), this._compiledRules) return this._parseMatches(result) } catch (err) { console.warn('[yara] Scan error:', err) From 96e4a0c0b991e397e739c9dccc73e4aed9515b8a Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:54:12 +0200 Subject: [PATCH 12/35] fix(ui): initialize YARA engine when database tab loads getYaraRulesInfo() now awaits getYaraEngine() before reporting status, so the Database tab shows "YARA (WASM)" instead of "Regex Fallback" when rules are cached but no scan has been run yet. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index fe9eebbf..a2ad8f6e 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -272,11 +272,12 @@ export function resetYaraEngine(): void { _yaraInitPromise = null } -function getYaraRulesInfo(): YaraRulesInfo { +async function getYaraRulesInfo(): Promise { + // Ensure engine is initialized before reporting status + const engine = await getYaraEngine() const meta = getRulesMetadata() const bundled = getBundledRulePaths() const cached = getCachedRulePaths() - const engine = _yaraEngine const available = engine !== null && engine.isReady() let source: YaraRulesInfo['source'] = 'none' From 16371f57661dc44ea634f7770596fafc6078110d Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:55:38 +0200 Subject: [PATCH 13/35] fix(security): require HTTPS for YARA rule updates, atomic cache writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reject http: URLs for update-yara-rules in packaged builds — SHA-256 doesn't provide authenticity when both payload and hash come from the same unauthenticated HTTP response - Stage rule cache updates in a .staging temp directory and swap into place atomically, so a failed write can't leave a partial ruleset that silently reduces detection coverage Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/cloud-agent.ts | 9 ++++-- src/main/services/yara-rules-store.ts | 44 +++++++++------------------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/main/services/cloud-agent.ts b/src/main/services/cloud-agent.ts index 8061dc7b..217ffbee 100644 --- a/src/main/services/cloud-agent.ts +++ b/src/main/services/cloud-agent.ts @@ -3089,10 +3089,15 @@ class CloudAgentService { return } - // SSRF validation: reuse the same private/loopback checks + // SSRF validation: require HTTPS in packaged builds (HTTP is MitM-able and + // the SHA-256 hash doesn't help since both payload and hash come from the same response) try { const parsed = new URL(url) - if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { + if (app.isPackaged && parsed.protocol !== 'https:') { + await this.postCommandResult(requestId, false, undefined, 'Only HTTPS URLs allowed') + return + } + if (!app.isPackaged && parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { await this.postCommandResult(requestId, false, undefined, 'Only HTTP(S) URLs allowed') return } diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index e667d12a..16b3fa99 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -1,4 +1,4 @@ -import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync, readdirSync } from 'fs' +import { readFileSync, writeFileSync, renameSync, unlinkSync, rmSync, existsSync, mkdirSync, readdirSync } from 'fs' import { join, basename } from 'path' import { createHash } from 'crypto' import { app } from 'electron' @@ -128,16 +128,6 @@ function validateMetadata(raw: unknown): boolean { ) } -function saveMetadata(meta: YaraRulesMetadata): void { - const dir = getCachedRulesDir() - if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) - - const path = getMetadataPath() - const tmpPath = path + '.tmp' - writeFileSync(tmpPath, JSON.stringify(meta, null, 2), 'utf-8') - renameSync(tmpPath, path) -} - // ─── Bundle validation ─────────────────────────────────────── export function validateRuleBundle(raw: unknown): YaraRuleBundle | null { @@ -244,33 +234,29 @@ export async function fetchAndCacheRules(url: string): Promise<{ return { success: false, error: 'Integrity check failed: SHA-256 mismatch' } } - // Write rules to disk cache + // Write rules atomically: stage in a temp directory, then swap into place. + // This avoids leaving a partial ruleset if a write fails mid-way. const dir = getCachedRulesDir() - if (!existsSync(dir)) mkdirSync(dir, { recursive: true }) + const stageDir = dir + '.staging' - // Remove stale .yar files not present in the new bundle - const newFilenames = new Set(bundle.rules.map(r => r.filename)) - try { - for (const existing of readdirSync(dir)) { - if (existing.endsWith('.yar') && !newFilenames.has(existing)) { - try { unlinkSync(join(dir, existing)) } catch { /* best effort */ } - } - } - } catch { /* directory read failed — not critical */ } + // Clean up any leftover staging dir from a previous failed attempt + if (existsSync(stageDir)) rmSync(stageDir, { recursive: true, force: true }) + mkdirSync(stageDir, { recursive: true }) + // Write all rule files + metadata into the staging directory for (const rule of bundle.rules) { - const filePath = join(dir, rule.filename) - const tmpPath = filePath + '.tmp' - writeFileSync(tmpPath, rule.content, 'utf-8') - renameSync(tmpPath, filePath) + writeFileSync(join(stageDir, rule.filename), rule.content, 'utf-8') } - - saveMetadata({ + writeFileSync(join(stageDir, 'metadata.json'), JSON.stringify({ version: bundle.version, updatedAt: bundle.updatedAt, rulesCount: bundle.rules.length, sha256: bundle.sha256, - }) + }, null, 2), 'utf-8') + + // Swap: remove old cache dir, rename staging into place + if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }) + renameSync(stageDir, dir) return { success: true, From de3e6c63ae02cfe8dd8db2aefc8b1de22b6ce5c8 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 03:58:35 +0200 Subject: [PATCH 14/35] fix(malware): add debug logging for SHA-256 mismatch diagnosis Log the server-provided hash vs client-computed hash and the sorted rule filenames with content lengths when integrity check fails, to help diagnose hash computation differences on the cloud side. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/yara-rules-store.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 16b3fa99..59df1c2a 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -231,6 +231,8 @@ export async function fetchAndCacheRules(url: string): Promise<{ // Verify integrity const computedHash = computeBundleHash(bundle.rules) if (computedHash !== bundle.sha256) { + console.warn(`[yara] SHA-256 mismatch — server: ${bundle.sha256}, computed: ${computedHash}`) + console.warn(`[yara] Rule files (sorted): ${[...bundle.rules].sort((a, b) => a.filename.localeCompare(b.filename)).map(r => `${r.filename}(${r.content.length})`).join(', ')}`) return { success: false, error: 'Integrity check failed: SHA-256 mismatch' } } From e9d2d964cadb6b6b25d10476f6f9d441fa92b47d Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 04:01:47 +0200 Subject: [PATCH 15/35] fix(malware): use deterministic sort for bundle hash computation Replace localeCompare with plain < > comparison for sorting rule filenames before SHA-256 hashing. localeCompare is locale-dependent and can produce different orderings on the cloud server vs the Electron client, causing hash mismatches on large rule bundles. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/yara-rules-store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 59df1c2a..23211333 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -164,7 +164,8 @@ export function validateRuleBundle(raw: unknown): YaraRuleBundle | null { * Hash is over concatenated content fields, sorted by filename. */ export function computeBundleHash(rules: YaraRuleFile[]): string { - const sorted = [...rules].sort((a, b) => a.filename.localeCompare(b.filename)) + // Use plain < > comparison (not localeCompare) for deterministic cross-platform sorting + const sorted = [...rules].sort((a, b) => a.filename < b.filename ? -1 : a.filename > b.filename ? 1 : 0) const combined = sorted.map(r => r.content).join('') return createHash('sha256').update(combined).digest('hex') } From 857aedbc038c588d2cf7bce11f7daa5a9fc82e05 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 04:07:41 +0200 Subject: [PATCH 16/35] fix(malware): check for rule updates before scan, update progress every batch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Before scanning, check if rules are stale (>1 hour since last fetch) and try to update from cloud. Non-blocking — scan proceeds with existing rules if the fetch fails. - Update the current-file display every 16-file batch instead of every 64 files, so the UI no longer appears stuck on a single filename. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 47 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index a2ad8f6e..53f8befe 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1178,6 +1178,22 @@ export async function scanMalware( const scannableExts = getScannableExtensions() const systemDirs = getSystemDirs() + // Try to update rules if stale (>1 hour since last fetch) before scanning + const meta = getRulesMetadata() + const RULE_STALE_MS = 60 * 60 * 1000 // 1 hour + const lastUpdate = meta?.updatedAt ? new Date(meta.updatedAt).getTime() : 0 + if (Date.now() - lastUpdate > RULE_STALE_MS) { + sendProgress({ step: 'init', stepLabel: 'Checking for signature updates...', engine: 'YARA Rules Engine' }) + try { + const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' + const updateResult = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) + if (updateResult.success && updateResult.stats) { + console.log(`[yara] Pre-scan update: v${updateResult.stats.version} (${updateResult.stats.rulesCount} rules)`) + resetYaraEngine() + } + } catch { /* non-critical — scan with existing rules */ } + } + // YARA engine: try to initialize once per app lifetime const yaraEngine = await getYaraEngine() if (yaraEngine) { @@ -1480,22 +1496,21 @@ export async function scanMalware( for (let batch = 0; batch < files.length; batch += SIG_CONCURRENCY) { const chunk = files.slice(batch, batch + SIG_CONCURRENCY) - if (batch % (SIG_CONCURRENCY * 4) === 0) { - const pct = Math.round((batch / totalFiles) * 100) - const currentStep = (batch / totalFiles) < 0.5 ? 'signatures' : 'heuristics' - updateCategory('signatures', { progress: Math.min(pct, 100), itemsScanned: batch, threatsFound: sigThreatsCount }) - updateCategory('heuristics', { progress: Math.min(pct, 100), itemsScanned: batch, threatsFound: heuristicThreatsCount }) - sendProgress({ - step: currentStep as MalwareScanProgress['step'], - stepLabel: currentStep === 'signatures' - ? (yaraEngine ? 'Scanning with YARA rules engine...' : 'Matching filenames, hashes & known malware patterns...') - : 'Analyzing PE headers, entropy & suspicious API imports...', - currentPath: chunk[0], - progress: 5 + (batch / totalFiles) * 35, - totalFiles, - engine: currentStep === 'signatures' ? sigEngineName : 'Heuristic Engine' - }) - } + // Update progress every batch (every 16 files) so the current-file display stays responsive + const pct = Math.round((batch / totalFiles) * 100) + const currentStep = (batch / totalFiles) < 0.5 ? 'signatures' : 'heuristics' + updateCategory('signatures', { progress: Math.min(pct, 100), itemsScanned: batch, threatsFound: sigThreatsCount }) + updateCategory('heuristics', { progress: Math.min(pct, 100), itemsScanned: batch, threatsFound: heuristicThreatsCount }) + sendProgress({ + step: currentStep as MalwareScanProgress['step'], + stepLabel: currentStep === 'signatures' + ? (yaraEngine ? 'Scanning with YARA rules engine...' : 'Matching filenames, hashes & known malware patterns...') + : 'Analyzing PE headers, entropy & suspicious API imports...', + currentPath: chunk[0], + progress: 5 + (batch / totalFiles) * 35, + totalFiles, + engine: currentStep === 'signatures' ? sigEngineName : 'Heuristic Engine' + }) await Promise.allSettled(chunk.map(f => scanFileSignatures(f))) filesScanned += chunk.length From cf530cca3d3791801fcfca288fb95fb5674c3083 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 04:15:37 +0200 Subject: [PATCH 17/35] perf(malware): only YARA-scan executable/script file types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libyara-wasm recompiles all rules from source on every run() call (~428ms with 8000 rules), making per-file YARA scanning very slow. Only read file content and run YARA for high-risk extensions (exe, dll, scripts, etc.) that could contain malicious payloads. Low-risk files (plists, configs, logs) skip the expensive content scan and get filename-only regex matching instead. This dramatically reduces the number of YARA run() calls — most of the 5000 scanned files are non-executable and now skip the 400ms/file compilation cost entirely. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 53f8befe..c880faf5 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1293,6 +1293,22 @@ export async function scanMalware( let sigThreatsCount = 0 let heuristicThreatsCount = 0 + // Extensions worth YARA content scanning — executables, scripts, and binaries + // that could contain malicious payloads. Other file types (plists, configs, etc.) + // only get filename-based checks to avoid the ~400ms/file YARA compilation cost. + const YARA_SCAN_EXTS = new Set([ + // Windows executables + '.exe', '.dll', '.sys', '.drv', '.scr', '.cpl', '.ocx', '.msi', '.com', + // Scripts + '.ps1', '.bat', '.cmd', '.vbs', '.vbe', '.wsh', '.wsf', '.js', '.jse', '.hta', + // macOS + '.app', '.dmg', '.pkg', '.dylib', '.so', '.bundle', + // Linux + '.elf', '.bin', '.run', + // Cross-platform + '.jar', '.py', '.rb', '.sh', '.lnk', + ]) + async function scanFileSignatures(filePath: string): Promise { const fileName = basename(filePath) const ext = extname(fileName).toLowerCase() @@ -1301,9 +1317,11 @@ export async function scanMalware( if (!fileStat) return const fileSize = fileStat.size - // Read file buffer (needed for YARA content scanning and heuristic analysis) + // Only read file content for extensions worth YARA/heuristic scanning. + // Low-risk extensions skip the expensive content scan entirely. + const shouldReadContent = YARA_SCAN_EXTS.has(ext) || hasDoubleExtension(fileName) let fileBuffer: Buffer | null = null - if (fileSize <= MAX_READ_SIZE) { + if (shouldReadContent && fileSize <= MAX_READ_SIZE) { fileBuffer = await readFile(filePath).catch(() => null) } From 221b957350c9e1903061a0e6a527b01f817da5db Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 04:21:01 +0200 Subject: [PATCH 18/35] =?UTF-8?q?perf(malware):=20batch=20YARA=20scanning?= =?UTF-8?q?=20=E2=80=94=2080x=20faster=20via=20single=20run()=20per=20chun?= =?UTF-8?q?k?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libyara-wasm recompiles all rules from source text on every run() call (~428ms with 8000 rules). Previously each file triggered its own run(), so 5000 files = 5000 recompilations. Now files are batched: read all buffers in a chunk, concatenate them, call run() once, then map matches back to source files via byte offsets. This pays the compilation cost once per 16-file batch instead of per file. Benchmark: 100 files went from 48,571ms to 611ms (79.6x speedup). - Add YaraEngine.scanBatch() that concatenates buffers and attributes matches back to files via binary search on byte offsets - Restructure Phase 3 loop: batch-read files, single YARA scan, then per-file heuristics with pre-computed YARA results Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 42 ++++++++++---- src/main/services/yara-engine.ts | 89 +++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index c880faf5..b092c420 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1309,7 +1309,7 @@ export async function scanMalware( '.jar', '.py', '.rb', '.sh', '.lnk', ]) - async function scanFileSignatures(filePath: string): Promise { + async function scanFileSignatures(filePath: string, yaraFileMatches: Map | null): Promise { const fileName = basename(filePath) const ext = extname(fileName).toLowerCase() @@ -1349,15 +1349,11 @@ export async function scanMalware( } } - // ── Signature matching: YARA or regex fallback ── - if (yaraEngine && fileBuffer) { - // YARA scans file content against all compiled rules (replaces regex + hash checks) + // ── Signature matching: regex fallback (YARA is done in batch above) ── + if (yaraFileMatches && yaraFileMatches.has(filePath)) { if (!threats.some(t => t.path === filePath)) { - const matches = yaraEngine.scanBuffer(fileBuffer) - for (const match of matches) { + for (const match of yaraFileMatches.get(filePath)!) { if (threats.some(t => t.path === filePath)) break - - // Skip filename-only rules — already handled by SUSPICIOUS_FILENAMES above if (match.metadata.filenameOnly === 'true') continue const fields = yaraMatchToThreatFields(match) @@ -1375,7 +1371,7 @@ export async function scanMalware( sigThreatsCount++ } } - } else if (!threats.some(t => t.path === filePath)) { + } else if (!yaraEngine && !threats.some(t => t.path === filePath)) { // Fallback: regex pattern matching + hash lookup (used when YARA is unavailable) for (const mal of KNOWN_MALWARE_PATTERNS) { if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { @@ -1514,7 +1510,7 @@ export async function scanMalware( for (let batch = 0; batch < files.length; batch += SIG_CONCURRENCY) { const chunk = files.slice(batch, batch + SIG_CONCURRENCY) - // Update progress every batch (every 16 files) so the current-file display stays responsive + // Update progress every batch so the current-file display stays responsive const pct = Math.round((batch / totalFiles) * 100) const currentStep = (batch / totalFiles) < 0.5 ? 'signatures' : 'heuristics' updateCategory('signatures', { progress: Math.min(pct, 100), itemsScanned: batch, threatsFound: sigThreatsCount }) @@ -1530,7 +1526,31 @@ export async function scanMalware( engine: currentStep === 'signatures' ? sigEngineName : 'Heuristic Engine' }) - await Promise.allSettled(chunk.map(f => scanFileSignatures(f))) + // Batch YARA scan: read all buffers for this chunk, scan once, then + // process per-file heuristics. This pays the ~400ms rule compilation + // cost once per batch instead of once per file. + let yaraFileMatches: Map | null = null + if (yaraEngine) { + const chunkBuffers: { path: string; buffer: Buffer }[] = [] + await Promise.allSettled(chunk.map(async (f) => { + const ext = extname(f).toLowerCase() + if (!YARA_SCAN_EXTS.has(ext) && !hasDoubleExtension(basename(f))) return + const st = await stat(f).catch(() => null) + if (!st || st.size > MAX_READ_SIZE) return + const buf = await readFile(f).catch(() => null) + if (buf) chunkBuffers.push({ path: f, buffer: buf }) + })) + + if (chunkBuffers.length > 0) { + const batchResult = yaraEngine.scanBatch(chunkBuffers.map(c => c.buffer)) + yaraFileMatches = new Map() + for (const [idx, matches] of batchResult) { + yaraFileMatches.set(chunkBuffers[idx].path, matches) + } + } + } + + await Promise.allSettled(chunk.map(f => scanFileSignatures(f, yaraFileMatches))) filesScanned += chunk.length } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index e266ed69..e824ca6c 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -129,16 +129,12 @@ export class YaraEngine { } /** - * Scan a buffer against loaded YARA rules. - * Returns matched rules with metadata. + * Scan a single buffer against loaded YARA rules. */ scanBuffer(buffer: Buffer): YaraMatch[] { if (!this._module || !this._compiledRules) return [] try { - // Pass raw bytes as Uint8Array — embind preserves them correctly. - // Using toString('binary') would corrupt high bytes (0x80-0xFF) through - // UTF-8 marshaling, breaking hash rules and hex pattern matching. const result = this._module.run(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength), this._compiledRules) return this._parseMatches(result) } catch (err) { @@ -147,6 +143,89 @@ export class YaraEngine { } } + /** + * Scan multiple buffers in a single run() call for dramatically better + * performance. libyara-wasm recompiles rules on every run(), so batching + * pays the ~400ms compilation cost once instead of per-file. + * + * Returns a Map from file index to its matches. + */ + scanBatch(buffers: Buffer[]): Map { + const result = new Map() + if (!this._module || !this._compiledRules || buffers.length === 0) return result + + try { + // Build a combined buffer and track each file's byte range + const offsets: number[] = [] + let totalSize = 0 + for (const buf of buffers) { + offsets.push(totalSize) + totalSize += buf.length + } + + const combined = new Uint8Array(totalSize) + for (let i = 0; i < buffers.length; i++) { + combined.set(new Uint8Array(buffers[i].buffer, buffers[i].byteOffset, buffers[i].byteLength), offsets[i]) + } + + const scanResult = this._module.run(combined, this._compiledRules) + const matchedRules = scanResult.matchedRules + + for (let i = 0; i < matchedRules.size(); i++) { + const rule = matchedRules.get(i) + + // Parse metadata + const metadata: YaraMatch['metadata'] = {} + const meta = rule.metadata + for (let j = 0; j < meta.size(); j++) { + const m = meta.get(j) + switch (m.identifier) { + case 'detectionName': metadata.detectionName = m.data; break + case 'severity': metadata.severity = m.data as YaraMatch['metadata']['severity']; break + case 'details': metadata.details = m.data; break + case 'filenameOnly': metadata.filenameOnly = m.data; break + } + } + + // Find which files this rule matched in via string match offsets + const resolved = rule.resolvedMatches + const fileIndices = new Set() + for (let j = 0; j < resolved.size(); j++) { + const loc = resolved.get(j).location + // Binary search for the file containing this offset + let lo = 0, hi = offsets.length - 1 + while (lo < hi) { + const mid = (lo + hi + 1) >> 1 + if (offsets[mid] <= loc) lo = mid; else hi = mid - 1 + } + fileIndices.add(lo) + } + + // If rule matched via condition-only (no strings, e.g. hash rules), + // resolvedMatches may be empty — attribute to all files in batch + if (resolved.size() === 0) { + for (let k = 0; k < buffers.length; k++) fileIndices.add(k) + } + + const match: YaraMatch = { + ruleName: rule.ruleName, + metadata, + matchedStrings: [], + } + + for (const idx of fileIndices) { + const existing = result.get(idx) + if (existing) existing.push(match) + else result.set(idx, [match]) + } + } + } catch (err) { + console.warn('[yara] Batch scan error:', err) + } + + return result + } + private _parseMatches(result: LibYaraResult): YaraMatch[] { const matches: YaraMatch[] = [] const matchedRules = result.matchedRules From e6630e0cd8154ec6efdd41cb3969c5447e2e0017 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 04:56:18 +0200 Subject: [PATCH 19/35] fix(malware): revert batch scanning, fix sendProgress TDZ, fix fetch timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert scanBatch — concatenating files into one buffer breaks YARA's per-file context (filesize, hash rules, offset conditions), causing false positives and missed detections. Per-file scanBuffer() is correct; the extension filter already skips most files for performance. Also fixes: - Move pre-scan rule update after sendProgress is defined to avoid TDZ ReferenceError on first run (when no metadata exists) - Keep fetch script abort timer active through response.json() so a stalling server can't hang CI builds indefinitely Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/fetch-yara-rules.js | 28 ++++------ src/main/ipc/malware-scanner.ipc.ts | 69 ++++++++---------------- src/main/services/yara-engine.ts | 83 ----------------------------- 3 files changed, 34 insertions(+), 146 deletions(-) diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js index d85d9f3a..ed11d821 100644 --- a/scripts/fetch-yara-rules.js +++ b/scripts/fetch-yara-rules.js @@ -27,12 +27,21 @@ async function main() { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS) - let response + let body try { - response = await fetch(RULES_URL, { + // Keep timeout active through full body read so a stalling server can't hang CI + const response = await fetch(RULES_URL, { signal: controller.signal, headers: { 'Accept': 'application/json' }, }) + + if (!response.ok) { + console.warn(`[fetch-yara-rules] API returned ${response.status} — skipping bundled rules`) + ensureEmptyDir() + return + } + + body = await response.json() } catch (err) { console.warn(`[fetch-yara-rules] Could not reach API — skipping bundled rules (${err.message})`) ensureEmptyDir() @@ -41,21 +50,6 @@ async function main() { clearTimeout(timeout) } - if (!response.ok) { - console.warn(`[fetch-yara-rules] API returned ${response.status} — skipping bundled rules`) - ensureEmptyDir() - return - } - - let body - try { - body = await response.json() - } catch { - console.warn('[fetch-yara-rules] Invalid JSON response — skipping bundled rules') - ensureEmptyDir() - return - } - if (!body.rules || !Array.isArray(body.rules) || body.rules.length === 0) { console.warn('[fetch-yara-rules] No rules in response — skipping bundled rules') ensureEmptyDir() diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index b092c420..e0ef9e44 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1178,22 +1178,6 @@ export async function scanMalware( const scannableExts = getScannableExtensions() const systemDirs = getSystemDirs() - // Try to update rules if stale (>1 hour since last fetch) before scanning - const meta = getRulesMetadata() - const RULE_STALE_MS = 60 * 60 * 1000 // 1 hour - const lastUpdate = meta?.updatedAt ? new Date(meta.updatedAt).getTime() : 0 - if (Date.now() - lastUpdate > RULE_STALE_MS) { - sendProgress({ step: 'init', stepLabel: 'Checking for signature updates...', engine: 'YARA Rules Engine' }) - try { - const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' - const updateResult = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) - if (updateResult.success && updateResult.stats) { - console.log(`[yara] Pre-scan update: v${updateResult.stats.version} (${updateResult.stats.rulesCount} rules)`) - resetYaraEngine() - } - } catch { /* non-critical — scan with existing rules */ } - } - // YARA engine: try to initialize once per app lifetime const yaraEngine = await getYaraEngine() if (yaraEngine) { @@ -1254,6 +1238,22 @@ export async function scanMalware( engine: hasNativeAv ? (process.platform === 'linux' ? 'ClamAV available' : 'Code Signing available') : 'Using built-in engines' }) + // Try to update rules if stale (>1 hour since last fetch) before scanning + const ruleMeta = getRulesMetadata() + const RULE_STALE_MS = 60 * 60 * 1000 // 1 hour + const lastUpdate = ruleMeta?.updatedAt ? new Date(ruleMeta.updatedAt).getTime() : 0 + if (Date.now() - lastUpdate > RULE_STALE_MS) { + sendProgress({ step: 'init', stepLabel: 'Checking for signature updates...', engine: 'YARA Rules Engine' }) + try { + const updateUrl = (app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com') + RULES_ENDPOINT + const updateResult = await fetchAndCacheRules(updateUrl) + if (updateResult.success && updateResult.stats) { + console.log(`[yara] Pre-scan update: v${updateResult.stats.version} (${updateResult.stats.rulesCount} rules)`) + resetYaraEngine() + } + } catch { /* non-critical — scan with existing rules */ } + } + // ── Phase 2: File discovery ── updateCategory('discovering', { status: 'running' }) const scanPaths = getScanPaths() @@ -1309,7 +1309,7 @@ export async function scanMalware( '.jar', '.py', '.rb', '.sh', '.lnk', ]) - async function scanFileSignatures(filePath: string, yaraFileMatches: Map | null): Promise { + async function scanFileSignatures(filePath: string): Promise { const fileName = basename(filePath) const ext = extname(fileName).toLowerCase() @@ -1349,10 +1349,11 @@ export async function scanMalware( } } - // ── Signature matching: regex fallback (YARA is done in batch above) ── - if (yaraFileMatches && yaraFileMatches.has(filePath)) { + // ── Signature matching: YARA or regex fallback ── + if (yaraEngine && fileBuffer) { if (!threats.some(t => t.path === filePath)) { - for (const match of yaraFileMatches.get(filePath)!) { + const matches = yaraEngine.scanBuffer(fileBuffer) + for (const match of matches) { if (threats.some(t => t.path === filePath)) break if (match.metadata.filenameOnly === 'true') continue @@ -1371,7 +1372,7 @@ export async function scanMalware( sigThreatsCount++ } } - } else if (!yaraEngine && !threats.some(t => t.path === filePath)) { + } else if (!threats.some(t => t.path === filePath)) { // Fallback: regex pattern matching + hash lookup (used when YARA is unavailable) for (const mal of KNOWN_MALWARE_PATTERNS) { if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { @@ -1526,31 +1527,7 @@ export async function scanMalware( engine: currentStep === 'signatures' ? sigEngineName : 'Heuristic Engine' }) - // Batch YARA scan: read all buffers for this chunk, scan once, then - // process per-file heuristics. This pays the ~400ms rule compilation - // cost once per batch instead of once per file. - let yaraFileMatches: Map | null = null - if (yaraEngine) { - const chunkBuffers: { path: string; buffer: Buffer }[] = [] - await Promise.allSettled(chunk.map(async (f) => { - const ext = extname(f).toLowerCase() - if (!YARA_SCAN_EXTS.has(ext) && !hasDoubleExtension(basename(f))) return - const st = await stat(f).catch(() => null) - if (!st || st.size > MAX_READ_SIZE) return - const buf = await readFile(f).catch(() => null) - if (buf) chunkBuffers.push({ path: f, buffer: buf }) - })) - - if (chunkBuffers.length > 0) { - const batchResult = yaraEngine.scanBatch(chunkBuffers.map(c => c.buffer)) - yaraFileMatches = new Map() - for (const [idx, matches] of batchResult) { - yaraFileMatches.set(chunkBuffers[idx].path, matches) - } - } - } - - await Promise.allSettled(chunk.map(f => scanFileSignatures(f, yaraFileMatches))) + await Promise.allSettled(chunk.map(f => scanFileSignatures(f))) filesScanned += chunk.length } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index e824ca6c..356877ad 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -143,89 +143,6 @@ export class YaraEngine { } } - /** - * Scan multiple buffers in a single run() call for dramatically better - * performance. libyara-wasm recompiles rules on every run(), so batching - * pays the ~400ms compilation cost once instead of per-file. - * - * Returns a Map from file index to its matches. - */ - scanBatch(buffers: Buffer[]): Map { - const result = new Map() - if (!this._module || !this._compiledRules || buffers.length === 0) return result - - try { - // Build a combined buffer and track each file's byte range - const offsets: number[] = [] - let totalSize = 0 - for (const buf of buffers) { - offsets.push(totalSize) - totalSize += buf.length - } - - const combined = new Uint8Array(totalSize) - for (let i = 0; i < buffers.length; i++) { - combined.set(new Uint8Array(buffers[i].buffer, buffers[i].byteOffset, buffers[i].byteLength), offsets[i]) - } - - const scanResult = this._module.run(combined, this._compiledRules) - const matchedRules = scanResult.matchedRules - - for (let i = 0; i < matchedRules.size(); i++) { - const rule = matchedRules.get(i) - - // Parse metadata - const metadata: YaraMatch['metadata'] = {} - const meta = rule.metadata - for (let j = 0; j < meta.size(); j++) { - const m = meta.get(j) - switch (m.identifier) { - case 'detectionName': metadata.detectionName = m.data; break - case 'severity': metadata.severity = m.data as YaraMatch['metadata']['severity']; break - case 'details': metadata.details = m.data; break - case 'filenameOnly': metadata.filenameOnly = m.data; break - } - } - - // Find which files this rule matched in via string match offsets - const resolved = rule.resolvedMatches - const fileIndices = new Set() - for (let j = 0; j < resolved.size(); j++) { - const loc = resolved.get(j).location - // Binary search for the file containing this offset - let lo = 0, hi = offsets.length - 1 - while (lo < hi) { - const mid = (lo + hi + 1) >> 1 - if (offsets[mid] <= loc) lo = mid; else hi = mid - 1 - } - fileIndices.add(lo) - } - - // If rule matched via condition-only (no strings, e.g. hash rules), - // resolvedMatches may be empty — attribute to all files in batch - if (resolved.size() === 0) { - for (let k = 0; k < buffers.length; k++) fileIndices.add(k) - } - - const match: YaraMatch = { - ruleName: rule.ruleName, - metadata, - matchedStrings: [], - } - - for (const idx of fileIndices) { - const existing = result.get(idx) - if (existing) existing.push(match) - else result.set(idx, [match]) - } - } - } catch (err) { - console.warn('[yara] Batch scan error:', err) - } - - return result - } - private _parseMatches(result: LibYaraResult): YaraMatch[] { const matches: YaraMatch[] = [] const matchedRules = result.matchedRules From bd417aac12250eae99731d8b25003e3c8e83793d Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 05:05:49 +0200 Subject: [PATCH 20/35] =?UTF-8?q?feat(malware):=20expand=20scan=20coverage?= =?UTF-8?q?=20=E2=80=94=20more=20paths,=20extensions,=20depth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows: - Add Program Files, Program Files (x86), C:\Users\Public, C:\Windows\Temp - Add user profile root for files dropped directly in home - Add .sys, .drv, .ocx extensions for driver/component scanning macOS: - Add /Applications, /Users/Shared, Application Support - Add /private/tmp, /var/tmp, user home root - Add .kext, .pl, .js extensions Linux: - Add /var/tmp, /dev/shm, /opt, /usr/local/bin - Add ~/.config, ~/.local/share, ~/.config/autostart - Add user home root - Add .ko, .deb, .rpm, .jar, .desktop, .js extensions All platforms: - Increase scan depth from 4 to 8 levels (catches deep AppData paths) - Increase per-path file cap from 5,000 to 10,000 - Update YARA_SCAN_EXTS to include all new extensions Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 12 ++++++------ src/main/platform/darwin/malware.ts | 6 +++--- src/main/platform/darwin/paths.ts | 6 ++++++ src/main/platform/linux/malware.ts | 5 ++++- src/main/platform/linux/paths.test.ts | 11 +++++++---- src/main/platform/linux/paths.ts | 9 +++++++++ src/main/platform/win32/malware.ts | 2 +- src/main/platform/win32/paths.ts | 5 +++++ 8 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index e0ef9e44..a7ce6f48 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1269,7 +1269,7 @@ export async function scanMalware( progress: (si / scanPaths.length) * 5, engine: 'File Discovery' }) - await collectFiles(scanPath, 4, 5000, files, scannableExts, exclusions) + await collectFiles(scanPath, 8, 10000, files, scannableExts, exclusions) } updateCategory('discovering', { status: 'done', progress: 100, itemsScanned: files.length, totalItems: files.length }) completedSteps.push(`File discovery — ${files.length.toLocaleString()} scannable files found`) @@ -1297,16 +1297,16 @@ export async function scanMalware( // that could contain malicious payloads. Other file types (plists, configs, etc.) // only get filename-based checks to avoid the ~400ms/file YARA compilation cost. const YARA_SCAN_EXTS = new Set([ - // Windows executables - '.exe', '.dll', '.sys', '.drv', '.scr', '.cpl', '.ocx', '.msi', '.com', + // Windows executables + drivers + '.exe', '.dll', '.sys', '.drv', '.scr', '.cpl', '.ocx', '.msi', '.com', '.pif', // Scripts '.ps1', '.bat', '.cmd', '.vbs', '.vbe', '.wsh', '.wsf', '.js', '.jse', '.hta', // macOS - '.app', '.dmg', '.pkg', '.dylib', '.so', '.bundle', + '.app', '.dmg', '.pkg', '.dylib', '.so', '.bundle', '.kext', // Linux - '.elf', '.bin', '.run', + '.elf', '.bin', '.run', '.ko', '.deb', '.rpm', '.AppImage', // Cross-platform - '.jar', '.py', '.rb', '.sh', '.lnk', + '.jar', '.py', '.rb', '.pl', '.sh', '.lnk', ]) async function scanFileSignatures(filePath: string): Promise { diff --git a/src/main/platform/darwin/malware.ts b/src/main/platform/darwin/malware.ts index eaade0fe..a73e4af6 100644 --- a/src/main/platform/darwin/malware.ts +++ b/src/main/platform/darwin/malware.ts @@ -44,10 +44,10 @@ export function createDarwinMalware(): PlatformMalware { scannableExtensions(): string[] { return [ - '.app', '.dmg', '.pkg', '.command', '.sh', '.py', '.rb', - '.dylib', '.so', '.bundle', + '.app', '.dmg', '.pkg', '.command', '.sh', '.py', '.rb', '.pl', + '.dylib', '.so', '.bundle', '.kext', '.plist', '.scpt', '.applescript', '.workflow', - '.jar', '.terminal', '.action', + '.jar', '.terminal', '.action', '.js', ] }, } diff --git a/src/main/platform/darwin/paths.ts b/src/main/platform/darwin/paths.ts index 45419086..3cb62ff5 100644 --- a/src/main/platform/darwin/paths.ts +++ b/src/main/platform/darwin/paths.ts @@ -41,7 +41,10 @@ export function createDarwinPaths(): PlatformPaths { join(HOME, 'Downloads'), join(HOME, 'Desktop'), join(HOME, 'Documents'), + HOME, // files dropped directly in user home '/tmp', + '/private/tmp', + '/var/tmp', join(LIBRARY, 'LaunchAgents'), join(LIBRARY, 'LaunchDaemons'), '/Library/LaunchAgents', @@ -53,6 +56,9 @@ export function createDarwinPaths(): PlatformPaths { join(LIBRARY, 'Workflows'), '/usr/local/bin', '/opt/local/bin', + '/Applications', + '/Users/Shared', + APP_SUPPORT, ] }, diff --git a/src/main/platform/linux/malware.ts b/src/main/platform/linux/malware.ts index f3e0d7a5..000e8d29 100644 --- a/src/main/platform/linux/malware.ts +++ b/src/main/platform/linux/malware.ts @@ -37,8 +37,11 @@ export function createLinuxMalware(): PlatformMalware { scannableExtensions(): string[] { return [ - '.sh', '.py', '.rb', '.pl', '.so', '.bin', + '.sh', '.py', '.rb', '.pl', '.js', + '.so', '.bin', '.ko', // shared libs + kernel modules '.elf', '.run', '.AppImage', + '.deb', '.rpm', // package files + '.jar', '.desktop', // autostart entries ] }, } diff --git a/src/main/platform/linux/paths.test.ts b/src/main/platform/linux/paths.test.ts index 3c5ad374..bd0b4c9f 100644 --- a/src/main/platform/linux/paths.test.ts +++ b/src/main/platform/linux/paths.test.ts @@ -6,7 +6,7 @@ vi.mock('os', () => ({ homedir: () => '/home/testuser', tmpdir: () => '/tmp' })) const { createLinuxPaths } = await import('./paths') -const HOME = join('/home', 'testuser') +const HOME = '/home/testuser' describe('linux paths', () => { const paths = createLinuxPaths() @@ -31,10 +31,13 @@ describe('linux paths', () => { expect(dirs).toContain('/tmp') }) - it('all user paths are under the home directory', () => { + it('all user paths are under the home directory or known system locations', () => { + const systemPrefixes = ['/tmp', '/var/tmp', '/dev/shm', '/usr/local/bin', '/opt'] for (const d of dirs) { - if (d !== '/tmp') { - expect(d.startsWith(HOME)).toBe(true) + const norm = d.replace(/\\/g, '/') + const isSystem = systemPrefixes.some(p => norm === p || norm.startsWith(p + '/')) + if (!isSystem) { + expect(norm.startsWith(HOME), `Expected "${norm}" to start with "${HOME}"`).toBe(true) } } }) diff --git a/src/main/platform/linux/paths.ts b/src/main/platform/linux/paths.ts index 69fd4bbd..3acb6236 100644 --- a/src/main/platform/linux/paths.ts +++ b/src/main/platform/linux/paths.ts @@ -41,7 +41,16 @@ export function createLinuxPaths(): PlatformPaths { join(HOME, 'Downloads'), join(HOME, 'Desktop'), join(HOME, 'Documents'), + HOME, // files dropped directly in user home '/tmp', + '/var/tmp', + '/dev/shm', + join(HOME, '.local', 'bin'), + join(HOME, '.config', 'autostart'), + CONFIG, + LOCAL_SHARE, + '/usr/local/bin', + '/opt', ] }, diff --git a/src/main/platform/win32/malware.ts b/src/main/platform/win32/malware.ts index 2b1c11fc..3498e1d1 100644 --- a/src/main/platform/win32/malware.ts +++ b/src/main/platform/win32/malware.ts @@ -1,7 +1,7 @@ import type { PlatformMalware } from '../types' const WIN_SCANNABLE_EXTENSIONS = [ - '.exe', '.dll', '.scr', '.bat', '.cmd', '.com', '.pif', + '.exe', '.dll', '.sys', '.drv', '.scr', '.bat', '.cmd', '.com', '.pif', '.ocx', '.vbs', '.vbe', '.js', '.jse', '.wsh', '.wsf', '.ps1', '.msi', '.jar', '.hta', '.cpl', '.inf', '.reg', '.lnk', ] diff --git a/src/main/platform/win32/paths.ts b/src/main/platform/win32/paths.ts index 52c06312..3cbb76de 100644 --- a/src/main/platform/win32/paths.ts +++ b/src/main/platform/win32/paths.ts @@ -45,10 +45,15 @@ export function createWin32Paths(): PlatformPaths { join(userProfile, 'Downloads'), join(userProfile, 'Desktop'), join(userProfile, 'Documents'), + userProfile, // files dropped directly in user home join(LOCALAPPDATA, 'Temp'), APPDATA, LOCALAPPDATA, PROGRAMDATA, + PROGRAMFILES, + PROGRAMFILES_X86, + 'C:\\Users\\Public', + 'C:\\Windows\\Temp', ] }, From eb34409dcc73ccac42df973667eaa13f69456302 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 05:25:28 +0200 Subject: [PATCH 21/35] perf(malware): switch from libyara-wasm to @litko/yara-x (39,000x faster) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace libyara-wasm (WASM, recompiles rules per scan ~428ms) with @litko/yara-x (native napi-rs bindings for yara-x). Rules compile once at startup, subsequent scans take ~0.01ms per file. - Rewrite YaraEngine to use @litko/yara-x compile-once API: addRuleSource/addRuleFile for compilation, scan/scanFile for matching - Use scanFile() to read from disk directly (avoids JS buffer copies) - Remove YARA_SCAN_EXTS extension filter — scanning is now fast enough to scan all collected files, not just executables - Remove libyara-wasm dependency - Update integration tests for the new @litko/yara-x API Benchmark: 5000 files in ~50ms (was ~35 minutes with libyara-wasm). Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 121 ++++++++++++++++-- package.json | 2 +- src/main/ipc/malware-scanner.ipc.ts | 46 +++---- src/main/services/yara-engine.test.ts | 98 +++++++-------- src/main/services/yara-engine.ts | 175 +++++++++++--------------- 5 files changed, 243 insertions(+), 199 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e7075be..205bc9f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,13 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "@litko/yara-x": "^0.5.0", "@tanstack/react-table": "^8.21.3", "better-sqlite3": "^12.8.0", "clsx": "^2.1.1", "electron-updater": "^6.8.3", "framer-motion": "^12.38.0", "i18next": "^25.9.0", - "libyara-wasm": "^1.2.1", "lucide-react": "^0.577.0", "pusher-js": "^8.4.0", "react-i18next": "^16.5.8", @@ -1788,6 +1788,119 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@litko/yara-x": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x/-/yara-x-0.5.0.tgz", + "integrity": "sha512-mK46LZwhenZnLEJXvU5+cO36iwQRdJRzLM/xD1PYpFbQO5o/IqXf7r4nqRxjQZp9WcR5qpby3Kdv3mzcyspuIQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@litko/yara-x-darwin-arm64": "0.5.0", + "@litko/yara-x-darwin-x64": "0.5.0", + "@litko/yara-x-linux-arm64-gnu": "0.5.0", + "@litko/yara-x-linux-x64-gnu": "0.5.0", + "@litko/yara-x-win32-arm64-msvc": "0.5.0", + "@litko/yara-x-win32-x64-msvc": "0.5.0" + } + }, + "node_modules/@litko/yara-x-darwin-arm64": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-darwin-arm64/-/yara-x-darwin-arm64-0.5.0.tgz", + "integrity": "sha512-Og7Phjx8UaALDJHgcX7UaRToiR+Jzpiq+h7d6RQde7AdhMZcunYsiEc47UVJAJ5Kqi1vD4dpyczTT0kvugGj5g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@litko/yara-x-darwin-x64": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-darwin-x64/-/yara-x-darwin-x64-0.5.0.tgz", + "integrity": "sha512-1X2TGdWAF2oQ+PT+5zPSvPUmtRmGRtNi/JI6+LzyFOccrZJRD7sinyscg5ABqXUV8n9IcVCjcCHyFzCoG1ofoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@litko/yara-x-linux-arm64-gnu": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-linux-arm64-gnu/-/yara-x-linux-arm64-gnu-0.5.0.tgz", + "integrity": "sha512-DCfJZxXgSOUHFRmsO/O5x+wFl0tpxRqhcJY1M4wR577Q0KJiQFfqwch1bmqC6YABjezaJCY8RSAWUo33W0TB1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@litko/yara-x-linux-x64-gnu": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-linux-x64-gnu/-/yara-x-linux-x64-gnu-0.5.0.tgz", + "integrity": "sha512-fZelUMMLg6Lfcb2SYrkbKvd8ntHqTNBPOypJRkj+dab9w5BPAO1o8Npa2zvzjT7w3VWmncUudhTLqUAWcAvRwA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@litko/yara-x-win32-arm64-msvc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-win32-arm64-msvc/-/yara-x-win32-arm64-msvc-0.5.0.tgz", + "integrity": "sha512-FLtMXyGdyuXdY9h79c1W0DEAst8eiri1AIiVLVTDOqq1pNSgphDmtq2xDZNgntLnrswR7iit9it4ynGt4MvphQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@litko/yara-x-win32-x64-msvc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@litko/yara-x-win32-x64-msvc/-/yara-x-win32-x64-msvc-0.5.0.tgz", + "integrity": "sha512-7hpMrTGY6m/Fx+2DJjcBmwW8fzbtm77JUE5sC9AbGozd/rfnW43EF7QyJ0RVhSgHp1x8izJkTWje/y4wyGWSiQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -6883,12 +6996,6 @@ "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", "license": "MIT" }, - "node_modules/libyara-wasm": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/libyara-wasm/-/libyara-wasm-1.2.1.tgz", - "integrity": "sha512-PNqUNWnwjZLe55iA8Rv6vLQRjSdO2OnVg24aRE8v+ytR8CRB8agIG6pS9h2VQejuJP1A/uR4pwcBggUxoNC7DA==", - "license": "ISC" - }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", diff --git a/package.json b/package.json index 76792573..52c9a26e 100644 --- a/package.json +++ b/package.json @@ -71,13 +71,13 @@ "vitest": "^4.1.0" }, "dependencies": { + "@litko/yara-x": "^0.5.0", "@tanstack/react-table": "^8.21.3", "better-sqlite3": "^12.8.0", "clsx": "^2.1.1", "electron-updater": "^6.8.3", "framer-motion": "^12.38.0", "i18next": "^25.9.0", - "libyara-wasm": "^1.2.1", "lucide-react": "^0.577.0", "pusher-js": "^8.4.0", "react-i18next": "^16.5.8", diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index a7ce6f48..92e0b16f 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1293,22 +1293,6 @@ export async function scanMalware( let sigThreatsCount = 0 let heuristicThreatsCount = 0 - // Extensions worth YARA content scanning — executables, scripts, and binaries - // that could contain malicious payloads. Other file types (plists, configs, etc.) - // only get filename-based checks to avoid the ~400ms/file YARA compilation cost. - const YARA_SCAN_EXTS = new Set([ - // Windows executables + drivers - '.exe', '.dll', '.sys', '.drv', '.scr', '.cpl', '.ocx', '.msi', '.com', '.pif', - // Scripts - '.ps1', '.bat', '.cmd', '.vbs', '.vbe', '.wsh', '.wsf', '.js', '.jse', '.hta', - // macOS - '.app', '.dmg', '.pkg', '.dylib', '.so', '.bundle', '.kext', - // Linux - '.elf', '.bin', '.run', '.ko', '.deb', '.rpm', '.AppImage', - // Cross-platform - '.jar', '.py', '.rb', '.pl', '.sh', '.lnk', - ]) - async function scanFileSignatures(filePath: string): Promise { const fileName = basename(filePath) const ext = extname(fileName).toLowerCase() @@ -1317,14 +1301,6 @@ export async function scanMalware( if (!fileStat) return const fileSize = fileStat.size - // Only read file content for extensions worth YARA/heuristic scanning. - // Low-risk extensions skip the expensive content scan entirely. - const shouldReadContent = YARA_SCAN_EXTS.has(ext) || hasDoubleExtension(fileName) - let fileBuffer: Buffer | null = null - if (shouldReadContent && fileSize <= MAX_READ_SIZE) { - fileBuffer = await readFile(filePath).catch(() => null) - } - // ── Suspicious filename check (always runs, regardless of YARA) ── const dirLower = filePath.toLowerCase().replace(/\\/g, '/') const isInSystemDir = systemDirs.some(sd => dirLower.startsWith(sd.toLowerCase().replace(/\\/g, '/'))) @@ -1349,10 +1325,10 @@ export async function scanMalware( } } - // ── Signature matching: YARA or regex fallback ── - if (yaraEngine && fileBuffer) { + // ── Signature matching: YARA (scanFile reads from disk) or regex fallback ── + if (yaraEngine && fileSize <= MAX_READ_SIZE) { if (!threats.some(t => t.path === filePath)) { - const matches = yaraEngine.scanBuffer(fileBuffer) + const matches = yaraEngine.scanFile(filePath) for (const match of matches) { if (threats.some(t => t.path === filePath)) break if (match.metadata.filenameOnly === 'true') continue @@ -1394,9 +1370,10 @@ export async function scanMalware( } } - // Hash lookup (fallback only — YARA handles hashes via the hash module) - if (fileBuffer && !threats.some(t => t.path === filePath)) { - const hash = createHash('sha256').update(fileBuffer).digest('hex') + // Hash lookup (fallback only — YARA handles hashes natively) + if (fileSize <= MAX_READ_SIZE && !threats.some(t => t.path === filePath)) { + const buf = await readFile(filePath).catch(() => null) + const hash = buf ? createHash('sha256').update(buf).digest('hex') : null const knownMalware = KNOWN_MALWARE_HASHES[hash] if (knownMalware) { threats.push({ @@ -1431,7 +1408,14 @@ export async function scanMalware( heuristicThreatsCount++ } - // ── PE + LNK heuristic analysis (always runs) ── + // ── PE + LNK heuristic analysis ── + // Read file buffer only for PE/LNK analysis (Windows executables and shortcuts) + const needsBuffer = (shouldAnalyzePE && (ext === '.exe' || ext === '.dll' || ext === '.scr' || ext === '.sys' || ext === '.drv')) + || (process.platform === 'win32' && ext === '.lnk') + let fileBuffer: Buffer | null = null + if (needsBuffer && fileSize <= MAX_READ_SIZE && !threats.some(t => t.path === filePath)) { + fileBuffer = await readFile(filePath).catch(() => null) + } if (fileBuffer && !threats.some(t => t.path === filePath)) { // PE heuristic analysis const pathLower = filePath.toLowerCase() diff --git a/src/main/services/yara-engine.test.ts b/src/main/services/yara-engine.test.ts index 1c18a2f8..38b3de47 100644 --- a/src/main/services/yara-engine.test.ts +++ b/src/main/services/yara-engine.test.ts @@ -109,14 +109,13 @@ describe('yaraMatchToThreatFields', () => { }) }) -// ─── YARA rule parsing (integration-style, tests the WASM module) ── +// ─── @litko/yara-x integration tests ──────────────────────────── -describe('libyara-wasm integration', () => { - it('matches a simple string rule', async () => { - const initYara = require('libyara-wasm') - const yara = await initYara() - - const rules = ` +describe('@litko/yara-x integration', () => { + it('compiles rules and scans a matching buffer', () => { + const yarax = require('@litko/yara-x') + const scanner = yarax.create() + scanner.addRuleSource(` rule Test_Simple { meta: detectionName = "Test.Simple" @@ -126,63 +125,50 @@ rule Test_Simple { $a = "malware_test" nocase condition: $a -}` - const result = yara.run('this contains MALWARE_TEST data', rules) - expect(result.matchedRules.size()).toBe(1) - expect(result.matchedRules.get(0).ruleName).toBe('Test_Simple') - - const meta = result.matchedRules.get(0).metadata - const metaMap: Record = {} - for (let i = 0; i < meta.size(); i++) { - const m = meta.get(i) - metaMap[m.identifier] = m.data - } - expect(metaMap.detectionName).toBe('Test.Simple') - expect(metaMap.severity).toBe('medium') +}`) + const results = scanner.scan(Buffer.from('this contains MALWARE_TEST data')) + expect(results.length).toBe(1) + expect(results[0].ruleIdentifier).toBe('Test_Simple') + expect(results[0].meta.detectionName).toBe('Test.Simple') + expect(results[0].meta.severity).toBe('medium') }) - it('reports compile errors for invalid rules', async () => { - const initYara = require('libyara-wasm') - const yara = await initYara() - - const result = yara.run('data', 'rule bad { invalid syntax here }') - expect(result.compileErrors.size()).toBeGreaterThan(0) + it('throws on invalid rule syntax', () => { + const yarax = require('@litko/yara-x') + const scanner = yarax.create() + expect(() => scanner.addRuleSource('rule bad { invalid syntax }')).toThrow() }) - it('returns no matches for clean data', async () => { - const initYara = require('libyara-wasm') - const yara = await initYara() + it('returns empty array for clean data', () => { + const yarax = require('@litko/yara-x') + const scanner = yarax.create() + scanner.addRuleSource('rule NoMatch { strings: $a = "wontmatch" condition: $a }') + const results = scanner.scan(Buffer.from('clean file content')) + expect(results.length).toBe(0) + }) - const rules = ` -rule Test_NoMatch { - strings: - $a = "this_will_not_match_anything" - condition: - $a -}` - const result = yara.run('clean file content', rules) - expect(result.matchedRules.size()).toBe(0) + it('preserves binary bytes correctly', () => { + const yarax = require('@litko/yara-x') + const scanner = yarax.create() + scanner.addRuleSource('rule HexPattern { strings: $h = { 4D 5A 90 00 } condition: $h }') + const pe = Buffer.from([0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00]) + const results = scanner.scan(pe) + expect(results.length).toBe(1) }) - it('supports hash module for SHA-256 matching', async () => { - const initYara = require('libyara-wasm') - const crypto = require('crypto') - const yara = await initYara() + it('scans multiple times with compiled rules (no recompilation)', () => { + const yarax = require('@litko/yara-x') + const scanner = yarax.create() + scanner.addRuleSource('rule Multi { strings: $a = "target" condition: $a }') - const testData = 'EICAR-STANDARD-ANTIVIRUS-TEST-FILE' - const hash = crypto.createHash('sha256').update(testData).digest('hex') + const clean = scanner.scan(Buffer.from('nothing here')) + expect(clean.length).toBe(0) - const rules = ` -import "hash" -rule Hash_Test { - meta: - detectionName = "Test.Hash" - severity = "low" - condition: - hash.sha256(0, filesize) == "${hash}" -}` - const result = yara.run(testData, rules) - expect(result.matchedRules.size()).toBe(1) - expect(result.matchedRules.get(0).ruleName).toBe('Hash_Test') + const match = scanner.scan(Buffer.from('has target inside')) + expect(match.length).toBe(1) + + // Scan again — should still work (rules not recompiled) + const match2 = scanner.scan(Buffer.from('another target file')) + expect(match2.length).toBe(1) }) }) diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 356877ad..021b537c 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -14,114 +14,89 @@ export interface YaraMatch { matchedStrings: string[] } -/** Embind vector — iterable via .size() and .get(i) */ -interface EmbindVector { - size(): number - get(i: number): T +/** Result shape from @litko/yara-x scan() */ +interface YaraXMatch { + ruleIdentifier: string + namespace: string + meta: Record + tags: string[] + matches: { offset: number; length: number; data: string; identifier: string }[] } -interface LibYaraResult { - matchedRules: EmbindVector<{ - ruleName: string - metadata: EmbindVector<{ identifier: string; data: string }> - resolvedMatches: EmbindVector<{ location: number; matchLength: number; data: string }> - }> - compileErrors: EmbindVector<{ message: string; lineNumber: number; warning: boolean }> - consoleLogs: EmbindVector +/** @litko/yara-x scanner instance — rules compiled once, scan many times */ +interface YaraXScanner { + addRuleSource(source: string): void + addRuleFile(path: string): void + scan(data: Buffer): YaraXMatch[] + scanFile(path: string): YaraXMatch[] + scanAsync(data: Buffer): Promise + scanFileAsync(path: string): Promise + getWarnings(): string[] } -interface LibYaraModule { - run(data: string | Uint8Array, rules: string): LibYaraResult +interface YaraXModule { + create(): YaraXScanner } // ─── Engine ────────────────────────────────────────────────── export class YaraEngine { - private _module: LibYaraModule | null = null - private _compiledRules: string = '' + private _scanner: YaraXScanner | null = null private _ready = false private _rulesLoaded = 0 - /** Load the WASM module. Call once before scanning. */ + /** Create the scanner instance. Call once before loading rules. */ async initialize(): Promise { try { - // libyara-wasm exports a factory that returns a ready promise - // eslint-disable-next-line @typescript-eslint/no-var-requires - const initYara = require('libyara-wasm') - this._module = await initYara() + const yarax: YaraXModule = require('@litko/yara-x') + this._scanner = yarax.create() this._ready = true } catch (err) { - console.warn('[yara] WASM initialization failed:', err) + console.warn('[yara] @litko/yara-x initialization failed:', err) this._ready = false throw err } } isReady(): boolean { - return this._ready && this._module !== null + return this._ready && this._scanner !== null } /** - * Load YARA rules from file paths and/or raw source strings. - * Returns the number of rules that compiled successfully and any errors. + * Compile YARA rules from file paths and/or raw source strings. + * Rules are compiled once — subsequent scan() calls are fast. + * Returns the number of rules loaded and any errors. */ loadRules(ruleFilePaths: string[], extraSources: string[] = []): { loaded: number; errors: string[] } { - if (!this._module) { + if (!this._scanner) { return { loaded: 0, errors: ['YARA engine not initialized'] } } const errors: string[] = [] - const ruleSources: string[] = [] + let loaded = 0 + // Load from file paths for (const filePath of ruleFilePaths) { try { - const source = readFileSync(filePath, 'utf-8') - ruleSources.push(`// File: ${basename(filePath)}\n${source}`) + this._scanner.addRuleFile(filePath) + loaded++ } catch (err) { - errors.push(`Failed to read ${basename(filePath)}: ${err instanceof Error ? err.message : String(err)}`) + errors.push(`${basename(filePath)}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) } } - // Append cloud / in-memory rule sources + // Load from source strings for (const source of extraSources) { - ruleSources.push(source) - } - - if (ruleSources.length === 0) { - this._compiledRules = '' - this._rulesLoaded = 0 - return { loaded: 0, errors } - } - - // Concatenate all rules and test-compile them to check for errors - const combined = ruleSources.join('\n\n') - const testResult = this._module.run('', combined) - const compileErrors = testResult.compileErrors - - let nonWarningErrors = 0 - for (let i = 0; i < compileErrors.size(); i++) { - const e = compileErrors.get(i) - if (!e.warning) { - errors.push(`Compile error (line ${e.lineNumber}): ${e.message}`) - nonWarningErrors++ + try { + this._scanner.addRuleSource(source) + loaded++ + } catch (err) { + errors.push(`source: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) } } - // Count rule declarations in source text - const ruleCount = (combined.match(/^\s*rule\s+\w+/gm) || []).length - - // If every rule had a compile error, nothing actually compiled — report 0 - if (nonWarningErrors > 0 && nonWarningErrors >= ruleCount) { - this._compiledRules = '' - this._rulesLoaded = 0 - return { loaded: 0, errors } - } - - // Even with partial errors, YARA uses what it can. - this._compiledRules = combined - this._rulesLoaded = ruleCount - - return { loaded: ruleCount, errors } + this._rulesLoaded = loaded + return { loaded, errors } } get rulesLoaded(): number { @@ -129,60 +104,52 @@ export class YaraEngine { } /** - * Scan a single buffer against loaded YARA rules. + * Scan a buffer against all compiled rules. + * Fast — rules are already compiled, only pattern matching runs. */ scanBuffer(buffer: Buffer): YaraMatch[] { - if (!this._module || !this._compiledRules) return [] + if (!this._scanner) return [] try { - const result = this._module.run(new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength), this._compiledRules) - return this._parseMatches(result) + const results = this._scanner.scan(buffer) + return results.map(r => this._convertMatch(r)) } catch (err) { console.warn('[yara] Scan error:', err) return [] } } - private _parseMatches(result: LibYaraResult): YaraMatch[] { - const matches: YaraMatch[] = [] - const matchedRules = result.matchedRules - - for (let i = 0; i < matchedRules.size(); i++) { - const rule = matchedRules.get(i) - - // Parse metadata into a typed map - const metadata: YaraMatch['metadata'] = {} - const meta = rule.metadata - for (let j = 0; j < meta.size(); j++) { - const m = meta.get(j) - switch (m.identifier) { - case 'detectionName': metadata.detectionName = m.data; break - case 'severity': metadata.severity = m.data as YaraMatch['metadata']['severity']; break - case 'details': metadata.details = m.data; break - case 'filenameOnly': metadata.filenameOnly = m.data; break - } - } - - // Collect matched strings - const matchedStrings: string[] = [] - const resolved = rule.resolvedMatches - for (let j = 0; j < resolved.size(); j++) { - matchedStrings.push(resolved.get(j).data) - } + /** + * Scan a file directly from disk (avoids reading into JS memory). + */ + scanFile(filePath: string): YaraMatch[] { + if (!this._scanner) return [] - matches.push({ - ruleName: rule.ruleName, - metadata, - matchedStrings, - }) + try { + const results = this._scanner.scanFile(filePath) + return results.map(r => this._convertMatch(r)) + } catch (err) { + console.warn('[yara] File scan error:', err) + return [] } + } - return matches + private _convertMatch(r: YaraXMatch): YaraMatch { + const metadata: YaraMatch['metadata'] = {} + if (r.meta.detectionName) metadata.detectionName = r.meta.detectionName + if (r.meta.severity) metadata.severity = r.meta.severity as YaraMatch['metadata']['severity'] + if (r.meta.details) metadata.details = r.meta.details + if (r.meta.filenameOnly) metadata.filenameOnly = r.meta.filenameOnly + + return { + ruleName: r.ruleIdentifier, + metadata, + matchedStrings: r.matches.map(m => m.data), + } } dispose(): void { - this._module = null - this._compiledRules = '' + this._scanner = null this._ready = false this._rulesLoaded = 0 } From cfea8bbe20f6e394873b2bc019e9e38bc7b506d9 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 05:51:01 +0200 Subject: [PATCH 22/35] =?UTF-8?q?fix(malware):=20self-review=20=E2=80=94?= =?UTF-8?q?=20severity=20validation,=20atomic=20staging,=20consistent=20UR?= =?UTF-8?q?Ls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Validate and lowercase severity in _convertMatch() before casting, so YARA rules with uppercase severity like "CRITICAL" are handled correctly instead of passing through as invalid values - Use unique staging directory names (timestamp + random suffix) to prevent races between concurrent fetchAndCacheRules calls. Rename old dir before swapping new one in, and clean up on failure. - Extract CLOUD_SERVER_URL constant — pre-scan update, manual update, and periodic checks all use the same URL instead of duplicated ternaries Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 10 +++---- src/main/services/yara-engine.ts | 11 ++++--- src/main/services/yara-rules-store.ts | 43 ++++++++++++++++----------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 92e0b16f..60660e3f 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -23,6 +23,7 @@ import { createYaraEngine, yaraMatchToThreatFields, type YaraEngine } from '../s import { getAllRulePaths, getBundledRulePaths, getCachedRulePaths, getRulesMetadata, startPeriodicRuleChecks, fetchAndCacheRules, RULES_ENDPOINT } from '../services/yara-rules-store' const execFileAsync = promisify(execFile) +const CLOUD_SERVER_URL = 'https://cloud.usekudu.com' function psArgs(script: string): string[] { return ['-NoProfile', '-NonInteractive', '-Command', psUtf8(script)] @@ -1245,7 +1246,7 @@ export async function scanMalware( if (Date.now() - lastUpdate > RULE_STALE_MS) { sendProgress({ step: 'init', stepLabel: 'Checking for signature updates...', engine: 'YARA Rules Engine' }) try { - const updateUrl = (app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com') + RULES_ENDPOINT + const updateUrl = CLOUD_SERVER_URL + RULES_ENDPOINT const updateResult = await fetchAndCacheRules(updateUrl) if (updateResult.success && updateResult.stats) { console.log(`[yara] Pre-scan update: v${updateResult.stats.version} (${updateResult.stats.rulesCount} rules)`) @@ -1976,11 +1977,8 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { ipcMain.handle(IPC.MALWARE_YARA_INFO, () => getYaraRulesInfo()) - // Cloud URL for rule fetching — used by both manual update and periodic checks - const serverUrl = app.isPackaged ? 'https://cloud.usekudu.com' : 'https://cloud.usekudu.com' - ipcMain.handle(IPC.MALWARE_YARA_UPDATE, async () => { - const result = await fetchAndCacheRules(`${serverUrl}${RULES_ENDPOINT}`) + const result = await fetchAndCacheRules(`${CLOUD_SERVER_URL}${RULES_ENDPOINT}`) if (result.success && result.stats) { resetYaraEngine() } @@ -1988,5 +1986,5 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { }) // Start periodic YARA rule updates — runs for all users, not just cloud-linked - startPeriodicRuleChecks(serverUrl, () => resetYaraEngine()) + startPeriodicRuleChecks(CLOUD_SERVER_URL, () => resetYaraEngine()) } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 021b537c..017d40e2 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -136,10 +136,13 @@ export class YaraEngine { private _convertMatch(r: YaraXMatch): YaraMatch { const metadata: YaraMatch['metadata'] = {} - if (r.meta.detectionName) metadata.detectionName = r.meta.detectionName - if (r.meta.severity) metadata.severity = r.meta.severity as YaraMatch['metadata']['severity'] - if (r.meta.details) metadata.details = r.meta.details - if (r.meta.filenameOnly) metadata.filenameOnly = r.meta.filenameOnly + if (r.meta.detectionName) metadata.detectionName = String(r.meta.detectionName) + if (r.meta.severity) { + const sev = String(r.meta.severity).toLowerCase() + if (VALID_SEVERITIES.has(sev as any)) metadata.severity = sev as YaraMatch['metadata']['severity'] + } + if (r.meta.details) metadata.details = String(r.meta.details) + if (r.meta.filenameOnly) metadata.filenameOnly = String(r.meta.filenameOnly) return { ruleName: r.ruleIdentifier, diff --git a/src/main/services/yara-rules-store.ts b/src/main/services/yara-rules-store.ts index 23211333..56521a45 100644 --- a/src/main/services/yara-rules-store.ts +++ b/src/main/services/yara-rules-store.ts @@ -237,29 +237,36 @@ export async function fetchAndCacheRules(url: string): Promise<{ return { success: false, error: 'Integrity check failed: SHA-256 mismatch' } } - // Write rules atomically: stage in a temp directory, then swap into place. - // This avoids leaving a partial ruleset if a write fails mid-way. + // Write rules atomically: stage in a uniquely-named temp directory, + // then swap into place. Unique name prevents races between concurrent updates. const dir = getCachedRulesDir() - const stageDir = dir + '.staging' + const stageDir = `${dir}.staging-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` - // Clean up any leftover staging dir from a previous failed attempt - if (existsSync(stageDir)) rmSync(stageDir, { recursive: true, force: true }) mkdirSync(stageDir, { recursive: true }) - // Write all rule files + metadata into the staging directory - for (const rule of bundle.rules) { - writeFileSync(join(stageDir, rule.filename), rule.content, 'utf-8') + try { + // Write all rule files + metadata into the staging directory + for (const rule of bundle.rules) { + writeFileSync(join(stageDir, rule.filename), rule.content, 'utf-8') + } + writeFileSync(join(stageDir, 'metadata.json'), JSON.stringify({ + version: bundle.version, + updatedAt: bundle.updatedAt, + rulesCount: bundle.rules.length, + sha256: bundle.sha256, + }, null, 2), 'utf-8') + + // Swap: remove old cache dir, rename staging into place + const oldDir = `${dir}.old-${Date.now()}` + if (existsSync(dir)) renameSync(dir, oldDir) + renameSync(stageDir, dir) + // Clean up old dir in the background + if (existsSync(oldDir)) rmSync(oldDir, { recursive: true, force: true }) + } catch (err) { + // Clean up staging dir on failure + try { rmSync(stageDir, { recursive: true, force: true }) } catch { /* best effort */ } + throw err } - writeFileSync(join(stageDir, 'metadata.json'), JSON.stringify({ - version: bundle.version, - updatedAt: bundle.updatedAt, - rulesCount: bundle.rules.length, - sha256: bundle.sha256, - }, null, 2), 'utf-8') - - // Swap: remove old cache dir, rename staging into place - if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }) - renameSync(stageDir, dir) return { success: true, From eedfd9e07cfb399b3d5876cea5f28cbe7abd8237 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 05:57:15 +0200 Subject: [PATCH 23/35] fix(ui): non-blocking YARA compilation with live progress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The YARA engine compiles 1400+ rule files at startup (~5-8ms each), which blocked the main Electron process for 10+ seconds, freezing the UI completely. Fix: loadRules() now yields to the event loop every 20 files via setImmediate, keeping the UI responsive during compilation. Progress is pushed to the renderer via IPC. - loadRules() is now async with chunked compilation + onProgress callback - getYaraRulesInfo() is non-blocking — reports current state without triggering compilation - ensureYaraEngineStarted() kicks off background compilation at app startup (in registerMalwareScannerIpc) - New IPC channel MALWARE_YARA_COMPILE_PROGRESS pushes progress to UI - Database tab shows a progress bar during compilation: "Compiling signature rules (345/1400)..." - Engine status shows "Compiling..." (blue dot) instead of freezing - Auto-refreshes info when compilation completes Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 48 +++++++++++++++---- src/main/services/yara-engine.ts | 29 ++++++++--- src/preload/index.ts | 5 ++ src/renderer/src/locales/en/malware.json | 4 +- src/renderer/src/pages/MalwareScannerPage.tsx | 36 +++++++++++++- src/shared/channels.ts | 1 + src/shared/types.ts | 3 +- 7 files changed, 108 insertions(+), 18 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 60660e3f..dc884dad 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1,4 +1,4 @@ -import { ipcMain, app } from 'electron' +import { ipcMain, app, BrowserWindow } from 'electron' import { execFile } from 'child_process' import { promisify } from 'util' import { readFile, readdir, writeFile, stat, mkdir, rename, rm } from 'fs/promises' @@ -233,6 +233,7 @@ const TRUSTED_PATHS = [ let _yaraEngine: YaraEngine | null = null let _yaraInitPromise: Promise | null = null +let _yaraCompileProgress: { loaded: number; total: number } | null = null async function getYaraEngine(): Promise { if (_yaraInitPromise) return _yaraInitPromise @@ -240,15 +241,36 @@ async function getYaraEngine(): Promise { return _yaraInitPromise } +/** Start engine compilation in the background (non-blocking). */ +function ensureYaraEngineStarted(): void { + if (!_yaraInitPromise) { + _yaraInitPromise = _initYaraEngine() + } +} + async function _initYaraEngine(): Promise { try { const engine = createYaraEngine() await engine.initialize() const ruleFiles = getAllRulePaths() - const result = engine.loadRules(ruleFiles) - console.log(`[yara] Loaded ${result.loaded} rules from ${ruleFiles.length} files`) + _yaraCompileProgress = { loaded: 0, total: ruleFiles.length } + + // loadRules is async and yields to the event loop between chunks, + // so the UI stays responsive during compilation. + const result = await engine.loadRules(ruleFiles, [], (loaded, total) => { + _yaraCompileProgress = { loaded, total } + // Push progress to all renderer windows + for (const win of BrowserWindow.getAllWindows()) { + if (!win.isDestroyed()) { + win.webContents.send(IPC.MALWARE_YARA_COMPILE_PROGRESS, { loaded, total }) + } + } + }) + + _yaraCompileProgress = null + console.log(`[yara] Loaded ${result.loaded} rule files (${result.errors.length} errors)`) if (result.errors.length > 0) { - console.warn('[yara] Rule load warnings:', result.errors) + console.warn('[yara] Rule load warnings:', result.errors.slice(0, 20)) } if (result.loaded === 0) { console.warn('[yara] No rules loaded — falling back to regex patterns') @@ -258,6 +280,7 @@ async function _initYaraEngine(): Promise { _yaraEngine = engine return engine } catch (err) { + _yaraCompileProgress = null console.warn('[yara] Init failed, falling back to regex patterns:', err) return null } @@ -271,15 +294,18 @@ async function _initYaraEngine(): Promise { export function resetYaraEngine(): void { _yaraEngine = null _yaraInitPromise = null + _yaraCompileProgress = null } -async function getYaraRulesInfo(): Promise { - // Ensure engine is initialized before reporting status - const engine = await getYaraEngine() +function getYaraRulesInfo(): YaraRulesInfo { + // Report current state WITHOUT triggering compilation. + // Compilation is started in the background by ensureYaraEngineStarted(). + const engine = _yaraEngine const meta = getRulesMetadata() const bundled = getBundledRulePaths() const cached = getCachedRulePaths() const available = engine !== null && engine.isReady() + const compiling = _yaraCompileProgress !== null let source: YaraRulesInfo['source'] = 'none' if (cached.length > 0) source = 'cloud' @@ -287,13 +313,14 @@ async function getYaraRulesInfo(): Promise { return { available, - engine: available ? 'yara' : 'regex-fallback', + engine: compiling ? 'compiling' : available ? 'yara' : 'regex-fallback', rulesLoaded: engine?.rulesLoaded ?? 0, version: meta?.version ?? null, updatedAt: meta?.updatedAt ?? null, source, bundledRules: bundled.length, cachedRules: cached.length, + compileProgress: _yaraCompileProgress, } } @@ -1987,4 +2014,9 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { // Start periodic YARA rule updates — runs for all users, not just cloud-linked startPeriodicRuleChecks(CLOUD_SERVER_URL, () => resetYaraEngine()) + + // Start YARA engine compilation in the background so it's ready by the + // time the user runs a scan. This is non-blocking — yields to the event + // loop between rule file chunks so the UI stays responsive. + ensureYaraEngineStarted() } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 017d40e2..65910b27 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -65,23 +65,39 @@ export class YaraEngine { /** * Compile YARA rules from file paths and/or raw source strings. * Rules are compiled once — subsequent scan() calls are fast. - * Returns the number of rules loaded and any errors. + * + * Compilation is chunked with event-loop yields so the main process + * stays responsive (each addRuleFile takes ~5-8ms, and with 1400+ + * files this would otherwise block the UI for 10+ seconds). + * + * @param onProgress Optional callback fired with (loaded, total) counts */ - loadRules(ruleFilePaths: string[], extraSources: string[] = []): { loaded: number; errors: string[] } { + async loadRules( + ruleFilePaths: string[], + extraSources: string[] = [], + onProgress?: (loaded: number, total: number) => void, + ): Promise<{ loaded: number; errors: string[] }> { if (!this._scanner) { return { loaded: 0, errors: ['YARA engine not initialized'] } } const errors: string[] = [] let loaded = 0 + const total = ruleFilePaths.length + extraSources.length + const CHUNK_SIZE = 20 // yield to event loop every N files - // Load from file paths - for (const filePath of ruleFilePaths) { + // Load from file paths in chunks + for (let i = 0; i < ruleFilePaths.length; i++) { try { - this._scanner.addRuleFile(filePath) + this._scanner.addRuleFile(ruleFilePaths[i]) loaded++ } catch (err) { - errors.push(`${basename(filePath)}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) + errors.push(`${basename(ruleFilePaths[i])}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) + } + // Yield to the event loop periodically so the UI stays responsive + if ((i + 1) % CHUNK_SIZE === 0) { + onProgress?.(loaded, total) + await new Promise(resolve => setImmediate(resolve)) } } @@ -95,6 +111,7 @@ export class YaraEngine { } } + onProgress?.(loaded, total) this._rulesLoaded = loaded return { loaded, errors } } diff --git a/src/preload/index.ts b/src/preload/index.ts index b743836c..3d79a218 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -277,6 +277,11 @@ const api = { ipcRenderer.invoke(IPC.MALWARE_YARA_INFO), malwareYaraUpdate: (): Promise<{ success: boolean; error?: string; stats?: { rulesCount: number; version: string } }> => ipcRenderer.invoke(IPC.MALWARE_YARA_UPDATE), + onYaraCompileProgress: (callback: (data: { loaded: number; total: number }) => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: { loaded: number; total: number }) => callback(data) + ipcRenderer.on(IPC.MALWARE_YARA_COMPILE_PROGRESS, handler) + return () => { ipcRenderer.removeListener(IPC.MALWARE_YARA_COMPILE_PROGRESS, handler) } + }, // Driver Manager driverScan: (): Promise => ipcRenderer.invoke(IPC.DRIVER_SCAN), diff --git a/src/renderer/src/locales/en/malware.json b/src/renderer/src/locales/en/malware.json index 43e14d2f..37d2aee0 100644 --- a/src/renderer/src/locales/en/malware.json +++ b/src/renderer/src/locales/en/malware.json @@ -69,8 +69,10 @@ "dbFetchLatest": "Check for Updates", "dbUpdating": "Updating...", "dbEngine": "Engine", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Native)", "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "Compiling...", + "dbCompiling": "Compiling signature rules ({{loaded}}/{{total}})...", "dbRulesLoaded": "Rules Loaded", "dbVersion": "Signature Version", "dbLastUpdated": "Last Updated", diff --git a/src/renderer/src/pages/MalwareScannerPage.tsx b/src/renderer/src/pages/MalwareScannerPage.tsx index 37b4986d..ec3ccc41 100644 --- a/src/renderer/src/pages/MalwareScannerPage.tsx +++ b/src/renderer/src/pages/MalwareScannerPage.tsx @@ -276,6 +276,18 @@ export function MalwareScannerPage() { if (viewMode === 'database') loadYaraInfo() }, [viewMode, loadQuarantineItems, loadYaraInfo]) + // Listen for YARA compile progress and auto-refresh info when done + useEffect(() => { + const cleanup = window.kudu.onYaraCompileProgress((data) => { + setYaraInfo(prev => prev ? { ...prev, engine: 'compiling', compileProgress: data } : prev) + if (data.loaded >= data.total) { + // Compilation finished — refresh full info after a short delay + setTimeout(loadYaraInfo, 500) + } + }) + return cleanup + }, [loadYaraInfo]) + const isScanning = status === 'scanning' const isActing = status === 'acting' const hasThreats = threats.length > 0 @@ -1137,6 +1149,24 @@ export function MalwareScannerPage() {
+ {/* Compile progress banner */} + {yaraInfo?.engine === 'compiling' && yaraInfo.compileProgress && ( +
+
+ + + {t('dbCompiling', { loaded: yaraInfo.compileProgress.loaded, total: yaraInfo.compileProgress.total })} + +
+
+
+
+
+ )} + {/* Info grid */} {yaraInfo ? (
@@ -1144,9 +1174,11 @@ export function MalwareScannerPage() {

{t('dbEngine')}

-
+
- {yaraInfo.available ? t('dbEngineYara') : t('dbEngineRegex')} + {yaraInfo.engine === 'compiling' ? t('dbEngineCompiling') : yaraInfo.available ? t('dbEngineYara') : t('dbEngineRegex')}
diff --git a/src/shared/channels.ts b/src/shared/channels.ts index 7c2bdd44..3bc5a817 100644 --- a/src/shared/channels.ts +++ b/src/shared/channels.ts @@ -118,6 +118,7 @@ export const IPC = { MALWARE_QUARANTINE_LIST: 'malware:quarantine:list', MALWARE_YARA_INFO: 'malware:yara:info', MALWARE_YARA_UPDATE: 'malware:yara:update', + MALWARE_YARA_COMPILE_PROGRESS: 'malware:yara:compile-progress', // Privacy Shield PRIVACY_SCAN: 'privacy:scan', diff --git a/src/shared/types.ts b/src/shared/types.ts index 64fb0c21..4ed18c4a 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -299,13 +299,14 @@ export interface QuarantinedItem { export interface YaraRulesInfo { available: boolean - engine: 'yara' | 'regex-fallback' + engine: 'yara' | 'regex-fallback' | 'compiling' rulesLoaded: number version: string | null updatedAt: string | null source: 'cloud' | 'bundled' | 'none' bundledRules: number cachedRules: number + compileProgress: { loaded: number; total: number } | null } // ─── Privacy Shield ────────────────────────────────────────── From f2b43c0bad2793bc8d5ad39e735efc69458fecdb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 29 Mar 2026 03:58:45 +0000 Subject: [PATCH 24/35] chore: auto-update translations --- src/renderer/src/locales/.checksums.json | 58 ++++++++++----------- src/renderer/src/locales/ar/malware.json | 26 ++++----- src/renderer/src/locales/cs/malware.json | 16 +++--- src/renderer/src/locales/da/malware.json | 14 ++--- src/renderer/src/locales/de/malware.json | 26 ++++----- src/renderer/src/locales/el/malware.json | 20 +++---- src/renderer/src/locales/es/malware.json | 14 ++--- src/renderer/src/locales/fi/malware.json | 26 ++++----- src/renderer/src/locales/fr/malware.json | 14 ++--- src/renderer/src/locales/he/malware.json | 24 +++++---- src/renderer/src/locales/hi/malware.json | 48 +++++++++-------- src/renderer/src/locales/hu/malware.json | 26 ++++----- src/renderer/src/locales/id/malware.json | 28 +++++----- src/renderer/src/locales/it/malware.json | 14 ++--- src/renderer/src/locales/ja/malware.json | 24 +++++---- src/renderer/src/locales/ko/malware.json | 14 ++--- src/renderer/src/locales/ms/malware.json | 16 +++--- src/renderer/src/locales/nl/malware.json | 22 ++++---- src/renderer/src/locales/no/malware.json | 16 +++--- src/renderer/src/locales/pl/malware.json | 18 ++++--- src/renderer/src/locales/pt/malware.json | 28 +++++----- src/renderer/src/locales/ro/malware.json | 22 ++++---- src/renderer/src/locales/ru/malware.json | 20 +++---- src/renderer/src/locales/sv/malware.json | 14 ++--- src/renderer/src/locales/th/malware.json | 34 ++++++------ src/renderer/src/locales/tr/malware.json | 16 +++--- src/renderer/src/locales/uk/malware.json | 44 ++++++++-------- src/renderer/src/locales/vi/malware.json | 12 +++-- src/renderer/src/locales/zh-CN/malware.json | 18 ++++--- src/renderer/src/locales/zh-TW/malware.json | 34 ++++++------ 30 files changed, 382 insertions(+), 324 deletions(-) diff --git a/src/renderer/src/locales/.checksums.json b/src/renderer/src/locales/.checksums.json index ba52b694..9a2218b7 100644 --- a/src/renderer/src/locales/.checksums.json +++ b/src/renderer/src/locales/.checksums.json @@ -3,7 +3,7 @@ "es/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "es/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "es/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "es/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "es/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "es/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "es/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "es/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -22,7 +22,7 @@ "fr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fr/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "fr/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "fr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -41,7 +41,7 @@ "de/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "de/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "de/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "de/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "de/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "de/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "de/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "de/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -60,7 +60,7 @@ "pt/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pt/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pt/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pt/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "pt/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "pt/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pt/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pt/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -79,7 +79,7 @@ "it/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "it/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "it/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "it/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "it/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "it/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "it/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "it/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -98,7 +98,7 @@ "ja/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ja/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ja/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ja/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ja/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ja/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ja/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ja/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -117,7 +117,7 @@ "ko/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ko/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "ko/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "ko/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ko/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ko/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ko/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ko/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -136,7 +136,7 @@ "zh-CN/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-CN/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-CN/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-CN/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "zh-CN/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "zh-CN/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-CN/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "zh-CN/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -155,7 +155,7 @@ "zh-TW/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-TW/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-TW/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-TW/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "zh-TW/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "zh-TW/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-TW/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "zh-TW/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -174,7 +174,7 @@ "ru/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ru/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ru/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ru/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ru/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ru/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ru/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ru/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -193,7 +193,7 @@ "ar/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ar/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ar/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ar/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ar/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ar/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ar/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ar/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -212,7 +212,7 @@ "hi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "hi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "hi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -231,7 +231,7 @@ "tr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "tr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "tr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "tr/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "tr/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "tr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "tr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "tr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -250,7 +250,7 @@ "nl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "nl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "nl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "nl/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "nl/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "nl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "nl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "nl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -269,7 +269,7 @@ "pl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pl/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "pl/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "pl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -288,7 +288,7 @@ "sv/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "sv/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "sv/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "sv/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "sv/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "sv/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "sv/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "sv/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -307,7 +307,7 @@ "no/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "no/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "no/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "no/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "no/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "no/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "no/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "no/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -326,7 +326,7 @@ "da/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "da/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "da/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "da/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "da/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "da/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "da/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "da/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -345,7 +345,7 @@ "fi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "fi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "fi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -364,7 +364,7 @@ "cs/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "cs/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "cs/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "cs/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "cs/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "cs/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "cs/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "cs/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -383,7 +383,7 @@ "th/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "th/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "th/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "th/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "th/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "th/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "th/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "th/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -402,7 +402,7 @@ "vi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "vi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "vi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "vi/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "vi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "vi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "vi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "vi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -421,7 +421,7 @@ "id/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "id/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "id/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "id/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "id/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "id/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "id/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "id/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -440,7 +440,7 @@ "ms/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ms/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ms/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ms/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ms/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ms/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ms/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ms/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -459,7 +459,7 @@ "uk/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "uk/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "uk/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "uk/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "uk/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "uk/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "uk/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "uk/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -478,7 +478,7 @@ "ro/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ro/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ro/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ro/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "ro/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "ro/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ro/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "ro/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -497,7 +497,7 @@ "el/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "el/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "el/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "el/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "el/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "el/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "el/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "el/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -516,7 +516,7 @@ "he/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "he/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "he/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "he/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "he/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "he/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "he/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "he/hardening": "262c9c4b827b486f7b9f5bfdeb27eca2d63c3c0e439ae12de4f9ea9fb0319bec", @@ -535,7 +535,7 @@ "hu/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hu/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hu/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hu/malware": "ce85151f4cfff9e5621e2a03d3d01b85d200fc49c4d84577c4c1b15ce3b83bcd", + "hu/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", "hu/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hu/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hu/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", diff --git a/src/renderer/src/locales/ar/malware.json b/src/renderer/src/locales/ar/malware.json index 52464801..2b240140 100644 --- a/src/renderer/src/locales/ar/malware.json +++ b/src/renderer/src/locales/ar/malware.json @@ -1,6 +1,6 @@ { - "pageTitle": "فاحص البرامج الضارة", - "pageDescription": "اكتشاف التهديدات عبر عدة محركات — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", + "pageTitle": "فاحص البرمجيات الخبيثة", + "pageDescription": "اكتشاف التهديدات متعدد المحركات — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", "scanButtonScanning": "جارٍ الفحص...", "scanButton": "فحص", "quarantineButton": "الحجر", @@ -39,9 +39,9 @@ "actionResultDeleted": "تم حذف {{count}}", "actionResultFailed": "فشل {{count}}", "noThreatsDetectedTitle": "لم يتم اكتشاف أي تهديدات", - "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملف عبر {{engineCount}} محرك خلال {{duration}} ثانية — نظامك نظيف.", - "emptyStateTitle": "فاحص البرامج الضارة", - "emptyStateDescription": "انقر فوق \"فحص\" للتحقق من نظامك بحثًا عن البرامج الضارة والبرامج الإعلانية وعمال تعدين العملات المشفرة والملفات المشبوهة.", + "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملفًا عبر {{engineCount}} محركات خلال {{duration}} ثانية — نظامك نظيف.", + "emptyStateTitle": "فاحص البرمجيات الخبيثة", + "emptyStateDescription": "انقر على \"فحص\" للتحقق من نظامك بحثًا عن البرمجيات الخبيثة والبرامج الإعلانية وعمال تعدين العملات المشفرة والملفات المشبوهة.", "detectedThreatsHeading": "التهديدات المكتشفة", "detectedThreatsCount": "{{count}} تهديد", "detectedThreatsCountPlural": "{{count}} تهديدات", @@ -51,11 +51,11 @@ "threatDetailPath": "المسار", "confirmQuarantineTitle": "نقل التهديدات إلى الحجر", "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا إذا لزم الأمر.", - "confirmQuarantineLabel": "نقل إلى الحجر الآن", + "confirmQuarantineLabel": "النقل إلى الحجر الآن", "confirmDeleteTitle": "حذف التهديدات", "confirmDeleteDescription": "سيؤدي هذا إلى حذف {{count}} من التهديدات المكتشفة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", "confirmDeleteLabel": "حذف نهائي", - "toastScanFailed": "فشل فحص البرامج الضارة", + "toastScanFailed": "فشل فحص البرمجيات الخبيثة", "toastActionFailed": "فشل {{action}} التهديدات", "toastActionFailedDescription": "حاول التشغيل كمسؤول", "errorOperationFailed": "فشلت العملية — حاول التشغيل كمسؤول", @@ -63,12 +63,14 @@ "tabQuarantine": "الحجر", "tabDatabase": "قاعدة البيانات", "dbTitle": "قاعدة بيانات التواقيع", - "dbDescription": "قواعد YARA لاكتشاف البرامج الضارة المستخدمة بواسطة الفاحص", + "dbDescription": "قواعد YARA لاكتشاف البرمجيات الخبيثة المستخدمة بواسطة الفاحص", "dbFetchLatest": "التحقق من وجود تحديثات", "dbUpdating": "جارٍ التحديث...", "dbEngine": "المحرك", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (أصلي)", "dbEngineRegex": "Regex احتياطي", + "dbEngineCompiling": "جارٍ التجميع...", + "dbCompiling": "جارٍ تجميع قواعد التواقيع ({{loaded}}/{{total}})...", "dbRulesLoaded": "القواعد المحمّلة", "dbVersion": "إصدار التواقيع", "dbLastUpdated": "آخر تحديث", @@ -83,8 +85,8 @@ "dbAlreadyCurrent": "التواقيع محدّثة بالفعل", "dbUpdateFailed": "فشل تحديث التواقيع", "quarantineEmptyTitle": "لا توجد عناصر في الحجر", - "quarantineEmptyDescription": "ستظهر هنا الملفات التي تم نقلها إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", - "quarantineHeading": "الملفات المعزولة", + "quarantineEmptyDescription": "ستظهر هنا الملفات المنقولة إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", + "quarantineHeading": "الملفات الموجودة في الحجر", "quarantineCount": "{{count}} ملف", "quarantineCountPlural": "{{count}} ملفات", "quarantineColumnFile": "الملف", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "سيؤدي هذا إلى استعادة {{count}} من الملفات إلى موقعها الأصلي. استعد فقط الملفات التي تثق بها.", "confirmRestoreLabel": "استعادة الآن", "confirmDeleteQuarantineTitle": "حذف نهائي", - "confirmDeleteQuarantineDescription": "سيؤدي هذا إلى حذف {{count}} من الملفات المعزولة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", + "confirmDeleteQuarantineDescription": "سيؤدي هذا إلى حذف {{count}} من الملفات الموجودة في الحجر نهائيًا. لا يمكن التراجع عن هذا الإجراء.", "confirmDeleteQuarantineLabel": "حذف نهائي", "quarantineLoading": "جارٍ تحميل الحجر...", "quarantineRestoring": "جارٍ استعادة الملفات...", diff --git a/src/renderer/src/locales/cs/malware.json b/src/renderer/src/locales/cs/malware.json index 1c245072..dd9ed52b 100644 --- a/src/renderer/src/locales/cs/malware.json +++ b/src/renderer/src/locales/cs/malware.json @@ -35,13 +35,13 @@ "actingDeleting": "Odstraňování vybraných hrozeb...", "actionCompleteQuarantine": "Přesun do karantény dokončen", "actionCompleteDeletion": "Odstranění dokončeno", - "actionResultQuarantined": "Přesunuto do karantény: {{count}}", + "actionResultQuarantined": "Do karantény přesunuto: {{count}}", "actionResultDeleted": "Odstraněno: {{count}}", "actionResultFailed": "Nezdařilo se: {{count}}", "noThreatsDetectedTitle": "Nebyly zjištěny žádné hrozby", "noThreatsDetectedDescription": "Bylo zkontrolováno {{filesScanned}} souborů pomocí {{engineCount}} enginů za {{duration}} s — váš systém je čistý.", "emptyStateTitle": "Skener malwaru", - "emptyStateDescription": "Kliknutím na „Kontrolovat“ zkontrolujete systém na malware, adware, kryptotěžaře a podezřelé soubory.", + "emptyStateDescription": "Kliknutím na „Kontrolovat“ zkontrolujete systém na malware, adware, kryptoměnové těžaře a podezřelé soubory.", "detectedThreatsHeading": "Zjištěné hrozby", "detectedThreatsCount": "{{count}} hrozba", "detectedThreatsCountPlural": "{{count}} hrozeb", @@ -56,7 +56,7 @@ "confirmDeleteDescription": "Tímto trvale odstraníte {{count}} zjištěných hrozeb. Tuto akci nelze vrátit zpět.", "confirmDeleteLabel": "Trvale odstranit", "toastScanFailed": "Kontrola malwaru se nezdařila", - "toastActionFailed": "Akci {{action}} u hrozeb se nepodařilo provést", + "toastActionFailed": "Akci {{action}} u hrozeb se nepodařilo dokončit", "toastActionFailedDescription": "Zkuste spustit jako správce", "errorOperationFailed": "Operace se nezdařila — zkuste spustit jako správce", "tabScanner": "Skener", @@ -64,11 +64,13 @@ "tabDatabase": "Databáze", "dbTitle": "Databáze signatur", "dbDescription": "Pravidla YARA pro detekci malwaru používaná skenerem", - "dbFetchLatest": "Vyhledat aktualizace", + "dbFetchLatest": "Zkontrolovat aktualizace", "dbUpdating": "Probíhá aktualizace...", "dbEngine": "Engine", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (nativní)", "dbEngineRegex": "Záložní Regex", + "dbEngineCompiling": "Probíhá kompilace...", + "dbCompiling": "Kompilace pravidel signatur ({{loaded}}/{{total}})...", "dbRulesLoaded": "Načtená pravidla", "dbVersion": "Verze signatur", "dbLastUpdated": "Naposledy aktualizováno", @@ -78,10 +80,10 @@ "dbSourceBundled": "Součást aplikace", "dbSourceNone": "Nejsou načtena žádná pravidla", "dbRuleFiles": "Soubory pravidel", - "dbRuleFilesCounts": "{{bundled}} součástí aplikace, {{cached}} z cloudu", + "dbRuleFilesCounts": "{{bundled}} v aplikaci, {{cached}} z cloudu", "dbUpdateSuccess": "Aktualizováno na v{{version}} ({{count}} pravidel)", "dbAlreadyCurrent": "Signatury jsou již aktuální", - "dbUpdateFailed": "Aktualizace signatur se nezdařila", + "dbUpdateFailed": "Aktualizaci signatur se nepodařilo provést", "quarantineEmptyTitle": "Žádné položky v karanténě", "quarantineEmptyDescription": "Zde se zobrazí soubory přesunuté do karantény. Můžete je obnovit nebo trvale odstranit.", "quarantineHeading": "Soubory v karanténě", diff --git a/src/renderer/src/locales/da/malware.json b/src/renderer/src/locales/da/malware.json index 99b23ec7..f73f1241 100644 --- a/src/renderer/src/locales/da/malware.json +++ b/src/renderer/src/locales/da/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malware-scanner", - "pageDescription": "Trusselsregistrering med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning for persistens", + "pageDescription": "Registrering af trusler med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning for persistens", "scanButtonScanning": "Scanner...", "scanButton": "Scan", "quarantineButton": "Sæt i karantæne", @@ -67,8 +67,10 @@ "dbFetchLatest": "Søg efter opdateringer", "dbUpdating": "Opdaterer...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (indbygget)", "dbEngineRegex": "Regex-reserve", + "dbEngineCompiling": "Kompilerer...", + "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regler indlæst", "dbVersion": "Signaturversion", "dbLastUpdated": "Senest opdateret", @@ -97,16 +99,16 @@ "quarantineSelectAll": "Vælg alle", "quarantineDeselectAll": "Fravælg alle", "confirmRestoreTitle": "Gendan filer", - "confirmRestoreDescription": "Dette gendanner {{count}} fil(er) til deres oprindelige placering. Gendan kun filer, du har tillid til.", + "confirmRestoreDescription": "Dette gendanner {{count}} fil/filer til deres oprindelige placering. Gendan kun filer, du har tillid til.", "confirmRestoreLabel": "Gendan nu", "confirmDeleteQuarantineTitle": "Slet permanent", - "confirmDeleteQuarantineDescription": "Dette sletter permanent {{count}} fil(er) i karantæne. Denne handling kan ikke fortrydes.", + "confirmDeleteQuarantineDescription": "Dette sletter permanent {{count}} fil/filer i karantæne. Denne handling kan ikke fortrydes.", "confirmDeleteQuarantineLabel": "Slet permanent", "quarantineLoading": "Indlæser karantæne...", "quarantineRestoring": "Gendanner filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil(er) gendannet", + "toastRestoreSuccess": "{{count}} fil/filer gendannet", "toastRestoreFailed": "Kunne ikke gendanne nogle filer", - "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", "toastRestoreNoOriginal": "Kan ikke gendanne — oprindelig placering er ukendt" } diff --git a/src/renderer/src/locales/de/malware.json b/src/renderer/src/locales/de/malware.json index ce79c6d1..f87ea611 100644 --- a/src/renderer/src/locales/de/malware.json +++ b/src/renderer/src/locales/de/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Malware-Scanner", - "pageDescription": "Bedrohungserkennung mit mehreren Engines — Signaturen, Heuristiken, Skriptanalyse, Systemintegrität und Persistenzprüfung", - "scanButtonScanning": "Wird gescannt...", + "pageDescription": "Bedrohungserkennung mit mehreren Engines — Signaturen, Heuristik, Skriptanalyse, Systemintegrität und Persistenzprüfung", + "scanButtonScanning": "Scan läuft...", "scanButton": "Scannen", "quarantineButton": "In Quarantäne verschieben", "deleteButton": "Löschen", @@ -9,8 +9,8 @@ "severityHigh": "Hoch", "severityMedium": "Mittel", "severityLow": "Niedrig", - "sourceDefenderWindows": "Integriertes AV", - "sourceDefenderMac": "Codesignierung", + "sourceDefenderWindows": "Integrierter AV", + "sourceDefenderMac": "Code Signing", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristische Analyse", "sourceSignature": "Bekannte Signatur", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} Bedrohungen", "threatFound": "{{count}} gefunden", "scanCategoryClean": "sauber", - "scanCategoryNA": "N/V", + "scanCategoryNA": "k. A.", "scanSummarySystemClean": "System sauber", "scanSummaryThreatsDetected": "{{count}} Bedrohungen erkannt", "scanStatFiles": "Dateien", @@ -50,15 +50,15 @@ "threatDetailSize": "Größe", "threatDetailPath": "Pfad", "confirmQuarantineTitle": "Bedrohungen in Quarantäne verschieben", - "confirmQuarantineDescription": "Dadurch werden {{count}} erkannte Bedrohung(en) in Quarantäne verschoben. Dateien können bei Bedarf später wiederhergestellt werden.", + "confirmQuarantineDescription": "{{count}} erkannte Bedrohung(en) werden in Quarantäne verschoben. Dateien können bei Bedarf später wiederhergestellt werden.", "confirmQuarantineLabel": "Jetzt in Quarantäne verschieben", "confirmDeleteTitle": "Bedrohungen löschen", - "confirmDeleteDescription": "Dadurch werden {{count}} erkannte Bedrohung(en) dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmDeleteDescription": "{{count}} erkannte Bedrohung(en) werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeleteLabel": "Dauerhaft löschen", "toastScanFailed": "Malware-Scan fehlgeschlagen", "toastActionFailed": "Bedrohungen konnten nicht {{action}} werden", - "toastActionFailedDescription": "Versuchen Sie, das Programm als Administrator auszuführen", - "errorOperationFailed": "Vorgang fehlgeschlagen — versuchen Sie, das Programm als Administrator auszuführen", + "toastActionFailedDescription": "Versuchen Sie, die Anwendung als Administrator auszuführen", + "errorOperationFailed": "Vorgang fehlgeschlagen — versuchen Sie, die Anwendung als Administrator auszuführen", "tabScanner": "Scanner", "tabQuarantine": "Quarantäne", "tabDatabase": "Datenbank", @@ -67,8 +67,10 @@ "dbFetchLatest": "Nach Updates suchen", "dbUpdating": "Wird aktualisiert...", "dbEngine": "Engine", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (nativ)", "dbEngineRegex": "Regex-Fallback", + "dbEngineCompiling": "Wird kompiliert...", + "dbCompiling": "Signaturregeln werden kompiliert ({{loaded}}/{{total}})...", "dbRulesLoaded": "Geladene Regeln", "dbVersion": "Signaturversion", "dbLastUpdated": "Zuletzt aktualisiert", @@ -97,10 +99,10 @@ "quarantineSelectAll": "Alle auswählen", "quarantineDeselectAll": "Auswahl aufheben", "confirmRestoreTitle": "Dateien wiederherstellen", - "confirmRestoreDescription": "Dadurch werden {{count}} Datei(en) an ihrem ursprünglichen Speicherort wiederhergestellt. Stellen Sie nur Dateien wieder her, denen Sie vertrauen.", + "confirmRestoreDescription": "{{count}} Datei(en) werden an ihrem ursprünglichen Speicherort wiederhergestellt. Stellen Sie nur Dateien wieder her, denen Sie vertrauen.", "confirmRestoreLabel": "Jetzt wiederherstellen", "confirmDeleteQuarantineTitle": "Dauerhaft löschen", - "confirmDeleteQuarantineDescription": "Dadurch werden {{count}} Datei(en) in Quarantäne dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmDeleteQuarantineDescription": "{{count}} Datei(en) in Quarantäne werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeleteQuarantineLabel": "Dauerhaft löschen", "quarantineLoading": "Quarantäne wird geladen...", "quarantineRestoring": "Dateien werden wiederhergestellt...", diff --git a/src/renderer/src/locales/el/malware.json b/src/renderer/src/locales/el/malware.json index 761f4f20..8bcb5a83 100644 --- a/src/renderer/src/locales/el/malware.json +++ b/src/renderer/src/locales/el/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Σαρωτής κακόβουλου λογισμικού", - "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση μονιμότητας", - "scanButtonScanning": "Γίνεται σάρωση...", + "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση μηχανισμών επιμονής", + "scanButtonScanning": "Σάρωση...", "scanButton": "Σάρωση", "quarantineButton": "Καραντίνα", "deleteButton": "Διαγραφή", @@ -21,7 +21,7 @@ "threatFound": "Βρέθηκαν {{count}}", "scanCategoryClean": "καθαρό", "scanCategoryNA": "Δ/Υ", - "scanSummarySystemClean": "Το σύστημα είναι καθαρό", + "scanSummarySystemClean": "Καθαρό σύστημα", "scanSummaryThreatsDetected": "Εντοπίστηκαν {{count}} απειλές", "scanStatFiles": "Αρχεία", "scanStatDuration": "Διάρκεια", @@ -31,8 +31,8 @@ "selectedOfThreats": "από {{count}} απειλές", "selectAll": "Επιλογή όλων", "deselectAll": "Κατάργηση επιλογής όλων", - "actingQuarantining": "Τα επιλεγμένα στοιχεία μεταφέρονται σε καραντίνα...", - "actingDeleting": "Τα επιλεγμένα στοιχεία διαγράφονται...", + "actingQuarantining": "Μεταφορά των επιλεγμένων απειλών σε καραντίνα...", + "actingDeleting": "Διαγραφή των επιλεγμένων απειλών...", "actionCompleteQuarantine": "Η μεταφορά σε καραντίνα ολοκληρώθηκε", "actionCompleteDeletion": "Η διαγραφή ολοκληρώθηκε", "actionResultQuarantined": "{{count}} σε καραντίνα", @@ -67,8 +67,10 @@ "dbFetchLatest": "Έλεγχος για ενημερώσεις", "dbUpdating": "Γίνεται ενημέρωση...", "dbEngine": "Μηχανή", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Εγγενές)", "dbEngineRegex": "Εφεδρικό Regex", + "dbEngineCompiling": "Μεταγλώττιση...", + "dbCompiling": "Μεταγλώττιση κανόνων υπογραφών ({{loaded}}/{{total}})...", "dbRulesLoaded": "Φορτωμένοι κανόνες", "dbVersion": "Έκδοση υπογραφών", "dbLastUpdated": "Τελευταία ενημέρωση", @@ -76,7 +78,7 @@ "dbSource": "Πηγή κανόνων", "dbSourceCloud": "Cloud (στη μνήμη cache)", "dbSourceBundled": "Περιλαμβάνεται στην εφαρμογή", - "dbSourceNone": "Δεν φορτώθηκαν κανόνες", + "dbSourceNone": "Δεν έχουν φορτωθεί κανόνες", "dbRuleFiles": "Αρχεία κανόνων", "dbRuleFilesCounts": "{{bundled}} ενσωματωμένα, {{cached}} από το cloud", "dbUpdateSuccess": "Ενημερώθηκε σε v{{version}} ({{count}} κανόνες)", @@ -103,8 +105,8 @@ "confirmDeleteQuarantineDescription": "Αυτό θα διαγράψει οριστικά {{count}} αρχείο(-α) σε καραντίνα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmDeleteQuarantineLabel": "Οριστική διαγραφή", "quarantineLoading": "Φόρτωση καραντίνας...", - "quarantineRestoring": "Γίνεται αποκατάσταση αρχείων...", - "quarantineDeleting": "Γίνεται διαγραφή αρχείων...", + "quarantineRestoring": "Αποκατάσταση αρχείων...", + "quarantineDeleting": "Διαγραφή αρχείων...", "toastRestoreSuccess": "Αποκαταστάθηκαν {{count}} αρχείο(-α)", "toastRestoreFailed": "Αποτυχία αποκατάστασης ορισμένων αρχείων", "toastDeleteQuarantineSuccess": "Διαγράφηκαν οριστικά {{count}} αρχείο(-α)", diff --git a/src/renderer/src/locales/es/malware.json b/src/renderer/src/locales/es/malware.json index 4a6f9296..fee23fba 100644 --- a/src/renderer/src/locales/es/malware.json +++ b/src/renderer/src/locales/es/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} eliminadas", "actionResultFailed": "{{count}} con error", "noThreatsDetectedTitle": "No se detectaron amenazas", - "noThreatsDetectedDescription": "Se analizaron {{filesScanned}} archivos con {{engineCount}} motores en {{duration}} s: el sistema está limpio.", + "noThreatsDetectedDescription": "Se analizaron {{filesScanned}} archivos con {{engineCount}} motores en {{duration}} s; su sistema está limpio.", "emptyStateTitle": "Escáner de malware", - "emptyStateDescription": "Haga clic en \"Analizar\" para comprobar si hay malware, adware, mineros de criptomonedas y archivos sospechosos en el sistema.", + "emptyStateDescription": "Haga clic en \"Analizar\" para comprobar si hay malware, adware, mineros de criptomonedas y archivos sospechosos en su sistema.", "detectedThreatsHeading": "Amenazas detectadas", "detectedThreatsCount": "{{count}} amenaza", "detectedThreatsCountPlural": "{{count}} amenazas", @@ -50,7 +50,7 @@ "threatDetailSize": "Tamaño", "threatDetailPath": "Ruta", "confirmQuarantineTitle": "Poner amenazas en cuarentena", - "confirmQuarantineDescription": "Esto moverá {{count}} amenaza(s) detectada(s) a la cuarentena. Los archivos se podrán restaurar más adelante si es necesario.", + "confirmQuarantineDescription": "Esto moverá {{count}} amenaza(s) detectada(s) a la cuarentena. Los archivos podrán restaurarse más adelante si es necesario.", "confirmQuarantineLabel": "Poner en cuarentena ahora", "confirmDeleteTitle": "Eliminar amenazas", "confirmDeleteDescription": "Esto eliminará permanentemente {{count}} amenaza(s) detectada(s). Esta acción no se puede deshacer.", @@ -67,8 +67,10 @@ "dbFetchLatest": "Buscar actualizaciones", "dbUpdating": "Actualizando...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (nativo)", "dbEngineRegex": "Alternativa Regex", + "dbEngineCompiling": "Compilando...", + "dbCompiling": "Compilando reglas de firmas ({{loaded}}/{{total}})...", "dbRulesLoaded": "Reglas cargadas", "dbVersion": "Versión de firmas", "dbLastUpdated": "Última actualización", @@ -83,7 +85,7 @@ "dbAlreadyCurrent": "Las firmas ya están actualizadas", "dbUpdateFailed": "No se pudieron actualizar las firmas", "quarantineEmptyTitle": "No hay elementos en cuarentena", - "quarantineEmptyDescription": "Los archivos movidos a la cuarentena aparecerán aquí. Puede restaurarlos o eliminarlos permanentemente.", + "quarantineEmptyDescription": "Los archivos movidos a cuarentena aparecerán aquí. Puede restaurarlos o eliminarlos permanentemente.", "quarantineHeading": "Archivos en cuarentena", "quarantineCount": "{{count}} archivo", "quarantineCountPlural": "{{count}} archivos", @@ -108,5 +110,5 @@ "toastRestoreSuccess": "{{count}} archivo(s) restaurado(s)", "toastRestoreFailed": "No se pudieron restaurar algunos archivos", "toastDeleteQuarantineSuccess": "{{count}} archivo(s) eliminado(s) permanentemente", - "toastRestoreNoOriginal": "No se puede restaurar: ubicación original desconocida" + "toastRestoreNoOriginal": "No se puede restaurar; se desconoce la ubicación original" } diff --git a/src/renderer/src/locales/fi/malware.json b/src/renderer/src/locales/fi/malware.json index 88bf0444..55b2f700 100644 --- a/src/renderer/src/locales/fi/malware.json +++ b/src/renderer/src/locales/fi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Haittaohjelmaskanneri", - "pageDescription": "Monimoottorinen uhkien tunnistus — tunnisteet, heuristiikka, skriptianalyysi, järjestelmän eheys ja pysyvyysskannaus", + "pageDescription": "Monimoottorinen uhkien tunnistus — allekirjoitukset, heuristiikka, komentosarja-analyysi, järjestelmän eheys ja pysyvyyden tarkistus", "scanButtonScanning": "Skannataan...", "scanButton": "Skannaa", "quarantineButton": "Siirrä karanteeniin", @@ -13,12 +13,12 @@ "sourceDefenderMac": "Koodin allekirjoitus", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristinen analyysi", - "sourceSignature": "Tunnettu tunniste", + "sourceSignature": "Tunnettu allekirjoitus", "initializingScanEngines": "Alustetaan skannausmoottoreita...", "filesScanned": "{{count}} tiedostoa skannattu", "threatCount": "{{count}} uhka", "threatCountPlural": "{{count}} uhkaa", - "threatFound": "{{count}} löydetty", + "threatFound": "{{count}} löytyi", "scanCategoryClean": "puhdas", "scanCategoryNA": "Ei käytettävissä", "scanSummarySystemClean": "Järjestelmä puhdas", @@ -62,15 +62,17 @@ "tabScanner": "Skanneri", "tabQuarantine": "Karanteeni", "tabDatabase": "Tietokanta", - "dbTitle": "Tunnistetietokanta", + "dbTitle": "Allekirjoitustietokanta", "dbDescription": "Skannerin käyttämät YARA-haittaohjelmien tunnistussäännöt", "dbFetchLatest": "Tarkista päivitykset", "dbUpdating": "Päivitetään...", "dbEngine": "Moottori", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (natiivi)", "dbEngineRegex": "Regex-varajärjestelmä", + "dbEngineCompiling": "Käännetään...", + "dbCompiling": "Käännetään allekirjoitussääntöjä ({{loaded}}/{{total}})...", "dbRulesLoaded": "Sääntöjä ladattu", - "dbVersion": "Tunnisteversio", + "dbVersion": "Allekirjoitusversio", "dbLastUpdated": "Viimeksi päivitetty", "dbNever": "Ei koskaan", "dbSource": "Sääntöjen lähde", @@ -80,11 +82,11 @@ "dbRuleFiles": "Sääntötiedostot", "dbRuleFilesCounts": "{{bundled}} sisältyy sovellukseen, {{cached}} pilvestä", "dbUpdateSuccess": "Päivitetty versioon v{{version}} ({{count}} sääntöä)", - "dbAlreadyCurrent": "Tunnisteet ovat jo ajan tasalla", - "dbUpdateFailed": "Tunnisteiden päivitys epäonnistui", - "quarantineEmptyTitle": "Ei karanteenissa olevia kohteita", + "dbAlreadyCurrent": "Allekirjoitukset ovat jo ajan tasalla", + "dbUpdateFailed": "Allekirjoitusten päivitys epäonnistui", + "quarantineEmptyTitle": "Ei karanteeniin siirrettyjä kohteita", "quarantineEmptyDescription": "Karanteeniin siirretyt tiedostot näkyvät täällä. Voit palauttaa ne tai poistaa ne pysyvästi.", - "quarantineHeading": "Karanteenissa olevat tiedostot", + "quarantineHeading": "Karanteeniin siirretyt tiedostot", "quarantineCount": "{{count}} tiedosto", "quarantineCountPlural": "{{count}} tiedostoa", "quarantineColumnFile": "Tiedosto", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "Tämä palauttaa {{count}} tiedostoa niiden alkuperäiseen sijaintiin. Palauta vain tiedostoja, joihin luotat.", "confirmRestoreLabel": "Palauta nyt", "confirmDeleteQuarantineTitle": "Poista pysyvästi", - "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteenissa olevaa tiedostoa. Tätä toimintoa ei voi kumota.", + "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteeniin siirrettyä tiedostoa. Tätä toimintoa ei voi kumota.", "confirmDeleteQuarantineLabel": "Poista pysyvästi", "quarantineLoading": "Ladataan karanteenia...", "quarantineRestoring": "Palautetaan tiedostoja...", @@ -108,5 +110,5 @@ "toastRestoreSuccess": "{{count}} tiedosto(a) palautettu", "toastRestoreFailed": "Joidenkin tiedostojen palautus epäonnistui", "toastDeleteQuarantineSuccess": "{{count}} tiedosto(a) poistettu pysyvästi", - "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti on tuntematon" + "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti ei ole tiedossa" } diff --git a/src/renderer/src/locales/fr/malware.json b/src/renderer/src/locales/fr/malware.json index 02291250..3400cd2a 100644 --- a/src/renderer/src/locales/fr/malware.json +++ b/src/renderer/src/locales/fr/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} supprimées", "actionResultFailed": "{{count}} échecs", "noThreatsDetectedTitle": "Aucune menace détectée", - "noThreatsDetectedDescription": "Analyse de {{filesScanned}} fichiers avec {{engineCount}} moteurs en {{duration}} s — votre système est propre.", + "noThreatsDetectedDescription": "Analyse de {{filesScanned}} fichiers avec {{engineCount}} moteurs en {{duration}}s — votre système est propre.", "emptyStateTitle": "Analyseur de malware", - "emptyStateDescription": "Cliquez sur \"Analyser\" pour vérifier la présence de malware, d’adware, de mineurs de cryptomonnaie et de fichiers suspects sur votre système.", + "emptyStateDescription": "Cliquez sur \"Analyser\" pour rechercher des malware, adware, mineurs de cryptomonnaie et fichiers suspects sur votre système.", "detectedThreatsHeading": "Menaces détectées", "detectedThreatsCount": "{{count}} menace", "detectedThreatsCountPlural": "{{count}} menaces", @@ -65,20 +65,22 @@ "dbTitle": "Base de signatures", "dbDescription": "Règles de détection de malware YARA utilisées par l’analyseur", "dbFetchLatest": "Rechercher des mises à jour", - "dbUpdating": "Mise à jour en cours...", + "dbUpdating": "Mise à jour...", "dbEngine": "Moteur", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (natif)", "dbEngineRegex": "Secours Regex", + "dbEngineCompiling": "Compilation...", + "dbCompiling": "Compilation des règles de signature ({{loaded}}/{{total}})...", "dbRulesLoaded": "Règles chargées", "dbVersion": "Version des signatures", "dbLastUpdated": "Dernière mise à jour", "dbNever": "Jamais", "dbSource": "Source des règles", "dbSourceCloud": "Cloud (en cache)", - "dbSourceBundled": "Inclus avec l’application", + "dbSourceBundled": "Incluses avec l’application", "dbSourceNone": "Aucune règle chargée", "dbRuleFiles": "Fichiers de règles", - "dbRuleFilesCounts": "{{bundled}} inclus, {{cached}} depuis le cloud", + "dbRuleFilesCounts": "{{bundled}} incluses, {{cached}} depuis le cloud", "dbUpdateSuccess": "Mise à jour vers v{{version}} ({{count}} règles)", "dbAlreadyCurrent": "Les signatures sont déjà à jour", "dbUpdateFailed": "Échec de la mise à jour des signatures", diff --git a/src/renderer/src/locales/he/malware.json b/src/renderer/src/locales/he/malware.json index 2c1e52f1..f96e0046 100644 --- a/src/renderer/src/locales/he/malware.json +++ b/src/renderer/src/locales/he/malware.json @@ -15,10 +15,10 @@ "sourceHeuristic": "ניתוח היוריסטי", "sourceSignature": "חתימה מוכרת", "initializingScanEngines": "מאתחל מנועי סריקה...", - "filesScanned": "נסרקו {{count}} קבצים", + "filesScanned": "{{count}} קבצים נסרקו", "threatCount": "{{count}} איום", "threatCountPlural": "{{count}} איומים", - "threatFound": "נמצאו {{count}}", + "threatFound": "{{count}} נמצאו", "scanCategoryClean": "נקי", "scanCategoryNA": "לא זמין", "scanSummarySystemClean": "המערכת נקייה", @@ -50,32 +50,34 @@ "threatDetailSize": "גודל", "threatDetailPath": "נתיב", "confirmQuarantineTitle": "העבר איומים להסגר", - "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן יהיה לשחזר קבצים מאוחר יותר אם יהיה צורך.", - "confirmQuarantineLabel": "העבר להסגר כעת", + "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן לשחזר קבצים מאוחר יותר במידת הצורך.", + "confirmQuarantineLabel": "העבר להסגר עכשיו", "confirmDeleteTitle": "מחק איומים", "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איום/איומים שזוהו. לא ניתן לבטל פעולה זו.", "confirmDeleteLabel": "מחק לצמיתות", "toastScanFailed": "סריקת הנוזקות נכשלה", - "toastActionFailed": "נכשל הניסיון לבצע {{action}} באיומים", + "toastActionFailed": "הפעולה {{action}} האיומים נכשלה", "toastActionFailedDescription": "נסה להפעיל כמנהל מערכת", "errorOperationFailed": "הפעולה נכשלה — נסה להפעיל כמנהל מערכת", "tabScanner": "סורק", "tabQuarantine": "הסגר", "tabDatabase": "מסד נתונים", "dbTitle": "מסד נתוני חתימות", - "dbDescription": "כללי זיהוי נוזקות של YARA שבהם הסורק משתמש", + "dbDescription": "כללי זיהוי נוזקות של YARA המשמשים את הסורק", "dbFetchLatest": "בדוק אם קיימים עדכונים", "dbUpdating": "מעדכן...", "dbEngine": "מנוע", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "חלופת Regex", + "dbEngineYara": "YARA-X (מובנה)", + "dbEngineRegex": "Regex חלופי", + "dbEngineCompiling": "מהדר...", + "dbCompiling": "מהדר כללי חתימות ({{loaded}}/{{total}})...", "dbRulesLoaded": "כללים שנטענו", "dbVersion": "גרסת חתימות", "dbLastUpdated": "עודכן לאחרונה", "dbNever": "מעולם לא", "dbSource": "מקור הכללים", "dbSourceCloud": "ענן (במטמון)", - "dbSourceBundled": "כלול ביישום", + "dbSourceBundled": "כלול עם היישום", "dbSourceNone": "לא נטענו כללים", "dbRuleFiles": "קובצי כללים", "dbRuleFilesCounts": "{{bundled}} כלולים, {{cached}} מהענן", @@ -98,11 +100,11 @@ "quarantineDeselectAll": "בטל בחירה של הכול", "confirmRestoreTitle": "שחזר קבצים", "confirmRestoreDescription": "פעולה זו תשחזר {{count}} קובץ/קבצים למיקומם המקורי. שחזר רק קבצים שאתה סומך עליהם.", - "confirmRestoreLabel": "שחזר כעת", + "confirmRestoreLabel": "שחזר עכשיו", "confirmDeleteQuarantineTitle": "מחק לצמיתות", "confirmDeleteQuarantineDescription": "פעולה זו תמחק לצמיתות {{count}} קובץ/קבצים שבהסגר. לא ניתן לבטל פעולה זו.", "confirmDeleteQuarantineLabel": "מחק לצמיתות", - "quarantineLoading": "טוען את ההסגר...", + "quarantineLoading": "טוען הסגר...", "quarantineRestoring": "משחזר קבצים...", "quarantineDeleting": "מוחק קבצים...", "toastRestoreSuccess": "{{count}} קובץ/קבצים שוחזרו", diff --git a/src/renderer/src/locales/hi/malware.json b/src/renderer/src/locales/hi/malware.json index 9275cb03..49620931 100644 --- a/src/renderer/src/locales/hi/malware.json +++ b/src/renderer/src/locales/hi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "मैलवेयर स्कैनर", - "pageDescription": "मल्टी-इंजन खतरा पहचान — सिग्नेचर, ह्यूरिस्टिक्स, स्क्रिप्ट विश्लेषण, सिस्टम अखंडता और पर्सिस्टेंस स्कैनिंग", + "pageDescription": "मल्टी-इंजन खतरा पहचान — signatures, heuristics, script analysis, system integrity, और persistence scanning", "scanButtonScanning": "स्कैन किया जा रहा है...", "scanButton": "स्कैन", "quarantineButton": "क्वारंटीन", @@ -10,10 +10,10 @@ "severityMedium": "मध्यम", "severityLow": "निम्न", "sourceDefenderWindows": "मूल AV", - "sourceDefenderMac": "कोड साइनिंग", + "sourceDefenderMac": "Code Signing", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "ह्यूरिस्टिक विश्लेषण", - "sourceSignature": "ज्ञात सिग्नेचर", + "sourceSignature": "ज्ञात Signature", "initializingScanEngines": "स्कैन इंजन प्रारंभ किए जा रहे हैं...", "filesScanned": "{{count}} फ़ाइलें स्कैन की गईं", "threatCount": "{{count}} खतरा", @@ -33,15 +33,15 @@ "deselectAll": "सभी का चयन हटाएँ", "actingQuarantining": "चयनित खतरों को क्वारंटीन किया जा रहा है...", "actingDeleting": "चयनित खतरों को हटाया जा रहा है...", - "actionCompleteQuarantine": "क्वारंटीन पूर्ण", - "actionCompleteDeletion": "हटाना पूर्ण", + "actionCompleteQuarantine": "क्वारंटीन पूरा हुआ", + "actionCompleteDeletion": "हटाना पूरा हुआ", "actionResultQuarantined": "{{count}} क्वारंटीन किए गए", "actionResultDeleted": "{{count}} हटाए गए", "actionResultFailed": "{{count}} विफल", "noThreatsDetectedTitle": "कोई खतरा नहीं मिला", "noThreatsDetectedDescription": "{{duration}}s में {{engineCount}} इंजनों पर {{filesScanned}} फ़ाइलें स्कैन की गईं — आपका सिस्टम साफ़ है।", "emptyStateTitle": "मैलवेयर स्कैनर", - "emptyStateDescription": "\"स्कैन\" पर क्लिक करके अपने सिस्टम में मैलवेयर, एडवेयर, क्रिप्टो माइनर और संदिग्ध फ़ाइलों की जाँच करें।", + "emptyStateDescription": "अपने सिस्टम में malware, adware, crypto miners, और संदिग्ध फ़ाइलों की जाँच के लिए \"स्कैन\" पर क्लिक करें।", "detectedThreatsHeading": "पाए गए खतरे", "detectedThreatsCount": "{{count}} खतरा", "detectedThreatsCountPlural": "{{count}} खतरे", @@ -50,38 +50,40 @@ "threatDetailSize": "आकार", "threatDetailPath": "पथ", "confirmQuarantineTitle": "खतरों को क्वारंटीन करें", - "confirmQuarantineDescription": "इससे पाए गए {{count}} खतरे क्वारंटीन में भेज दिए जाएँगे। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", + "confirmQuarantineDescription": "इससे पाए गए {{count}} threat(s) क्वारंटीन में भेज दिए जाएँगे। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", "confirmQuarantineLabel": "अभी क्वारंटीन करें", "confirmDeleteTitle": "खतरों को हटाएँ", - "confirmDeleteDescription": "इससे पाए गए {{count}} खतरे स्थायी रूप से हटा दिए जाएँगे। इस क्रिया को पूर्ववत नहीं किया जा सकता।", + "confirmDeleteDescription": "इससे पाए गए {{count}} threat(s) स्थायी रूप से हटा दिए जाएँगे। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteLabel": "स्थायी रूप से हटाएँ", "toastScanFailed": "मैलवेयर स्कैन विफल हुआ", "toastActionFailed": "खतरों को {{action}} करने में विफल", - "toastActionFailedDescription": "व्यवस्थापक के रूप में चलाने का प्रयास करें", - "errorOperationFailed": "कार्रवाई विफल हुई — व्यवस्थापक के रूप में चलाने का प्रयास करें", + "toastActionFailedDescription": "प्रशासक के रूप में चलाने का प्रयास करें", + "errorOperationFailed": "क्रिया विफल हुई — प्रशासक के रूप में चलाने का प्रयास करें", "tabScanner": "स्कैनर", "tabQuarantine": "क्वारंटीन", "tabDatabase": "डेटाबेस", - "dbTitle": "सिग्नेचर डेटाबेस", - "dbDescription": "स्कैनर द्वारा उपयोग किए जाने वाले YARA मैलवेयर पहचान नियम", + "dbTitle": "Signature डेटाबेस", + "dbDescription": "स्कैनर द्वारा उपयोग किए जाने वाले YARA malware detection rules", "dbFetchLatest": "अपडेट की जाँच करें", "dbUpdating": "अपडेट किया जा रहा है...", "dbEngine": "इंजन", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Regex फ़ॉलबैक", + "dbEngineYara": "YARA-X (Native)", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "कंपाइल किया जा रहा है...", + "dbCompiling": "Signature rules कंपाइल किए जा रहे हैं ({{loaded}}/{{total}})...", "dbRulesLoaded": "लोड किए गए नियम", - "dbVersion": "सिग्नेचर संस्करण", + "dbVersion": "Signature संस्करण", "dbLastUpdated": "अंतिम अपडेट", "dbNever": "कभी नहीं", "dbSource": "नियम स्रोत", - "dbSourceCloud": "क्लाउड (कैश्ड)", + "dbSourceCloud": "Cloud (cached)", "dbSourceBundled": "ऐप के साथ शामिल", "dbSourceNone": "कोई नियम लोड नहीं किए गए", "dbRuleFiles": "नियम फ़ाइलें", - "dbRuleFilesCounts": "{{bundled}} शामिल, {{cached}} क्लाउड से", + "dbRuleFilesCounts": "{{bundled}} शामिल, {{cached}} cloud से", "dbUpdateSuccess": "v{{version}} में अपडेट किया गया ({{count}} नियम)", - "dbAlreadyCurrent": "सिग्नेचर पहले से अद्यतित हैं", - "dbUpdateFailed": "सिग्नेचर अपडेट करने में विफल", + "dbAlreadyCurrent": "Signatures पहले से अद्यतित हैं", + "dbUpdateFailed": "Signatures अपडेट करने में विफल", "quarantineEmptyTitle": "कोई क्वारंटीन आइटम नहीं", "quarantineEmptyDescription": "क्वारंटीन में भेजी गई फ़ाइलें यहाँ दिखाई देंगी। आप उन्हें पुनर्स्थापित कर सकते हैं या स्थायी रूप से हटा सकते हैं।", "quarantineHeading": "क्वारंटीन की गई फ़ाइलें", @@ -97,16 +99,16 @@ "quarantineSelectAll": "सभी चुनें", "quarantineDeselectAll": "सभी का चयन हटाएँ", "confirmRestoreTitle": "फ़ाइलें पुनर्स्थापित करें", - "confirmRestoreDescription": "इससे {{count}} फ़ाइलें उनके मूल स्थान पर पुनर्स्थापित कर दी जाएँगी। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", + "confirmRestoreDescription": "इससे {{count}} file(s) उनके मूल स्थान पर पुनर्स्थापित कर दी जाएँगी। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", "confirmRestoreLabel": "अभी पुनर्स्थापित करें", "confirmDeleteQuarantineTitle": "स्थायी रूप से हटाएँ", - "confirmDeleteQuarantineDescription": "इससे {{count}} क्वारंटीन की गई फ़ाइलें स्थायी रूप से हटा दी जाएँगी। इस क्रिया को पूर्ववत नहीं किया जा सकता।", + "confirmDeleteQuarantineDescription": "इससे {{count}} quarantined file(s) स्थायी रूप से हटा दी जाएँगी। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteQuarantineLabel": "स्थायी रूप से हटाएँ", "quarantineLoading": "क्वारंटीन लोड किया जा रहा है...", "quarantineRestoring": "फ़ाइलें पुनर्स्थापित की जा रही हैं...", "quarantineDeleting": "फ़ाइलें हटाई जा रही हैं...", - "toastRestoreSuccess": "{{count}} फ़ाइलें पुनर्स्थापित की गईं", + "toastRestoreSuccess": "{{count}} file(s) पुनर्स्थापित की गईं", "toastRestoreFailed": "कुछ फ़ाइलों को पुनर्स्थापित करने में विफल", - "toastDeleteQuarantineSuccess": "{{count}} फ़ाइलें स्थायी रूप से हटाई गईं", + "toastDeleteQuarantineSuccess": "{{count}} file(s) स्थायी रूप से हटाई गईं", "toastRestoreNoOriginal": "पुनर्स्थापित नहीं किया जा सकता — मूल स्थान अज्ञात है" } diff --git a/src/renderer/src/locales/hu/malware.json b/src/renderer/src/locales/hu/malware.json index ea0d9603..90d0c1a1 100644 --- a/src/renderer/src/locales/hu/malware.json +++ b/src/renderer/src/locales/hu/malware.json @@ -9,13 +9,13 @@ "severityHigh": "Magas", "severityMedium": "Közepes", "severityLow": "Alacsony", - "sourceDefenderWindows": "Beépített AV", + "sourceDefenderWindows": "Natív AV", "sourceDefenderMac": "Kódaláírás", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heurisztikus elemzés", "sourceSignature": "Ismert szignatúra", "initializingScanEngines": "Vizsgálómotorok inicializálása...", - "filesScanned": "{{count}} fájl vizsgálva", + "filesScanned": "{{count}} fájl átvizsgálva", "threatCount": "{{count}} fenyegetés", "threatCountPlural": "{{count}} fenyegetés", "threatFound": "{{count}} találat", @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} törölve", "actionResultFailed": "{{count}} sikertelen", "noThreatsDetectedTitle": "Nem észlelhető fenyegetés", - "noThreatsDetectedDescription": "{{filesScanned}} fájl vizsgálva {{engineCount}} motorral {{duration}} mp alatt — a rendszer tiszta.", + "noThreatsDetectedDescription": "{{filesScanned}} fájl átvizsgálva {{engineCount}} motorral {{duration}} mp alatt — a rendszer tiszta.", "emptyStateTitle": "Kártevőkereső", - "emptyStateDescription": "Kattintson a „Vizsgálat” gombra, hogy ellenőrizze rendszerét kártevők, reklámprogramok, kriptobányászok és gyanús fájlok után.", + "emptyStateDescription": "Kattintson a „Vizsgálat” gombra a rendszer kártevők, reklámprogramok, kriptobányászok és gyanús fájlok utáni ellenőrzéséhez.", "detectedThreatsHeading": "Észlelt fenyegetések", "detectedThreatsCount": "{{count}} fenyegetés", "detectedThreatsCountPlural": "{{count}} fenyegetés", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Ez a művelet {{count}} észlelt fenyegetés(eke)t helyez karanténba. A fájlok szükség esetén később visszaállíthatók.", "confirmQuarantineLabel": "Karanténba helyezés most", "confirmDeleteTitle": "Fenyegetések törlése", - "confirmDeleteDescription": "Ez a művelet véglegesen törli a(z) {{count}} észlelt fenyegetés(eke)t. A művelet nem vonható vissza.", + "confirmDeleteDescription": "Ez a művelet véglegesen törli a(z) {{count}} észlelt fenyegetés(eke)t. Ez a művelet nem vonható vissza.", "confirmDeleteLabel": "Végleges törlés", "toastScanFailed": "A kártevővizsgálat sikertelen", "toastActionFailed": "Nem sikerült a fenyegetések {{action}} művelete", @@ -62,23 +62,25 @@ "tabScanner": "Kereső", "tabQuarantine": "Karantén", "tabDatabase": "Adatbázis", - "dbTitle": "Szignatúra-adatbázis", + "dbTitle": "Szignatúraadatbázis", "dbDescription": "A kereső által használt YARA kártevőészlelési szabályok", "dbFetchLatest": "Frissítések keresése", "dbUpdating": "Frissítés...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Regex tartalék mód", + "dbEngineYara": "YARA-X (natív)", + "dbEngineRegex": "Regex tartalék", + "dbEngineCompiling": "Fordítás...", + "dbCompiling": "Szignatúraszabályok fordítása ({{loaded}}/{{total}})...", "dbRulesLoaded": "Betöltött szabályok", - "dbVersion": "Szignatúra verziója", - "dbLastUpdated": "Utoljára frissítve", + "dbVersion": "Szignatúra verzió", + "dbLastUpdated": "Utolsó frissítés", "dbNever": "Soha", "dbSource": "Szabályforrás", "dbSourceCloud": "Felhő (gyorsítótárazva)", "dbSourceBundled": "Az alkalmazással együtt", "dbSourceNone": "Nincs betöltött szabály", "dbRuleFiles": "Szabályfájlok", - "dbRuleFilesCounts": "{{bundled}} csomagban, {{cached}} a felhőből", + "dbRuleFilesCounts": "{{bundled}} csomagolt, {{cached}} a felhőből", "dbUpdateSuccess": "Frissítve erre: v{{version}} ({{count}} szabály)", "dbAlreadyCurrent": "A szignatúrák már naprakészek", "dbUpdateFailed": "Nem sikerült frissíteni a szignatúrákat", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "Ez a művelet {{count}} fájl(oka)t állít vissza az eredeti helyére. Csak megbízható fájlokat állítson vissza.", "confirmRestoreLabel": "Visszaállítás most", "confirmDeleteQuarantineTitle": "Végleges törlés", - "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli {{count}} karanténba helyezett fájl(oka)t. A művelet nem vonható vissza.", + "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli {{count}} karanténba helyezett fájl(oka)t. Ez a művelet nem vonható vissza.", "confirmDeleteQuarantineLabel": "Végleges törlés", "quarantineLoading": "Karantén betöltése...", "quarantineRestoring": "Fájlok visszaállítása...", diff --git a/src/renderer/src/locales/id/malware.json b/src/renderer/src/locales/id/malware.json index ec683206..b082a932 100644 --- a/src/renderer/src/locales/id/malware.json +++ b/src/renderer/src/locales/id/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Pemindai Malware", - "pageDescription": "Deteksi ancaman multi-mesin — signature, heuristik, analisis skrip, integritas sistem, dan pemindaian persistensi", + "pageDescription": "Deteksi ancaman multi-engine — signature, heuristik, analisis skrip, integritas sistem, dan pemindaian persistensi", "scanButtonScanning": "Memindai...", "scanButton": "Pindai", "quarantineButton": "Karantina", @@ -9,12 +9,12 @@ "severityHigh": "Tinggi", "severityMedium": "Sedang", "severityLow": "Rendah", - "sourceDefenderWindows": "AV bawaan", + "sourceDefenderWindows": "AV Bawaan", "sourceDefenderMac": "Penandatanganan Kode", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analisis Heuristik", "sourceSignature": "Signature Dikenal", - "initializingScanEngines": "Menginisialisasi mesin pemindaian...", + "initializingScanEngines": "Menginisialisasi engine pemindaian...", "filesScanned": "{{count}} file dipindai", "threatCount": "{{count}} ancaman", "threatCountPlural": "{{count}} ancaman", @@ -25,12 +25,12 @@ "scanSummaryThreatsDetected": "{{count}} Ancaman Terdeteksi", "scanStatFiles": "File", "scanStatDuration": "Durasi", - "scanStatEngines": "Mesin", + "scanStatEngines": "Engine", "severityHeading": "Tingkat Keparahan", "selectedHeading": "Dipilih", "selectedOfThreats": "dari {{count}} ancaman", "selectAll": "Pilih Semua", - "deselectAll": "Batalkan Pilih Semua", + "deselectAll": "Batalkan Pilihan Semua", "actingQuarantining": "Mengarantina ancaman yang dipilih...", "actingDeleting": "Menghapus ancaman yang dipilih...", "actionCompleteQuarantine": "Karantina selesai", @@ -39,7 +39,7 @@ "actionResultDeleted": "{{count}} dihapus", "actionResultFailed": "{{count}} gagal", "noThreatsDetectedTitle": "Tidak Ada Ancaman Terdeteksi", - "noThreatsDetectedDescription": "Memindai {{filesScanned}} file di {{engineCount}} mesin dalam {{duration}} dtk — sistem Anda bersih.", + "noThreatsDetectedDescription": "Memindai {{filesScanned}} file di {{engineCount}} engine dalam {{duration}} dtk — sistem Anda bersih.", "emptyStateTitle": "Pemindai Malware", "emptyStateDescription": "Klik \"Pindai\" untuk memeriksa sistem Anda dari malware, adware, penambang kripto, dan file mencurigakan.", "detectedThreatsHeading": "Ancaman Terdeteksi", @@ -48,7 +48,7 @@ "threatDetailDetails": "Detail", "threatDetailFile": "File", "threatDetailSize": "Ukuran", - "threatDetailPath": "Jalur", + "threatDetailPath": "Lokasi", "confirmQuarantineTitle": "Karantina Ancaman", "confirmQuarantineDescription": "Ini akan memindahkan {{count}} ancaman yang terdeteksi ke karantina. File dapat dipulihkan nanti jika diperlukan.", "confirmQuarantineLabel": "Karantina Sekarang", @@ -66,9 +66,11 @@ "dbDescription": "Aturan deteksi malware YARA yang digunakan oleh pemindai", "dbFetchLatest": "Periksa Pembaruan", "dbUpdating": "Memperbarui...", - "dbEngine": "Mesin", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Cadangan Regex", + "dbEngine": "Engine", + "dbEngineYara": "YARA-X (Bawaan)", + "dbEngineRegex": "Fallback Regex", + "dbEngineCompiling": "Mengompilasi...", + "dbCompiling": "Mengompilasi aturan signature ({{loaded}}/{{total}})...", "dbRulesLoaded": "Aturan Dimuat", "dbVersion": "Versi Signature", "dbLastUpdated": "Terakhir Diperbarui", @@ -80,7 +82,7 @@ "dbRuleFiles": "File Aturan", "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari cloud", "dbUpdateSuccess": "Diperbarui ke v{{version}} ({{count}} aturan)", - "dbAlreadyCurrent": "Signature sudah versi terbaru", + "dbAlreadyCurrent": "Signature sudah yang terbaru", "dbUpdateFailed": "Gagal memperbarui signature", "quarantineEmptyTitle": "Tidak Ada Item yang Dikarantina", "quarantineEmptyDescription": "File yang dipindahkan ke karantina akan muncul di sini. Anda dapat memulihkan atau menghapusnya secara permanen.", @@ -95,7 +97,7 @@ "quarantineRestoreButton": "Pulihkan", "quarantineDeleteButton": "Hapus Permanen", "quarantineSelectAll": "Pilih Semua", - "quarantineDeselectAll": "Batalkan Pilih Semua", + "quarantineDeselectAll": "Batalkan Pilihan Semua", "confirmRestoreTitle": "Pulihkan File", "confirmRestoreDescription": "Ini akan memulihkan {{count}} file ke lokasi aslinya. Hanya pulihkan file yang Anda percayai.", "confirmRestoreLabel": "Pulihkan Sekarang", @@ -107,6 +109,6 @@ "quarantineDeleting": "Menghapus file...", "toastRestoreSuccess": "{{count}} file dipulihkan", "toastRestoreFailed": "Gagal memulihkan beberapa file", - "toastDeleteQuarantineSuccess": "{{count}} file dihapus permanen", + "toastDeleteQuarantineSuccess": "{{count}} file dihapus secara permanen", "toastRestoreNoOriginal": "Tidak dapat memulihkan — lokasi asli tidak diketahui" } diff --git a/src/renderer/src/locales/it/malware.json b/src/renderer/src/locales/it/malware.json index 351b56f7..6b25f8b8 100644 --- a/src/renderer/src/locales/it/malware.json +++ b/src/renderer/src/locales/it/malware.json @@ -18,7 +18,7 @@ "filesScanned": "{{count}} file analizzati", "threatCount": "{{count}} minaccia", "threatCountPlural": "{{count}} minacce", - "threatFound": "{{count}} trovata", + "threatFound": "{{count}} trovate", "scanCategoryClean": "pulito", "scanCategoryNA": "N/D", "scanSummarySystemClean": "Sistema pulito", @@ -31,7 +31,7 @@ "selectedOfThreats": "di {{count}} minacce", "selectAll": "Seleziona tutto", "deselectAll": "Deseleziona tutto", - "actingQuarantining": "Messa in quarantena delle minacce selezionate...", + "actingQuarantining": "Spostamento in quarantena delle minacce selezionate...", "actingDeleting": "Eliminazione delle minacce selezionate...", "actionCompleteQuarantine": "Quarantena completata", "actionCompleteDeletion": "Eliminazione completata", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Nessuna minaccia rilevata", "noThreatsDetectedDescription": "Analizzati {{filesScanned}} file con {{engineCount}} motori in {{duration}} s — il sistema è pulito.", "emptyStateTitle": "Scanner malware", - "emptyStateDescription": "Fai clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, crypto miner e file sospetti.", + "emptyStateDescription": "Fai clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, cryptominer e file sospetti.", "detectedThreatsHeading": "Minacce rilevate", "detectedThreatsCount": "{{count}} minaccia", "detectedThreatsCountPlural": "{{count}} minacce", @@ -63,12 +63,14 @@ "tabQuarantine": "Quarantena", "tabDatabase": "Database", "dbTitle": "Database delle firme", - "dbDescription": "Regole di rilevamento malware YARA utilizzate dallo scanner", + "dbDescription": "Regole YARA di rilevamento malware utilizzate dallo scanner", "dbFetchLatest": "Controlla aggiornamenti", "dbUpdating": "Aggiornamento in corso...", "dbEngine": "Motore", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (nativo)", "dbEngineRegex": "Fallback Regex", + "dbEngineCompiling": "Compilazione in corso...", + "dbCompiling": "Compilazione delle regole di firma ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regole caricate", "dbVersion": "Versione delle firme", "dbLastUpdated": "Ultimo aggiornamento", @@ -83,7 +85,7 @@ "dbAlreadyCurrent": "Le firme sono già aggiornate", "dbUpdateFailed": "Aggiornamento delle firme non riuscito", "quarantineEmptyTitle": "Nessun elemento in quarantena", - "quarantineEmptyDescription": "I file spostati in quarantena verranno visualizzati qui. Puoi ripristinarli o eliminarli definitivamente.", + "quarantineEmptyDescription": "I file spostati in quarantena appariranno qui. Puoi ripristinarli o eliminarli definitivamente.", "quarantineHeading": "File in quarantena", "quarantineCount": "{{count}} file", "quarantineCountPlural": "{{count}} file", diff --git a/src/renderer/src/locales/ja/malware.json b/src/renderer/src/locales/ja/malware.json index 9ad7f22a..deddcce5 100644 --- a/src/renderer/src/locales/ja/malware.json +++ b/src/renderer/src/locales/ja/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} 件を削除", "actionResultFailed": "{{count}} 件失敗", "noThreatsDetectedTitle": "脅威は検出されませんでした", - "noThreatsDetectedDescription": "{{engineCount}} 個のエンジンで {{filesScanned}} 個のファイルを {{duration}} 秒間スキャンしました — システムはクリーンです。", + "noThreatsDetectedDescription": "{{duration}} 秒で {{engineCount}} 個のエンジンにより {{filesScanned}} 個のファイルをスキャンしました — システムはクリーンです。", "emptyStateTitle": "マルウェア スキャナー", - "emptyStateDescription": "\"スキャン\" をクリックして、システム内のマルウェア、アドウェア、暗号資産マイナー、不審なファイルを確認します。", + "emptyStateDescription": "「スキャン」をクリックして、システム内のマルウェア、アドウェア、暗号通貨マイナー、不審なファイルを確認します。", "detectedThreatsHeading": "検出された脅威", "detectedThreatsCount": "{{count}} 件の脅威", "detectedThreatsCountPlural": "{{count}} 件の脅威", @@ -50,7 +50,7 @@ "threatDetailSize": "サイズ", "threatDetailPath": "パス", "confirmQuarantineTitle": "脅威を隔離", - "confirmQuarantineDescription": "検出された脅威 {{count}} 件を隔離に移動します。必要に応じて、後でファイルを復元できます。", + "confirmQuarantineDescription": "検出された脅威 {{count}} 件を隔離に移動します。必要に応じて後でファイルを復元できます。", "confirmQuarantineLabel": "今すぐ隔離", "confirmDeleteTitle": "脅威を削除", "confirmDeleteDescription": "検出された脅威 {{count}} 件を完全に削除します。この操作は元に戻せません。", @@ -67,23 +67,25 @@ "dbFetchLatest": "更新プログラムの確認", "dbUpdating": "更新しています...", "dbEngine": "エンジン", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (ネイティブ)", "dbEngineRegex": "Regex フォールバック", - "dbRulesLoaded": "読み込まれたルール", + "dbEngineCompiling": "コンパイルしています...", + "dbCompiling": "シグネチャ ルールをコンパイルしています ({{loaded}}/{{total}})...", + "dbRulesLoaded": "読み込み済みルール", "dbVersion": "シグネチャ バージョン", "dbLastUpdated": "最終更新", "dbNever": "なし", "dbSource": "ルール ソース", - "dbSourceCloud": "クラウド(キャッシュ済み)", + "dbSourceCloud": "クラウド (キャッシュ済み)", "dbSourceBundled": "アプリに同梱", - "dbSourceNone": "ルールが読み込まれていません", + "dbSourceNone": "読み込まれたルールはありません", "dbRuleFiles": "ルール ファイル", - "dbRuleFilesCounts": "{{bundled}} 個同梱、{{cached}} 個をクラウドから取得", - "dbUpdateSuccess": "v{{version}} に更新しました({{count}} ルール)", + "dbRuleFilesCounts": "同梱 {{bundled}}、クラウドから {{cached}}", + "dbUpdateSuccess": "v{{version}} に更新しました ({{count}} ルール)", "dbAlreadyCurrent": "シグネチャはすでに最新です", "dbUpdateFailed": "シグネチャの更新に失敗しました", "quarantineEmptyTitle": "隔離された項目はありません", - "quarantineEmptyDescription": "隔離に移動されたファイルはここに表示されます。復元または完全に削除できます。", + "quarantineEmptyDescription": "隔離に移動したファイルはここに表示されます。復元または完全に削除できます。", "quarantineHeading": "隔離されたファイル", "quarantineCount": "{{count}} 個のファイル", "quarantineCountPlural": "{{count}} 個のファイル", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "{{count}} 個のファイルを元の場所に復元します。信頼できるファイルのみ復元してください。", "confirmRestoreLabel": "今すぐ復元", "confirmDeleteQuarantineTitle": "完全に削除", - "confirmDeleteQuarantineDescription": "隔離されたファイル {{count}} 個を完全に削除します。この操作は元に戻せません。", + "confirmDeleteQuarantineDescription": "{{count}} 個の隔離されたファイルを完全に削除します。この操作は元に戻せません。", "confirmDeleteQuarantineLabel": "完全に削除", "quarantineLoading": "隔離を読み込んでいます...", "quarantineRestoring": "ファイルを復元しています...", diff --git a/src/renderer/src/locales/ko/malware.json b/src/renderer/src/locales/ko/malware.json index 7a1313da..4b0aa48e 100644 --- a/src/renderer/src/locales/ko/malware.json +++ b/src/renderer/src/locales/ko/malware.json @@ -18,7 +18,7 @@ "filesScanned": "{{count}}개 파일 검사됨", "threatCount": "{{count}}개 위협", "threatCountPlural": "{{count}}개 위협", - "threatFound": "{{count}}개 발견", + "threatFound": "{{count}}개 발견됨", "scanCategoryClean": "정상", "scanCategoryNA": "해당 없음", "scanSummarySystemClean": "시스템 정상", @@ -38,7 +38,7 @@ "actionResultQuarantined": "{{count}}개 격리됨", "actionResultDeleted": "{{count}}개 삭제됨", "actionResultFailed": "{{count}}개 실패", - "noThreatsDetectedTitle": "위협이 탐지되지 않았습니다", + "noThreatsDetectedTitle": "위협이 탐지되지 않음", "noThreatsDetectedDescription": "{{duration}}초 동안 {{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 검사했습니다 — 시스템이 정상입니다.", "emptyStateTitle": "악성코드 검사기", "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 악성코드, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", @@ -67,13 +67,15 @@ "dbFetchLatest": "업데이트 확인", "dbUpdating": "업데이트 중...", "dbEngine": "엔진", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (기본)", "dbEngineRegex": "Regex 대체", + "dbEngineCompiling": "컴파일 중...", + "dbCompiling": "시그니처 규칙 컴파일 중 ({{loaded}}/{{total}})...", "dbRulesLoaded": "로드된 규칙", "dbVersion": "시그니처 버전", "dbLastUpdated": "마지막 업데이트", "dbNever": "없음", - "dbSource": "규칙 소스", + "dbSource": "규칙 원본", "dbSourceCloud": "클라우드(캐시됨)", "dbSourceBundled": "앱에 포함됨", "dbSourceNone": "로드된 규칙 없음", @@ -82,7 +84,7 @@ "dbUpdateSuccess": "v{{version}}로 업데이트됨 (규칙 {{count}}개)", "dbAlreadyCurrent": "시그니처가 이미 최신 상태입니다", "dbUpdateFailed": "시그니처 업데이트에 실패했습니다", - "quarantineEmptyTitle": "격리된 항목이 없습니다", + "quarantineEmptyTitle": "격리된 항목 없음", "quarantineEmptyDescription": "격리로 이동된 파일이 여기에 표시됩니다. 파일을 복원하거나 영구적으로 삭제할 수 있습니다.", "quarantineHeading": "격리된 파일", "quarantineCount": "{{count}}개 파일", @@ -108,5 +110,5 @@ "toastRestoreSuccess": "{{count}}개 파일 복원됨", "toastRestoreFailed": "일부 파일 복원에 실패했습니다", "toastDeleteQuarantineSuccess": "{{count}}개 파일이 영구적으로 삭제됨", - "toastRestoreNoOriginal": "복원할 수 없습니다 — 원래 위치를 알 수 없습니다" + "toastRestoreNoOriginal": "복원할 수 없음 — 원래 위치를 알 수 없음" } diff --git a/src/renderer/src/locales/ms/malware.json b/src/renderer/src/locales/ms/malware.json index 11b55619..891354d1 100644 --- a/src/renderer/src/locales/ms/malware.json +++ b/src/renderer/src/locales/ms/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Pengimbas Malware", "pageDescription": "Pengesanan ancaman berbilang enjin — tandatangan, heuristik, analisis skrip, integriti sistem dan pengimbasan ketekalan", - "scanButtonScanning": "Mengimbas...", + "scanButtonScanning": "Sedang mengimbas...", "scanButton": "Imbas", "quarantineButton": "Kuarantin", "deleteButton": "Padam", @@ -31,9 +31,9 @@ "selectedOfThreats": "daripada {{count}} ancaman", "selectAll": "Pilih Semua", "deselectAll": "Nyahpilih Semua", - "actingQuarantining": "Menguarantinkan ancaman yang dipilih...", + "actingQuarantining": "Menguarantin ancaman yang dipilih...", "actingDeleting": "Memadam ancaman yang dipilih...", - "actionCompleteQuarantine": "Kuarantin selesai", + "actionCompleteQuarantine": "Pengkuarantinan selesai", "actionCompleteDeletion": "Pemadaman selesai", "actionResultQuarantined": "{{count}} dikuarantin", "actionResultDeleted": "{{count}} dipadam", @@ -49,9 +49,9 @@ "threatDetailFile": "Fail", "threatDetailSize": "Saiz", "threatDetailPath": "Laluan", - "confirmQuarantineTitle": "Kuarantinkan Ancaman", + "confirmQuarantineTitle": "Kuarantin Ancaman", "confirmQuarantineDescription": "Ini akan memindahkan {{count}} ancaman yang dikesan ke kuarantin. Fail boleh dipulihkan kemudian jika perlu.", - "confirmQuarantineLabel": "Kuarantinkan Sekarang", + "confirmQuarantineLabel": "Kuarantin Sekarang", "confirmDeleteTitle": "Padam Ancaman", "confirmDeleteDescription": "Ini akan memadam secara kekal {{count}} ancaman yang dikesan. Tindakan ini tidak boleh dibuat asal.", "confirmDeleteLabel": "Padam Secara Kekal", @@ -65,10 +65,12 @@ "dbTitle": "Pangkalan Data Tandatangan", "dbDescription": "Peraturan pengesanan malware YARA yang digunakan oleh pengimbas", "dbFetchLatest": "Semak Kemas Kini", - "dbUpdating": "Mengemas kini...", + "dbUpdating": "Sedang mengemas kini...", "dbEngine": "Enjin", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Asli)", "dbEngineRegex": "Sandaran Regex", + "dbEngineCompiling": "Sedang menyusun...", + "dbCompiling": "Menyusun peraturan tandatangan ({{loaded}}/{{total}})...", "dbRulesLoaded": "Peraturan Dimuatkan", "dbVersion": "Versi Tandatangan", "dbLastUpdated": "Terakhir Dikemas Kini", diff --git a/src/renderer/src/locales/nl/malware.json b/src/renderer/src/locales/nl/malware.json index 1435a6c0..ada0a4a0 100644 --- a/src/renderer/src/locales/nl/malware.json +++ b/src/renderer/src/locales/nl/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malwarescanner", - "pageDescription": "Detectie van bedreigingen met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en persistentiescans", + "pageDescription": "Dreigingsdetectie met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en persistentiescans", "scanButtonScanning": "Scannen...", "scanButton": "Scannen", "quarantineButton": "In quarantaine plaatsen", @@ -9,7 +9,7 @@ "severityHigh": "Hoog", "severityMedium": "Gemiddeld", "severityLow": "Laag", - "sourceDefenderWindows": "Native AV", + "sourceDefenderWindows": "Ingebouwde AV", "sourceDefenderMac": "Code Signing", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Heuristische analyse", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} bedreigingen", "threatFound": "{{count}} gevonden", "scanCategoryClean": "schoon", - "scanCategoryNA": "n.v.t.", + "scanCategoryNA": "N.v.t.", "scanSummarySystemClean": "Systeem schoon", "scanSummaryThreatsDetected": "{{count}} bedreigingen gedetecteerd", "scanStatFiles": "Bestanden", @@ -50,7 +50,7 @@ "threatDetailSize": "Grootte", "threatDetailPath": "Pad", "confirmQuarantineTitle": "Bedreigingen in quarantaine plaatsen", - "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) in quarantaine geplaatst. Bestanden kunnen later indien nodig worden hersteld.", + "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) naar de quarantaine verplaatst. Bestanden kunnen later indien nodig worden hersteld.", "confirmQuarantineLabel": "Nu in quarantaine plaatsen", "confirmDeleteTitle": "Bedreigingen verwijderen", "confirmDeleteDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.", @@ -67,23 +67,25 @@ "dbFetchLatest": "Controleren op updates", "dbUpdating": "Bijwerken...", "dbEngine": "Engine", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Native)", "dbEngineRegex": "Regex-terugvaloptie", + "dbEngineCompiling": "Compileren...", + "dbCompiling": "Handtekeningregels compileren ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regels geladen", - "dbVersion": "Handtekeningenversie", + "dbVersion": "Handtekeningversie", "dbLastUpdated": "Laatst bijgewerkt", "dbNever": "Nooit", - "dbSource": "Bron van regels", + "dbSource": "Regelbron", "dbSourceCloud": "Cloud (in cache)", "dbSourceBundled": "Meegeleverd met app", "dbSourceNone": "Geen regels geladen", "dbRuleFiles": "Regelbestanden", - "dbRuleFilesCounts": "{{bundled}} meegeleverd, {{cached}} uit cloud", + "dbRuleFilesCounts": "{{bundled}} meegeleverd, {{cached}} uit de cloud", "dbUpdateSuccess": "Bijgewerkt naar v{{version}} ({{count}} regels)", "dbAlreadyCurrent": "Handtekeningen zijn al up-to-date", "dbUpdateFailed": "Bijwerken van handtekeningen mislukt", "quarantineEmptyTitle": "Geen items in quarantaine", - "quarantineEmptyDescription": "Bestanden die in quarantaine zijn geplaatst, worden hier weergegeven. U kunt ze herstellen of permanent verwijderen.", + "quarantineEmptyDescription": "Bestanden die naar de quarantaine zijn verplaatst, verschijnen hier. U kunt ze herstellen of permanent verwijderen.", "quarantineHeading": "Bestanden in quarantaine", "quarantineCount": "{{count}} bestand", "quarantineCountPlural": "{{count}} bestanden", @@ -106,7 +108,7 @@ "quarantineRestoring": "Bestanden herstellen...", "quarantineDeleting": "Bestanden verwijderen...", "toastRestoreSuccess": "{{count}} bestand(en) hersteld", - "toastRestoreFailed": "Kan sommige bestanden niet herstellen", + "toastRestoreFailed": "Herstellen van sommige bestanden mislukt", "toastDeleteQuarantineSuccess": "{{count}} bestand(en) permanent verwijderd", "toastRestoreNoOriginal": "Kan niet herstellen — oorspronkelijke locatie onbekend" } diff --git a/src/renderer/src/locales/no/malware.json b/src/renderer/src/locales/no/malware.json index 65764a17..b8a94c33 100644 --- a/src/renderer/src/locales/no/malware.json +++ b/src/renderer/src/locales/no/malware.json @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Ingen trusler oppdaget", "noThreatsDetectedDescription": "Skannet {{filesScanned}} filer med {{engineCount}} motorer på {{duration}} s — systemet ditt er rent.", "emptyStateTitle": "Skanner for skadelig programvare", - "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet ditt for skadelig programvare, reklameprogrammer, kryptoutvinnere og mistenkelige filer.", + "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet for skadelig programvare, reklameprogrammer, kryptogravere og mistenkelige filer.", "detectedThreatsHeading": "Oppdagede trusler", "detectedThreatsCount": "{{count}} trussel", "detectedThreatsCountPlural": "{{count}} trusler", @@ -63,12 +63,14 @@ "tabQuarantine": "Karantene", "tabDatabase": "Database", "dbTitle": "Signaturdatabase", - "dbDescription": "YARA-regler for oppdagelse av skadelig programvare som brukes av skanneren", + "dbDescription": "YARA-regler for deteksjon av skadelig programvare som brukes av skanneren", "dbFetchLatest": "Se etter oppdateringer", "dbUpdating": "Oppdaterer...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (innebygd)", "dbEngineRegex": "Regex-reserveløsning", + "dbEngineCompiling": "Kompilerer...", + "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regler lastet inn", "dbVersion": "Signaturversjon", "dbLastUpdated": "Sist oppdatert", @@ -97,16 +99,16 @@ "quarantineSelectAll": "Velg alle", "quarantineDeselectAll": "Fjern markering", "confirmRestoreTitle": "Gjenopprett filer", - "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil(er) til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", + "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil/filer til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", "confirmRestoreLabel": "Gjenopprett nå", "confirmDeleteQuarantineTitle": "Slett permanent", - "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} filer i karantene permanent. Denne handlingen kan ikke angres.", + "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} fil/filer i karantene permanent. Denne handlingen kan ikke angres.", "confirmDeleteQuarantineLabel": "Slett permanent", "quarantineLoading": "Laster inn karantene...", "quarantineRestoring": "Gjenoppretter filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil(er) gjenopprettet", + "toastRestoreSuccess": "{{count}} fil/filer gjenopprettet", "toastRestoreFailed": "Kunne ikke gjenopprette noen filer", - "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", "toastRestoreNoOriginal": "Kan ikke gjenopprette — opprinnelig plassering er ukjent" } diff --git a/src/renderer/src/locales/pl/malware.json b/src/renderer/src/locales/pl/malware.json index 9bf1e6d6..a48d6303 100644 --- a/src/renderer/src/locales/pl/malware.json +++ b/src/renderer/src/locales/pl/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Skaner malware", - "pageDescription": "Wielosilnikowe wykrywanie zagrożeń — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie trwałości", + "pageDescription": "Wielosilnikowe wykrywanie zagrożeń — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie mechanizmów utrwalania", "scanButtonScanning": "Skanowanie...", "scanButton": "Skanuj", "quarantineButton": "Kwarantanna", @@ -14,15 +14,15 @@ "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analiza heurystyczna", "sourceSignature": "Znana sygnatura", - "initializingScanEngines": "Inicjalizowanie silników skanowania...", + "initializingScanEngines": "Inicjowanie silników skanowania...", "filesScanned": "Przeskanowano {{count}} plików", "threatCount": "{{count}} zagrożenie", "threatCountPlural": "{{count}} zagrożenia", "threatFound": "Znaleziono {{count}}", "scanCategoryClean": "czysty", "scanCategoryNA": "N/D", - "scanSummarySystemClean": "System czysty", - "scanSummaryThreatsDetected": "Wykryto zagrożenia: {{count}}", + "scanSummarySystemClean": "System jest czysty", + "scanSummaryThreatsDetected": "Wykryto {{count}} zagrożeń", "scanStatFiles": "Pliki", "scanStatDuration": "Czas trwania", "scanStatEngines": "Silniki", @@ -67,8 +67,10 @@ "dbFetchLatest": "Sprawdź aktualizacje", "dbUpdating": "Aktualizowanie...", "dbEngine": "Silnik", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (natywny)", "dbEngineRegex": "Awaryjny Regex", + "dbEngineCompiling": "Kompilowanie...", + "dbCompiling": "Kompilowanie reguł sygnatur ({{loaded}}/{{total}})...", "dbRulesLoaded": "Załadowane reguły", "dbVersion": "Wersja sygnatur", "dbLastUpdated": "Ostatnia aktualizacja", @@ -79,7 +81,7 @@ "dbSourceNone": "Nie załadowano reguł", "dbRuleFiles": "Pliki reguł", "dbRuleFilesCounts": "{{bundled}} dołączonych, {{cached}} z chmury", - "dbUpdateSuccess": "Zaktualizowano do v{{version}} (reguł: {{count}})", + "dbUpdateSuccess": "Zaktualizowano do wersji {{version}} ({{count}} reguł)", "dbAlreadyCurrent": "Sygnatury są już aktualne", "dbUpdateFailed": "Nie udało się zaktualizować sygnatur", "quarantineEmptyTitle": "Brak elementów w kwarantannie", @@ -105,8 +107,8 @@ "quarantineLoading": "Ładowanie kwarantanny...", "quarantineRestoring": "Przywracanie plików...", "quarantineDeleting": "Usuwanie plików...", - "toastRestoreSuccess": "Przywrócono plików: {{count}}", + "toastRestoreSuccess": "Przywrócono pliki: {{count}}", "toastRestoreFailed": "Nie udało się przywrócić niektórych plików", - "toastDeleteQuarantineSuccess": "Trwale usunięto plików: {{count}}", + "toastDeleteQuarantineSuccess": "Trwale usunięto pliki: {{count}}", "toastRestoreNoOriginal": "Nie można przywrócić — oryginalna lokalizacja jest nieznana" } diff --git a/src/renderer/src/locales/pt/malware.json b/src/renderer/src/locales/pt/malware.json index 0d6e83d4..ebfe2f87 100644 --- a/src/renderer/src/locales/pt/malware.json +++ b/src/renderer/src/locales/pt/malware.json @@ -30,9 +30,9 @@ "selectedHeading": "Selecionadas", "selectedOfThreats": "de {{count}} ameaças", "selectAll": "Selecionar tudo", - "deselectAll": "Desmarcar tudo", - "actingQuarantining": "A colocar ameaças selecionadas em quarentena...", - "actingDeleting": "A eliminar ameaças selecionadas...", + "deselectAll": "Desselecionar tudo", + "actingQuarantining": "A colocar as ameaças selecionadas em quarentena...", + "actingDeleting": "A eliminar as ameaças selecionadas...", "actionCompleteQuarantine": "Quarentena concluída", "actionCompleteDeletion": "Eliminação concluída", "actionResultQuarantined": "{{count}} em quarentena", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Nenhuma ameaça detetada", "noThreatsDetectedDescription": "Foram analisados {{filesScanned}} ficheiros em {{engineCount}} motores em {{duration}}s — o seu sistema está limpo.", "emptyStateTitle": "Scanner de Malware", - "emptyStateDescription": "Clique em \"Analisar\" para verificar o seu sistema quanto a malware, adware, mineradores de criptomoedas e ficheiros suspeitos.", + "emptyStateDescription": "Clique em \"Analisar\" para verificar o seu sistema à procura de malware, adware, mineradores de criptomoedas e ficheiros suspeitos.", "detectedThreatsHeading": "Ameaças detetadas", "detectedThreatsCount": "{{count}} ameaça", "detectedThreatsCountPlural": "{{count}} ameaças", @@ -63,25 +63,27 @@ "tabQuarantine": "Quarentena", "tabDatabase": "Base de dados", "dbTitle": "Base de dados de assinaturas", - "dbDescription": "Regras YARA de deteção de malware utilizadas pelo scanner", + "dbDescription": "Regras de deteção de malware YARA utilizadas pelo scanner", "dbFetchLatest": "Procurar atualizações", "dbUpdating": "A atualizar...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Fallback Regex", + "dbEngineYara": "YARA-X (Nativo)", + "dbEngineRegex": "Alternativa Regex", + "dbEngineCompiling": "A compilar...", + "dbCompiling": "A compilar regras de assinatura ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regras carregadas", "dbVersion": "Versão das assinaturas", "dbLastUpdated": "Última atualização", "dbNever": "Nunca", "dbSource": "Origem das regras", - "dbSourceCloud": "Cloud (em cache)", + "dbSourceCloud": "Nuvem (em cache)", "dbSourceBundled": "Incluídas na aplicação", "dbSourceNone": "Nenhuma regra carregada", "dbRuleFiles": "Ficheiros de regras", - "dbRuleFilesCounts": "{{bundled}} incluídas, {{cached}} da cloud", + "dbRuleFilesCounts": "{{bundled}} incluídas, {{cached}} da nuvem", "dbUpdateSuccess": "Atualizado para v{{version}} ({{count}} regras)", "dbAlreadyCurrent": "As assinaturas já estão atualizadas", - "dbUpdateFailed": "Falha ao atualizar assinaturas", + "dbUpdateFailed": "Falha ao atualizar as assinaturas", "quarantineEmptyTitle": "Nenhum item em quarentena", "quarantineEmptyDescription": "Os ficheiros movidos para a quarentena aparecerão aqui. Pode restaurá-los ou eliminá-los permanentemente.", "quarantineHeading": "Ficheiros em quarentena", @@ -95,9 +97,9 @@ "quarantineRestoreButton": "Restaurar", "quarantineDeleteButton": "Eliminar permanentemente", "quarantineSelectAll": "Selecionar tudo", - "quarantineDeselectAll": "Desmarcar tudo", + "quarantineDeselectAll": "Desselecionar tudo", "confirmRestoreTitle": "Restaurar ficheiros", - "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a respetiva localização original. Restaure apenas ficheiros em que confia.", + "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a sua localização original. Restaure apenas ficheiros em que confia.", "confirmRestoreLabel": "Restaurar agora", "confirmDeleteQuarantineTitle": "Eliminar permanentemente", "confirmDeleteQuarantineDescription": "Isto eliminará permanentemente {{count}} ficheiro(s) em quarentena. Esta ação não pode ser anulada.", @@ -107,6 +109,6 @@ "quarantineDeleting": "A eliminar ficheiros...", "toastRestoreSuccess": "{{count}} ficheiro(s) restaurado(s)", "toastRestoreFailed": "Falha ao restaurar alguns ficheiros", - "toastDeleteQuarantineSuccess": "{{count}} ficheiro(s) eliminados permanentemente", + "toastDeleteQuarantineSuccess": "{{count}} ficheiro(s) eliminado(s) permanentemente", "toastRestoreNoOriginal": "Não é possível restaurar — localização original desconhecida" } diff --git a/src/renderer/src/locales/ro/malware.json b/src/renderer/src/locales/ro/malware.json index 5f570440..65080063 100644 --- a/src/renderer/src/locales/ro/malware.json +++ b/src/renderer/src/locales/ro/malware.json @@ -1,9 +1,9 @@ { "pageTitle": "Scanner malware", - "pageDescription": "Detectare a amenințărilor cu mai multe motoare — semnături, euristică, analiză de scripturi, integritatea sistemului și scanare a persistenței", + "pageDescription": "Detectare multi-engine a amenințărilor — semnături, euristică, analiză de scripturi, integritatea sistemului și scanare a persistenței", "scanButtonScanning": "Se scanează...", "scanButton": "Scanează", - "quarantineButton": "Carantinează", + "quarantineButton": "Pune în carantină", "deleteButton": "Șterge", "severityCritical": "Critică", "severityHigh": "Ridicată", @@ -31,11 +31,11 @@ "selectedOfThreats": "din {{count}} amenințări", "selectAll": "Selectează tot", "deselectAll": "Deselectează tot", - "actingQuarantining": "Se carantinează amenințările selectate...", + "actingQuarantining": "Se pun în carantină amenințările selectate...", "actingDeleting": "Se șterg amenințările selectate...", "actionCompleteQuarantine": "Carantinare finalizată", "actionCompleteDeletion": "Ștergere finalizată", - "actionResultQuarantined": "{{count}} carantinate", + "actionResultQuarantined": "{{count}} puse în carantină", "actionResultDeleted": "{{count}} șterse", "actionResultFailed": "{{count}} eșuate", "noThreatsDetectedTitle": "Nu au fost detectate amenințări", @@ -49,14 +49,14 @@ "threatDetailFile": "Fișier", "threatDetailSize": "Dimensiune", "threatDetailPath": "Cale", - "confirmQuarantineTitle": "Carantinează amenințările", + "confirmQuarantineTitle": "Pune amenințările în carantină", "confirmQuarantineDescription": "Aceasta va muta {{count}} amenințare(ări) detectată(e) în carantină. Fișierele pot fi restaurate ulterior, dacă este necesar.", - "confirmQuarantineLabel": "Carantinează acum", + "confirmQuarantineLabel": "Pune acum în carantină", "confirmDeleteTitle": "Șterge amenințările", "confirmDeleteDescription": "Aceasta va șterge definitiv {{count}} amenințare(ări) detectată(e). Această acțiune nu poate fi anulată.", "confirmDeleteLabel": "Șterge definitiv", "toastScanFailed": "Scanarea malware a eșuat", - "toastActionFailed": "Nu s-a reușit acțiunea {{action}} asupra amenințărilor", + "toastActionFailed": "Nu s-au putut {{action}} amenințările", "toastActionFailedDescription": "Încercați să rulați ca administrator", "errorOperationFailed": "Operațiunea a eșuat — încercați să rulați ca administrator", "tabScanner": "Scanner", @@ -67,8 +67,10 @@ "dbFetchLatest": "Verifică actualizările", "dbUpdating": "Se actualizează...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Nativ)", "dbEngineRegex": "Fallback Regex", + "dbEngineCompiling": "Se compilează...", + "dbCompiling": "Se compilează regulile de semnătură ({{loaded}}/{{total}})...", "dbRulesLoaded": "Reguli încărcate", "dbVersion": "Versiune semnături", "dbLastUpdated": "Ultima actualizare", @@ -97,7 +99,7 @@ "quarantineSelectAll": "Selectează tot", "quarantineDeselectAll": "Deselectează tot", "confirmRestoreTitle": "Restaurează fișierele", - "confirmRestoreDescription": "Aceasta va restaura {{count}} fișier(e) în locația lor originală. Restaurați doar fișierele în care aveți încredere.", + "confirmRestoreDescription": "Aceasta va restaura {{count}} fișier(e) în locația originală. Restaurați doar fișierele în care aveți încredere.", "confirmRestoreLabel": "Restaurează acum", "confirmDeleteQuarantineTitle": "Ștergere definitivă", "confirmDeleteQuarantineDescription": "Aceasta va șterge definitiv {{count}} fișier(e) din carantină. Această acțiune nu poate fi anulată.", @@ -106,7 +108,7 @@ "quarantineRestoring": "Se restaurează fișierele...", "quarantineDeleting": "Se șterg fișierele...", "toastRestoreSuccess": "{{count}} fișier(e) restaurat(e)", - "toastRestoreFailed": "Nu s-au putut restaura unele fișiere", + "toastRestoreFailed": "Restaurarea unor fișiere a eșuat", "toastDeleteQuarantineSuccess": "{{count}} fișier(e) șters(e) definitiv", "toastRestoreNoOriginal": "Nu se poate restaura — locația originală este necunoscută" } diff --git a/src/renderer/src/locales/ru/malware.json b/src/renderer/src/locales/ru/malware.json index 09d8d690..5db53bb4 100644 --- a/src/renderer/src/locales/ru/malware.json +++ b/src/renderer/src/locales/ru/malware.json @@ -26,7 +26,7 @@ "scanStatFiles": "Файлы", "scanStatDuration": "Длительность", "scanStatEngines": "Модули", - "severityHeading": "Уровень опасности", + "severityHeading": "Серьёзность", "selectedHeading": "Выбрано", "selectedOfThreats": "из {{count}} угроз", "selectAll": "Выбрать все", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Угроз не обнаружено", "noThreatsDetectedDescription": "Проверено файлов: {{filesScanned}}, модулей: {{engineCount}}, за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер вредоносных программ", - "emptyStateDescription": "Нажмите «Сканировать», чтобы проверить систему на вредоносные программы, рекламное ПО, криптомайнеры и подозрительные файлы.", + "emptyStateDescription": "Нажмите «Сканировать», чтобы проверить систему на наличие вредоносных программ, рекламного ПО, криптомайнеров и подозрительных файлов.", "detectedThreatsHeading": "Обнаруженные угрозы", "detectedThreatsCount": "{{count}} угроза", "detectedThreatsCountPlural": "{{count}} угроз", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Это действие переместит {{count}} обнаруженных угроз(ы) в карантин. При необходимости файлы можно будет восстановить позже.", "confirmQuarantineLabel": "Поместить в карантин", "confirmDeleteTitle": "Удалить угрозы", - "confirmDeleteDescription": "Это действие безвозвратно удалит {{count}} обнаруженных угроз(ы). Отменить это действие невозможно.", + "confirmDeleteDescription": "Это действие безвозвратно удалит {{count}} обнаруженных угроз(ы). Это действие нельзя отменить.", "confirmDeleteLabel": "Удалить безвозвратно", "toastScanFailed": "Не удалось выполнить сканирование на вредоносные программы", "toastActionFailed": "Не удалось выполнить действие «{{action}}» для угроз", @@ -67,22 +67,24 @@ "dbFetchLatest": "Проверить обновления", "dbUpdating": "Обновление...", "dbEngine": "Модуль", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Native)", "dbEngineRegex": "Резервный Regex", + "dbEngineCompiling": "Компиляция...", + "dbCompiling": "Компиляция правил сигнатур ({{loaded}}/{{total}})...", "dbRulesLoaded": "Загружено правил", "dbVersion": "Версия сигнатур", "dbLastUpdated": "Последнее обновление", "dbNever": "Никогда", "dbSource": "Источник правил", "dbSourceCloud": "Облако (кэшировано)", - "dbSourceBundled": "Встроено в приложение", + "dbSourceBundled": "В составе приложения", "dbSourceNone": "Правила не загружены", "dbRuleFiles": "Файлы правил", - "dbRuleFilesCounts": "{{bundled}} встроено, {{cached}} из облака", + "dbRuleFilesCounts": "{{bundled}} в составе приложения, {{cached}} из облака", "dbUpdateSuccess": "Обновлено до v{{version}} (правил: {{count}})", "dbAlreadyCurrent": "Сигнатуры уже актуальны", "dbUpdateFailed": "Не удалось обновить сигнатуры", - "quarantineEmptyTitle": "Нет элементов в карантине", + "quarantineEmptyTitle": "Нет объектов в карантине", "quarantineEmptyDescription": "Файлы, перемещённые в карантин, будут отображаться здесь. Вы можете восстановить их или удалить безвозвратно.", "quarantineHeading": "Файлы в карантине", "quarantineCount": "{{count}} файл", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "Это действие восстановит {{count}} файл(ов) в исходное расположение. Восстанавливайте только те файлы, которым доверяете.", "confirmRestoreLabel": "Восстановить", "confirmDeleteQuarantineTitle": "Удалить безвозвратно", - "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Отменить это действие невозможно.", + "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Это действие нельзя отменить.", "confirmDeleteQuarantineLabel": "Удалить безвозвратно", "quarantineLoading": "Загрузка карантина...", "quarantineRestoring": "Восстановление файлов...", @@ -108,5 +110,5 @@ "toastRestoreSuccess": "Восстановлено файлов: {{count}}", "toastRestoreFailed": "Не удалось восстановить некоторые файлы", "toastDeleteQuarantineSuccess": "Безвозвратно удалено файлов: {{count}}", - "toastRestoreNoOriginal": "Не удаётся восстановить — исходное расположение неизвестно" + "toastRestoreNoOriginal": "Невозможно восстановить — исходное расположение неизвестно" } diff --git a/src/renderer/src/locales/sv/malware.json b/src/renderer/src/locales/sv/malware.json index 2c1ed3e4..45a79f77 100644 --- a/src/renderer/src/locales/sv/malware.json +++ b/src/renderer/src/locales/sv/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} borttagna", "actionResultFailed": "{{count}} misslyckades", "noThreatsDetectedTitle": "Inga hot identifierades", - "noThreatsDetectedDescription": "Skannade {{filesScanned}} filer med {{engineCount}} motorer på {{duration}}s — ditt system är rent.", + "noThreatsDetectedDescription": "Skannade {{filesScanned}} filer med {{engineCount}} motorer på {{duration}} s — ditt system är rent.", "emptyStateTitle": "Skanner för skadlig kod", - "emptyStateDescription": "Klicka på \"Skanna\" för att kontrollera systemet efter skadlig kod, annonsprogram, kryptoutvinnare och misstänkta filer.", + "emptyStateDescription": "Klicka på \"Skanna\" för att kontrollera systemet efter skadlig kod, annonsprogram, kryptominers och misstänkta filer.", "detectedThreatsHeading": "Identifierade hot", "detectedThreatsCount": "{{count}} hot", "detectedThreatsCountPlural": "{{count}} hot", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Detta flyttar {{count}} identifierade hot till karantän. Filer kan återställas senare vid behov.", "confirmQuarantineLabel": "Sätt i karantän nu", "confirmDeleteTitle": "Ta bort hot", - "confirmDeleteDescription": "Detta tar permanent bort {{count}} identifierade hot. Åtgärden kan inte ångras.", + "confirmDeleteDescription": "Detta tar permanent bort {{count}} identifierade hot. Den här åtgärden kan inte ångras.", "confirmDeleteLabel": "Ta bort permanent", "toastScanFailed": "Skanning efter skadlig kod misslyckades", "toastActionFailed": "Det gick inte att {{action}} hot", @@ -67,8 +67,10 @@ "dbFetchLatest": "Sök efter uppdateringar", "dbUpdating": "Uppdaterar...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Regex-reservlösning", + "dbEngineYara": "YARA-X (inbyggd)", + "dbEngineRegex": "Regex-reserv", + "dbEngineCompiling": "Kompilerar...", + "dbCompiling": "Kompilerar signaturregler ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regler inlästa", "dbVersion": "Signaturversion", "dbLastUpdated": "Senast uppdaterad", @@ -100,7 +102,7 @@ "confirmRestoreDescription": "Detta återställer {{count}} fil(er) till deras ursprungliga plats. Återställ endast filer som du litar på.", "confirmRestoreLabel": "Återställ nu", "confirmDeleteQuarantineTitle": "Ta bort permanent", - "confirmDeleteQuarantineDescription": "Detta tar permanent bort {{count}} fil(er) i karantän. Åtgärden kan inte ångras.", + "confirmDeleteQuarantineDescription": "Detta tar permanent bort {{count}} fil(er) i karantän. Den här åtgärden kan inte ångras.", "confirmDeleteQuarantineLabel": "Ta bort permanent", "quarantineLoading": "Läser in karantän...", "quarantineRestoring": "Återställer filer...", diff --git a/src/renderer/src/locales/th/malware.json b/src/renderer/src/locales/th/malware.json index ff7ffd9e..1d8a91d0 100644 --- a/src/renderer/src/locales/th/malware.json +++ b/src/renderer/src/locales/th/malware.json @@ -1,15 +1,15 @@ { "pageTitle": "ตัวสแกนมัลแวร์", - "pageDescription": "การตรวจจับภัยคุกคามด้วยหลายเอนจิน — ลายเซ็น, การวิเคราะห์เชิงฮิวริสติก, การวิเคราะห์สคริปต์, ความสมบูรณ์ของระบบ และการสแกนการคงอยู่", + "pageDescription": "การตรวจจับภัยคุกคามด้วยหลายเอนจิน — ลายเซ็น การวิเคราะห์เชิงฮิวริสติก การวิเคราะห์สคริปต์ ความสมบูรณ์ของระบบ และการสแกนการคงอยู่", "scanButtonScanning": "กำลังสแกน...", "scanButton": "สแกน", "quarantineButton": "กักกัน", "deleteButton": "ลบ", - "severityCritical": "ร้ายแรง", + "severityCritical": "วิกฤต", "severityHigh": "สูง", "severityMedium": "ปานกลาง", "severityLow": "ต่ำ", - "sourceDefenderWindows": "โปรแกรมป้องกันไวรัสในตัว", + "sourceDefenderWindows": "AV ในระบบ", "sourceDefenderMac": "การลงนามโค้ด", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "การวิเคราะห์เชิงฮิวริสติก", @@ -28,20 +28,20 @@ "scanStatEngines": "เอนจิน", "severityHeading": "ระดับความรุนแรง", "selectedHeading": "ที่เลือก", - "selectedOfThreats": "จากภัยคุกคาม {{count}} รายการ", + "selectedOfThreats": "จาก {{count}} ภัยคุกคาม", "selectAll": "เลือกทั้งหมด", "deselectAll": "ยกเลิกการเลือกทั้งหมด", "actingQuarantining": "กำลังกักกันภัยคุกคามที่เลือก...", "actingDeleting": "กำลังลบภัยคุกคามที่เลือก...", - "actionCompleteQuarantine": "กักกันเสร็จสมบูรณ์", - "actionCompleteDeletion": "ลบเสร็จสมบูรณ์", + "actionCompleteQuarantine": "กักกันเสร็จสิ้น", + "actionCompleteDeletion": "ลบเสร็จสิ้น", "actionResultQuarantined": "กักกันแล้ว {{count}} รายการ", "actionResultDeleted": "ลบแล้ว {{count}} รายการ", "actionResultFailed": "ล้มเหลว {{count}} รายการ", "noThreatsDetectedTitle": "ไม่พบภัยคุกคาม", "noThreatsDetectedDescription": "สแกน {{filesScanned}} ไฟล์ด้วย {{engineCount}} เอนจินใน {{duration}} วินาที — ระบบของคุณสะอาด", "emptyStateTitle": "ตัวสแกนมัลแวร์", - "emptyStateDescription": "คลิก \"สแกน\" เพื่อตรวจสอบระบบของคุณหามัลแวร์, แอดแวร์, ตัวขุดคริปโต และไฟล์ที่น่าสงสัย", + "emptyStateDescription": "คลิก \"สแกน\" เพื่อตรวจสอบระบบของคุณหามัลแวร์ แอดแวร์ ตัวขุดคริปโต และไฟล์ที่น่าสงสัย", "detectedThreatsHeading": "ภัยคุกคามที่ตรวจพบ", "detectedThreatsCount": "{{count}} ภัยคุกคาม", "detectedThreatsCountPlural": "{{count}} ภัยคุกคาม", @@ -50,10 +50,10 @@ "threatDetailSize": "ขนาด", "threatDetailPath": "เส้นทาง", "confirmQuarantineTitle": "กักกันภัยคุกคาม", - "confirmQuarantineDescription": "การดำเนินการนี้จะย้ายภัยคุกคามที่ตรวจพบ {{count}} รายการไปยังกักกัน สามารถกู้คืนไฟล์ได้ภายหลังหากจำเป็น", + "confirmQuarantineDescription": "การดำเนินการนี้จะย้ายภัยคุกคามที่ตรวจพบ {{count}} รายการไปยังกักกัน และสามารถกู้คืนไฟล์ได้ภายหลังหากจำเป็น", "confirmQuarantineLabel": "กักกันตอนนี้", "confirmDeleteTitle": "ลบภัยคุกคาม", - "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้", + "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร และไม่สามารถยกเลิกได้", "confirmDeleteLabel": "ลบอย่างถาวร", "toastScanFailed": "การสแกนมัลแวร์ล้มเหลว", "toastActionFailed": "ไม่สามารถ{{action}}ภัยคุกคามได้", @@ -67,21 +67,23 @@ "dbFetchLatest": "ตรวจหาการอัปเดต", "dbUpdating": "กำลังอัปเดต...", "dbEngine": "เอนจิน", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Native)", "dbEngineRegex": "Regex สำรอง", + "dbEngineCompiling": "กำลังคอมไพล์...", + "dbCompiling": "กำลังคอมไพล์กฎลายเซ็น ({{loaded}}/{{total}})...", "dbRulesLoaded": "กฎที่โหลดแล้ว", "dbVersion": "เวอร์ชันลายเซ็น", "dbLastUpdated": "อัปเดตล่าสุด", "dbNever": "ไม่เคย", "dbSource": "แหล่งที่มาของกฎ", - "dbSourceCloud": "Cloud (แคชแล้ว)", + "dbSourceCloud": "Cloud (แคชไว้)", "dbSourceBundled": "มาพร้อมแอป", - "dbSourceNone": "ไม่ได้โหลดกฎ", + "dbSourceNone": "ไม่มีการโหลดกฎ", "dbRuleFiles": "ไฟล์กฎ", - "dbRuleFilesCounts": "มาพร้อมแอป {{bundled}} รายการ, จาก cloud {{cached}} รายการ", + "dbRuleFilesCounts": "มาพร้อมแอป {{bundled}} ไฟล์, จาก cloud {{cached}} ไฟล์", "dbUpdateSuccess": "อัปเดตเป็น v{{version}} แล้ว ({{count}} กฎ)", "dbAlreadyCurrent": "ลายเซ็นเป็นเวอร์ชันล่าสุดอยู่แล้ว", - "dbUpdateFailed": "ไม่สามารถอัปเดตลายเซ็นได้", + "dbUpdateFailed": "อัปเดตลายเซ็นไม่สำเร็จ", "quarantineEmptyTitle": "ไม่มีรายการที่ถูกกักกัน", "quarantineEmptyDescription": "ไฟล์ที่ย้ายไปยังกักกันจะแสดงที่นี่ คุณสามารถกู้คืนหรือลบอย่างถาวรได้", "quarantineHeading": "ไฟล์ที่ถูกกักกัน", @@ -100,9 +102,9 @@ "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} ไฟล์ไปยังตำแหน่งเดิม กู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", "confirmRestoreLabel": "กู้คืนตอนนี้", "confirmDeleteQuarantineTitle": "ลบอย่างถาวร", - "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} ไฟล์อย่างถาวร การกระทำนี้ไม่สามารถย้อนกลับได้", + "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} ไฟล์อย่างถาวร และไม่สามารถยกเลิกได้", "confirmDeleteQuarantineLabel": "ลบอย่างถาวร", - "quarantineLoading": "กำลังโหลดกักกัน...", + "quarantineLoading": "กำลังโหลดรายการกักกัน...", "quarantineRestoring": "กำลังกู้คืนไฟล์...", "quarantineDeleting": "กำลังลบไฟล์...", "toastRestoreSuccess": "กู้คืนไฟล์แล้ว {{count}} ไฟล์", diff --git a/src/renderer/src/locales/tr/malware.json b/src/renderer/src/locales/tr/malware.json index d9e35208..20da0bad 100644 --- a/src/renderer/src/locales/tr/malware.json +++ b/src/renderer/src/locales/tr/malware.json @@ -28,7 +28,7 @@ "scanStatEngines": "Motorlar", "severityHeading": "Önem Derecesi", "selectedHeading": "Seçili", - "selectedOfThreats": "{{count}} tehdidin", + "selectedOfThreats": "{{count}} tehditten", "selectAll": "Tümünü Seç", "deselectAll": "Tüm Seçimi Kaldır", "actingQuarantining": "Seçili tehditler karantinaya alınıyor...", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Tehdit Algılanmadı", "noThreatsDetectedDescription": "{{duration}} sn içinde {{engineCount}} motorla {{filesScanned}} dosya tarandı — sisteminiz temiz.", "emptyStateTitle": "Kötü Amaçlı Yazılım Tarayıcısı", - "emptyStateDescription": "Sisteminizi kötü amaçlı yazılım, reklam yazılımı, kripto madencileri ve şüpheli dosyalar açısından denetlemek için \"Tara\" seçeneğine tıklayın.", + "emptyStateDescription": "Sisteminizi kötü amaçlı yazılımlar, reklam yazılımları, kripto madencileri ve şüpheli dosyalar için denetlemek üzere \"Tara\" seçeneğine tıklayın.", "detectedThreatsHeading": "Algılanan Tehditler", "detectedThreatsCount": "{{count}} tehdit", "detectedThreatsCountPlural": "{{count}} tehdit", @@ -67,8 +67,10 @@ "dbFetchLatest": "Güncellemeleri Denetle", "dbUpdating": "Güncelleniyor...", "dbEngine": "Motor", - "dbEngineYara": "YARA (WASM)", - "dbEngineRegex": "Regex Geri Dönüşü", + "dbEngineYara": "YARA-X (Yerel)", + "dbEngineRegex": "Regex Yedek Seçeneği", + "dbEngineCompiling": "Derleniyor...", + "dbCompiling": "İmza kuralları derleniyor ({{loaded}}/{{total}})...", "dbRulesLoaded": "Yüklenen Kurallar", "dbVersion": "İmza Sürümü", "dbLastUpdated": "Son Güncelleme", @@ -76,15 +78,15 @@ "dbSource": "Kural Kaynağı", "dbSourceCloud": "Bulut (önbelleğe alınmış)", "dbSourceBundled": "Uygulamayla birlikte gelir", - "dbSourceNone": "Hiç kural yüklenmedi", + "dbSourceNone": "Hiçbir kural yüklenmedi", "dbRuleFiles": "Kural Dosyaları", - "dbRuleFilesCounts": "{{bundled}} paketlenmiş, {{cached}} buluttan", + "dbRuleFilesCounts": "{{bundled}} paketle birlikte, {{cached}} buluttan", "dbUpdateSuccess": "v{{version}} sürümüne güncellendi ({{count}} kural)", "dbAlreadyCurrent": "İmzalar zaten güncel", "dbUpdateFailed": "İmzalar güncellenemedi", "quarantineEmptyTitle": "Karantinaya Alınmış Öğe Yok", "quarantineEmptyDescription": "Karantinaya taşınan dosyalar burada görünür. Bunları geri yükleyebilir veya kalıcı olarak silebilirsiniz.", - "quarantineHeading": "Karantinaya Alınmış Dosyalar", + "quarantineHeading": "Karantinaya Alınan Dosyalar", "quarantineCount": "{{count}} dosya", "quarantineCountPlural": "{{count}} dosya", "quarantineColumnFile": "Dosya", diff --git a/src/renderer/src/locales/uk/malware.json b/src/renderer/src/locales/uk/malware.json index 88a4b332..e5a4d5d6 100644 --- a/src/renderer/src/locales/uk/malware.json +++ b/src/renderer/src/locales/uk/malware.json @@ -26,22 +26,22 @@ "scanStatFiles": "Файли", "scanStatDuration": "Тривалість", "scanStatEngines": "Рушії", - "severityHeading": "Серйозність", + "severityHeading": "Рівень небезпеки", "selectedHeading": "Вибрано", - "selectedOfThreats": "з {{count}} загроз", + "selectedOfThreats": "із {{count}} загроз", "selectAll": "Вибрати все", - "deselectAll": "Скасувати вибір", + "deselectAll": "Зняти вибір з усього", "actingQuarantining": "Переміщення вибраних загроз до карантину...", "actingDeleting": "Видалення вибраних загроз...", "actionCompleteQuarantine": "Переміщення до карантину завершено", "actionCompleteDeletion": "Видалення завершено", - "actionResultQuarantined": "Переміщено до карантину: {{count}}", + "actionResultQuarantined": "Поміщено до карантину: {{count}}", "actionResultDeleted": "Видалено: {{count}}", "actionResultFailed": "Не вдалося: {{count}}", "noThreatsDetectedTitle": "Загроз не виявлено", - "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів у {{engineCount}} рушіях за {{duration}} с — ваша система чиста.", + "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів за допомогою {{engineCount}} рушіїв за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер шкідливого ПЗ", - "emptyStateDescription": "Натисніть \"Сканувати\", щоб перевірити систему на шкідливе ПЗ, рекламне ПЗ, криптомайнери та підозрілі файли.", + "emptyStateDescription": "Натисніть «Сканувати», щоб перевірити систему на шкідливе ПЗ, рекламне ПЗ, криптомайнери та підозрілі файли.", "detectedThreatsHeading": "Виявлені загрози", "detectedThreatsCount": "{{count}} загроза", "detectedThreatsCountPlural": "{{count}} загроз", @@ -49,16 +49,16 @@ "threatDetailFile": "Файл", "threatDetailSize": "Розмір", "threatDetailPath": "Шлях", - "confirmQuarantineTitle": "Перемістити загрози до карантину", + "confirmQuarantineTitle": "Помістити загрози до карантину", "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(и) до карантину. За потреби файли можна буде відновити пізніше.", - "confirmQuarantineLabel": "Перемістити до карантину", + "confirmQuarantineLabel": "Помістити до карантину", "confirmDeleteTitle": "Видалити загрози", - "confirmDeleteDescription": "Буде остаточно видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", - "confirmDeleteLabel": "Видалити остаточно", + "confirmDeleteDescription": "Буде назавжди видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", + "confirmDeleteLabel": "Видалити назавжди", "toastScanFailed": "Не вдалося виконати сканування на шкідливе ПЗ", "toastActionFailed": "Не вдалося {{action}} загрози", "toastActionFailedDescription": "Спробуйте запустити від імені адміністратора", - "errorOperationFailed": "Операція не вдалася — спробуйте запустити від імені адміністратора", + "errorOperationFailed": "Не вдалося виконати операцію — спробуйте запустити від імені адміністратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", "tabDatabase": "База даних", @@ -67,9 +67,11 @@ "dbFetchLatest": "Перевірити наявність оновлень", "dbUpdating": "Оновлення...", "dbEngine": "Рушій", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (вбудований)", "dbEngineRegex": "Резервний Regex", - "dbRulesLoaded": "Завантажено правил", + "dbEngineCompiling": "Компіляція...", + "dbCompiling": "Компіляція правил сигнатур ({{loaded}}/{{total}})...", + "dbRulesLoaded": "Правил завантажено", "dbVersion": "Версія сигнатур", "dbLastUpdated": "Останнє оновлення", "dbNever": "Ніколи", @@ -79,11 +81,11 @@ "dbSourceNone": "Правила не завантажено", "dbRuleFiles": "Файли правил", "dbRuleFilesCounts": "{{bundled}} у комплекті, {{cached}} із хмари", - "dbUpdateSuccess": "Оновлено до v{{version}} ({{count}} правил)", + "dbUpdateSuccess": "Оновлено до v{{version}} (правил: {{count}})", "dbAlreadyCurrent": "Сигнатури вже актуальні", "dbUpdateFailed": "Не вдалося оновити сигнатури", "quarantineEmptyTitle": "Елементів у карантині немає", - "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити остаточно.", + "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити назавжди.", "quarantineHeading": "Файли в карантині", "quarantineCount": "{{count}} файл", "quarantineCountPlural": "{{count}} файлів", @@ -93,20 +95,20 @@ "quarantineColumnOriginal": "Початкове розташування", "quarantineOriginalUnknown": "Невідомо", "quarantineRestoreButton": "Відновити", - "quarantineDeleteButton": "Видалити остаточно", + "quarantineDeleteButton": "Видалити назавжди", "quarantineSelectAll": "Вибрати все", - "quarantineDeselectAll": "Скасувати вибір", + "quarantineDeselectAll": "Зняти вибір з усього", "confirmRestoreTitle": "Відновити файли", "confirmRestoreDescription": "Буде відновлено {{count}} файл(ів) до початкового розташування. Відновлюйте лише ті файли, яким довіряєте.", "confirmRestoreLabel": "Відновити зараз", - "confirmDeleteQuarantineTitle": "Видалити остаточно", - "confirmDeleteQuarantineDescription": "Буде остаточно видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", - "confirmDeleteQuarantineLabel": "Видалити остаточно", + "confirmDeleteQuarantineTitle": "Видалити назавжди", + "confirmDeleteQuarantineDescription": "Буде назавжди видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", + "confirmDeleteQuarantineLabel": "Видалити назавжди", "quarantineLoading": "Завантаження карантину...", "quarantineRestoring": "Відновлення файлів...", "quarantineDeleting": "Видалення файлів...", "toastRestoreSuccess": "Відновлено файл(ів): {{count}}", "toastRestoreFailed": "Не вдалося відновити деякі файли", - "toastDeleteQuarantineSuccess": "Остаточно видалено файл(ів): {{count}}", + "toastDeleteQuarantineSuccess": "Назавжди видалено файл(ів): {{count}}", "toastRestoreNoOriginal": "Неможливо відновити — початкове розташування невідоме" } diff --git a/src/renderer/src/locales/vi/malware.json b/src/renderer/src/locales/vi/malware.json index f5e5f60c..91200090 100644 --- a/src/renderer/src/locales/vi/malware.json +++ b/src/renderer/src/locales/vi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Trình quét phần mềm độc hại", - "pageDescription": "Phát hiện mối đe dọa bằng nhiều công cụ — chữ ký, phân tích heuristic, phân tích tập lệnh, tính toàn vẹn hệ thống và quét cơ chế duy trì", + "pageDescription": "Phát hiện mối đe dọa bằng nhiều công cụ — chữ ký, phân tích heuristic, phân tích tập lệnh, tính toàn vẹn hệ thống và quét cơ chế bám trụ", "scanButtonScanning": "Đang quét...", "scanButton": "Quét", "quarantineButton": "Cách ly", @@ -39,7 +39,7 @@ "actionResultDeleted": "Đã xóa {{count}}", "actionResultFailed": "{{count}} thất bại", "noThreatsDetectedTitle": "Không phát hiện mối đe dọa", - "noThreatsDetectedDescription": "Đã quét {{filesScanned}} tệp trên {{engineCount}} công cụ trong {{duration}} giây — hệ thống của bạn sạch.", + "noThreatsDetectedDescription": "Đã quét {{filesScanned}} tệp bằng {{engineCount}} công cụ trong {{duration}} giây — hệ thống của bạn sạch.", "emptyStateTitle": "Trình quét phần mềm độc hại", "emptyStateDescription": "Nhấp vào \"Quét\" để kiểm tra hệ thống của bạn tìm phần mềm độc hại, phần mềm quảng cáo, trình đào tiền mã hóa và các tệp đáng ngờ.", "detectedThreatsHeading": "Các mối đe dọa đã phát hiện", @@ -67,8 +67,10 @@ "dbFetchLatest": "Kiểm tra bản cập nhật", "dbUpdating": "Đang cập nhật...", "dbEngine": "Công cụ", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X (Gốc)", "dbEngineRegex": "Regex dự phòng", + "dbEngineCompiling": "Đang biên dịch...", + "dbCompiling": "Đang biên dịch các quy tắc chữ ký ({{loaded}}/{{total}})...", "dbRulesLoaded": "Quy tắc đã tải", "dbVersion": "Phiên bản chữ ký", "dbLastUpdated": "Cập nhật lần cuối", @@ -80,11 +82,11 @@ "dbRuleFiles": "Tệp quy tắc", "dbRuleFilesCounts": "{{bundled}} đi kèm, {{cached}} từ đám mây", "dbUpdateSuccess": "Đã cập nhật lên v{{version}} ({{count}} quy tắc)", - "dbAlreadyCurrent": "Các chữ ký đã được cập nhật mới nhất", + "dbAlreadyCurrent": "Các chữ ký đã được cập nhật", "dbUpdateFailed": "Không thể cập nhật chữ ký", "quarantineEmptyTitle": "Không có mục nào trong vùng cách ly", "quarantineEmptyDescription": "Các tệp được chuyển vào vùng cách ly sẽ xuất hiện tại đây. Bạn có thể khôi phục hoặc xóa vĩnh viễn chúng.", - "quarantineHeading": "Tệp đã cách ly", + "quarantineHeading": "Các tệp đã cách ly", "quarantineCount": "{{count}} tệp", "quarantineCountPlural": "{{count}} tệp", "quarantineColumnFile": "Tệp", diff --git a/src/renderer/src/locales/zh-CN/malware.json b/src/renderer/src/locales/zh-CN/malware.json index 6c101032..e1fef44d 100644 --- a/src/renderer/src/locales/zh-CN/malware.json +++ b/src/renderer/src/locales/zh-CN/malware.json @@ -26,9 +26,9 @@ "scanStatFiles": "文件", "scanStatDuration": "时长", "scanStatEngines": "引擎", - "severityHeading": "严重级别", + "severityHeading": "严重程度", "selectedHeading": "已选择", - "selectedOfThreats": "/ 共 {{count}} 个威胁", + "selectedOfThreats": "共 {{count}} 个威胁", "selectAll": "全选", "deselectAll": "取消全选", "actingQuarantining": "正在隔离所选威胁...", @@ -50,7 +50,7 @@ "threatDetailSize": "大小", "threatDetailPath": "路径", "confirmQuarantineTitle": "隔离威胁", - "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以恢复这些文件。", + "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以恢复文件。", "confirmQuarantineLabel": "立即隔离", "confirmDeleteTitle": "删除威胁", "confirmDeleteDescription": "这将永久删除检测到的 {{count}} 个威胁。此操作无法撤销。", @@ -67,8 +67,10 @@ "dbFetchLatest": "检查更新", "dbUpdating": "正在更新...", "dbEngine": "引擎", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X(原生)", "dbEngineRegex": "Regex 回退", + "dbEngineCompiling": "正在编译...", + "dbCompiling": "正在编译签名规则({{loaded}}/{{total}})...", "dbRulesLoaded": "已加载规则", "dbVersion": "签名版本", "dbLastUpdated": "上次更新", @@ -78,13 +80,13 @@ "dbSourceBundled": "随应用附带", "dbSourceNone": "未加载规则", "dbRuleFiles": "规则文件", - "dbRuleFilesCounts": "{{bundled}} 个随附,{{cached}} 个来自云端", + "dbRuleFilesCounts": "{{bundled}} 个内置,{{cached}} 个来自云端", "dbUpdateSuccess": "已更新到 v{{version}}({{count}} 条规则)", "dbAlreadyCurrent": "签名已是最新", "dbUpdateFailed": "更新签名失败", - "quarantineEmptyTitle": "没有已隔离的项目", - "quarantineEmptyDescription": "移至隔离区的文件将显示在此处。您可以恢复或永久删除它们。", - "quarantineHeading": "已隔离的文件", + "quarantineEmptyTitle": "没有已隔离项目", + "quarantineEmptyDescription": "移至隔离区的文件将显示在这里。您可以恢复或永久删除它们。", + "quarantineHeading": "已隔离文件", "quarantineCount": "{{count}} 个文件", "quarantineCountPlural": "{{count}} 个文件", "quarantineColumnFile": "文件", diff --git a/src/renderer/src/locales/zh-TW/malware.json b/src/renderer/src/locales/zh-TW/malware.json index 594ccd43..fcf84116 100644 --- a/src/renderer/src/locales/zh-TW/malware.json +++ b/src/renderer/src/locales/zh-TW/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "惡意軟體掃描器", - "pageDescription": "多引擎威脅偵測 — 特徵碼、啟發式分析、指令碼分析、系統完整性與持續性掃描", + "pageDescription": "多引擎威脅偵測 — 簽章、啟發式分析、指令碼分析、系統完整性與持續性掃描", "scanButtonScanning": "掃描中...", "scanButton": "掃描", "quarantineButton": "隔離", @@ -13,14 +13,14 @@ "sourceDefenderMac": "程式碼簽署", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "啟發式分析", - "sourceSignature": "已知特徵碼", + "sourceSignature": "已知簽章", "initializingScanEngines": "正在初始化掃描引擎...", "filesScanned": "已掃描 {{count}} 個檔案", "threatCount": "{{count}} 個威脅", "threatCountPlural": "{{count}} 個威脅", "threatFound": "找到 {{count}} 個", "scanCategoryClean": "乾淨", - "scanCategoryNA": "不適用", + "scanCategoryNA": "N/A", "scanSummarySystemClean": "系統乾淨", "scanSummaryThreatsDetected": "已偵測到 {{count}} 個威脅", "scanStatFiles": "檔案", @@ -28,20 +28,20 @@ "scanStatEngines": "引擎", "severityHeading": "嚴重性", "selectedHeading": "已選取", - "selectedOfThreats": "共 {{count}} 個威脅", + "selectedOfThreats": "/ {{count}} 個威脅", "selectAll": "全選", "deselectAll": "取消全選", - "actingQuarantining": "正在隔離所選威脅...", - "actingDeleting": "正在刪除所選威脅...", + "actingQuarantining": "正在隔離選取的威脅...", + "actingDeleting": "正在刪除選取的威脅...", "actionCompleteQuarantine": "隔離完成", "actionCompleteDeletion": "刪除完成", "actionResultQuarantined": "已隔離 {{count}} 個", "actionResultDeleted": "已刪除 {{count}} 個", "actionResultFailed": "{{count}} 個失敗", "noThreatsDetectedTitle": "未偵測到威脅", - "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統是乾淨的。", + "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統很乾淨。", "emptyStateTitle": "惡意軟體掃描器", - "emptyStateDescription": "按一下「掃描」以檢查您的系統是否有惡意軟體、廣告軟體、加密貨幣挖礦程式與可疑檔案。", + "emptyStateDescription": "按一下「掃描」以檢查您的系統是否有惡意軟體、廣告軟體、加密貨幣挖礦程式和可疑檔案。", "detectedThreatsHeading": "已偵測到的威脅", "detectedThreatsCount": "{{count}} 個威脅", "detectedThreatsCountPlural": "{{count}} 個威脅", @@ -62,26 +62,28 @@ "tabScanner": "掃描器", "tabQuarantine": "隔離區", "tabDatabase": "資料庫", - "dbTitle": "特徵碼資料庫", + "dbTitle": "簽章資料庫", "dbDescription": "掃描器使用的 YARA 惡意軟體偵測規則", "dbFetchLatest": "檢查更新", "dbUpdating": "更新中...", "dbEngine": "引擎", - "dbEngineYara": "YARA (WASM)", + "dbEngineYara": "YARA-X(原生)", "dbEngineRegex": "Regex 備援", + "dbEngineCompiling": "編譯中...", + "dbCompiling": "正在編譯簽章規則({{loaded}}/{{total}})...", "dbRulesLoaded": "已載入規則", - "dbVersion": "特徵碼版本", + "dbVersion": "簽章版本", "dbLastUpdated": "上次更新", "dbNever": "從未", "dbSource": "規則來源", "dbSourceCloud": "雲端(已快取)", "dbSourceBundled": "隨應用程式附帶", - "dbSourceNone": "未載入規則", + "dbSourceNone": "未載入任何規則", "dbRuleFiles": "規則檔案", - "dbRuleFilesCounts": "{{bundled}} 個隨附,{{cached}} 個來自雲端", + "dbRuleFilesCounts": "{{bundled}} 個內建,{{cached}} 個來自雲端", "dbUpdateSuccess": "已更新至 v{{version}}({{count}} 條規則)", - "dbAlreadyCurrent": "特徵碼已是最新版本", - "dbUpdateFailed": "更新特徵碼失敗", + "dbAlreadyCurrent": "簽章已是最新版本", + "dbUpdateFailed": "更新簽章失敗", "quarantineEmptyTitle": "沒有已隔離的項目", "quarantineEmptyDescription": "移至隔離區的檔案會顯示在這裡。您可以還原或永久刪除它們。", "quarantineHeading": "已隔離的檔案", @@ -97,7 +99,7 @@ "quarantineSelectAll": "全選", "quarantineDeselectAll": "取消全選", "confirmRestoreTitle": "還原檔案", - "confirmRestoreDescription": "這會將 {{count}} 個檔案還原到其原始位置。請僅還原您信任的檔案。", + "confirmRestoreDescription": "這會將 {{count}} 個檔案還原到其原始位置。請只還原您信任的檔案。", "confirmRestoreLabel": "立即還原", "confirmDeleteQuarantineTitle": "永久刪除", "confirmDeleteQuarantineDescription": "這會永久刪除 {{count}} 個已隔離的檔案。此動作無法復原。", From 311fae4edce958547c81266083ff1eed5f1df564 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 06:02:06 +0200 Subject: [PATCH 25/35] fix(ui): rename engine label to "Kudu Cloud Signatures" Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/src/locales/en/malware.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/locales/en/malware.json b/src/renderer/src/locales/en/malware.json index 37d2aee0..5c755479 100644 --- a/src/renderer/src/locales/en/malware.json +++ b/src/renderer/src/locales/en/malware.json @@ -69,7 +69,7 @@ "dbFetchLatest": "Check for Updates", "dbUpdating": "Updating...", "dbEngine": "Engine", - "dbEngineYara": "YARA-X (Native)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex Fallback", "dbEngineCompiling": "Compiling...", "dbCompiling": "Compiling signature rules ({{loaded}}/{{total}})...", From a8fca09398c195042fd9066a7763fa6c365cf7ba Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 06:03:30 +0200 Subject: [PATCH 26/35] perf(startup): defer YARA compilation to first scan, reduce chunk size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove ensureYaraEngineStarted() from app startup — was blocking the main process for 10+ seconds even with setImmediate yields, causing a black screen on launch. YARA compilation now happens lazily on first scan. The app loads instantly. Also reduce chunk size from 20 to 5 files per yield (~35ms per chunk) so the UI stays smooth during compilation. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 6 ++---- src/main/services/yara-engine.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index dc884dad..18aac794 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -2015,8 +2015,6 @@ export function registerMalwareScannerIpc(getWindow: WindowGetter): void { // Start periodic YARA rule updates — runs for all users, not just cloud-linked startPeriodicRuleChecks(CLOUD_SERVER_URL, () => resetYaraEngine()) - // Start YARA engine compilation in the background so it's ready by the - // time the user runs a scan. This is non-blocking — yields to the event - // loop between rule file chunks so the UI stays responsive. - ensureYaraEngineStarted() + // YARA compilation is lazy — happens on first scan or when the user + // opens the Database tab and clicks "Check for Updates". No startup cost. } diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 65910b27..edf7b1ce 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -84,7 +84,7 @@ export class YaraEngine { const errors: string[] = [] let loaded = 0 const total = ruleFilePaths.length + extraSources.length - const CHUNK_SIZE = 20 // yield to event loop every N files + const CHUNK_SIZE = 5 // yield to event loop every N files (~35ms per chunk) // Load from file paths in chunks for (let i = 0; i < ruleFilePaths.length; i++) { From 0c1cf31ba640b3dbec06eb178cdf3bb4349f35e8 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 06:04:51 +0200 Subject: [PATCH 27/35] fix(ui): show Kudu Cloud Signatures when rules are cached but not yet compiled Engine status was showing "Regex Fallback" when rules exist on disk but haven't been compiled yet (compilation is lazy). Now shows "Kudu Cloud Signatures" when cached or bundled rules are present, regardless of compilation state. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 18aac794..5422517c 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -311,9 +311,11 @@ function getYaraRulesInfo(): YaraRulesInfo { if (cached.length > 0) source = 'cloud' else if (bundled.length > 0) source = 'bundled' + const hasRules = cached.length > 0 || bundled.length > 0 + return { available, - engine: compiling ? 'compiling' : available ? 'yara' : 'regex-fallback', + engine: compiling ? 'compiling' : available ? 'yara' : hasRules ? 'yara' : 'regex-fallback', rulesLoaded: engine?.rulesLoaded ?? 0, version: meta?.version ?? null, updatedAt: meta?.updatedAt ?? null, From e93ee6b1ff78242ac44f7ccf471e8ca742da9d83 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 29 Mar 2026 04:06:33 +0000 Subject: [PATCH 28/35] chore: auto-update translations --- src/renderer/src/locales/.checksums.json | 58 ++++++++++----------- src/renderer/src/locales/ar/malware.json | 26 ++++----- src/renderer/src/locales/cs/malware.json | 8 +-- src/renderer/src/locales/da/malware.json | 14 ++--- src/renderer/src/locales/de/malware.json | 24 ++++----- src/renderer/src/locales/el/malware.json | 38 +++++++------- src/renderer/src/locales/es/malware.json | 10 ++-- src/renderer/src/locales/fi/malware.json | 30 +++++------ src/renderer/src/locales/fr/malware.json | 26 ++++----- src/renderer/src/locales/he/malware.json | 28 +++++----- src/renderer/src/locales/hi/malware.json | 24 ++++----- src/renderer/src/locales/hu/malware.json | 24 ++++----- src/renderer/src/locales/id/malware.json | 18 +++---- src/renderer/src/locales/it/malware.json | 20 +++---- src/renderer/src/locales/ja/malware.json | 18 +++---- src/renderer/src/locales/ko/malware.json | 30 +++++------ src/renderer/src/locales/ms/malware.json | 20 +++---- src/renderer/src/locales/nl/malware.json | 18 +++---- src/renderer/src/locales/no/malware.json | 24 ++++----- src/renderer/src/locales/pl/malware.json | 40 +++++++------- src/renderer/src/locales/pt/malware.json | 14 ++--- src/renderer/src/locales/ro/malware.json | 18 +++---- src/renderer/src/locales/ru/malware.json | 18 +++---- src/renderer/src/locales/sv/malware.json | 10 ++-- src/renderer/src/locales/th/malware.json | 24 ++++----- src/renderer/src/locales/tr/malware.json | 22 ++++---- src/renderer/src/locales/uk/malware.json | 36 ++++++------- src/renderer/src/locales/vi/malware.json | 18 +++---- src/renderer/src/locales/zh-CN/malware.json | 14 ++--- src/renderer/src/locales/zh-TW/malware.json | 42 +++++++-------- 30 files changed, 357 insertions(+), 357 deletions(-) diff --git a/src/renderer/src/locales/.checksums.json b/src/renderer/src/locales/.checksums.json index 9a2218b7..364da9c1 100644 --- a/src/renderer/src/locales/.checksums.json +++ b/src/renderer/src/locales/.checksums.json @@ -3,7 +3,7 @@ "es/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "es/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "es/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "es/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "es/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "es/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "es/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "es/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -22,7 +22,7 @@ "fr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fr/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "fr/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "fr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -41,7 +41,7 @@ "de/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "de/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "de/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "de/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "de/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "de/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "de/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "de/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -60,7 +60,7 @@ "pt/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pt/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pt/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pt/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "pt/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "pt/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pt/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pt/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -79,7 +79,7 @@ "it/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "it/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "it/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "it/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "it/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "it/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "it/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "it/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -98,7 +98,7 @@ "ja/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ja/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ja/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ja/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ja/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ja/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ja/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ja/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -117,7 +117,7 @@ "ko/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ko/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "ko/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "ko/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ko/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ko/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ko/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ko/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -136,7 +136,7 @@ "zh-CN/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-CN/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-CN/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-CN/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "zh-CN/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "zh-CN/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-CN/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "zh-CN/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -155,7 +155,7 @@ "zh-TW/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "zh-TW/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "zh-TW/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "zh-TW/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "zh-TW/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "zh-TW/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "zh-TW/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "zh-TW/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -174,7 +174,7 @@ "ru/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ru/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ru/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ru/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ru/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ru/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ru/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ru/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -193,7 +193,7 @@ "ar/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ar/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ar/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ar/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ar/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ar/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ar/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ar/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -212,7 +212,7 @@ "hi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "hi/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "hi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -231,7 +231,7 @@ "tr/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "tr/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "tr/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "tr/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "tr/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "tr/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "tr/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "tr/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -250,7 +250,7 @@ "nl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "nl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "nl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "nl/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "nl/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "nl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "nl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "nl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -269,7 +269,7 @@ "pl/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "pl/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "pl/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "pl/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "pl/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "pl/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "pl/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "pl/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -288,7 +288,7 @@ "sv/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "sv/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "sv/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "sv/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "sv/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "sv/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "sv/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "sv/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -307,7 +307,7 @@ "no/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "no/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "no/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "no/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "no/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "no/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "no/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "no/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -326,7 +326,7 @@ "da/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "da/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "da/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "da/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "da/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "da/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "da/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "da/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -345,7 +345,7 @@ "fi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "fi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "fi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "fi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "fi/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "fi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "fi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "fi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -364,7 +364,7 @@ "cs/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "cs/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "cs/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "cs/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "cs/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "cs/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "cs/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "cs/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -383,7 +383,7 @@ "th/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "th/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "th/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "th/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "th/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "th/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "th/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "th/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -402,7 +402,7 @@ "vi/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "vi/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "vi/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "vi/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "vi/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "vi/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "vi/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "vi/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -421,7 +421,7 @@ "id/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "id/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "id/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "id/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "id/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "id/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "id/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "id/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -440,7 +440,7 @@ "ms/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ms/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ms/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ms/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ms/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ms/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ms/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "ms/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -459,7 +459,7 @@ "uk/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "uk/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", "uk/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", - "uk/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "uk/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "uk/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "uk/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "uk/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -478,7 +478,7 @@ "ro/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "ro/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "ro/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "ro/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "ro/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "ro/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "ro/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", "ro/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", @@ -497,7 +497,7 @@ "el/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "el/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "el/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "el/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "el/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "el/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "el/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "el/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", @@ -516,7 +516,7 @@ "he/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "he/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "he/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "he/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "he/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "he/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "he/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "he/hardening": "262c9c4b827b486f7b9f5bfdeb27eca2d63c3c0e439ae12de4f9ea9fb0319bec", @@ -535,7 +535,7 @@ "hu/cleaner": "ed440cf4ecfa4e928c667cf3415b2956ebd2688093c32ef0a47be6f1a6a46bfa", "hu/disk": "cb0036f0447062b13fbec3f37cf91f8fa7642be7c227ee6696709a20167c4c9c", "hu/dashboard": "d6ab3a604f360cd67acafa3e91e2251517da28d1ab4ce328ddf727045c52b70a", - "hu/malware": "29b37cd27705b06564ff85268a9cdce327c7b18ca0d9921828da0f0259083b95", + "hu/malware": "6398ae4a7cb90cddcf44aa9d1a46d1b04054b83313520eb1b37820e69949e28c", "hu/history": "445b5ceb891d6df4e09be1278a8272ff57531c087ab25834e70e83e61f700190", "hu/network": "673cd9572d1c5b76433c823ebb4887d21e75d6794df81cc77930c85499ecc7fc", "hu/onboarding": "749b6bfee636f97b1bc573a2598458e0df8af681be28f6e285104f54c3a0c54a", diff --git a/src/renderer/src/locales/ar/malware.json b/src/renderer/src/locales/ar/malware.json index 2b240140..7c15635b 100644 --- a/src/renderer/src/locales/ar/malware.json +++ b/src/renderer/src/locales/ar/malware.json @@ -1,12 +1,12 @@ { - "pageTitle": "فاحص البرمجيات الخبيثة", - "pageDescription": "اكتشاف التهديدات متعدد المحركات — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", + "pageTitle": "فاحص البرامج الضارة", + "pageDescription": "اكتشاف التهديدات بمحركات متعددة — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", "scanButtonScanning": "جارٍ الفحص...", "scanButton": "فحص", "quarantineButton": "الحجر", "deleteButton": "حذف", "severityCritical": "حرج", - "severityHigh": "مرتفع", + "severityHigh": "عالٍ", "severityMedium": "متوسط", "severityLow": "منخفض", "sourceDefenderWindows": "مضاد فيروسات أصلي", @@ -39,9 +39,9 @@ "actionResultDeleted": "تم حذف {{count}}", "actionResultFailed": "فشل {{count}}", "noThreatsDetectedTitle": "لم يتم اكتشاف أي تهديدات", - "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملفًا عبر {{engineCount}} محركات خلال {{duration}} ثانية — نظامك نظيف.", - "emptyStateTitle": "فاحص البرمجيات الخبيثة", - "emptyStateDescription": "انقر على \"فحص\" للتحقق من نظامك بحثًا عن البرمجيات الخبيثة والبرامج الإعلانية وعمال تعدين العملات المشفرة والملفات المشبوهة.", + "noThreatsDetectedDescription": "تم فحص {{filesScanned}} ملف عبر {{engineCount}} محرك خلال {{duration}} ثانية — نظامك نظيف.", + "emptyStateTitle": "فاحص البرامج الضارة", + "emptyStateDescription": "انقر فوق \"فحص\" للتحقق من نظامك بحثًا عن البرامج الضارة، والبرامج الإعلانية، ومُعدّني العملات الرقمية، والملفات المشبوهة.", "detectedThreatsHeading": "التهديدات المكتشفة", "detectedThreatsCount": "{{count}} تهديد", "detectedThreatsCountPlural": "{{count}} تهديدات", @@ -50,12 +50,12 @@ "threatDetailSize": "الحجم", "threatDetailPath": "المسار", "confirmQuarantineTitle": "نقل التهديدات إلى الحجر", - "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا إذا لزم الأمر.", + "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا عند الحاجة.", "confirmQuarantineLabel": "النقل إلى الحجر الآن", "confirmDeleteTitle": "حذف التهديدات", "confirmDeleteDescription": "سيؤدي هذا إلى حذف {{count}} من التهديدات المكتشفة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", "confirmDeleteLabel": "حذف نهائي", - "toastScanFailed": "فشل فحص البرمجيات الخبيثة", + "toastScanFailed": "فشل فحص البرامج الضارة", "toastActionFailed": "فشل {{action}} التهديدات", "toastActionFailedDescription": "حاول التشغيل كمسؤول", "errorOperationFailed": "فشلت العملية — حاول التشغيل كمسؤول", @@ -63,11 +63,11 @@ "tabQuarantine": "الحجر", "tabDatabase": "قاعدة البيانات", "dbTitle": "قاعدة بيانات التواقيع", - "dbDescription": "قواعد YARA لاكتشاف البرمجيات الخبيثة المستخدمة بواسطة الفاحص", + "dbDescription": "قواعد YARA لاكتشاف البرامج الضارة المستخدمة بواسطة الفاحص", "dbFetchLatest": "التحقق من وجود تحديثات", "dbUpdating": "جارٍ التحديث...", "dbEngine": "المحرك", - "dbEngineYara": "YARA-X (أصلي)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex احتياطي", "dbEngineCompiling": "جارٍ التجميع...", "dbCompiling": "جارٍ تجميع قواعد التواقيع ({{loaded}}/{{total}})...", @@ -77,15 +77,15 @@ "dbNever": "أبدًا", "dbSource": "مصدر القواعد", "dbSourceCloud": "السحابة (مخزنة مؤقتًا)", - "dbSourceBundled": "مضمّنة مع التطبيق", + "dbSourceBundled": "مضمنة مع التطبيق", "dbSourceNone": "لم يتم تحميل أي قواعد", "dbRuleFiles": "ملفات القواعد", - "dbRuleFilesCounts": "{{bundled}} مضمّنة، {{cached}} من السحابة", + "dbRuleFilesCounts": "{{bundled}} مضمنة، {{cached}} من السحابة", "dbUpdateSuccess": "تم التحديث إلى v{{version}} ({{count}} قاعدة)", "dbAlreadyCurrent": "التواقيع محدّثة بالفعل", "dbUpdateFailed": "فشل تحديث التواقيع", "quarantineEmptyTitle": "لا توجد عناصر في الحجر", - "quarantineEmptyDescription": "ستظهر هنا الملفات المنقولة إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", + "quarantineEmptyDescription": "ستظهر هنا الملفات التي تم نقلها إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", "quarantineHeading": "الملفات الموجودة في الحجر", "quarantineCount": "{{count}} ملف", "quarantineCountPlural": "{{count}} ملفات", diff --git a/src/renderer/src/locales/cs/malware.json b/src/renderer/src/locales/cs/malware.json index dd9ed52b..f629bfbe 100644 --- a/src/renderer/src/locales/cs/malware.json +++ b/src/renderer/src/locales/cs/malware.json @@ -64,10 +64,10 @@ "tabDatabase": "Databáze", "dbTitle": "Databáze signatur", "dbDescription": "Pravidla YARA pro detekci malwaru používaná skenerem", - "dbFetchLatest": "Zkontrolovat aktualizace", + "dbFetchLatest": "Vyhledat aktualizace", "dbUpdating": "Probíhá aktualizace...", "dbEngine": "Engine", - "dbEngineYara": "YARA-X (nativní)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Záložní Regex", "dbEngineCompiling": "Probíhá kompilace...", "dbCompiling": "Kompilace pravidel signatur ({{loaded}}/{{total}})...", @@ -80,10 +80,10 @@ "dbSourceBundled": "Součást aplikace", "dbSourceNone": "Nejsou načtena žádná pravidla", "dbRuleFiles": "Soubory pravidel", - "dbRuleFilesCounts": "{{bundled}} v aplikaci, {{cached}} z cloudu", + "dbRuleFilesCounts": "{{bundled}} součástí aplikace, {{cached}} z cloudu", "dbUpdateSuccess": "Aktualizováno na v{{version}} ({{count}} pravidel)", "dbAlreadyCurrent": "Signatury jsou již aktuální", - "dbUpdateFailed": "Aktualizaci signatur se nepodařilo provést", + "dbUpdateFailed": "Aktualizace signatur se nezdařila", "quarantineEmptyTitle": "Žádné položky v karanténě", "quarantineEmptyDescription": "Zde se zobrazí soubory přesunuté do karantény. Můžete je obnovit nebo trvale odstranit.", "quarantineHeading": "Soubory v karanténě", diff --git a/src/renderer/src/locales/da/malware.json b/src/renderer/src/locales/da/malware.json index f73f1241..d33274ab 100644 --- a/src/renderer/src/locales/da/malware.json +++ b/src/renderer/src/locales/da/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malware-scanner", - "pageDescription": "Registrering af trusler med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning for persistens", + "pageDescription": "Trusselsregistrering med flere motorer — signaturer, heuristik, scriptanalyse, systemintegritet og scanning for persistens", "scanButtonScanning": "Scanner...", "scanButton": "Scan", "quarantineButton": "Sæt i karantæne", @@ -67,13 +67,13 @@ "dbFetchLatest": "Søg efter opdateringer", "dbUpdating": "Opdaterer...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (indbygget)", + "dbEngineYara": "Kudu Cloud-signaturer", "dbEngineRegex": "Regex-reserve", "dbEngineCompiling": "Kompilerer...", "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regler indlæst", "dbVersion": "Signaturversion", - "dbLastUpdated": "Senest opdateret", + "dbLastUpdated": "Sidst opdateret", "dbNever": "Aldrig", "dbSource": "Regelkilde", "dbSourceCloud": "Cloud (cachelagret)", @@ -99,16 +99,16 @@ "quarantineSelectAll": "Vælg alle", "quarantineDeselectAll": "Fravælg alle", "confirmRestoreTitle": "Gendan filer", - "confirmRestoreDescription": "Dette gendanner {{count}} fil/filer til deres oprindelige placering. Gendan kun filer, du har tillid til.", + "confirmRestoreDescription": "Dette gendanner {{count}} fil(er) til deres oprindelige placering. Gendan kun filer, du har tillid til.", "confirmRestoreLabel": "Gendan nu", "confirmDeleteQuarantineTitle": "Slet permanent", - "confirmDeleteQuarantineDescription": "Dette sletter permanent {{count}} fil/filer i karantæne. Denne handling kan ikke fortrydes.", + "confirmDeleteQuarantineDescription": "Dette sletter permanent {{count}} fil(er) i karantæne. Denne handling kan ikke fortrydes.", "confirmDeleteQuarantineLabel": "Slet permanent", "quarantineLoading": "Indlæser karantæne...", "quarantineRestoring": "Gendanner filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil/filer gendannet", + "toastRestoreSuccess": "{{count}} fil(er) gendannet", "toastRestoreFailed": "Kunne ikke gendanne nogle filer", - "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", "toastRestoreNoOriginal": "Kan ikke gendanne — oprindelig placering er ukendt" } diff --git a/src/renderer/src/locales/de/malware.json b/src/renderer/src/locales/de/malware.json index f87ea611..91d1c9a1 100644 --- a/src/renderer/src/locales/de/malware.json +++ b/src/renderer/src/locales/de/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Malware-Scanner", - "pageDescription": "Bedrohungserkennung mit mehreren Engines — Signaturen, Heuristik, Skriptanalyse, Systemintegrität und Persistenzprüfung", - "scanButtonScanning": "Scan läuft...", + "pageDescription": "Bedrohungserkennung mit mehreren Engines — Signaturen, Heuristiken, Skriptanalyse, Systemintegrität und Persistenzprüfung", + "scanButtonScanning": "Scan wird ausgeführt...", "scanButton": "Scannen", "quarantineButton": "In Quarantäne verschieben", "deleteButton": "Löschen", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} Bedrohungen", "threatFound": "{{count}} gefunden", "scanCategoryClean": "sauber", - "scanCategoryNA": "k. A.", + "scanCategoryNA": "N/V", "scanSummarySystemClean": "System sauber", "scanSummaryThreatsDetected": "{{count}} Bedrohungen erkannt", "scanStatFiles": "Dateien", @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} gelöscht", "actionResultFailed": "{{count}} fehlgeschlagen", "noThreatsDetectedTitle": "Keine Bedrohungen erkannt", - "noThreatsDetectedDescription": "{{filesScanned}} Dateien mit {{engineCount}} Engines in {{duration}}s gescannt — Ihr System ist sauber.", + "noThreatsDetectedDescription": "{{filesScanned}} Dateien mit {{engineCount}} Engines in {{duration}} s gescannt — Ihr System ist sauber.", "emptyStateTitle": "Malware-Scanner", - "emptyStateDescription": "Klicken Sie auf „Scannen“, um Ihr System auf Malware, Adware, Krypto-Miner und verdächtige Dateien zu überprüfen.", + "emptyStateDescription": "Klicken Sie auf \"Scannen\", um Ihr System auf Malware, Adware, Krypto-Miner und verdächtige Dateien zu überprüfen.", "detectedThreatsHeading": "Erkannte Bedrohungen", "detectedThreatsCount": "{{count}} Bedrohung", "detectedThreatsCountPlural": "{{count}} Bedrohungen", @@ -50,15 +50,15 @@ "threatDetailSize": "Größe", "threatDetailPath": "Pfad", "confirmQuarantineTitle": "Bedrohungen in Quarantäne verschieben", - "confirmQuarantineDescription": "{{count}} erkannte Bedrohung(en) werden in Quarantäne verschoben. Dateien können bei Bedarf später wiederhergestellt werden.", + "confirmQuarantineDescription": "Dadurch werden {{count}} erkannte Bedrohung(en) in Quarantäne verschoben. Dateien können bei Bedarf später wiederhergestellt werden.", "confirmQuarantineLabel": "Jetzt in Quarantäne verschieben", "confirmDeleteTitle": "Bedrohungen löschen", - "confirmDeleteDescription": "{{count}} erkannte Bedrohung(en) werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmDeleteDescription": "Dadurch werden {{count}} erkannte Bedrohung(en) dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeleteLabel": "Dauerhaft löschen", "toastScanFailed": "Malware-Scan fehlgeschlagen", "toastActionFailed": "Bedrohungen konnten nicht {{action}} werden", - "toastActionFailedDescription": "Versuchen Sie, die Anwendung als Administrator auszuführen", - "errorOperationFailed": "Vorgang fehlgeschlagen — versuchen Sie, die Anwendung als Administrator auszuführen", + "toastActionFailedDescription": "Versuchen Sie, das Programm als Administrator auszuführen", + "errorOperationFailed": "Vorgang fehlgeschlagen — versuchen Sie, das Programm als Administrator auszuführen", "tabScanner": "Scanner", "tabQuarantine": "Quarantäne", "tabDatabase": "Datenbank", @@ -67,7 +67,7 @@ "dbFetchLatest": "Nach Updates suchen", "dbUpdating": "Wird aktualisiert...", "dbEngine": "Engine", - "dbEngineYara": "YARA-X (nativ)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex-Fallback", "dbEngineCompiling": "Wird kompiliert...", "dbCompiling": "Signaturregeln werden kompiliert ({{loaded}}/{{total}})...", @@ -99,10 +99,10 @@ "quarantineSelectAll": "Alle auswählen", "quarantineDeselectAll": "Auswahl aufheben", "confirmRestoreTitle": "Dateien wiederherstellen", - "confirmRestoreDescription": "{{count}} Datei(en) werden an ihrem ursprünglichen Speicherort wiederhergestellt. Stellen Sie nur Dateien wieder her, denen Sie vertrauen.", + "confirmRestoreDescription": "Dadurch werden {{count}} Datei(en) an ihrem ursprünglichen Speicherort wiederhergestellt. Stellen Sie nur Dateien wieder her, denen Sie vertrauen.", "confirmRestoreLabel": "Jetzt wiederherstellen", "confirmDeleteQuarantineTitle": "Dauerhaft löschen", - "confirmDeleteQuarantineDescription": "{{count}} Datei(en) in Quarantäne werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", + "confirmDeleteQuarantineDescription": "Dadurch werden {{count}} Datei(en) in Quarantäne dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.", "confirmDeleteQuarantineLabel": "Dauerhaft löschen", "quarantineLoading": "Quarantäne wird geladen...", "quarantineRestoring": "Dateien werden wiederhergestellt...", diff --git a/src/renderer/src/locales/el/malware.json b/src/renderer/src/locales/el/malware.json index 8bcb5a83..3d7e4db1 100644 --- a/src/renderer/src/locales/el/malware.json +++ b/src/renderer/src/locales/el/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Σαρωτής κακόβουλου λογισμικού", - "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση μηχανισμών επιμονής", - "scanButtonScanning": "Σάρωση...", + "pageDescription": "Ανίχνευση απειλών πολλαπλών μηχανών — υπογραφές, ευρετική ανάλυση, ανάλυση σεναρίων, ακεραιότητα συστήματος και σάρωση μονιμότητας", + "scanButtonScanning": "Γίνεται σάρωση...", "scanButton": "Σάρωση", "quarantineButton": "Καραντίνα", "deleteButton": "Διαγραφή", @@ -21,7 +21,7 @@ "threatFound": "Βρέθηκαν {{count}}", "scanCategoryClean": "καθαρό", "scanCategoryNA": "Δ/Υ", - "scanSummarySystemClean": "Καθαρό σύστημα", + "scanSummarySystemClean": "Το σύστημα είναι καθαρό", "scanSummaryThreatsDetected": "Εντοπίστηκαν {{count}} απειλές", "scanStatFiles": "Αρχεία", "scanStatDuration": "Διάρκεια", @@ -31,8 +31,8 @@ "selectedOfThreats": "από {{count}} απειλές", "selectAll": "Επιλογή όλων", "deselectAll": "Κατάργηση επιλογής όλων", - "actingQuarantining": "Μεταφορά των επιλεγμένων απειλών σε καραντίνα...", - "actingDeleting": "Διαγραφή των επιλεγμένων απειλών...", + "actingQuarantining": "Τα επιλεγμένα στοιχεία μεταφέρονται σε καραντίνα...", + "actingDeleting": "Τα επιλεγμένα στοιχεία διαγράφονται...", "actionCompleteQuarantine": "Η μεταφορά σε καραντίνα ολοκληρώθηκε", "actionCompleteDeletion": "Η διαγραφή ολοκληρώθηκε", "actionResultQuarantined": "{{count}} σε καραντίνα", @@ -50,10 +50,10 @@ "threatDetailSize": "Μέγεθος", "threatDetailPath": "Διαδρομή", "confirmQuarantineTitle": "Μεταφορά απειλών σε καραντίνα", - "confirmQuarantineDescription": "Αυτό θα μεταφέρει {{count}} εντοπισμένη(-ες) απειλή(-ές) σε καραντίνα. Τα αρχεία μπορούν να αποκατασταθούν αργότερα, αν χρειαστεί.", + "confirmQuarantineDescription": "Αυτό θα μεταφέρει {{count}} εντοπισμένες απειλές σε καραντίνα. Τα αρχεία μπορούν να αποκατασταθούν αργότερα, αν χρειαστεί.", "confirmQuarantineLabel": "Μεταφορά σε καραντίνα τώρα", "confirmDeleteTitle": "Διαγραφή απειλών", - "confirmDeleteDescription": "Αυτό θα διαγράψει οριστικά {{count}} εντοπισμένη(-ες) απειλή(-ές). Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "confirmDeleteDescription": "Αυτό θα διαγράψει οριστικά {{count}} εντοπισμένες απειλές. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmDeleteLabel": "Οριστική διαγραφή", "toastScanFailed": "Η σάρωση για κακόβουλο λογισμικό απέτυχε", "toastActionFailed": "Αποτυχία {{action}} απειλών", @@ -67,9 +67,9 @@ "dbFetchLatest": "Έλεγχος για ενημερώσεις", "dbUpdating": "Γίνεται ενημέρωση...", "dbEngine": "Μηχανή", - "dbEngineYara": "YARA-X (Εγγενές)", - "dbEngineRegex": "Εφεδρικό Regex", - "dbEngineCompiling": "Μεταγλώττιση...", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "Γίνεται μεταγλώττιση...", "dbCompiling": "Μεταγλώττιση κανόνων υπογραφών ({{loaded}}/{{total}})...", "dbRulesLoaded": "Φορτωμένοι κανόνες", "dbVersion": "Έκδοση υπογραφών", @@ -77,11 +77,11 @@ "dbNever": "Ποτέ", "dbSource": "Πηγή κανόνων", "dbSourceCloud": "Cloud (στη μνήμη cache)", - "dbSourceBundled": "Περιλαμβάνεται στην εφαρμογή", - "dbSourceNone": "Δεν έχουν φορτωθεί κανόνες", + "dbSourceBundled": "Περιλαμβάνεται με την εφαρμογή", + "dbSourceNone": "Δεν φορτώθηκαν κανόνες", "dbRuleFiles": "Αρχεία κανόνων", "dbRuleFilesCounts": "{{bundled}} ενσωματωμένα, {{cached}} από το cloud", - "dbUpdateSuccess": "Ενημερώθηκε σε v{{version}} ({{count}} κανόνες)", + "dbUpdateSuccess": "Ενημερώθηκε στην έκδοση v{{version}} ({{count}} κανόνες)", "dbAlreadyCurrent": "Οι υπογραφές είναι ήδη ενημερωμένες", "dbUpdateFailed": "Αποτυχία ενημέρωσης υπογραφών", "quarantineEmptyTitle": "Δεν υπάρχουν στοιχεία σε καραντίνα", @@ -99,16 +99,16 @@ "quarantineSelectAll": "Επιλογή όλων", "quarantineDeselectAll": "Κατάργηση επιλογής όλων", "confirmRestoreTitle": "Αποκατάσταση αρχείων", - "confirmRestoreDescription": "Αυτό θα αποκαταστήσει {{count}} αρχείο(-α) στην αρχική του θέση. Αποκαθιστάτε μόνο αρχεία που εμπιστεύεστε.", + "confirmRestoreDescription": "Αυτό θα αποκαταστήσει {{count}} αρχεία στην αρχική τους θέση. Αποκαθιστάτε μόνο αρχεία που εμπιστεύεστε.", "confirmRestoreLabel": "Αποκατάσταση τώρα", "confirmDeleteQuarantineTitle": "Οριστική διαγραφή", - "confirmDeleteQuarantineDescription": "Αυτό θα διαγράψει οριστικά {{count}} αρχείο(-α) σε καραντίνα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "confirmDeleteQuarantineDescription": "Αυτό θα διαγράψει οριστικά {{count}} αρχεία σε καραντίνα. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", "confirmDeleteQuarantineLabel": "Οριστική διαγραφή", "quarantineLoading": "Φόρτωση καραντίνας...", - "quarantineRestoring": "Αποκατάσταση αρχείων...", - "quarantineDeleting": "Διαγραφή αρχείων...", - "toastRestoreSuccess": "Αποκαταστάθηκαν {{count}} αρχείο(-α)", + "quarantineRestoring": "Γίνεται αποκατάσταση αρχείων...", + "quarantineDeleting": "Γίνεται διαγραφή αρχείων...", + "toastRestoreSuccess": "Αποκαταστάθηκαν {{count}} αρχεία", "toastRestoreFailed": "Αποτυχία αποκατάστασης ορισμένων αρχείων", - "toastDeleteQuarantineSuccess": "Διαγράφηκαν οριστικά {{count}} αρχείο(-α)", + "toastDeleteQuarantineSuccess": "Διαγράφηκαν οριστικά {{count}} αρχεία", "toastRestoreNoOriginal": "Δεν είναι δυνατή η αποκατάσταση — η αρχική θέση είναι άγνωστη" } diff --git a/src/renderer/src/locales/es/malware.json b/src/renderer/src/locales/es/malware.json index fee23fba..1bd5b44a 100644 --- a/src/renderer/src/locales/es/malware.json +++ b/src/renderer/src/locales/es/malware.json @@ -55,7 +55,7 @@ "confirmDeleteTitle": "Eliminar amenazas", "confirmDeleteDescription": "Esto eliminará permanentemente {{count}} amenaza(s) detectada(s). Esta acción no se puede deshacer.", "confirmDeleteLabel": "Eliminar permanentemente", - "toastScanFailed": "Error al analizar en busca de malware", + "toastScanFailed": "Error al analizar malware", "toastActionFailed": "No se pudieron {{action}} las amenazas", "toastActionFailedDescription": "Intente ejecutar como administrador", "errorOperationFailed": "La operación falló; intente ejecutar como administrador", @@ -67,12 +67,12 @@ "dbFetchLatest": "Buscar actualizaciones", "dbUpdating": "Actualizando...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (nativo)", + "dbEngineYara": "Firmas en la nube de Kudu", "dbEngineRegex": "Alternativa Regex", "dbEngineCompiling": "Compilando...", "dbCompiling": "Compilando reglas de firmas ({{loaded}}/{{total}})...", "dbRulesLoaded": "Reglas cargadas", - "dbVersion": "Versión de firmas", + "dbVersion": "Versión de las firmas", "dbLastUpdated": "Última actualización", "dbNever": "Nunca", "dbSource": "Origen de las reglas", @@ -85,7 +85,7 @@ "dbAlreadyCurrent": "Las firmas ya están actualizadas", "dbUpdateFailed": "No se pudieron actualizar las firmas", "quarantineEmptyTitle": "No hay elementos en cuarentena", - "quarantineEmptyDescription": "Los archivos movidos a cuarentena aparecerán aquí. Puede restaurarlos o eliminarlos permanentemente.", + "quarantineEmptyDescription": "Los archivos movidos a la cuarentena aparecerán aquí. Puede restaurarlos o eliminarlos permanentemente.", "quarantineHeading": "Archivos en cuarentena", "quarantineCount": "{{count}} archivo", "quarantineCountPlural": "{{count}} archivos", @@ -110,5 +110,5 @@ "toastRestoreSuccess": "{{count}} archivo(s) restaurado(s)", "toastRestoreFailed": "No se pudieron restaurar algunos archivos", "toastDeleteQuarantineSuccess": "{{count}} archivo(s) eliminado(s) permanentemente", - "toastRestoreNoOriginal": "No se puede restaurar; se desconoce la ubicación original" + "toastRestoreNoOriginal": "No se puede restaurar; la ubicación original es desconocida" } diff --git a/src/renderer/src/locales/fi/malware.json b/src/renderer/src/locales/fi/malware.json index 55b2f700..9f44e0be 100644 --- a/src/renderer/src/locales/fi/malware.json +++ b/src/renderer/src/locales/fi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Haittaohjelmaskanneri", - "pageDescription": "Monimoottorinen uhkien tunnistus — allekirjoitukset, heuristiikka, komentosarja-analyysi, järjestelmän eheys ja pysyvyyden tarkistus", + "pageDescription": "Monimoottorinen uhkien tunnistus — allekirjoitukset, heuristiikka, skriptianalyysi, järjestelmän eheys ja pysyvyyden tarkistus", "scanButtonScanning": "Skannataan...", "scanButton": "Skannaa", "quarantineButton": "Siirrä karanteeniin", @@ -18,7 +18,7 @@ "filesScanned": "{{count}} tiedostoa skannattu", "threatCount": "{{count}} uhka", "threatCountPlural": "{{count}} uhkaa", - "threatFound": "{{count}} löytyi", + "threatFound": "{{count}} löydetty", "scanCategoryClean": "puhdas", "scanCategoryNA": "Ei käytettävissä", "scanSummarySystemClean": "Järjestelmä puhdas", @@ -49,14 +49,14 @@ "threatDetailFile": "Tiedosto", "threatDetailSize": "Koko", "threatDetailPath": "Polku", - "confirmQuarantineTitle": "Siirrä uhkat karanteeniin", - "confirmQuarantineDescription": "Tämä siirtää {{count}} havaittua uhkaa karanteeniin. Tiedostot voidaan tarvittaessa palauttaa myöhemmin.", + "confirmQuarantineTitle": "Siirrä uhkia karanteeniin", + "confirmQuarantineDescription": "Tämä siirtää {{count}} havaitun uhan karanteeniin. Tiedostot voidaan tarvittaessa palauttaa myöhemmin.", "confirmQuarantineLabel": "Siirrä karanteeniin nyt", - "confirmDeleteTitle": "Poista uhat", + "confirmDeleteTitle": "Poista uhkia", "confirmDeleteDescription": "Tämä poistaa pysyvästi {{count}} havaittua uhkaa. Tätä toimintoa ei voi kumota.", "confirmDeleteLabel": "Poista pysyvästi", "toastScanFailed": "Haittaohjelmaskannaus epäonnistui", - "toastActionFailed": "Uhkiin kohdistuva toiminto {{action}} epäonnistui", + "toastActionFailed": "Uhien toiminto \"{{action}}\" epäonnistui", "toastActionFailedDescription": "Yritä suorittaa järjestelmänvalvojana", "errorOperationFailed": "Toiminto epäonnistui — yritä suorittaa järjestelmänvalvojana", "tabScanner": "Skanneri", @@ -67,11 +67,11 @@ "dbFetchLatest": "Tarkista päivitykset", "dbUpdating": "Päivitetään...", "dbEngine": "Moottori", - "dbEngineYara": "YARA-X (natiivi)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex-varajärjestelmä", "dbEngineCompiling": "Käännetään...", "dbCompiling": "Käännetään allekirjoitussääntöjä ({{loaded}}/{{total}})...", - "dbRulesLoaded": "Sääntöjä ladattu", + "dbRulesLoaded": "Säännöt ladattu", "dbVersion": "Allekirjoitusversio", "dbLastUpdated": "Viimeksi päivitetty", "dbNever": "Ei koskaan", @@ -84,9 +84,9 @@ "dbUpdateSuccess": "Päivitetty versioon v{{version}} ({{count}} sääntöä)", "dbAlreadyCurrent": "Allekirjoitukset ovat jo ajan tasalla", "dbUpdateFailed": "Allekirjoitusten päivitys epäonnistui", - "quarantineEmptyTitle": "Ei karanteeniin siirrettyjä kohteita", + "quarantineEmptyTitle": "Ei karanteenissa olevia kohteita", "quarantineEmptyDescription": "Karanteeniin siirretyt tiedostot näkyvät täällä. Voit palauttaa ne tai poistaa ne pysyvästi.", - "quarantineHeading": "Karanteeniin siirretyt tiedostot", + "quarantineHeading": "Karanteenissa olevat tiedostot", "quarantineCount": "{{count}} tiedosto", "quarantineCountPlural": "{{count}} tiedostoa", "quarantineColumnFile": "Tiedosto", @@ -98,17 +98,17 @@ "quarantineDeleteButton": "Poista pysyvästi", "quarantineSelectAll": "Valitse kaikki", "quarantineDeselectAll": "Poista kaikkien valinta", - "confirmRestoreTitle": "Palauta tiedostot", + "confirmRestoreTitle": "Palauta tiedostoja", "confirmRestoreDescription": "Tämä palauttaa {{count}} tiedostoa niiden alkuperäiseen sijaintiin. Palauta vain tiedostoja, joihin luotat.", "confirmRestoreLabel": "Palauta nyt", "confirmDeleteQuarantineTitle": "Poista pysyvästi", - "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteeniin siirrettyä tiedostoa. Tätä toimintoa ei voi kumota.", + "confirmDeleteQuarantineDescription": "Tämä poistaa pysyvästi {{count}} karanteenissa olevaa tiedostoa. Tätä toimintoa ei voi kumota.", "confirmDeleteQuarantineLabel": "Poista pysyvästi", "quarantineLoading": "Ladataan karanteenia...", "quarantineRestoring": "Palautetaan tiedostoja...", "quarantineDeleting": "Poistetaan tiedostoja...", - "toastRestoreSuccess": "{{count}} tiedosto(a) palautettu", + "toastRestoreSuccess": "{{count}} tiedostoa palautettu", "toastRestoreFailed": "Joidenkin tiedostojen palautus epäonnistui", - "toastDeleteQuarantineSuccess": "{{count}} tiedosto(a) poistettu pysyvästi", - "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti ei ole tiedossa" + "toastDeleteQuarantineSuccess": "{{count}} tiedostoa poistettu pysyvästi", + "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti tuntematon" } diff --git a/src/renderer/src/locales/fr/malware.json b/src/renderer/src/locales/fr/malware.json index 3400cd2a..e6acaef0 100644 --- a/src/renderer/src/locales/fr/malware.json +++ b/src/renderer/src/locales/fr/malware.json @@ -14,7 +14,7 @@ "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analyse heuristique", "sourceSignature": "Signature connue", - "initializingScanEngines": "Initialisation des moteurs d’analyse...", + "initializingScanEngines": "Initialisation des moteurs d'analyse...", "filesScanned": "{{count}} fichiers analysés", "threatCount": "{{count}} menace", "threatCountPlural": "{{count}} menaces", @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} supprimées", "actionResultFailed": "{{count}} échecs", "noThreatsDetectedTitle": "Aucune menace détectée", - "noThreatsDetectedDescription": "Analyse de {{filesScanned}} fichiers avec {{engineCount}} moteurs en {{duration}}s — votre système est propre.", + "noThreatsDetectedDescription": "Analyse de {{filesScanned}} fichiers avec {{engineCount}} moteurs en {{duration}} s — votre système est propre.", "emptyStateTitle": "Analyseur de malware", - "emptyStateDescription": "Cliquez sur \"Analyser\" pour rechercher des malware, adware, mineurs de cryptomonnaie et fichiers suspects sur votre système.", + "emptyStateDescription": "Cliquez sur \"Analyser\" pour rechercher des malware, adwares, mineurs de cryptomonnaie et fichiers suspects sur votre système.", "detectedThreatsHeading": "Menaces détectées", "detectedThreatsCount": "{{count}} menace", "detectedThreatsCountPlural": "{{count}} menaces", @@ -55,19 +55,19 @@ "confirmDeleteTitle": "Supprimer les menaces", "confirmDeleteDescription": "Cette action supprimera définitivement {{count}} menace(s) détectée(s). Cette action est irréversible.", "confirmDeleteLabel": "Supprimer définitivement", - "toastScanFailed": "L’analyse des malware a échoué", - "toastActionFailed": "Échec de l’action {{action}} sur les menaces", - "toastActionFailedDescription": "Essayez d’exécuter en tant qu’administrateur", - "errorOperationFailed": "L’opération a échoué — essayez d’exécuter en tant qu’administrateur", + "toastScanFailed": "L'analyse des malware a échoué", + "toastActionFailed": "Échec de l'action {{action}} sur les menaces", + "toastActionFailedDescription": "Essayez d'exécuter en tant qu'administrateur", + "errorOperationFailed": "L'opération a échoué — essayez d'exécuter en tant qu'administrateur", "tabScanner": "Analyseur", "tabQuarantine": "Quarantaine", "tabDatabase": "Base de données", "dbTitle": "Base de signatures", - "dbDescription": "Règles de détection de malware YARA utilisées par l’analyseur", + "dbDescription": "Règles de détection de malware YARA utilisées par l'analyseur", "dbFetchLatest": "Rechercher des mises à jour", "dbUpdating": "Mise à jour...", "dbEngine": "Moteur", - "dbEngineYara": "YARA-X (natif)", + "dbEngineYara": "Signatures cloud Kudu", "dbEngineRegex": "Secours Regex", "dbEngineCompiling": "Compilation...", "dbCompiling": "Compilation des règles de signature ({{loaded}}/{{total}})...", @@ -77,7 +77,7 @@ "dbNever": "Jamais", "dbSource": "Source des règles", "dbSourceCloud": "Cloud (en cache)", - "dbSourceBundled": "Incluses avec l’application", + "dbSourceBundled": "Incluses avec l'application", "dbSourceNone": "Aucune règle chargée", "dbRuleFiles": "Fichiers de règles", "dbRuleFilesCounts": "{{bundled}} incluses, {{cached}} depuis le cloud", @@ -92,14 +92,14 @@ "quarantineColumnFile": "Fichier", "quarantineColumnDate": "Date", "quarantineColumnSize": "Taille", - "quarantineColumnOriginal": "Emplacement d’origine", + "quarantineColumnOriginal": "Emplacement d'origine", "quarantineOriginalUnknown": "Inconnu", "quarantineRestoreButton": "Restaurer", "quarantineDeleteButton": "Supprimer définitivement", "quarantineSelectAll": "Tout sélectionner", "quarantineDeselectAll": "Tout désélectionner", "confirmRestoreTitle": "Restaurer les fichiers", - "confirmRestoreDescription": "Cette action restaurera {{count}} fichier(s) à leur emplacement d’origine. Ne restaurez que les fichiers auxquels vous faites confiance.", + "confirmRestoreDescription": "Cette action restaurera {{count}} fichier(s) à leur emplacement d'origine. Ne restaurez que les fichiers auxquels vous faites confiance.", "confirmRestoreLabel": "Restaurer maintenant", "confirmDeleteQuarantineTitle": "Supprimer définitivement", "confirmDeleteQuarantineDescription": "Cette action supprimera définitivement {{count}} fichier(s) en quarantaine. Cette action est irréversible.", @@ -110,5 +110,5 @@ "toastRestoreSuccess": "{{count}} fichier(s) restauré(s)", "toastRestoreFailed": "Échec de la restauration de certains fichiers", "toastDeleteQuarantineSuccess": "{{count}} fichier(s) supprimé(s) définitivement", - "toastRestoreNoOriginal": "Impossible de restaurer — emplacement d’origine inconnu" + "toastRestoreNoOriginal": "Impossible de restaurer — emplacement d'origine inconnu" } diff --git a/src/renderer/src/locales/he/malware.json b/src/renderer/src/locales/he/malware.json index f96e0046..8b87d813 100644 --- a/src/renderer/src/locales/he/malware.json +++ b/src/renderer/src/locales/he/malware.json @@ -22,7 +22,7 @@ "scanCategoryClean": "נקי", "scanCategoryNA": "לא זמין", "scanSummarySystemClean": "המערכת נקייה", - "scanSummaryThreatsDetected": "זוהו {{count}} איומים", + "scanSummaryThreatsDetected": "{{count}} איומים זוהו", "scanStatFiles": "קבצים", "scanStatDuration": "משך", "scanStatEngines": "מנועים", @@ -50,25 +50,25 @@ "threatDetailSize": "גודל", "threatDetailPath": "נתיב", "confirmQuarantineTitle": "העבר איומים להסגר", - "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן לשחזר קבצים מאוחר יותר במידת הצורך.", - "confirmQuarantineLabel": "העבר להסגר עכשיו", + "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איומים שזוהו להסגר. ניתן יהיה לשחזר את הקבצים מאוחר יותר במידת הצורך.", + "confirmQuarantineLabel": "העבר להסגר כעת", "confirmDeleteTitle": "מחק איומים", - "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איום/איומים שזוהו. לא ניתן לבטל פעולה זו.", + "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איומים שזוהו. לא ניתן לבטל פעולה זו.", "confirmDeleteLabel": "מחק לצמיתות", "toastScanFailed": "סריקת הנוזקות נכשלה", - "toastActionFailed": "הפעולה {{action}} האיומים נכשלה", + "toastActionFailed": "לא ניתן היה {{action}} את האיומים", "toastActionFailedDescription": "נסה להפעיל כמנהל מערכת", "errorOperationFailed": "הפעולה נכשלה — נסה להפעיל כמנהל מערכת", "tabScanner": "סורק", "tabQuarantine": "הסגר", "tabDatabase": "מסד נתונים", "dbTitle": "מסד נתוני חתימות", - "dbDescription": "כללי זיהוי נוזקות של YARA המשמשים את הסורק", + "dbDescription": "כללי זיהוי נוזקות של YARA שבהם הסורק משתמש", "dbFetchLatest": "בדוק אם קיימים עדכונים", "dbUpdating": "מעדכן...", "dbEngine": "מנוע", - "dbEngineYara": "YARA-X (מובנה)", - "dbEngineRegex": "Regex חלופי", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", "dbEngineCompiling": "מהדר...", "dbCompiling": "מהדר כללי חתימות ({{loaded}}/{{total}})...", "dbRulesLoaded": "כללים שנטענו", @@ -77,7 +77,7 @@ "dbNever": "מעולם לא", "dbSource": "מקור הכללים", "dbSourceCloud": "ענן (במטמון)", - "dbSourceBundled": "כלול עם היישום", + "dbSourceBundled": "כלול ביישום", "dbSourceNone": "לא נטענו כללים", "dbRuleFiles": "קובצי כללים", "dbRuleFilesCounts": "{{bundled}} כלולים, {{cached}} מהענן", @@ -99,16 +99,16 @@ "quarantineSelectAll": "בחר הכול", "quarantineDeselectAll": "בטל בחירה של הכול", "confirmRestoreTitle": "שחזר קבצים", - "confirmRestoreDescription": "פעולה זו תשחזר {{count}} קובץ/קבצים למיקומם המקורי. שחזר רק קבצים שאתה סומך עליהם.", - "confirmRestoreLabel": "שחזר עכשיו", + "confirmRestoreDescription": "פעולה זו תשחזר {{count}} קבצים למיקומם המקורי. שחזר רק קבצים שאתה סומך עליהם.", + "confirmRestoreLabel": "שחזר כעת", "confirmDeleteQuarantineTitle": "מחק לצמיתות", - "confirmDeleteQuarantineDescription": "פעולה זו תמחק לצמיתות {{count}} קובץ/קבצים שבהסגר. לא ניתן לבטל פעולה זו.", + "confirmDeleteQuarantineDescription": "פעולה זו תמחק לצמיתות {{count}} קבצים שבהסגר. לא ניתן לבטל פעולה זו.", "confirmDeleteQuarantineLabel": "מחק לצמיתות", "quarantineLoading": "טוען הסגר...", "quarantineRestoring": "משחזר קבצים...", "quarantineDeleting": "מוחק קבצים...", - "toastRestoreSuccess": "{{count}} קובץ/קבצים שוחזרו", + "toastRestoreSuccess": "{{count}} קבצים שוחזרו", "toastRestoreFailed": "שחזור חלק מהקבצים נכשל", - "toastDeleteQuarantineSuccess": "{{count}} קובץ/קבצים נמחקו לצמיתות", + "toastDeleteQuarantineSuccess": "{{count}} קבצים נמחקו לצמיתות", "toastRestoreNoOriginal": "לא ניתן לשחזר — המיקום המקורי אינו ידוע" } diff --git a/src/renderer/src/locales/hi/malware.json b/src/renderer/src/locales/hi/malware.json index 49620931..f485077a 100644 --- a/src/renderer/src/locales/hi/malware.json +++ b/src/renderer/src/locales/hi/malware.json @@ -12,16 +12,16 @@ "sourceDefenderWindows": "मूल AV", "sourceDefenderMac": "Code Signing", "sourceDefenderLinux": "ClamAV", - "sourceHeuristic": "ह्यूरिस्टिक विश्लेषण", + "sourceHeuristic": "Heuristic Analysis", "sourceSignature": "ज्ञात Signature", "initializingScanEngines": "स्कैन इंजन प्रारंभ किए जा रहे हैं...", "filesScanned": "{{count}} फ़ाइलें स्कैन की गईं", "threatCount": "{{count}} खतरा", "threatCountPlural": "{{count}} खतरे", "threatFound": "{{count}} मिला", - "scanCategoryClean": "साफ़", + "scanCategoryClean": "स्वच्छ", "scanCategoryNA": "लागू नहीं", - "scanSummarySystemClean": "सिस्टम साफ़ है", + "scanSummarySystemClean": "सिस्टम स्वच्छ", "scanSummaryThreatsDetected": "{{count}} खतरे पाए गए", "scanStatFiles": "फ़ाइलें", "scanStatDuration": "अवधि", @@ -33,15 +33,15 @@ "deselectAll": "सभी का चयन हटाएँ", "actingQuarantining": "चयनित खतरों को क्वारंटीन किया जा रहा है...", "actingDeleting": "चयनित खतरों को हटाया जा रहा है...", - "actionCompleteQuarantine": "क्वारंटीन पूरा हुआ", - "actionCompleteDeletion": "हटाना पूरा हुआ", + "actionCompleteQuarantine": "क्वारंटीन पूर्ण", + "actionCompleteDeletion": "हटाना पूर्ण", "actionResultQuarantined": "{{count}} क्वारंटीन किए गए", "actionResultDeleted": "{{count}} हटाए गए", "actionResultFailed": "{{count}} विफल", "noThreatsDetectedTitle": "कोई खतरा नहीं मिला", - "noThreatsDetectedDescription": "{{duration}}s में {{engineCount}} इंजनों पर {{filesScanned}} फ़ाइलें स्कैन की गईं — आपका सिस्टम साफ़ है।", + "noThreatsDetectedDescription": "{{duration}}s में {{engineCount}} इंजनों पर {{filesScanned}} फ़ाइलें स्कैन की गईं — आपका सिस्टम स्वच्छ है।", "emptyStateTitle": "मैलवेयर स्कैनर", - "emptyStateDescription": "अपने सिस्टम में malware, adware, crypto miners, और संदिग्ध फ़ाइलों की जाँच के लिए \"स्कैन\" पर क्लिक करें।", + "emptyStateDescription": "\"स्कैन\" पर क्लिक करके अपने सिस्टम में malware, adware, crypto miners, और संदिग्ध फ़ाइलों की जाँच करें।", "detectedThreatsHeading": "पाए गए खतरे", "detectedThreatsCount": "{{count}} खतरा", "detectedThreatsCountPlural": "{{count}} खतरे", @@ -57,8 +57,8 @@ "confirmDeleteLabel": "स्थायी रूप से हटाएँ", "toastScanFailed": "मैलवेयर स्कैन विफल हुआ", "toastActionFailed": "खतरों को {{action}} करने में विफल", - "toastActionFailedDescription": "प्रशासक के रूप में चलाने का प्रयास करें", - "errorOperationFailed": "क्रिया विफल हुई — प्रशासक के रूप में चलाने का प्रयास करें", + "toastActionFailedDescription": "व्यवस्थापक के रूप में चलाने का प्रयास करें", + "errorOperationFailed": "क्रिया विफल हुई — व्यवस्थापक के रूप में चलाने का प्रयास करें", "tabScanner": "स्कैनर", "tabQuarantine": "क्वारंटीन", "tabDatabase": "डेटाबेस", @@ -67,11 +67,11 @@ "dbFetchLatest": "अपडेट की जाँच करें", "dbUpdating": "अपडेट किया जा रहा है...", "dbEngine": "इंजन", - "dbEngineYara": "YARA-X (Native)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex Fallback", "dbEngineCompiling": "कंपाइल किया जा रहा है...", "dbCompiling": "Signature rules कंपाइल किए जा रहे हैं ({{loaded}}/{{total}})...", - "dbRulesLoaded": "लोड किए गए नियम", + "dbRulesLoaded": "नियम लोड किए गए", "dbVersion": "Signature संस्करण", "dbLastUpdated": "अंतिम अपडेट", "dbNever": "कभी नहीं", @@ -108,7 +108,7 @@ "quarantineRestoring": "फ़ाइलें पुनर्स्थापित की जा रही हैं...", "quarantineDeleting": "फ़ाइलें हटाई जा रही हैं...", "toastRestoreSuccess": "{{count}} file(s) पुनर्स्थापित की गईं", - "toastRestoreFailed": "कुछ फ़ाइलों को पुनर्स्थापित करने में विफल", + "toastRestoreFailed": "कुछ फ़ाइलें पुनर्स्थापित करने में विफल", "toastDeleteQuarantineSuccess": "{{count}} file(s) स्थायी रूप से हटाई गईं", "toastRestoreNoOriginal": "पुनर्स्थापित नहीं किया जा सकता — मूल स्थान अज्ञात है" } diff --git a/src/renderer/src/locales/hu/malware.json b/src/renderer/src/locales/hu/malware.json index 90d0c1a1..67af93a7 100644 --- a/src/renderer/src/locales/hu/malware.json +++ b/src/renderer/src/locales/hu/malware.json @@ -15,7 +15,7 @@ "sourceHeuristic": "Heurisztikus elemzés", "sourceSignature": "Ismert szignatúra", "initializingScanEngines": "Vizsgálómotorok inicializálása...", - "filesScanned": "{{count}} fájl átvizsgálva", + "filesScanned": "{{count}} fájl vizsgálva", "threatCount": "{{count}} fenyegetés", "threatCountPlural": "{{count}} fenyegetés", "threatFound": "{{count}} találat", @@ -30,7 +30,7 @@ "selectedHeading": "Kijelölve", "selectedOfThreats": "{{count}} fenyegetésből", "selectAll": "Összes kijelölése", - "deselectAll": "Összes kijelölés megszüntetése", + "deselectAll": "Kijelölés megszüntetése", "actingQuarantining": "A kijelölt fenyegetések karanténba helyezése...", "actingDeleting": "A kijelölt fenyegetések törlése...", "actionCompleteQuarantine": "Karanténba helyezés kész", @@ -38,10 +38,10 @@ "actionResultQuarantined": "{{count}} karanténba helyezve", "actionResultDeleted": "{{count}} törölve", "actionResultFailed": "{{count}} sikertelen", - "noThreatsDetectedTitle": "Nem észlelhető fenyegetés", - "noThreatsDetectedDescription": "{{filesScanned}} fájl átvizsgálva {{engineCount}} motorral {{duration}} mp alatt — a rendszer tiszta.", + "noThreatsDetectedTitle": "Nem észleltünk fenyegetést", + "noThreatsDetectedDescription": "{{filesScanned}} fájl vizsgálva {{engineCount}} motorral {{duration}} mp alatt — a rendszer tiszta.", "emptyStateTitle": "Kártevőkereső", - "emptyStateDescription": "Kattintson a „Vizsgálat” gombra a rendszer kártevők, reklámprogramok, kriptobányászok és gyanús fájlok utáni ellenőrzéséhez.", + "emptyStateDescription": "Kattintson a „Vizsgálat” gombra a rendszer kártevők, reklámprogramok, kriptobányászok és gyanús fájlok kereséséhez.", "detectedThreatsHeading": "Észlelt fenyegetések", "detectedThreatsCount": "{{count}} fenyegetés", "detectedThreatsCountPlural": "{{count}} fenyegetés", @@ -55,7 +55,7 @@ "confirmDeleteTitle": "Fenyegetések törlése", "confirmDeleteDescription": "Ez a művelet véglegesen törli a(z) {{count}} észlelt fenyegetés(eke)t. Ez a művelet nem vonható vissza.", "confirmDeleteLabel": "Végleges törlés", - "toastScanFailed": "A kártevővizsgálat sikertelen", + "toastScanFailed": "A kártevővizsgálat sikertelen volt", "toastActionFailed": "Nem sikerült a fenyegetések {{action}} művelete", "toastActionFailedDescription": "Próbálja rendszergazdaként futtatni", "errorOperationFailed": "A művelet sikertelen — próbálja rendszergazdaként futtatni", @@ -67,12 +67,12 @@ "dbFetchLatest": "Frissítések keresése", "dbUpdating": "Frissítés...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (natív)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex tartalék", "dbEngineCompiling": "Fordítás...", "dbCompiling": "Szignatúraszabályok fordítása ({{loaded}}/{{total}})...", "dbRulesLoaded": "Betöltött szabályok", - "dbVersion": "Szignatúra verzió", + "dbVersion": "Szignatúra verziója", "dbLastUpdated": "Utolsó frissítés", "dbNever": "Soha", "dbSource": "Szabályforrás", @@ -80,7 +80,7 @@ "dbSourceBundled": "Az alkalmazással együtt", "dbSourceNone": "Nincs betöltött szabály", "dbRuleFiles": "Szabályfájlok", - "dbRuleFilesCounts": "{{bundled}} csomagolt, {{cached}} a felhőből", + "dbRuleFilesCounts": "{{bundled}} beépített, {{cached}} a felhőből", "dbUpdateSuccess": "Frissítve erre: v{{version}} ({{count}} szabály)", "dbAlreadyCurrent": "A szignatúrák már naprakészek", "dbUpdateFailed": "Nem sikerült frissíteni a szignatúrákat", @@ -97,12 +97,12 @@ "quarantineRestoreButton": "Visszaállítás", "quarantineDeleteButton": "Végleges törlés", "quarantineSelectAll": "Összes kijelölése", - "quarantineDeselectAll": "Összes kijelölés megszüntetése", + "quarantineDeselectAll": "Kijelölés megszüntetése", "confirmRestoreTitle": "Fájlok visszaállítása", - "confirmRestoreDescription": "Ez a művelet {{count}} fájl(oka)t állít vissza az eredeti helyére. Csak megbízható fájlokat állítson vissza.", + "confirmRestoreDescription": "Ez a művelet visszaállít {{count}} fájl(oka)t az eredeti helyére. Csak olyan fájlokat állítson vissza, amelyekben megbízik.", "confirmRestoreLabel": "Visszaállítás most", "confirmDeleteQuarantineTitle": "Végleges törlés", - "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli {{count}} karanténba helyezett fájl(oka)t. Ez a művelet nem vonható vissza.", + "confirmDeleteQuarantineDescription": "Ez a művelet véglegesen törli a(z) {{count}} karanténba helyezett fájl(oka)t. Ez a művelet nem vonható vissza.", "confirmDeleteQuarantineLabel": "Végleges törlés", "quarantineLoading": "Karantén betöltése...", "quarantineRestoring": "Fájlok visszaállítása...", diff --git a/src/renderer/src/locales/id/malware.json b/src/renderer/src/locales/id/malware.json index b082a932..1a10cbbf 100644 --- a/src/renderer/src/locales/id/malware.json +++ b/src/renderer/src/locales/id/malware.json @@ -30,7 +30,7 @@ "selectedHeading": "Dipilih", "selectedOfThreats": "dari {{count}} ancaman", "selectAll": "Pilih Semua", - "deselectAll": "Batalkan Pilihan Semua", + "deselectAll": "Batalkan Pilih Semua", "actingQuarantining": "Mengarantina ancaman yang dipilih...", "actingDeleting": "Menghapus ancaman yang dipilih...", "actionCompleteQuarantine": "Karantina selesai", @@ -67,7 +67,7 @@ "dbFetchLatest": "Periksa Pembaruan", "dbUpdating": "Memperbarui...", "dbEngine": "Engine", - "dbEngineYara": "YARA-X (Bawaan)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Fallback Regex", "dbEngineCompiling": "Mengompilasi...", "dbCompiling": "Mengompilasi aturan signature ({{loaded}}/{{total}})...", @@ -84,22 +84,22 @@ "dbUpdateSuccess": "Diperbarui ke v{{version}} ({{count}} aturan)", "dbAlreadyCurrent": "Signature sudah yang terbaru", "dbUpdateFailed": "Gagal memperbarui signature", - "quarantineEmptyTitle": "Tidak Ada Item yang Dikarantina", + "quarantineEmptyTitle": "Tidak Ada Item di Karantina", "quarantineEmptyDescription": "File yang dipindahkan ke karantina akan muncul di sini. Anda dapat memulihkan atau menghapusnya secara permanen.", - "quarantineHeading": "File yang Dikarantina", + "quarantineHeading": "File di Karantina", "quarantineCount": "{{count}} file", "quarantineCountPlural": "{{count}} file", "quarantineColumnFile": "File", "quarantineColumnDate": "Tanggal", "quarantineColumnSize": "Ukuran", - "quarantineColumnOriginal": "Lokasi Asli", + "quarantineColumnOriginal": "Lokasi Asal", "quarantineOriginalUnknown": "Tidak diketahui", "quarantineRestoreButton": "Pulihkan", "quarantineDeleteButton": "Hapus Permanen", "quarantineSelectAll": "Pilih Semua", - "quarantineDeselectAll": "Batalkan Pilihan Semua", + "quarantineDeselectAll": "Batalkan Pilih Semua", "confirmRestoreTitle": "Pulihkan File", - "confirmRestoreDescription": "Ini akan memulihkan {{count}} file ke lokasi aslinya. Hanya pulihkan file yang Anda percayai.", + "confirmRestoreDescription": "Ini akan memulihkan {{count}} file ke lokasi asalnya. Pulihkan hanya file yang Anda percayai.", "confirmRestoreLabel": "Pulihkan Sekarang", "confirmDeleteQuarantineTitle": "Hapus Permanen", "confirmDeleteQuarantineDescription": "Ini akan menghapus permanen {{count}} file yang dikarantina. Tindakan ini tidak dapat dibatalkan.", @@ -109,6 +109,6 @@ "quarantineDeleting": "Menghapus file...", "toastRestoreSuccess": "{{count}} file dipulihkan", "toastRestoreFailed": "Gagal memulihkan beberapa file", - "toastDeleteQuarantineSuccess": "{{count}} file dihapus secara permanen", - "toastRestoreNoOriginal": "Tidak dapat memulihkan — lokasi asli tidak diketahui" + "toastDeleteQuarantineSuccess": "{{count}} file dihapus permanen", + "toastRestoreNoOriginal": "Tidak dapat memulihkan — lokasi asal tidak diketahui" } diff --git a/src/renderer/src/locales/it/malware.json b/src/renderer/src/locales/it/malware.json index 6b25f8b8..541b60a7 100644 --- a/src/renderer/src/locales/it/malware.json +++ b/src/renderer/src/locales/it/malware.json @@ -27,21 +27,21 @@ "scanStatDuration": "Durata", "scanStatEngines": "Motori", "severityHeading": "Gravità", - "selectedHeading": "Selezionati", + "selectedHeading": "Selezionate", "selectedOfThreats": "di {{count}} minacce", "selectAll": "Seleziona tutto", "deselectAll": "Deseleziona tutto", - "actingQuarantining": "Spostamento in quarantena delle minacce selezionate...", + "actingQuarantining": "Messa in quarantena delle minacce selezionate...", "actingDeleting": "Eliminazione delle minacce selezionate...", "actionCompleteQuarantine": "Quarantena completata", "actionCompleteDeletion": "Eliminazione completata", "actionResultQuarantined": "{{count}} in quarantena", - "actionResultDeleted": "{{count}} eliminati", - "actionResultFailed": "{{count}} non riusciti", + "actionResultDeleted": "{{count}} eliminate", + "actionResultFailed": "{{count}} non riuscite", "noThreatsDetectedTitle": "Nessuna minaccia rilevata", "noThreatsDetectedDescription": "Analizzati {{filesScanned}} file con {{engineCount}} motori in {{duration}} s — il sistema è pulito.", "emptyStateTitle": "Scanner malware", - "emptyStateDescription": "Fai clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, cryptominer e file sospetti.", + "emptyStateDescription": "Fai clic su \"Scansiona\" per controllare il sistema alla ricerca di malware, adware, crypto miner e file sospetti.", "detectedThreatsHeading": "Minacce rilevate", "detectedThreatsCount": "{{count}} minaccia", "detectedThreatsCountPlural": "{{count}} minacce", @@ -64,10 +64,10 @@ "tabDatabase": "Database", "dbTitle": "Database delle firme", "dbDescription": "Regole YARA di rilevamento malware utilizzate dallo scanner", - "dbFetchLatest": "Controlla aggiornamenti", + "dbFetchLatest": "Verifica aggiornamenti", "dbUpdating": "Aggiornamento in corso...", "dbEngine": "Motore", - "dbEngineYara": "YARA-X (nativo)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Fallback Regex", "dbEngineCompiling": "Compilazione in corso...", "dbCompiling": "Compilazione delle regole di firma ({{loaded}}/{{total}})...", @@ -85,7 +85,7 @@ "dbAlreadyCurrent": "Le firme sono già aggiornate", "dbUpdateFailed": "Aggiornamento delle firme non riuscito", "quarantineEmptyTitle": "Nessun elemento in quarantena", - "quarantineEmptyDescription": "I file spostati in quarantena appariranno qui. Puoi ripristinarli o eliminarli definitivamente.", + "quarantineEmptyDescription": "I file spostati in quarantena verranno visualizzati qui. Puoi ripristinarli o eliminarli definitivamente.", "quarantineHeading": "File in quarantena", "quarantineCount": "{{count}} file", "quarantineCountPlural": "{{count}} file", @@ -105,8 +105,8 @@ "confirmDeleteQuarantineDescription": "Questa operazione eliminerà definitivamente {{count}} file in quarantena. L'azione non può essere annullata.", "confirmDeleteQuarantineLabel": "Elimina definitivamente", "quarantineLoading": "Caricamento della quarantena...", - "quarantineRestoring": "Ripristino dei file in corso...", - "quarantineDeleting": "Eliminazione dei file in corso...", + "quarantineRestoring": "Ripristino dei file...", + "quarantineDeleting": "Eliminazione dei file...", "toastRestoreSuccess": "{{count}} file ripristinati", "toastRestoreFailed": "Impossibile ripristinare alcuni file", "toastDeleteQuarantineSuccess": "{{count}} file eliminati definitivamente", diff --git a/src/renderer/src/locales/ja/malware.json b/src/renderer/src/locales/ja/malware.json index deddcce5..fa5b9188 100644 --- a/src/renderer/src/locales/ja/malware.json +++ b/src/renderer/src/locales/ja/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} 件を削除", "actionResultFailed": "{{count}} 件失敗", "noThreatsDetectedTitle": "脅威は検出されませんでした", - "noThreatsDetectedDescription": "{{duration}} 秒で {{engineCount}} 個のエンジンにより {{filesScanned}} 個のファイルをスキャンしました — システムはクリーンです。", + "noThreatsDetectedDescription": "{{engineCount}} 個のエンジンで {{filesScanned}} 個のファイルを {{duration}} 秒でスキャンしました — システムはクリーンです。", "emptyStateTitle": "マルウェア スキャナー", - "emptyStateDescription": "「スキャン」をクリックして、システム内のマルウェア、アドウェア、暗号通貨マイナー、不審なファイルを確認します。", + "emptyStateDescription": "「スキャン」をクリックして、システム内のマルウェア、アドウェア、暗号資産マイナー、不審なファイルを確認します。", "detectedThreatsHeading": "検出された脅威", "detectedThreatsCount": "{{count}} 件の脅威", "detectedThreatsCountPlural": "{{count}} 件の脅威", @@ -67,25 +67,25 @@ "dbFetchLatest": "更新プログラムの確認", "dbUpdating": "更新しています...", "dbEngine": "エンジン", - "dbEngineYara": "YARA-X (ネイティブ)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex フォールバック", "dbEngineCompiling": "コンパイルしています...", "dbCompiling": "シグネチャ ルールをコンパイルしています ({{loaded}}/{{total}})...", - "dbRulesLoaded": "読み込み済みルール", - "dbVersion": "シグネチャ バージョン", + "dbRulesLoaded": "読み込まれたルール", + "dbVersion": "シグネチャのバージョン", "dbLastUpdated": "最終更新", "dbNever": "なし", "dbSource": "ルール ソース", "dbSourceCloud": "クラウド (キャッシュ済み)", "dbSourceBundled": "アプリに同梱", - "dbSourceNone": "読み込まれたルールはありません", + "dbSourceNone": "ルールが読み込まれていません", "dbRuleFiles": "ルール ファイル", - "dbRuleFilesCounts": "同梱 {{bundled}}、クラウドから {{cached}}", + "dbRuleFilesCounts": "同梱 {{bundled}}、クラウド {{cached}}", "dbUpdateSuccess": "v{{version}} に更新しました ({{count}} ルール)", "dbAlreadyCurrent": "シグネチャはすでに最新です", "dbUpdateFailed": "シグネチャの更新に失敗しました", "quarantineEmptyTitle": "隔離された項目はありません", - "quarantineEmptyDescription": "隔離に移動したファイルはここに表示されます。復元または完全に削除できます。", + "quarantineEmptyDescription": "隔離に移動されたファイルはここに表示されます。復元または完全に削除できます。", "quarantineHeading": "隔離されたファイル", "quarantineCount": "{{count}} 個のファイル", "quarantineCountPlural": "{{count}} 個のファイル", @@ -102,7 +102,7 @@ "confirmRestoreDescription": "{{count}} 個のファイルを元の場所に復元します。信頼できるファイルのみ復元してください。", "confirmRestoreLabel": "今すぐ復元", "confirmDeleteQuarantineTitle": "完全に削除", - "confirmDeleteQuarantineDescription": "{{count}} 個の隔離されたファイルを完全に削除します。この操作は元に戻せません。", + "confirmDeleteQuarantineDescription": "隔離されたファイル {{count}} 個を完全に削除します。この操作は元に戻せません。", "confirmDeleteQuarantineLabel": "完全に削除", "quarantineLoading": "隔離を読み込んでいます...", "quarantineRestoring": "ファイルを復元しています...", diff --git a/src/renderer/src/locales/ko/malware.json b/src/renderer/src/locales/ko/malware.json index 4b0aa48e..4de471cc 100644 --- a/src/renderer/src/locales/ko/malware.json +++ b/src/renderer/src/locales/ko/malware.json @@ -1,5 +1,5 @@ { - "pageTitle": "악성코드 검사기", + "pageTitle": "멀웨어 스캐너", "pageDescription": "다중 엔진 위협 탐지 — 시그니처, 휴리스틱, 스크립트 분석, 시스템 무결성 및 지속성 검사", "scanButtonScanning": "검사 중...", "scanButton": "검사", @@ -38,10 +38,10 @@ "actionResultQuarantined": "{{count}}개 격리됨", "actionResultDeleted": "{{count}}개 삭제됨", "actionResultFailed": "{{count}}개 실패", - "noThreatsDetectedTitle": "위협이 탐지되지 않음", - "noThreatsDetectedDescription": "{{duration}}초 동안 {{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 검사했습니다 — 시스템이 정상입니다.", - "emptyStateTitle": "악성코드 검사기", - "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 악성코드, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", + "noThreatsDetectedTitle": "탐지된 위협 없음", + "noThreatsDetectedDescription": "{{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 {{duration}}초 동안 검사했습니다 — 시스템이 정상입니다.", + "emptyStateTitle": "멀웨어 스캐너", + "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 멀웨어, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", "detectedThreatsHeading": "탐지된 위협", "detectedThreatsCount": "{{count}}개 위협", "detectedThreatsCountPlural": "{{count}}개 위협", @@ -55,19 +55,19 @@ "confirmDeleteTitle": "위협 삭제", "confirmDeleteDescription": "탐지된 위협 {{count}}개를 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다.", "confirmDeleteLabel": "영구 삭제", - "toastScanFailed": "악성코드 검사에 실패했습니다", - "toastActionFailed": "위협 {{action}}에 실패했습니다", + "toastScanFailed": "멀웨어 검사 실패", + "toastActionFailed": "위협 {{action}} 실패", "toastActionFailedDescription": "관리자 권한으로 실행해 보세요", - "errorOperationFailed": "작업에 실패했습니다 — 관리자 권한으로 실행해 보세요", - "tabScanner": "검사기", + "errorOperationFailed": "작업 실패 — 관리자 권한으로 실행해 보세요", + "tabScanner": "스캐너", "tabQuarantine": "격리", "tabDatabase": "데이터베이스", "dbTitle": "시그니처 데이터베이스", - "dbDescription": "검사기에서 사용하는 YARA 악성코드 탐지 규칙", + "dbDescription": "스캐너에서 사용하는 YARA 멀웨어 탐지 규칙", "dbFetchLatest": "업데이트 확인", "dbUpdating": "업데이트 중...", "dbEngine": "엔진", - "dbEngineYara": "YARA-X (기본)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex 대체", "dbEngineCompiling": "컴파일 중...", "dbCompiling": "시그니처 규칙 컴파일 중 ({{loaded}}/{{total}})...", @@ -75,15 +75,15 @@ "dbVersion": "시그니처 버전", "dbLastUpdated": "마지막 업데이트", "dbNever": "없음", - "dbSource": "규칙 원본", + "dbSource": "규칙 소스", "dbSourceCloud": "클라우드(캐시됨)", "dbSourceBundled": "앱에 포함됨", "dbSourceNone": "로드된 규칙 없음", "dbRuleFiles": "규칙 파일", - "dbRuleFilesCounts": "앱 포함 {{bundled}}개, 클라우드 {{cached}}개", + "dbRuleFilesCounts": "기본 제공 {{bundled}}개, 클라우드 {{cached}}개", "dbUpdateSuccess": "v{{version}}로 업데이트됨 (규칙 {{count}}개)", "dbAlreadyCurrent": "시그니처가 이미 최신 상태입니다", - "dbUpdateFailed": "시그니처 업데이트에 실패했습니다", + "dbUpdateFailed": "시그니처 업데이트 실패", "quarantineEmptyTitle": "격리된 항목 없음", "quarantineEmptyDescription": "격리로 이동된 파일이 여기에 표시됩니다. 파일을 복원하거나 영구적으로 삭제할 수 있습니다.", "quarantineHeading": "격리된 파일", @@ -108,7 +108,7 @@ "quarantineRestoring": "파일 복원 중...", "quarantineDeleting": "파일 삭제 중...", "toastRestoreSuccess": "{{count}}개 파일 복원됨", - "toastRestoreFailed": "일부 파일 복원에 실패했습니다", + "toastRestoreFailed": "일부 파일을 복원하지 못했습니다", "toastDeleteQuarantineSuccess": "{{count}}개 파일이 영구적으로 삭제됨", "toastRestoreNoOriginal": "복원할 수 없음 — 원래 위치를 알 수 없음" } diff --git a/src/renderer/src/locales/ms/malware.json b/src/renderer/src/locales/ms/malware.json index 891354d1..4932a540 100644 --- a/src/renderer/src/locales/ms/malware.json +++ b/src/renderer/src/locales/ms/malware.json @@ -1,7 +1,7 @@ { "pageTitle": "Pengimbas Malware", "pageDescription": "Pengesanan ancaman berbilang enjin — tandatangan, heuristik, analisis skrip, integriti sistem dan pengimbasan ketekalan", - "scanButtonScanning": "Sedang mengimbas...", + "scanButtonScanning": "Mengimbas...", "scanButton": "Imbas", "quarantineButton": "Kuarantin", "deleteButton": "Padam", @@ -20,20 +20,20 @@ "threatCountPlural": "{{count}} ancaman", "threatFound": "{{count}} ditemui", "scanCategoryClean": "bersih", - "scanCategoryNA": "N/A", + "scanCategoryNA": "T/A", "scanSummarySystemClean": "Sistem Bersih", "scanSummaryThreatsDetected": "{{count}} Ancaman Dikesan", "scanStatFiles": "Fail", "scanStatDuration": "Tempoh", "scanStatEngines": "Enjin", - "severityHeading": "Tahap Keterukan", + "severityHeading": "Keterukan", "selectedHeading": "Dipilih", "selectedOfThreats": "daripada {{count}} ancaman", "selectAll": "Pilih Semua", "deselectAll": "Nyahpilih Semua", - "actingQuarantining": "Menguarantin ancaman yang dipilih...", + "actingQuarantining": "Mengkuarantin ancaman yang dipilih...", "actingDeleting": "Memadam ancaman yang dipilih...", - "actionCompleteQuarantine": "Pengkuarantinan selesai", + "actionCompleteQuarantine": "Kuarantin selesai", "actionCompleteDeletion": "Pemadaman selesai", "actionResultQuarantined": "{{count}} dikuarantin", "actionResultDeleted": "{{count}} dipadam", @@ -65,22 +65,22 @@ "dbTitle": "Pangkalan Data Tandatangan", "dbDescription": "Peraturan pengesanan malware YARA yang digunakan oleh pengimbas", "dbFetchLatest": "Semak Kemas Kini", - "dbUpdating": "Sedang mengemas kini...", + "dbUpdating": "Mengemas kini...", "dbEngine": "Enjin", - "dbEngineYara": "YARA-X (Asli)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Sandaran Regex", - "dbEngineCompiling": "Sedang menyusun...", + "dbEngineCompiling": "Menyusun...", "dbCompiling": "Menyusun peraturan tandatangan ({{loaded}}/{{total}})...", "dbRulesLoaded": "Peraturan Dimuatkan", "dbVersion": "Versi Tandatangan", "dbLastUpdated": "Terakhir Dikemas Kini", "dbNever": "Tidak Pernah", "dbSource": "Sumber Peraturan", - "dbSourceCloud": "Awan (cache)", + "dbSourceCloud": "Cloud (cache)", "dbSourceBundled": "Disertakan dengan aplikasi", "dbSourceNone": "Tiada peraturan dimuatkan", "dbRuleFiles": "Fail Peraturan", - "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari awan", + "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari cloud", "dbUpdateSuccess": "Dikemas kini ke v{{version}} ({{count}} peraturan)", "dbAlreadyCurrent": "Tandatangan sudah pun terkini", "dbUpdateFailed": "Gagal mengemas kini tandatangan", diff --git a/src/renderer/src/locales/nl/malware.json b/src/renderer/src/locales/nl/malware.json index ada0a4a0..00aa0c6f 100644 --- a/src/renderer/src/locales/nl/malware.json +++ b/src/renderer/src/locales/nl/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Malwarescanner", - "pageDescription": "Dreigingsdetectie met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en persistentiescans", + "pageDescription": "Detectie van bedreigingen met meerdere engines — handtekeningen, heuristiek, scriptanalyse, systeemintegriteit en scan op persistentie", "scanButtonScanning": "Scannen...", "scanButton": "Scannen", "quarantineButton": "In quarantaine plaatsen", @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} bedreigingen", "threatFound": "{{count}} gevonden", "scanCategoryClean": "schoon", - "scanCategoryNA": "N.v.t.", + "scanCategoryNA": "n.v.t.", "scanSummarySystemClean": "Systeem schoon", "scanSummaryThreatsDetected": "{{count}} bedreigingen gedetecteerd", "scanStatFiles": "Bestanden", @@ -33,7 +33,7 @@ "deselectAll": "Alles deselecteren", "actingQuarantining": "Geselecteerde bedreigingen in quarantaine plaatsen...", "actingDeleting": "Geselecteerde bedreigingen verwijderen...", - "actionCompleteQuarantine": "Quarantaine voltooid", + "actionCompleteQuarantine": "In quarantaine plaatsen voltooid", "actionCompleteDeletion": "Verwijderen voltooid", "actionResultQuarantined": "{{count}} in quarantaine geplaatst", "actionResultDeleted": "{{count}} verwijderd", @@ -50,13 +50,13 @@ "threatDetailSize": "Grootte", "threatDetailPath": "Pad", "confirmQuarantineTitle": "Bedreigingen in quarantaine plaatsen", - "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) naar de quarantaine verplaatst. Bestanden kunnen later indien nodig worden hersteld.", + "confirmQuarantineDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) naar quarantaine verplaatst. Bestanden kunnen later indien nodig worden hersteld.", "confirmQuarantineLabel": "Nu in quarantaine plaatsen", "confirmDeleteTitle": "Bedreigingen verwijderen", "confirmDeleteDescription": "Hiermee worden {{count}} gedetecteerde bedreiging(en) permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.", "confirmDeleteLabel": "Permanent verwijderen", "toastScanFailed": "Malwarescan mislukt", - "toastActionFailed": "Kan bedreigingen niet {{action}}", + "toastActionFailed": "Bedreigingen konden niet worden {{action}}", "toastActionFailedDescription": "Probeer uit te voeren als administrator", "errorOperationFailed": "Bewerking mislukt — probeer uit te voeren als administrator", "tabScanner": "Scanner", @@ -67,7 +67,7 @@ "dbFetchLatest": "Controleren op updates", "dbUpdating": "Bijwerken...", "dbEngine": "Engine", - "dbEngineYara": "YARA-X (Native)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex-terugvaloptie", "dbEngineCompiling": "Compileren...", "dbCompiling": "Handtekeningregels compileren ({{loaded}}/{{total}})...", @@ -75,7 +75,7 @@ "dbVersion": "Handtekeningversie", "dbLastUpdated": "Laatst bijgewerkt", "dbNever": "Nooit", - "dbSource": "Regelbron", + "dbSource": "Bron van regels", "dbSourceCloud": "Cloud (in cache)", "dbSourceBundled": "Meegeleverd met app", "dbSourceNone": "Geen regels geladen", @@ -85,7 +85,7 @@ "dbAlreadyCurrent": "Handtekeningen zijn al up-to-date", "dbUpdateFailed": "Bijwerken van handtekeningen mislukt", "quarantineEmptyTitle": "Geen items in quarantaine", - "quarantineEmptyDescription": "Bestanden die naar de quarantaine zijn verplaatst, verschijnen hier. U kunt ze herstellen of permanent verwijderen.", + "quarantineEmptyDescription": "Bestanden die naar quarantaine zijn verplaatst, worden hier weergegeven. U kunt ze herstellen of permanent verwijderen.", "quarantineHeading": "Bestanden in quarantaine", "quarantineCount": "{{count}} bestand", "quarantineCountPlural": "{{count}} bestanden", @@ -108,7 +108,7 @@ "quarantineRestoring": "Bestanden herstellen...", "quarantineDeleting": "Bestanden verwijderen...", "toastRestoreSuccess": "{{count}} bestand(en) hersteld", - "toastRestoreFailed": "Herstellen van sommige bestanden mislukt", + "toastRestoreFailed": "Sommige bestanden konden niet worden hersteld", "toastDeleteQuarantineSuccess": "{{count}} bestand(en) permanent verwijderd", "toastRestoreNoOriginal": "Kan niet herstellen — oorspronkelijke locatie onbekend" } diff --git a/src/renderer/src/locales/no/malware.json b/src/renderer/src/locales/no/malware.json index b8a94c33..569e5c3a 100644 --- a/src/renderer/src/locales/no/malware.json +++ b/src/renderer/src/locales/no/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Skanner for skadelig programvare", - "pageDescription": "Trusseldeteksjon med flere motorer — signaturer, heuristikk, skriptanalyse, systemintegritet og skanning av vedvarende trusler", + "pageDescription": "Trusseldeteksjon med flere motorer — signaturer, heuristikk, skriptanalyse, systemintegritet og skanning av persistens", "scanButtonScanning": "Skanner...", "scanButton": "Skann", "quarantineButton": "Sett i karantene", @@ -30,7 +30,7 @@ "selectedHeading": "Valgt", "selectedOfThreats": "av {{count}} trusler", "selectAll": "Velg alle", - "deselectAll": "Fjern markering", + "deselectAll": "Fjern alle valg", "actingQuarantining": "Setter valgte trusler i karantene...", "actingDeleting": "Sletter valgte trusler...", "actionCompleteQuarantine": "Karantene fullført", @@ -41,7 +41,7 @@ "noThreatsDetectedTitle": "Ingen trusler oppdaget", "noThreatsDetectedDescription": "Skannet {{filesScanned}} filer med {{engineCount}} motorer på {{duration}} s — systemet ditt er rent.", "emptyStateTitle": "Skanner for skadelig programvare", - "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet for skadelig programvare, reklameprogrammer, kryptogravere og mistenkelige filer.", + "emptyStateDescription": "Klikk på \"Skann\" for å kontrollere systemet ditt for skadelig programvare, reklameprogrammer, kryptoutvinnere og mistenkelige filer.", "detectedThreatsHeading": "Oppdagede trusler", "detectedThreatsCount": "{{count}} trussel", "detectedThreatsCountPlural": "{{count}} trusler", @@ -63,12 +63,12 @@ "tabQuarantine": "Karantene", "tabDatabase": "Database", "dbTitle": "Signaturdatabase", - "dbDescription": "YARA-regler for deteksjon av skadelig programvare som brukes av skanneren", + "dbDescription": "YARA-regler for deteksjon av skadelig programvare brukt av skanneren", "dbFetchLatest": "Se etter oppdateringer", "dbUpdating": "Oppdaterer...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (innebygd)", - "dbEngineRegex": "Regex-reserveløsning", + "dbEngineYara": "Kudu Cloud-signaturer", + "dbEngineRegex": "Regex-reserve", "dbEngineCompiling": "Kompilerer...", "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regler lastet inn", @@ -80,7 +80,7 @@ "dbSourceBundled": "Levert med appen", "dbSourceNone": "Ingen regler lastet inn", "dbRuleFiles": "Regelfiler", - "dbRuleFilesCounts": "{{bundled}} medfølgende, {{cached}} fra skyen", + "dbRuleFilesCounts": "{{bundled}} levert med appen, {{cached}} fra skyen", "dbUpdateSuccess": "Oppdatert til v{{version}} ({{count}} regler)", "dbAlreadyCurrent": "Signaturene er allerede oppdatert", "dbUpdateFailed": "Kunne ikke oppdatere signaturene", @@ -97,18 +97,18 @@ "quarantineRestoreButton": "Gjenopprett", "quarantineDeleteButton": "Slett permanent", "quarantineSelectAll": "Velg alle", - "quarantineDeselectAll": "Fjern markering", + "quarantineDeselectAll": "Fjern alle valg", "confirmRestoreTitle": "Gjenopprett filer", - "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil/filer til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", + "confirmRestoreDescription": "Dette vil gjenopprette {{count}} fil(er) til den opprinnelige plasseringen. Gjenopprett bare filer du stoler på.", "confirmRestoreLabel": "Gjenopprett nå", "confirmDeleteQuarantineTitle": "Slett permanent", - "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} fil/filer i karantene permanent. Denne handlingen kan ikke angres.", + "confirmDeleteQuarantineDescription": "Dette vil slette {{count}} fil(er) i karantene permanent. Denne handlingen kan ikke angres.", "confirmDeleteQuarantineLabel": "Slett permanent", "quarantineLoading": "Laster inn karantene...", "quarantineRestoring": "Gjenoppretter filer...", "quarantineDeleting": "Sletter filer...", - "toastRestoreSuccess": "{{count}} fil/filer gjenopprettet", + "toastRestoreSuccess": "{{count}} fil(er) gjenopprettet", "toastRestoreFailed": "Kunne ikke gjenopprette noen filer", - "toastDeleteQuarantineSuccess": "{{count}} fil/filer slettet permanent", + "toastDeleteQuarantineSuccess": "{{count}} fil(er) slettet permanent", "toastRestoreNoOriginal": "Kan ikke gjenopprette — opprinnelig plassering er ukjent" } diff --git a/src/renderer/src/locales/pl/malware.json b/src/renderer/src/locales/pl/malware.json index a48d6303..a9e87766 100644 --- a/src/renderer/src/locales/pl/malware.json +++ b/src/renderer/src/locales/pl/malware.json @@ -1,15 +1,15 @@ { "pageTitle": "Skaner malware", - "pageDescription": "Wielosilnikowe wykrywanie zagrożeń — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie mechanizmów utrwalania", + "pageDescription": "Wykrywanie zagrożeń wieloma silnikami — sygnatury, heurystyka, analiza skryptów, integralność systemu i skanowanie mechanizmów trwałości", "scanButtonScanning": "Skanowanie...", "scanButton": "Skanuj", - "quarantineButton": "Kwarantanna", + "quarantineButton": "Poddaj kwarantannie", "deleteButton": "Usuń", "severityCritical": "Krytyczny", "severityHigh": "Wysoki", "severityMedium": "Średni", "severityLow": "Niski", - "sourceDefenderWindows": "Natywny AV", + "sourceDefenderWindows": "Natywny program antywirusowy", "sourceDefenderMac": "Podpisywanie kodu", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analiza heurystyczna", @@ -18,28 +18,28 @@ "filesScanned": "Przeskanowano {{count}} plików", "threatCount": "{{count}} zagrożenie", "threatCountPlural": "{{count}} zagrożenia", - "threatFound": "Znaleziono {{count}}", + "threatFound": "Znaleziono: {{count}}", "scanCategoryClean": "czysty", - "scanCategoryNA": "N/D", - "scanSummarySystemClean": "System jest czysty", - "scanSummaryThreatsDetected": "Wykryto {{count}} zagrożeń", + "scanCategoryNA": "Nie dotyczy", + "scanSummarySystemClean": "System czysty", + "scanSummaryThreatsDetected": "Wykryto zagrożenia: {{count}}", "scanStatFiles": "Pliki", "scanStatDuration": "Czas trwania", "scanStatEngines": "Silniki", "severityHeading": "Poziom zagrożenia", - "selectedHeading": "Wybrane", + "selectedHeading": "Zaznaczono", "selectedOfThreats": "z {{count}} zagrożeń", "selectAll": "Zaznacz wszystko", "deselectAll": "Odznacz wszystko", - "actingQuarantining": "Przenoszenie wybranych zagrożeń do kwarantanny...", - "actingDeleting": "Usuwanie wybranych zagrożeń...", + "actingQuarantining": "Przenoszenie zaznaczonych zagrożeń do kwarantanny...", + "actingDeleting": "Usuwanie zaznaczonych zagrożeń...", "actionCompleteQuarantine": "Przenoszenie do kwarantanny zakończone", "actionCompleteDeletion": "Usuwanie zakończone", - "actionResultQuarantined": "Przeniesiono do kwarantanny: {{count}}", + "actionResultQuarantined": "Poddano kwarantannie: {{count}}", "actionResultDeleted": "Usunięto: {{count}}", - "actionResultFailed": "Niepowodzenia: {{count}}", + "actionResultFailed": "Niepowodzeń: {{count}}", "noThreatsDetectedTitle": "Nie wykryto zagrożeń", - "noThreatsDetectedDescription": "Przeskanowano {{filesScanned}} plików przy użyciu {{engineCount}} silników w {{duration}} s — system jest czysty.", + "noThreatsDetectedDescription": "Przeskanowano {{filesScanned}} plików przy użyciu {{engineCount}} silników w ciągu {{duration}} s — system jest czysty.", "emptyStateTitle": "Skaner malware", "emptyStateDescription": "Kliknij „Skanuj”, aby sprawdzić system pod kątem malware, adware, koparek kryptowalut i podejrzanych plików.", "detectedThreatsHeading": "Wykryte zagrożenia", @@ -49,14 +49,14 @@ "threatDetailFile": "Plik", "threatDetailSize": "Rozmiar", "threatDetailPath": "Ścieżka", - "confirmQuarantineTitle": "Przenieś zagrożenia do kwarantanny", + "confirmQuarantineTitle": "Poddaj zagrożenia kwarantannie", "confirmQuarantineDescription": "Spowoduje to przeniesienie {{count}} wykrytych zagrożeń do kwarantanny. W razie potrzeby pliki będzie można później przywrócić.", - "confirmQuarantineLabel": "Przenieś do kwarantanny", + "confirmQuarantineLabel": "Poddaj kwarantannie teraz", "confirmDeleteTitle": "Usuń zagrożenia", "confirmDeleteDescription": "Spowoduje to trwałe usunięcie {{count}} wykrytych zagrożeń. Tej operacji nie można cofnąć.", "confirmDeleteLabel": "Usuń trwale", "toastScanFailed": "Skanowanie malware nie powiodło się", - "toastActionFailed": "Nie udało się wykonać działania „{{action}}” na zagrożeniach", + "toastActionFailed": "Nie udało się wykonać akcji „{{action}}” dla zagrożeń", "toastActionFailedDescription": "Spróbuj uruchomić jako administrator", "errorOperationFailed": "Operacja nie powiodła się — spróbuj uruchomić jako administrator", "tabScanner": "Skaner", @@ -67,7 +67,7 @@ "dbFetchLatest": "Sprawdź aktualizacje", "dbUpdating": "Aktualizowanie...", "dbEngine": "Silnik", - "dbEngineYara": "YARA-X (natywny)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Awaryjny Regex", "dbEngineCompiling": "Kompilowanie...", "dbCompiling": "Kompilowanie reguł sygnatur ({{loaded}}/{{total}})...", @@ -81,7 +81,7 @@ "dbSourceNone": "Nie załadowano reguł", "dbRuleFiles": "Pliki reguł", "dbRuleFilesCounts": "{{bundled}} dołączonych, {{cached}} z chmury", - "dbUpdateSuccess": "Zaktualizowano do wersji {{version}} ({{count}} reguł)", + "dbUpdateSuccess": "Zaktualizowano do wersji v{{version}} ({{count}} reguł)", "dbAlreadyCurrent": "Sygnatury są już aktualne", "dbUpdateFailed": "Nie udało się zaktualizować sygnatur", "quarantineEmptyTitle": "Brak elementów w kwarantannie", @@ -107,8 +107,8 @@ "quarantineLoading": "Ładowanie kwarantanny...", "quarantineRestoring": "Przywracanie plików...", "quarantineDeleting": "Usuwanie plików...", - "toastRestoreSuccess": "Przywrócono pliki: {{count}}", + "toastRestoreSuccess": "Przywrócono {{count}} plików", "toastRestoreFailed": "Nie udało się przywrócić niektórych plików", - "toastDeleteQuarantineSuccess": "Trwale usunięto pliki: {{count}}", + "toastDeleteQuarantineSuccess": "Trwale usunięto {{count}} plików", "toastRestoreNoOriginal": "Nie można przywrócić — oryginalna lokalizacja jest nieznana" } diff --git a/src/renderer/src/locales/pt/malware.json b/src/renderer/src/locales/pt/malware.json index ebfe2f87..c4bf7f3b 100644 --- a/src/renderer/src/locales/pt/malware.json +++ b/src/renderer/src/locales/pt/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Scanner de Malware", - "pageDescription": "Deteção de ameaças com vários motores — assinaturas, heurística, análise de scripts, integridade do sistema e análise de persistência", + "pageDescription": "Deteção de ameaças com vários motores — assinaturas, heurísticas, análise de scripts, integridade do sistema e análise de persistência", "scanButtonScanning": "A analisar...", "scanButton": "Analisar", "quarantineButton": "Colocar em quarentena", @@ -63,12 +63,12 @@ "tabQuarantine": "Quarentena", "tabDatabase": "Base de dados", "dbTitle": "Base de dados de assinaturas", - "dbDescription": "Regras de deteção de malware YARA utilizadas pelo scanner", + "dbDescription": "Regras YARA de deteção de malware utilizadas pelo scanner", "dbFetchLatest": "Procurar atualizações", "dbUpdating": "A atualizar...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (Nativo)", - "dbEngineRegex": "Alternativa Regex", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Fallback Regex", "dbEngineCompiling": "A compilar...", "dbCompiling": "A compilar regras de assinatura ({{loaded}}/{{total}})...", "dbRulesLoaded": "Regras carregadas", @@ -76,11 +76,11 @@ "dbLastUpdated": "Última atualização", "dbNever": "Nunca", "dbSource": "Origem das regras", - "dbSourceCloud": "Nuvem (em cache)", + "dbSourceCloud": "Cloud (em cache)", "dbSourceBundled": "Incluídas na aplicação", "dbSourceNone": "Nenhuma regra carregada", "dbRuleFiles": "Ficheiros de regras", - "dbRuleFilesCounts": "{{bundled}} incluídas, {{cached}} da nuvem", + "dbRuleFilesCounts": "{{bundled}} incluídas, {{cached}} da cloud", "dbUpdateSuccess": "Atualizado para v{{version}} ({{count}} regras)", "dbAlreadyCurrent": "As assinaturas já estão atualizadas", "dbUpdateFailed": "Falha ao atualizar as assinaturas", @@ -99,7 +99,7 @@ "quarantineSelectAll": "Selecionar tudo", "quarantineDeselectAll": "Desselecionar tudo", "confirmRestoreTitle": "Restaurar ficheiros", - "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a sua localização original. Restaure apenas ficheiros em que confia.", + "confirmRestoreDescription": "Isto restaurará {{count}} ficheiro(s) para a respetiva localização original. Restaure apenas ficheiros em que confia.", "confirmRestoreLabel": "Restaurar agora", "confirmDeleteQuarantineTitle": "Eliminar permanentemente", "confirmDeleteQuarantineDescription": "Isto eliminará permanentemente {{count}} ficheiro(s) em quarentena. Esta ação não pode ser anulada.", diff --git a/src/renderer/src/locales/ro/malware.json b/src/renderer/src/locales/ro/malware.json index 65080063..885769ae 100644 --- a/src/renderer/src/locales/ro/malware.json +++ b/src/renderer/src/locales/ro/malware.json @@ -5,10 +5,10 @@ "scanButton": "Scanează", "quarantineButton": "Pune în carantină", "deleteButton": "Șterge", - "severityCritical": "Critică", - "severityHigh": "Ridicată", - "severityMedium": "Medie", - "severityLow": "Scăzută", + "severityCritical": "Critic", + "severityHigh": "Ridicat", + "severityMedium": "Mediu", + "severityLow": "Scăzut", "sourceDefenderWindows": "AV nativ", "sourceDefenderMac": "Semnare cod", "sourceDefenderLinux": "ClamAV", @@ -56,7 +56,7 @@ "confirmDeleteDescription": "Aceasta va șterge definitiv {{count}} amenințare(ări) detectată(e). Această acțiune nu poate fi anulată.", "confirmDeleteLabel": "Șterge definitiv", "toastScanFailed": "Scanarea malware a eșuat", - "toastActionFailed": "Nu s-au putut {{action}} amenințările", + "toastActionFailed": "Nu s-a reușit {{action}} amenințărilor", "toastActionFailedDescription": "Încercați să rulați ca administrator", "errorOperationFailed": "Operațiunea a eșuat — încercați să rulați ca administrator", "tabScanner": "Scanner", @@ -67,7 +67,7 @@ "dbFetchLatest": "Verifică actualizările", "dbUpdating": "Se actualizează...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (Nativ)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Fallback Regex", "dbEngineCompiling": "Se compilează...", "dbCompiling": "Se compilează regulile de semnătură ({{loaded}}/{{total}})...", @@ -84,7 +84,7 @@ "dbUpdateSuccess": "Actualizat la v{{version}} ({{count}} reguli)", "dbAlreadyCurrent": "Semnăturile sunt deja actualizate", "dbUpdateFailed": "Actualizarea semnăturilor a eșuat", - "quarantineEmptyTitle": "Niciun element în carantină", + "quarantineEmptyTitle": "Nu există elemente în carantină", "quarantineEmptyDescription": "Fișierele mutate în carantină vor apărea aici. Le puteți restaura sau șterge definitiv.", "quarantineHeading": "Fișiere în carantină", "quarantineCount": "{{count}} fișier", @@ -99,9 +99,9 @@ "quarantineSelectAll": "Selectează tot", "quarantineDeselectAll": "Deselectează tot", "confirmRestoreTitle": "Restaurează fișierele", - "confirmRestoreDescription": "Aceasta va restaura {{count}} fișier(e) în locația originală. Restaurați doar fișierele în care aveți încredere.", + "confirmRestoreDescription": "Aceasta va restaura {{count}} fișier(e) în locația lor originală. Restaurați doar fișierele în care aveți încredere.", "confirmRestoreLabel": "Restaurează acum", - "confirmDeleteQuarantineTitle": "Ștergere definitivă", + "confirmDeleteQuarantineTitle": "Șterge definitiv", "confirmDeleteQuarantineDescription": "Aceasta va șterge definitiv {{count}} fișier(e) din carantină. Această acțiune nu poate fi anulată.", "confirmDeleteQuarantineLabel": "Șterge definitiv", "quarantineLoading": "Se încarcă carantina...", diff --git a/src/renderer/src/locales/ru/malware.json b/src/renderer/src/locales/ru/malware.json index 5db53bb4..5bb315c8 100644 --- a/src/renderer/src/locales/ru/malware.json +++ b/src/renderer/src/locales/ru/malware.json @@ -26,7 +26,7 @@ "scanStatFiles": "Файлы", "scanStatDuration": "Длительность", "scanStatEngines": "Модули", - "severityHeading": "Серьёзность", + "severityHeading": "Уровень опасности", "selectedHeading": "Выбрано", "selectedOfThreats": "из {{count}} угроз", "selectAll": "Выбрать все", @@ -39,9 +39,9 @@ "actionResultDeleted": "Удалено: {{count}}", "actionResultFailed": "Не удалось: {{count}}", "noThreatsDetectedTitle": "Угроз не обнаружено", - "noThreatsDetectedDescription": "Проверено файлов: {{filesScanned}}, модулей: {{engineCount}}, за {{duration}} с — ваша система чиста.", + "noThreatsDetectedDescription": "Проверено {{filesScanned}} файлов с помощью {{engineCount}} модулей за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер вредоносных программ", - "emptyStateDescription": "Нажмите «Сканировать», чтобы проверить систему на наличие вредоносных программ, рекламного ПО, криптомайнеров и подозрительных файлов.", + "emptyStateDescription": "Нажмите «Сканировать», чтобы проверить систему на вредоносные программы, рекламное ПО, криптомайнеры и подозрительные файлы.", "detectedThreatsHeading": "Обнаруженные угрозы", "detectedThreatsCount": "{{count}} угроза", "detectedThreatsCountPlural": "{{count}} угроз", @@ -67,24 +67,24 @@ "dbFetchLatest": "Проверить обновления", "dbUpdating": "Обновление...", "dbEngine": "Модуль", - "dbEngineYara": "YARA-X (Native)", + "dbEngineYara": "Облачные сигнатуры Kudu", "dbEngineRegex": "Резервный Regex", "dbEngineCompiling": "Компиляция...", "dbCompiling": "Компиляция правил сигнатур ({{loaded}}/{{total}})...", - "dbRulesLoaded": "Загружено правил", + "dbRulesLoaded": "Правил загружено", "dbVersion": "Версия сигнатур", "dbLastUpdated": "Последнее обновление", "dbNever": "Никогда", "dbSource": "Источник правил", "dbSourceCloud": "Облако (кэшировано)", - "dbSourceBundled": "В составе приложения", + "dbSourceBundled": "В комплекте с приложением", "dbSourceNone": "Правила не загружены", "dbRuleFiles": "Файлы правил", - "dbRuleFilesCounts": "{{bundled}} в составе приложения, {{cached}} из облака", + "dbRuleFilesCounts": "{{bundled}} в комплекте, {{cached}} из облака", "dbUpdateSuccess": "Обновлено до v{{version}} (правил: {{count}})", "dbAlreadyCurrent": "Сигнатуры уже актуальны", "dbUpdateFailed": "Не удалось обновить сигнатуры", - "quarantineEmptyTitle": "Нет объектов в карантине", + "quarantineEmptyTitle": "Нет элементов в карантине", "quarantineEmptyDescription": "Файлы, перемещённые в карантин, будут отображаться здесь. Вы можете восстановить их или удалить безвозвратно.", "quarantineHeading": "Файлы в карантине", "quarantineCount": "{{count}} файл", @@ -110,5 +110,5 @@ "toastRestoreSuccess": "Восстановлено файлов: {{count}}", "toastRestoreFailed": "Не удалось восстановить некоторые файлы", "toastDeleteQuarantineSuccess": "Безвозвратно удалено файлов: {{count}}", - "toastRestoreNoOriginal": "Невозможно восстановить — исходное расположение неизвестно" + "toastRestoreNoOriginal": "Не удаётся восстановить — исходное расположение неизвестно" } diff --git a/src/renderer/src/locales/sv/malware.json b/src/renderer/src/locales/sv/malware.json index 45a79f77..41b9bc45 100644 --- a/src/renderer/src/locales/sv/malware.json +++ b/src/renderer/src/locales/sv/malware.json @@ -20,7 +20,7 @@ "threatCountPlural": "{{count}} hot", "threatFound": "{{count}} hittades", "scanCategoryClean": "ren", - "scanCategoryNA": "Ej tillämpligt", + "scanCategoryNA": "Ej tillgängligt", "scanSummarySystemClean": "Systemet är rent", "scanSummaryThreatsDetected": "{{count}} hot identifierade", "scanStatFiles": "Filer", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "Detta flyttar {{count}} identifierade hot till karantän. Filer kan återställas senare vid behov.", "confirmQuarantineLabel": "Sätt i karantän nu", "confirmDeleteTitle": "Ta bort hot", - "confirmDeleteDescription": "Detta tar permanent bort {{count}} identifierade hot. Den här åtgärden kan inte ångras.", + "confirmDeleteDescription": "Detta tar bort {{count}} identifierade hot permanent. Åtgärden kan inte ångras.", "confirmDeleteLabel": "Ta bort permanent", "toastScanFailed": "Skanning efter skadlig kod misslyckades", "toastActionFailed": "Det gick inte att {{action}} hot", @@ -67,7 +67,7 @@ "dbFetchLatest": "Sök efter uppdateringar", "dbUpdating": "Uppdaterar...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (inbyggd)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex-reserv", "dbEngineCompiling": "Kompilerar...", "dbCompiling": "Kompilerar signaturregler ({{loaded}}/{{total}})...", @@ -99,10 +99,10 @@ "quarantineSelectAll": "Markera alla", "quarantineDeselectAll": "Avmarkera alla", "confirmRestoreTitle": "Återställ filer", - "confirmRestoreDescription": "Detta återställer {{count}} fil(er) till deras ursprungliga plats. Återställ endast filer som du litar på.", + "confirmRestoreDescription": "Detta återställer {{count}} filer till deras ursprungliga plats. Återställ endast filer som du litar på.", "confirmRestoreLabel": "Återställ nu", "confirmDeleteQuarantineTitle": "Ta bort permanent", - "confirmDeleteQuarantineDescription": "Detta tar permanent bort {{count}} fil(er) i karantän. Den här åtgärden kan inte ångras.", + "confirmDeleteQuarantineDescription": "Detta tar bort {{count}} filer i karantän permanent. Åtgärden kan inte ångras.", "confirmDeleteQuarantineLabel": "Ta bort permanent", "quarantineLoading": "Läser in karantän...", "quarantineRestoring": "Återställer filer...", diff --git a/src/renderer/src/locales/th/malware.json b/src/renderer/src/locales/th/malware.json index 1d8a91d0..daad3665 100644 --- a/src/renderer/src/locales/th/malware.json +++ b/src/renderer/src/locales/th/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "ตัวสแกนมัลแวร์", - "pageDescription": "การตรวจจับภัยคุกคามด้วยหลายเอนจิน — ลายเซ็น การวิเคราะห์เชิงฮิวริสติก การวิเคราะห์สคริปต์ ความสมบูรณ์ของระบบ และการสแกนการคงอยู่", + "pageDescription": "การตรวจจับภัยคุกคามแบบหลายเอนจิน — ลายเซ็น การวิเคราะห์เชิงพฤติกรรม การวิเคราะห์สคริปต์ ความสมบูรณ์ของระบบ และการสแกนการคงอยู่", "scanButtonScanning": "กำลังสแกน...", "scanButton": "สแกน", "quarantineButton": "กักกัน", @@ -9,10 +9,10 @@ "severityHigh": "สูง", "severityMedium": "ปานกลาง", "severityLow": "ต่ำ", - "sourceDefenderWindows": "AV ในระบบ", + "sourceDefenderWindows": "โปรแกรมป้องกันไวรัสในระบบ", "sourceDefenderMac": "การลงนามโค้ด", "sourceDefenderLinux": "ClamAV", - "sourceHeuristic": "การวิเคราะห์เชิงฮิวริสติก", + "sourceHeuristic": "การวิเคราะห์เชิงพฤติกรรม", "sourceSignature": "ลายเซ็นที่รู้จัก", "initializingScanEngines": "กำลังเริ่มต้นเอนจินการสแกน...", "filesScanned": "สแกนแล้ว {{count}} ไฟล์", @@ -33,8 +33,8 @@ "deselectAll": "ยกเลิกการเลือกทั้งหมด", "actingQuarantining": "กำลังกักกันภัยคุกคามที่เลือก...", "actingDeleting": "กำลังลบภัยคุกคามที่เลือก...", - "actionCompleteQuarantine": "กักกันเสร็จสิ้น", - "actionCompleteDeletion": "ลบเสร็จสิ้น", + "actionCompleteQuarantine": "กักกันเสร็จสมบูรณ์", + "actionCompleteDeletion": "ลบเสร็จสมบูรณ์", "actionResultQuarantined": "กักกันแล้ว {{count}} รายการ", "actionResultDeleted": "ลบแล้ว {{count}} รายการ", "actionResultFailed": "ล้มเหลว {{count}} รายการ", @@ -53,7 +53,7 @@ "confirmQuarantineDescription": "การดำเนินการนี้จะย้ายภัยคุกคามที่ตรวจพบ {{count}} รายการไปยังกักกัน และสามารถกู้คืนไฟล์ได้ภายหลังหากจำเป็น", "confirmQuarantineLabel": "กักกันตอนนี้", "confirmDeleteTitle": "ลบภัยคุกคาม", - "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร และไม่สามารถยกเลิกได้", + "confirmDeleteDescription": "การดำเนินการนี้จะลบภัยคุกคามที่ตรวจพบ {{count}} รายการอย่างถาวร และไม่สามารถย้อนกลับได้", "confirmDeleteLabel": "ลบอย่างถาวร", "toastScanFailed": "การสแกนมัลแวร์ล้มเหลว", "toastActionFailed": "ไม่สามารถ{{action}}ภัยคุกคามได้", @@ -63,11 +63,11 @@ "tabQuarantine": "กักกัน", "tabDatabase": "ฐานข้อมูล", "dbTitle": "ฐานข้อมูลลายเซ็น", - "dbDescription": "กฎการตรวจจับมัลแวร์ YARA ที่ใช้โดยตัวสแกน", + "dbDescription": "กฎการตรวจจับมัลแวร์ YARA ที่ตัวสแกนใช้", "dbFetchLatest": "ตรวจหาการอัปเดต", "dbUpdating": "กำลังอัปเดต...", "dbEngine": "เอนจิน", - "dbEngineYara": "YARA-X (Native)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex สำรอง", "dbEngineCompiling": "กำลังคอมไพล์...", "dbCompiling": "กำลังคอมไพล์กฎลายเซ็น ({{loaded}}/{{total}})...", @@ -78,12 +78,12 @@ "dbSource": "แหล่งที่มาของกฎ", "dbSourceCloud": "Cloud (แคชไว้)", "dbSourceBundled": "มาพร้อมแอป", - "dbSourceNone": "ไม่มีการโหลดกฎ", + "dbSourceNone": "ไม่ได้โหลดกฎ", "dbRuleFiles": "ไฟล์กฎ", "dbRuleFilesCounts": "มาพร้อมแอป {{bundled}} ไฟล์, จาก cloud {{cached}} ไฟล์", "dbUpdateSuccess": "อัปเดตเป็น v{{version}} แล้ว ({{count}} กฎ)", "dbAlreadyCurrent": "ลายเซ็นเป็นเวอร์ชันล่าสุดอยู่แล้ว", - "dbUpdateFailed": "อัปเดตลายเซ็นไม่สำเร็จ", + "dbUpdateFailed": "ไม่สามารถอัปเดตลายเซ็นได้", "quarantineEmptyTitle": "ไม่มีรายการที่ถูกกักกัน", "quarantineEmptyDescription": "ไฟล์ที่ย้ายไปยังกักกันจะแสดงที่นี่ คุณสามารถกู้คืนหรือลบอย่างถาวรได้", "quarantineHeading": "ไฟล์ที่ถูกกักกัน", @@ -99,10 +99,10 @@ "quarantineSelectAll": "เลือกทั้งหมด", "quarantineDeselectAll": "ยกเลิกการเลือกทั้งหมด", "confirmRestoreTitle": "กู้คืนไฟล์", - "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} ไฟล์ไปยังตำแหน่งเดิม กู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", + "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} ไฟล์ไปยังตำแหน่งเดิม โปรดกู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", "confirmRestoreLabel": "กู้คืนตอนนี้", "confirmDeleteQuarantineTitle": "ลบอย่างถาวร", - "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} ไฟล์อย่างถาวร และไม่สามารถยกเลิกได้", + "confirmDeleteQuarantineDescription": "การดำเนินการนี้จะลบไฟล์ที่ถูกกักกัน {{count}} ไฟล์อย่างถาวร และไม่สามารถย้อนกลับได้", "confirmDeleteQuarantineLabel": "ลบอย่างถาวร", "quarantineLoading": "กำลังโหลดรายการกักกัน...", "quarantineRestoring": "กำลังกู้คืนไฟล์...", diff --git a/src/renderer/src/locales/tr/malware.json b/src/renderer/src/locales/tr/malware.json index 20da0bad..8baef03e 100644 --- a/src/renderer/src/locales/tr/malware.json +++ b/src/renderer/src/locales/tr/malware.json @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}} silindi", "actionResultFailed": "{{count}} başarısız", "noThreatsDetectedTitle": "Tehdit Algılanmadı", - "noThreatsDetectedDescription": "{{duration}} sn içinde {{engineCount}} motorla {{filesScanned}} dosya tarandı — sisteminiz temiz.", + "noThreatsDetectedDescription": "{{duration}} sn içinde {{engineCount}} motorda {{filesScanned}} dosya tarandı — sisteminiz temiz.", "emptyStateTitle": "Kötü Amaçlı Yazılım Tarayıcısı", - "emptyStateDescription": "Sisteminizi kötü amaçlı yazılımlar, reklam yazılımları, kripto madencileri ve şüpheli dosyalar için denetlemek üzere \"Tara\" seçeneğine tıklayın.", + "emptyStateDescription": "Sisteminizi kötü amaçlı yazılım, reklam yazılımı, kripto madencileri ve şüpheli dosyalar için denetlemek üzere \"Tara\" seçeneğine tıklayın.", "detectedThreatsHeading": "Algılanan Tehditler", "detectedThreatsCount": "{{count}} tehdit", "detectedThreatsCountPlural": "{{count}} tehdit", @@ -56,7 +56,7 @@ "confirmDeleteDescription": "Bu işlem, algılanan {{count}} tehdit(ler)i kalıcı olarak silecektir. Bu işlem geri alınamaz.", "confirmDeleteLabel": "Kalıcı Olarak Sil", "toastScanFailed": "Kötü amaçlı yazılım taraması başarısız oldu", - "toastActionFailed": "Tehditler {{action}} işlemi başarısız oldu", + "toastActionFailed": "Tehditler için {{action}} işlemi başarısız oldu", "toastActionFailedDescription": "Yönetici olarak çalıştırmayı deneyin", "errorOperationFailed": "İşlem başarısız oldu — yönetici olarak çalıştırmayı deneyin", "tabScanner": "Tarayıcı", @@ -67,26 +67,26 @@ "dbFetchLatest": "Güncellemeleri Denetle", "dbUpdating": "Güncelleniyor...", "dbEngine": "Motor", - "dbEngineYara": "YARA-X (Yerel)", - "dbEngineRegex": "Regex Yedek Seçeneği", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Yedeği", "dbEngineCompiling": "Derleniyor...", "dbCompiling": "İmza kuralları derleniyor ({{loaded}}/{{total}})...", "dbRulesLoaded": "Yüklenen Kurallar", "dbVersion": "İmza Sürümü", "dbLastUpdated": "Son Güncelleme", - "dbNever": "Hiçbir zaman", + "dbNever": "Asla", "dbSource": "Kural Kaynağı", - "dbSourceCloud": "Bulut (önbelleğe alınmış)", + "dbSourceCloud": "Cloud (önbelleğe alınmış)", "dbSourceBundled": "Uygulamayla birlikte gelir", - "dbSourceNone": "Hiçbir kural yüklenmedi", + "dbSourceNone": "Hiç kural yüklenmedi", "dbRuleFiles": "Kural Dosyaları", - "dbRuleFilesCounts": "{{bundled}} paketle birlikte, {{cached}} buluttan", + "dbRuleFilesCounts": "{{bundled}} paketle birlikte, {{cached}} Cloud'dan", "dbUpdateSuccess": "v{{version}} sürümüne güncellendi ({{count}} kural)", "dbAlreadyCurrent": "İmzalar zaten güncel", "dbUpdateFailed": "İmzalar güncellenemedi", "quarantineEmptyTitle": "Karantinaya Alınmış Öğe Yok", "quarantineEmptyDescription": "Karantinaya taşınan dosyalar burada görünür. Bunları geri yükleyebilir veya kalıcı olarak silebilirsiniz.", - "quarantineHeading": "Karantinaya Alınan Dosyalar", + "quarantineHeading": "Karantinaya Alınmış Dosyalar", "quarantineCount": "{{count}} dosya", "quarantineCountPlural": "{{count}} dosya", "quarantineColumnFile": "Dosya", @@ -99,7 +99,7 @@ "quarantineSelectAll": "Tümünü Seç", "quarantineDeselectAll": "Tüm Seçimi Kaldır", "confirmRestoreTitle": "Dosyaları Geri Yükle", - "confirmRestoreDescription": "Bu işlem, {{count}} dosya(lar)ı özgün konumuna geri yükleyecektir. Yalnızca güvendiğiniz dosyaları geri yükleyin.", + "confirmRestoreDescription": "Bu işlem, {{count}} dosya(lar)ı özgün konumlarına geri yükleyecektir. Yalnızca güvendiğiniz dosyaları geri yükleyin.", "confirmRestoreLabel": "Şimdi Geri Yükle", "confirmDeleteQuarantineTitle": "Kalıcı Olarak Sil", "confirmDeleteQuarantineDescription": "Bu işlem, karantinadaki {{count}} dosya(lar)ı kalıcı olarak silecektir. Bu işlem geri alınamaz.", diff --git a/src/renderer/src/locales/uk/malware.json b/src/renderer/src/locales/uk/malware.json index e5a4d5d6..99b4373f 100644 --- a/src/renderer/src/locales/uk/malware.json +++ b/src/renderer/src/locales/uk/malware.json @@ -35,30 +35,30 @@ "actingDeleting": "Видалення вибраних загроз...", "actionCompleteQuarantine": "Переміщення до карантину завершено", "actionCompleteDeletion": "Видалення завершено", - "actionResultQuarantined": "Поміщено до карантину: {{count}}", + "actionResultQuarantined": "Переміщено до карантину: {{count}}", "actionResultDeleted": "Видалено: {{count}}", "actionResultFailed": "Не вдалося: {{count}}", "noThreatsDetectedTitle": "Загроз не виявлено", "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів за допомогою {{engineCount}} рушіїв за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер шкідливого ПЗ", - "emptyStateDescription": "Натисніть «Сканувати», щоб перевірити систему на шкідливе ПЗ, рекламне ПЗ, криптомайнери та підозрілі файли.", + "emptyStateDescription": "Натисніть \"Сканувати\", щоб перевірити систему на наявність шкідливого ПЗ, рекламного ПЗ, криптомайнерів і підозрілих файлів.", "detectedThreatsHeading": "Виявлені загрози", "detectedThreatsCount": "{{count}} загроза", "detectedThreatsCountPlural": "{{count}} загроз", - "threatDetailDetails": "Відомості", + "threatDetailDetails": "Докладно", "threatDetailFile": "Файл", "threatDetailSize": "Розмір", "threatDetailPath": "Шлях", - "confirmQuarantineTitle": "Помістити загрози до карантину", + "confirmQuarantineTitle": "Перемістити загрози до карантину", "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(и) до карантину. За потреби файли можна буде відновити пізніше.", - "confirmQuarantineLabel": "Помістити до карантину", + "confirmQuarantineLabel": "Перемістити до карантину", "confirmDeleteTitle": "Видалити загрози", - "confirmDeleteDescription": "Буде назавжди видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", - "confirmDeleteLabel": "Видалити назавжди", + "confirmDeleteDescription": "Буде остаточно видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", + "confirmDeleteLabel": "Видалити остаточно", "toastScanFailed": "Не вдалося виконати сканування на шкідливе ПЗ", "toastActionFailed": "Не вдалося {{action}} загрози", "toastActionFailedDescription": "Спробуйте запустити від імені адміністратора", - "errorOperationFailed": "Не вдалося виконати операцію — спробуйте запустити від імені адміністратора", + "errorOperationFailed": "Операцію не вдалося виконати — спробуйте запустити від імені адміністратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", "tabDatabase": "База даних", @@ -67,11 +67,11 @@ "dbFetchLatest": "Перевірити наявність оновлень", "dbUpdating": "Оновлення...", "dbEngine": "Рушій", - "dbEngineYara": "YARA-X (вбудований)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Резервний Regex", "dbEngineCompiling": "Компіляція...", "dbCompiling": "Компіляція правил сигнатур ({{loaded}}/{{total}})...", - "dbRulesLoaded": "Правил завантажено", + "dbRulesLoaded": "Завантажено правил", "dbVersion": "Версія сигнатур", "dbLastUpdated": "Останнє оновлення", "dbNever": "Ніколи", @@ -80,12 +80,12 @@ "dbSourceBundled": "У комплекті з програмою", "dbSourceNone": "Правила не завантажено", "dbRuleFiles": "Файли правил", - "dbRuleFilesCounts": "{{bundled}} у комплекті, {{cached}} із хмари", + "dbRuleFilesCounts": "{{bundled}} у комплекті, {{cached}} з хмари", "dbUpdateSuccess": "Оновлено до v{{version}} (правил: {{count}})", "dbAlreadyCurrent": "Сигнатури вже актуальні", "dbUpdateFailed": "Не вдалося оновити сигнатури", - "quarantineEmptyTitle": "Елементів у карантині немає", - "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити назавжди.", + "quarantineEmptyTitle": "Немає елементів у карантині", + "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або остаточно видалити.", "quarantineHeading": "Файли в карантині", "quarantineCount": "{{count}} файл", "quarantineCountPlural": "{{count}} файлів", @@ -95,20 +95,20 @@ "quarantineColumnOriginal": "Початкове розташування", "quarantineOriginalUnknown": "Невідомо", "quarantineRestoreButton": "Відновити", - "quarantineDeleteButton": "Видалити назавжди", + "quarantineDeleteButton": "Видалити остаточно", "quarantineSelectAll": "Вибрати все", "quarantineDeselectAll": "Зняти вибір з усього", "confirmRestoreTitle": "Відновити файли", "confirmRestoreDescription": "Буде відновлено {{count}} файл(ів) до початкового розташування. Відновлюйте лише ті файли, яким довіряєте.", "confirmRestoreLabel": "Відновити зараз", - "confirmDeleteQuarantineTitle": "Видалити назавжди", - "confirmDeleteQuarantineDescription": "Буде назавжди видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", - "confirmDeleteQuarantineLabel": "Видалити назавжди", + "confirmDeleteQuarantineTitle": "Остаточно видалити", + "confirmDeleteQuarantineDescription": "Буде остаточно видалено {{count}} файл(ів) із карантину. Цю дію неможливо скасувати.", + "confirmDeleteQuarantineLabel": "Видалити остаточно", "quarantineLoading": "Завантаження карантину...", "quarantineRestoring": "Відновлення файлів...", "quarantineDeleting": "Видалення файлів...", "toastRestoreSuccess": "Відновлено файл(ів): {{count}}", "toastRestoreFailed": "Не вдалося відновити деякі файли", - "toastDeleteQuarantineSuccess": "Назавжди видалено файл(ів): {{count}}", + "toastDeleteQuarantineSuccess": "Остаточно видалено файл(ів): {{count}}", "toastRestoreNoOriginal": "Неможливо відновити — початкове розташування невідоме" } diff --git a/src/renderer/src/locales/vi/malware.json b/src/renderer/src/locales/vi/malware.json index 91200090..26d0e3c4 100644 --- a/src/renderer/src/locales/vi/malware.json +++ b/src/renderer/src/locales/vi/malware.json @@ -1,6 +1,6 @@ { "pageTitle": "Trình quét phần mềm độc hại", - "pageDescription": "Phát hiện mối đe dọa bằng nhiều công cụ — chữ ký, phân tích heuristic, phân tích tập lệnh, tính toàn vẹn hệ thống và quét cơ chế bám trụ", + "pageDescription": "Phát hiện mối đe dọa bằng nhiều công cụ — chữ ký, phân tích heuristic, phân tích tập lệnh, tính toàn vẹn hệ thống và quét cơ chế duy trì", "scanButtonScanning": "Đang quét...", "scanButton": "Quét", "quarantineButton": "Cách ly", @@ -50,7 +50,7 @@ "threatDetailSize": "Kích thước", "threatDetailPath": "Đường dẫn", "confirmQuarantineTitle": "Cách ly mối đe dọa", - "confirmQuarantineDescription": "Thao tác này sẽ chuyển {{count}} mối đe dọa đã phát hiện vào vùng cách ly. Có thể khôi phục tệp sau nếu cần.", + "confirmQuarantineDescription": "Thao tác này sẽ chuyển {{count}} mối đe dọa đã phát hiện vào khu vực cách ly. Có thể khôi phục tệp sau nếu cần.", "confirmQuarantineLabel": "Cách ly ngay", "confirmDeleteTitle": "Xóa mối đe dọa", "confirmDeleteDescription": "Thao tác này sẽ xóa vĩnh viễn {{count}} mối đe dọa đã phát hiện. Không thể hoàn tác hành động này.", @@ -67,7 +67,7 @@ "dbFetchLatest": "Kiểm tra bản cập nhật", "dbUpdating": "Đang cập nhật...", "dbEngine": "Công cụ", - "dbEngineYara": "YARA-X (Gốc)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex dự phòng", "dbEngineCompiling": "Đang biên dịch...", "dbCompiling": "Đang biên dịch các quy tắc chữ ký ({{loaded}}/{{total}})...", @@ -76,16 +76,16 @@ "dbLastUpdated": "Cập nhật lần cuối", "dbNever": "Chưa bao giờ", "dbSource": "Nguồn quy tắc", - "dbSourceCloud": "Đám mây (đã lưu đệm)", + "dbSourceCloud": "Cloud (đã lưu đệm)", "dbSourceBundled": "Đi kèm ứng dụng", "dbSourceNone": "Chưa tải quy tắc nào", "dbRuleFiles": "Tệp quy tắc", - "dbRuleFilesCounts": "{{bundled}} đi kèm, {{cached}} từ đám mây", + "dbRuleFilesCounts": "{{bundled}} đi kèm, {{cached}} từ cloud", "dbUpdateSuccess": "Đã cập nhật lên v{{version}} ({{count}} quy tắc)", - "dbAlreadyCurrent": "Các chữ ký đã được cập nhật", + "dbAlreadyCurrent": "Các chữ ký đã được cập nhật mới nhất", "dbUpdateFailed": "Không thể cập nhật chữ ký", - "quarantineEmptyTitle": "Không có mục nào trong vùng cách ly", - "quarantineEmptyDescription": "Các tệp được chuyển vào vùng cách ly sẽ xuất hiện tại đây. Bạn có thể khôi phục hoặc xóa vĩnh viễn chúng.", + "quarantineEmptyTitle": "Không có mục nào trong khu vực cách ly", + "quarantineEmptyDescription": "Các tệp được chuyển vào khu vực cách ly sẽ xuất hiện ở đây. Bạn có thể khôi phục hoặc xóa vĩnh viễn chúng.", "quarantineHeading": "Các tệp đã cách ly", "quarantineCount": "{{count}} tệp", "quarantineCountPlural": "{{count}} tệp", @@ -104,7 +104,7 @@ "confirmDeleteQuarantineTitle": "Xóa vĩnh viễn", "confirmDeleteQuarantineDescription": "Thao tác này sẽ xóa vĩnh viễn {{count}} tệp đã cách ly. Không thể hoàn tác hành động này.", "confirmDeleteQuarantineLabel": "Xóa vĩnh viễn", - "quarantineLoading": "Đang tải vùng cách ly...", + "quarantineLoading": "Đang tải khu vực cách ly...", "quarantineRestoring": "Đang khôi phục tệp...", "quarantineDeleting": "Đang xóa tệp...", "toastRestoreSuccess": "Đã khôi phục {{count}} tệp", diff --git a/src/renderer/src/locales/zh-CN/malware.json b/src/renderer/src/locales/zh-CN/malware.json index e1fef44d..1daca481 100644 --- a/src/renderer/src/locales/zh-CN/malware.json +++ b/src/renderer/src/locales/zh-CN/malware.json @@ -26,7 +26,7 @@ "scanStatFiles": "文件", "scanStatDuration": "时长", "scanStatEngines": "引擎", - "severityHeading": "严重程度", + "severityHeading": "严重级别", "selectedHeading": "已选择", "selectedOfThreats": "共 {{count}} 个威胁", "selectAll": "全选", @@ -50,7 +50,7 @@ "threatDetailSize": "大小", "threatDetailPath": "路径", "confirmQuarantineTitle": "隔离威胁", - "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以恢复文件。", + "confirmQuarantineDescription": "这将把检测到的 {{count}} 个威胁移至隔离区。如有需要,稍后可以恢复这些文件。", "confirmQuarantineLabel": "立即隔离", "confirmDeleteTitle": "删除威胁", "confirmDeleteDescription": "这将永久删除检测到的 {{count}} 个威胁。此操作无法撤销。", @@ -67,10 +67,10 @@ "dbFetchLatest": "检查更新", "dbUpdating": "正在更新...", "dbEngine": "引擎", - "dbEngineYara": "YARA-X(原生)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex 回退", "dbEngineCompiling": "正在编译...", - "dbCompiling": "正在编译签名规则({{loaded}}/{{total}})...", + "dbCompiling": "正在编译签名规则 ({{loaded}}/{{total}})...", "dbRulesLoaded": "已加载规则", "dbVersion": "签名版本", "dbLastUpdated": "上次更新", @@ -84,9 +84,9 @@ "dbUpdateSuccess": "已更新到 v{{version}}({{count}} 条规则)", "dbAlreadyCurrent": "签名已是最新", "dbUpdateFailed": "更新签名失败", - "quarantineEmptyTitle": "没有已隔离项目", + "quarantineEmptyTitle": "没有已隔离的项目", "quarantineEmptyDescription": "移至隔离区的文件将显示在这里。您可以恢复或永久删除它们。", - "quarantineHeading": "已隔离文件", + "quarantineHeading": "已隔离的文件", "quarantineCount": "{{count}} 个文件", "quarantineCountPlural": "{{count}} 个文件", "quarantineColumnFile": "文件", @@ -99,7 +99,7 @@ "quarantineSelectAll": "全选", "quarantineDeselectAll": "取消全选", "confirmRestoreTitle": "恢复文件", - "confirmRestoreDescription": "这将把 {{count}} 个文件恢复到其原始位置。请仅恢复您信任的文件。", + "confirmRestoreDescription": "这将把 {{count}} 个文件恢复到其原始位置。仅恢复您信任的文件。", "confirmRestoreLabel": "立即恢复", "confirmDeleteQuarantineTitle": "永久删除", "confirmDeleteQuarantineDescription": "这将永久删除 {{count}} 个已隔离文件。此操作无法撤销。", diff --git a/src/renderer/src/locales/zh-TW/malware.json b/src/renderer/src/locales/zh-TW/malware.json index fcf84116..d4f93840 100644 --- a/src/renderer/src/locales/zh-TW/malware.json +++ b/src/renderer/src/locales/zh-TW/malware.json @@ -1,11 +1,11 @@ { "pageTitle": "惡意軟體掃描器", - "pageDescription": "多引擎威脅偵測 — 簽章、啟發式分析、指令碼分析、系統完整性與持續性掃描", + "pageDescription": "多引擎威脅偵測 — 特徵碼、啟發式分析、指令碼分析、系統完整性與持續性掃描", "scanButtonScanning": "掃描中...", "scanButton": "掃描", "quarantineButton": "隔離", "deleteButton": "刪除", - "severityCritical": "嚴重", + "severityCritical": "重大", "severityHigh": "高", "severityMedium": "中", "severityLow": "低", @@ -13,14 +13,14 @@ "sourceDefenderMac": "程式碼簽署", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "啟發式分析", - "sourceSignature": "已知簽章", + "sourceSignature": "已知特徵碼", "initializingScanEngines": "正在初始化掃描引擎...", "filesScanned": "已掃描 {{count}} 個檔案", "threatCount": "{{count}} 個威脅", "threatCountPlural": "{{count}} 個威脅", "threatFound": "找到 {{count}} 個", "scanCategoryClean": "乾淨", - "scanCategoryNA": "N/A", + "scanCategoryNA": "不適用", "scanSummarySystemClean": "系統乾淨", "scanSummaryThreatsDetected": "已偵測到 {{count}} 個威脅", "scanStatFiles": "檔案", @@ -28,20 +28,20 @@ "scanStatEngines": "引擎", "severityHeading": "嚴重性", "selectedHeading": "已選取", - "selectedOfThreats": "/ {{count}} 個威脅", + "selectedOfThreats": "共 {{count}} 個威脅", "selectAll": "全選", "deselectAll": "取消全選", - "actingQuarantining": "正在隔離選取的威脅...", - "actingDeleting": "正在刪除選取的威脅...", + "actingQuarantining": "正在隔離所選威脅...", + "actingDeleting": "正在刪除所選威脅...", "actionCompleteQuarantine": "隔離完成", "actionCompleteDeletion": "刪除完成", "actionResultQuarantined": "已隔離 {{count}} 個", "actionResultDeleted": "已刪除 {{count}} 個", "actionResultFailed": "{{count}} 個失敗", "noThreatsDetectedTitle": "未偵測到威脅", - "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統很乾淨。", + "noThreatsDetectedDescription": "已在 {{duration}} 秒內使用 {{engineCount}} 個引擎掃描 {{filesScanned}} 個檔案 — 您的系統是乾淨的。", "emptyStateTitle": "惡意軟體掃描器", - "emptyStateDescription": "按一下「掃描」以檢查您的系統是否有惡意軟體、廣告軟體、加密貨幣挖礦程式和可疑檔案。", + "emptyStateDescription": "按一下「掃描」以檢查您的系統是否有惡意軟體、廣告軟體、加密貨幣挖礦程式與可疑檔案。", "detectedThreatsHeading": "已偵測到的威脅", "detectedThreatsCount": "{{count}} 個威脅", "detectedThreatsCountPlural": "{{count}} 個威脅", @@ -50,10 +50,10 @@ "threatDetailSize": "大小", "threatDetailPath": "路徑", "confirmQuarantineTitle": "隔離威脅", - "confirmQuarantineDescription": "這會將偵測到的 {{count}} 個威脅移至隔離區。如有需要,之後可還原檔案。", + "confirmQuarantineDescription": "這將把偵測到的 {{count}} 個威脅移至隔離區。如有需要,之後可還原檔案。", "confirmQuarantineLabel": "立即隔離", "confirmDeleteTitle": "刪除威脅", - "confirmDeleteDescription": "這會永久刪除偵測到的 {{count}} 個威脅。此動作無法復原。", + "confirmDeleteDescription": "這將永久刪除偵測到的 {{count}} 個威脅。此動作無法復原。", "confirmDeleteLabel": "永久刪除", "toastScanFailed": "惡意軟體掃描失敗", "toastActionFailed": "無法{{action}}威脅", @@ -62,28 +62,28 @@ "tabScanner": "掃描器", "tabQuarantine": "隔離區", "tabDatabase": "資料庫", - "dbTitle": "簽章資料庫", + "dbTitle": "特徵碼資料庫", "dbDescription": "掃描器使用的 YARA 惡意軟體偵測規則", "dbFetchLatest": "檢查更新", "dbUpdating": "更新中...", "dbEngine": "引擎", - "dbEngineYara": "YARA-X(原生)", + "dbEngineYara": "Kudu Cloud Signatures", "dbEngineRegex": "Regex 備援", "dbEngineCompiling": "編譯中...", - "dbCompiling": "正在編譯簽章規則({{loaded}}/{{total}})...", + "dbCompiling": "正在編譯特徵碼規則 ({{loaded}}/{{total}})...", "dbRulesLoaded": "已載入規則", - "dbVersion": "簽章版本", + "dbVersion": "特徵碼版本", "dbLastUpdated": "上次更新", "dbNever": "從未", "dbSource": "規則來源", "dbSourceCloud": "雲端(已快取)", - "dbSourceBundled": "隨應用程式附帶", + "dbSourceBundled": "隨應用程式提供", "dbSourceNone": "未載入任何規則", "dbRuleFiles": "規則檔案", - "dbRuleFilesCounts": "{{bundled}} 個內建,{{cached}} 個來自雲端", + "dbRuleFilesCounts": "{{bundled}} 個隨附,{{cached}} 個來自雲端", "dbUpdateSuccess": "已更新至 v{{version}}({{count}} 條規則)", - "dbAlreadyCurrent": "簽章已是最新版本", - "dbUpdateFailed": "更新簽章失敗", + "dbAlreadyCurrent": "特徵碼已是最新版本", + "dbUpdateFailed": "更新特徵碼失敗", "quarantineEmptyTitle": "沒有已隔離的項目", "quarantineEmptyDescription": "移至隔離區的檔案會顯示在這裡。您可以還原或永久刪除它們。", "quarantineHeading": "已隔離的檔案", @@ -99,10 +99,10 @@ "quarantineSelectAll": "全選", "quarantineDeselectAll": "取消全選", "confirmRestoreTitle": "還原檔案", - "confirmRestoreDescription": "這會將 {{count}} 個檔案還原到其原始位置。請只還原您信任的檔案。", + "confirmRestoreDescription": "這將把 {{count}} 個檔案還原至其原始位置。請僅還原您信任的檔案。", "confirmRestoreLabel": "立即還原", "confirmDeleteQuarantineTitle": "永久刪除", - "confirmDeleteQuarantineDescription": "這會永久刪除 {{count}} 個已隔離的檔案。此動作無法復原。", + "confirmDeleteQuarantineDescription": "這將永久刪除 {{count}} 個已隔離的檔案。此動作無法復原。", "confirmDeleteQuarantineLabel": "永久刪除", "quarantineLoading": "正在載入隔離區...", "quarantineRestoring": "正在還原檔案...", From 93872a36691c1678912b6f7d6e95205f93abcb03 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 06:16:53 +0200 Subject: [PATCH 29/35] fix(ui): use engine field instead of available for Database tab label The renderer was checking yaraInfo.available (false before compilation) instead of yaraInfo.engine ('yara' when rules exist on disk). Now correctly shows "Kudu Cloud Signatures" when cached rules are present. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/src/pages/MalwareScannerPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/pages/MalwareScannerPage.tsx b/src/renderer/src/pages/MalwareScannerPage.tsx index ec3ccc41..a06ee18b 100644 --- a/src/renderer/src/pages/MalwareScannerPage.tsx +++ b/src/renderer/src/pages/MalwareScannerPage.tsx @@ -1175,10 +1175,10 @@ export function MalwareScannerPage() {

{t('dbEngine')}

- {yaraInfo.engine === 'compiling' ? t('dbEngineCompiling') : yaraInfo.available ? t('dbEngineYara') : t('dbEngineRegex')} + {yaraInfo.engine === 'compiling' ? t('dbEngineCompiling') : yaraInfo.engine === 'yara' ? t('dbEngineYara') : t('dbEngineRegex')}
From 8145dbb2ef3739e02544c40e2df913e0b670a725 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 06:21:14 +0200 Subject: [PATCH 30/35] feat(ui): add Scan button to malware scanner empty state Add a prominent "Scan" button below the empty state description so users don't have to find the small button in the top-right corner. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/renderer/src/pages/MalwareScannerPage.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/renderer/src/pages/MalwareScannerPage.tsx b/src/renderer/src/pages/MalwareScannerPage.tsx index a06ee18b..2a55c66b 100644 --- a/src/renderer/src/pages/MalwareScannerPage.tsx +++ b/src/renderer/src/pages/MalwareScannerPage.tsx @@ -815,6 +815,21 @@ export function MalwareScannerPage() { icon={Shield} title={t('emptyStateTitle')} description={t('emptyStateDescription')} + action={ + + } /> )} From 6d43315dab6c244147ef8c2bde046f9bf120c093 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 07:01:52 +0200 Subject: [PATCH 31/35] fix(malware): remove pre-scan fetch, show compilation progress during init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-scan rule update had a 60-second timeout that could stall the scan for over a minute on slow/unreachable servers. Removed it entirely — rule freshness is handled by the periodic 6-hour check and the Database tab's manual update button. Phase 1 now shows "Compiling signature rules..." during YARA initialization so the user sees what's happening instead of a stuck "Initializing scan engines..." message. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 39 +++++++++++------------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 5422517c..5d46e482 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1208,14 +1208,9 @@ export async function scanMalware( const scannableExts = getScannableExtensions() const systemDirs = getSystemDirs() - // YARA engine: try to initialize once per app lifetime - const yaraEngine = await getYaraEngine() - if (yaraEngine) { - // Replace 'Known Signatures' with 'YARA Rules Engine' in the engines list - const sigIdx = engines.indexOf('Known Signatures') - if (sigIdx !== -1) engines[sigIdx] = 'YARA Rules Engine' - else engines.unshift('YARA Rules Engine') - } + // YARA engine init is deferred until after sendProgress is defined, + // so we can show compilation progress to the user during Phase 1. + let yaraEngine: YaraEngine | null = null // Native AV: ClamAV on Linux, codesign on macOS (skip Defender on Windows — real-time protection already covers it) const hasNativeAv = !!platform.malware.scanWithNativeAv && process.platform !== 'win32' @@ -1262,28 +1257,22 @@ export async function scanMalware( } // ── Phase 1: Initialize ── + // Compile YARA rules — shows progress via the loadRules onProgress callback. + // Rule updates are handled by periodic checks (every 6h) and the Database + // tab's "Check for Updates" button — no pre-scan fetch needed. + sendProgress({ step: 'init', stepLabel: 'Compiling signature rules...', engine: 'YARA Rules Engine' }) + yaraEngine = await getYaraEngine() + if (yaraEngine) { + const sigIdx = engines.indexOf('Known Signatures') + if (sigIdx !== -1) engines[sigIdx] = 'YARA Rules Engine' + else engines.unshift('YARA Rules Engine') + } sendProgress({ step: 'init', stepLabel: 'Initializing scan engines...', - engine: hasNativeAv ? (process.platform === 'linux' ? 'ClamAV available' : 'Code Signing available') : 'Using built-in engines' + engine: yaraEngine ? 'YARA Rules Engine' : (hasNativeAv ? (process.platform === 'linux' ? 'ClamAV available' : 'Code Signing available') : 'Using built-in engines') }) - // Try to update rules if stale (>1 hour since last fetch) before scanning - const ruleMeta = getRulesMetadata() - const RULE_STALE_MS = 60 * 60 * 1000 // 1 hour - const lastUpdate = ruleMeta?.updatedAt ? new Date(ruleMeta.updatedAt).getTime() : 0 - if (Date.now() - lastUpdate > RULE_STALE_MS) { - sendProgress({ step: 'init', stepLabel: 'Checking for signature updates...', engine: 'YARA Rules Engine' }) - try { - const updateUrl = CLOUD_SERVER_URL + RULES_ENDPOINT - const updateResult = await fetchAndCacheRules(updateUrl) - if (updateResult.success && updateResult.stats) { - console.log(`[yara] Pre-scan update: v${updateResult.stats.version} (${updateResult.stats.rulesCount} rules)`) - resetYaraEngine() - } - } catch { /* non-critical — scan with existing rules */ } - } - // ── Phase 2: File discovery ── updateCategory('discovering', { status: 'running' }) const scanPaths = getScanPaths() From 9673a422b0dd1dd7f30023635710fac3cf14e08e Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 07:24:40 +0200 Subject: [PATCH 32/35] =?UTF-8?q?perf(malware):=20single-call=20rule=20com?= =?UTF-8?q?pilation=20=E2=80=94=208=20minutes=20down=20to=202=20seconds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addRuleFile() recompiles the entire accumulated ruleset on every call, getting exponentially slower (1374 files took 8.4 minutes). Switch to concatenating all rule sources and calling compile() once (~2 seconds). If the bulk compile fails (bad rule syntax), falls back to per-file validation to find and exclude broken files (~6 seconds), then compiles the rest in a single call. Benchmark: 1374 rule files - Before: 502 seconds (8.4 minutes, blocked UI entirely) - After: ~2 seconds (fast path) or ~8 seconds (with bad rules) Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/yara-engine.ts | 87 +++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index edf7b1ce..1906e293 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -64,11 +64,13 @@ export class YaraEngine { /** * Compile YARA rules from file paths and/or raw source strings. - * Rules are compiled once — subsequent scan() calls are fast. * - * Compilation is chunked with event-loop yields so the main process - * stays responsive (each addRuleFile takes ~5-8ms, and with 1400+ - * files this would otherwise block the UI for 10+ seconds). + * Strategy: concatenate all sources and compile in a single call (~2s). + * If that fails (bad rule syntax), fall back to per-file validation to + * find and exclude the broken files, then compile the rest. + * + * This is ~240x faster than calling addRuleFile() per file, because + * addRuleFile recompiles the entire accumulated ruleset on every call. * * @param onProgress Optional callback fired with (loaded, total) counts */ @@ -81,39 +83,76 @@ export class YaraEngine { return { loaded: 0, errors: ['YARA engine not initialized'] } } + const yarax: YaraXModule = require('@litko/yara-x') const errors: string[] = [] - let loaded = 0 const total = ruleFilePaths.length + extraSources.length - const CHUNK_SIZE = 5 // yield to event loop every N files (~35ms per chunk) - // Load from file paths in chunks - for (let i = 0; i < ruleFilePaths.length; i++) { + // Read all sources + onProgress?.(0, total) + const sources: { name: string; content: string }[] = [] + for (const filePath of ruleFilePaths) { try { - this._scanner.addRuleFile(ruleFilePaths[i]) - loaded++ + sources.push({ name: basename(filePath), content: readFileSync(filePath, 'utf-8') }) } catch (err) { - errors.push(`${basename(ruleFilePaths[i])}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) - } - // Yield to the event loop periodically so the UI stays responsive - if ((i + 1) % CHUNK_SIZE === 0) { - onProgress?.(loaded, total) - await new Promise(resolve => setImmediate(resolve)) + errors.push(`${basename(filePath)}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) } } + for (let i = 0; i < extraSources.length; i++) { + sources.push({ name: `source-${i}`, content: extraSources[i] }) + } + + if (sources.length === 0) { + this._rulesLoaded = 0 + return { loaded: 0, errors } + } + + // Try fast path: compile everything in one call (~2s for 1400 files) + const combined = sources.map(s => s.content).join('\n') + try { + onProgress?.(Math.floor(total * 0.5), total) + await new Promise(resolve => setImmediate(resolve)) + this._scanner = yarax.compile(combined) + this._rulesLoaded = sources.length + onProgress?.(total, total) + return { loaded: sources.length, errors } + } catch { + // Fast path failed — some rule has bad syntax. Fall back to per-file + // validation to find and exclude broken files. + console.warn('[yara] Bulk compile failed, falling back to per-file validation...') + } - // Load from source strings - for (const source of extraSources) { + // Slow path: validate each file individually, exclude broken ones + const validSources: string[] = [] + for (let i = 0; i < sources.length; i++) { try { - this._scanner.addRuleSource(source) - loaded++ + yarax.compile(sources[i].content) + validSources.push(sources[i].content) } catch (err) { - errors.push(`source: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) + errors.push(`${sources[i].name}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) } + if ((i + 1) % 20 === 0) { + onProgress?.(i + 1, total) + await new Promise(resolve => setImmediate(resolve)) + } + } + + if (validSources.length === 0) { + this._rulesLoaded = 0 + return { loaded: 0, errors } + } + + // Compile the valid rules + try { + onProgress?.(total, total) + this._scanner = yarax.compile(validSources.join('\n')) + } catch (err) { + errors.push(`Final compile: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) + this._rulesLoaded = 0 + return { loaded: 0, errors } } - onProgress?.(loaded, total) - this._rulesLoaded = loaded - return { loaded, errors } + this._rulesLoaded = validSources.length + return { loaded: validSources.length, errors } } get rulesLoaded(): number { From 38fe4b1b22dbcdeed3f98f12d08d78a41dddc96d Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 07:45:39 +0200 Subject: [PATCH 33/35] =?UTF-8?q?feat(malware):=20per-path=20scan=20limits?= =?UTF-8?q?=20=E2=80=94=20more=20files,=20smarter=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the flat 10,000-file global cap with per-path depth and file limits tuned by risk level: - High-risk (Downloads, Desktop, Temp): depth 6, up to 10k files - Medium-risk (AppData, ProgramData): depth 4-5, up to 8k files - Lower-risk (Program Files): depth 2, up to 3k files - User home root: depth 1 only (catch dropped files, skip subdirs) Total scannable files across all paths: ~65k (was 10k hard cap). Safe locations scan shallow to avoid wasting time on known-good directories while high-risk locations get thorough deep scans. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/ipc/malware-scanner.ipc.ts | 16 ++++----- src/main/platform/darwin/paths.test.ts | 2 +- src/main/platform/darwin/paths.ts | 49 ++++++++++++++------------ src/main/platform/linux/paths.test.ts | 2 +- src/main/platform/linux/paths.ts | 33 +++++++++-------- src/main/platform/types.ts | 10 ++++-- src/main/platform/win32/paths.test.ts | 2 +- src/main/platform/win32/paths.ts | 31 +++++++++------- 8 files changed, 83 insertions(+), 62 deletions(-) diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index 5d46e482..b1e743ef 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1275,20 +1275,20 @@ export async function scanMalware( // ── Phase 2: File discovery ── updateCategory('discovering', { status: 'running' }) - const scanPaths = getScanPaths() + const scanDirs = getScanPaths() const exclusions = getSettings().exclusions const files: string[] = [] - for (let si = 0; si < scanPaths.length; si++) { - const scanPath = scanPaths[si] - updateCategory('discovering', { progress: Math.round((si / scanPaths.length) * 100) }) + for (let si = 0; si < scanDirs.length; si++) { + const dir = scanDirs[si] + updateCategory('discovering', { progress: Math.round((si / scanDirs.length) * 100) }) sendProgress({ step: 'discovering', - stepLabel: `Searching for scannable files (${si + 1}/${scanPaths.length})`, - currentPath: scanPath, - progress: (si / scanPaths.length) * 5, + stepLabel: `Searching for scannable files (${si + 1}/${scanDirs.length})`, + currentPath: dir.path, + progress: (si / scanDirs.length) * 5, engine: 'File Discovery' }) - await collectFiles(scanPath, 8, 10000, files, scannableExts, exclusions) + await collectFiles(dir.path, dir.maxDepth, dir.maxFiles, files, scannableExts, exclusions) } updateCategory('discovering', { status: 'done', progress: 100, itemsScanned: files.length, totalItems: files.length }) completedSteps.push(`File discovery — ${files.length.toLocaleString()} scannable files found`) diff --git a/src/main/platform/darwin/paths.test.ts b/src/main/platform/darwin/paths.test.ts index 67924a64..93b8d763 100644 --- a/src/main/platform/darwin/paths.test.ts +++ b/src/main/platform/darwin/paths.test.ts @@ -9,7 +9,7 @@ describe('darwin paths', () => { const paths = createDarwinPaths() describe('malwareScanDirs', () => { - const dirs = paths.malwareScanDirs() + const dirs = paths.malwareScanDirs().map(d => d.path) it('returns a non-empty array', () => { expect(dirs.length).toBeGreaterThan(5) diff --git a/src/main/platform/darwin/paths.ts b/src/main/platform/darwin/paths.ts index 3cb62ff5..af165738 100644 --- a/src/main/platform/darwin/paths.ts +++ b/src/main/platform/darwin/paths.ts @@ -36,29 +36,34 @@ export function createDarwinPaths(): PlatformPaths { return { ...cleanerPaths, - malwareScanDirs(): string[] { + malwareScanDirs() { return [ - join(HOME, 'Downloads'), - join(HOME, 'Desktop'), - join(HOME, 'Documents'), - HOME, // files dropped directly in user home - '/tmp', - '/private/tmp', - '/var/tmp', - join(LIBRARY, 'LaunchAgents'), - join(LIBRARY, 'LaunchDaemons'), - '/Library/LaunchAgents', - '/Library/LaunchDaemons', - '/Library/StartupItems', - join(HOME, '.local', 'bin'), - join(LIBRARY, 'Application Scripts'), - join(LIBRARY, 'Services'), - join(LIBRARY, 'Workflows'), - '/usr/local/bin', - '/opt/local/bin', - '/Applications', - '/Users/Shared', - APP_SUPPORT, + // High-risk: common drop locations + { path: join(HOME, 'Downloads'), maxDepth: 6, maxFiles: 10000 }, + { path: join(HOME, 'Desktop'), maxDepth: 4, maxFiles: 5000 }, + { path: join(HOME, 'Documents'), maxDepth: 4, maxFiles: 5000 }, + { path: HOME, maxDepth: 1, maxFiles: 500 }, + { path: '/tmp', maxDepth: 3, maxFiles: 5000 }, + { path: '/private/tmp', maxDepth: 3, maxFiles: 5000 }, + { path: '/var/tmp', maxDepth: 3, maxFiles: 3000 }, + { path: '/Users/Shared', maxDepth: 4, maxFiles: 3000 }, + + // Persistence locations — deep scan + { path: join(LIBRARY, 'LaunchAgents'), maxDepth: 2, maxFiles: 2000 }, + { path: join(LIBRARY, 'LaunchDaemons'), maxDepth: 2, maxFiles: 2000 }, + { path: '/Library/LaunchAgents', maxDepth: 2, maxFiles: 2000 }, + { path: '/Library/LaunchDaemons', maxDepth: 2, maxFiles: 2000 }, + { path: '/Library/StartupItems', maxDepth: 2, maxFiles: 1000 }, + { path: join(HOME, '.local', 'bin'), maxDepth: 2, maxFiles: 1000 }, + { path: join(LIBRARY, 'Application Scripts'), maxDepth: 3, maxFiles: 2000 }, + { path: join(LIBRARY, 'Services'), maxDepth: 3, maxFiles: 2000 }, + { path: join(LIBRARY, 'Workflows'), maxDepth: 3, maxFiles: 2000 }, + + // Medium-risk: installed software + { path: '/usr/local/bin', maxDepth: 1, maxFiles: 2000 }, + { path: '/opt/local/bin', maxDepth: 1, maxFiles: 2000 }, + { path: '/Applications', maxDepth: 2, maxFiles: 3000 }, + { path: APP_SUPPORT, maxDepth: 3, maxFiles: 5000 }, ] }, diff --git a/src/main/platform/linux/paths.test.ts b/src/main/platform/linux/paths.test.ts index bd0b4c9f..d15d9b7c 100644 --- a/src/main/platform/linux/paths.test.ts +++ b/src/main/platform/linux/paths.test.ts @@ -12,7 +12,7 @@ describe('linux paths', () => { const paths = createLinuxPaths() describe('malwareScanDirs', () => { - const dirs = paths.malwareScanDirs() + const dirs = paths.malwareScanDirs().map(d => d.path) it('returns an array of scan directories', () => { expect(dirs.length).toBeGreaterThanOrEqual(3) diff --git a/src/main/platform/linux/paths.ts b/src/main/platform/linux/paths.ts index 3acb6236..70d8fdd4 100644 --- a/src/main/platform/linux/paths.ts +++ b/src/main/platform/linux/paths.ts @@ -36,21 +36,26 @@ export function createLinuxPaths(): PlatformPaths { return { ...cleanerPaths, - malwareScanDirs(): string[] { + malwareScanDirs() { return [ - join(HOME, 'Downloads'), - join(HOME, 'Desktop'), - join(HOME, 'Documents'), - HOME, // files dropped directly in user home - '/tmp', - '/var/tmp', - '/dev/shm', - join(HOME, '.local', 'bin'), - join(HOME, '.config', 'autostart'), - CONFIG, - LOCAL_SHARE, - '/usr/local/bin', - '/opt', + // High-risk: common drop locations + { path: join(HOME, 'Downloads'), maxDepth: 6, maxFiles: 10000 }, + { path: join(HOME, 'Desktop'), maxDepth: 4, maxFiles: 5000 }, + { path: join(HOME, 'Documents'), maxDepth: 4, maxFiles: 5000 }, + { path: HOME, maxDepth: 1, maxFiles: 500 }, + { path: '/tmp', maxDepth: 3, maxFiles: 5000 }, + { path: '/var/tmp', maxDepth: 3, maxFiles: 3000 }, + { path: '/dev/shm', maxDepth: 2, maxFiles: 2000 }, + + // Persistence & config locations + { path: join(HOME, '.local', 'bin'), maxDepth: 2, maxFiles: 1000 }, + { path: join(HOME, '.config', 'autostart'), maxDepth: 2, maxFiles: 1000 }, + { path: CONFIG, maxDepth: 3, maxFiles: 5000 }, + { path: LOCAL_SHARE, maxDepth: 3, maxFiles: 5000 }, + + // System binaries — shallow scan + { path: '/usr/local/bin', maxDepth: 1, maxFiles: 2000 }, + { path: '/opt', maxDepth: 2, maxFiles: 3000 }, ] }, diff --git a/src/main/platform/types.ts b/src/main/platform/types.ts index 9f0a4bdf..5bcbc5fb 100644 --- a/src/main/platform/types.ts +++ b/src/main/platform/types.ts @@ -68,6 +68,12 @@ export interface UninstallLeftoverDir { path: string } +export interface MalwareScanDir { + path: string + maxDepth: number + maxFiles: number +} + export interface PlatformPaths { /** System cleanup targets (temp files, caches, logs, etc.) */ systemCleanTargets(): CleanTarget[] @@ -90,8 +96,8 @@ export interface PlatformPaths { /** GPU shader cache paths */ gpuCachePaths(): AppCacheDef[] - /** Directories to scan for malware */ - malwareScanDirs(): string[] + /** Directories to scan for malware, with per-path scan limits */ + malwareScanDirs(): MalwareScanDir[] /** System directories to exclude from suspicious filename checks */ malwareSystemDirs(): string[] diff --git a/src/main/platform/win32/paths.test.ts b/src/main/platform/win32/paths.test.ts index 3e667244..7c54cea8 100644 --- a/src/main/platform/win32/paths.test.ts +++ b/src/main/platform/win32/paths.test.ts @@ -186,7 +186,7 @@ describe('win32 paths', () => { }) describe('malwareScanDirs', () => { - const dirs = paths.malwareScanDirs() + const dirs = paths.malwareScanDirs().map(d => d.path) it('includes user Downloads and Desktop', () => { expect(dirs.some((d) => d.includes('Downloads'))).toBe(true) diff --git a/src/main/platform/win32/paths.ts b/src/main/platform/win32/paths.ts index 3cbb76de..16e502b5 100644 --- a/src/main/platform/win32/paths.ts +++ b/src/main/platform/win32/paths.ts @@ -39,21 +39,26 @@ export function createWin32Paths(): PlatformPaths { return { ...cleanerPaths, - malwareScanDirs(): string[] { + malwareScanDirs() { const userProfile = process.env.USERPROFILE || HOME return [ - join(userProfile, 'Downloads'), - join(userProfile, 'Desktop'), - join(userProfile, 'Documents'), - userProfile, // files dropped directly in user home - join(LOCALAPPDATA, 'Temp'), - APPDATA, - LOCALAPPDATA, - PROGRAMDATA, - PROGRAMFILES, - PROGRAMFILES_X86, - 'C:\\Users\\Public', - 'C:\\Windows\\Temp', + // High-risk: common malware drop locations — deep scan, high file limits + { path: join(userProfile, 'Downloads'), maxDepth: 6, maxFiles: 10000 }, + { path: join(userProfile, 'Desktop'), maxDepth: 4, maxFiles: 5000 }, + { path: join(userProfile, 'Documents'), maxDepth: 4, maxFiles: 5000 }, + { path: userProfile, maxDepth: 1, maxFiles: 500 }, + { path: join(LOCALAPPDATA, 'Temp'), maxDepth: 4, maxFiles: 10000 }, + { path: 'C:\\Windows\\Temp', maxDepth: 3, maxFiles: 5000 }, + { path: 'C:\\Users\\Public', maxDepth: 4, maxFiles: 3000 }, + + // Medium-risk: persistence & dropper locations — moderate scan + { path: APPDATA, maxDepth: 5, maxFiles: 8000 }, + { path: LOCALAPPDATA, maxDepth: 4, maxFiles: 8000 }, + { path: PROGRAMDATA, maxDepth: 3, maxFiles: 5000 }, + + // Lower-risk: installed programs — shallow scan for trojaned executables + { path: PROGRAMFILES, maxDepth: 2, maxFiles: 3000 }, + { path: PROGRAMFILES_X86, maxDepth: 2, maxFiles: 3000 }, ] }, From 8ed17dc5ace2564df52025333f1d512c10a986f2 Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 07:49:18 +0200 Subject: [PATCH 34/35] perf(malware): skip rules for other platforms before compilation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter rule files by filename before compiling — files containing _windows_, _linux_, _macos_, or _darwin_ in their name are skipped if they don't match the current OS. This happens client-side after the integrity-checked bundle download, so no cloud changes needed. Impact: On Windows skips 266 Linux/macOS files (19%), on macOS skips 611 files (44%), on Linux skips 425 files (31%). Reduces both compilation time and memory usage proportionally. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/main/services/yara-engine.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/services/yara-engine.ts b/src/main/services/yara-engine.ts index 1906e293..03701195 100644 --- a/src/main/services/yara-engine.ts +++ b/src/main/services/yara-engine.ts @@ -87,19 +87,38 @@ export class YaraEngine { const errors: string[] = [] const total = ruleFilePaths.length + extraSources.length - // Read all sources + // Platform filter: skip rule files for other OSes to reduce compilation cost. + // Files are named like elastic_Linux_Trojan_Mirai.yar or elastic_Windows_Generic.yar. + // We skip files containing a platform tag that doesn't match the current OS. + const platformSkip: string[] = [] + if (process.platform !== 'win32') platformSkip.push('_windows_', '_win32_') + if (process.platform !== 'linux') platformSkip.push('_linux_') + if (process.platform !== 'darwin') platformSkip.push('_macos_', '_darwin_') + + function shouldSkipForPlatform(name: string): boolean { + const lower = name.toLowerCase() + return platformSkip.some(tag => lower.includes(tag)) + } + + // Read all sources (skipping irrelevant platforms) onProgress?.(0, total) const sources: { name: string; content: string }[] = [] + let skippedPlatform = 0 for (const filePath of ruleFilePaths) { + const name = basename(filePath) + if (shouldSkipForPlatform(name)) { skippedPlatform++; continue } try { - sources.push({ name: basename(filePath), content: readFileSync(filePath, 'utf-8') }) + sources.push({ name, content: readFileSync(filePath, 'utf-8') }) } catch (err) { - errors.push(`${basename(filePath)}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) + errors.push(`${name}: ${err instanceof Error ? err.message.split('\n')[0] : String(err)}`) } } for (let i = 0; i < extraSources.length; i++) { sources.push({ name: `source-${i}`, content: extraSources[i] }) } + if (skippedPlatform > 0) { + console.log(`[yara] Skipped ${skippedPlatform} rule files for other platforms`) + } if (sources.length === 0) { this._rulesLoaded = 0 From 289c972406a89c4632d18167d48f8c3ebaadb5ba Mon Sep 17 00:00:00 2001 From: Dave Date: Sun, 29 Mar 2026 08:11:15 +0200 Subject: [PATCH 35/35] =?UTF-8?q?fix(malware):=20codex=20review=20?= =?UTF-8?q?=E2=80=94=20path=20validators,=20filenameOnly,=20sort,=20IPv6?= =?UTF-8?q?=20SSRF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix getScanPaths() to return string[] for path validators (quarantine, delete, restore) while getScanDirs() returns MalwareScanDir[] for file discovery. Prevents ERR_INVALID_ARG_TYPE on remediation actions. - Handle filenameOnly YARA matches with path context (skip only in system dirs on Windows) instead of discarding them entirely. Cloud- pushed masquerade rules now produce detections when YARA is active. - Use deterministic sort (< >) instead of localeCompare in the build fetch script, matching computeBundleHash to prevent false SHA mismatches. - Block private IPv6 ranges (fc00::/7, fe80::/10, ::ffff: mapped) in the YARA update SSRF check, closing the IPv6 literal bypass. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/fetch-yara-rules.js | 2 +- src/main/ipc/malware-scanner.ipc.ts | 13 ++++++++++--- src/main/services/cloud-agent.ts | 13 ++++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js index ed11d821..2b3e0558 100644 --- a/scripts/fetch-yara-rules.js +++ b/scripts/fetch-yara-rules.js @@ -58,7 +58,7 @@ async function main() { // Verify integrity if sha256 is present if (body.sha256) { - const sorted = [...body.rules].sort((a, b) => a.filename.localeCompare(b.filename)) + const sorted = [...body.rules].sort((a, b) => a.filename < b.filename ? -1 : a.filename > b.filename ? 1 : 0) const combined = sorted.map(r => r.content).join('') const computed = createHash('sha256').update(combined).digest('hex') if (computed !== body.sha256) { diff --git a/src/main/ipc/malware-scanner.ipc.ts b/src/main/ipc/malware-scanner.ipc.ts index b1e743ef..241d1585 100644 --- a/src/main/ipc/malware-scanner.ipc.ts +++ b/src/main/ipc/malware-scanner.ipc.ts @@ -1129,10 +1129,14 @@ async function scanDarwinPersistence(): Promise { // ─── Scan locations ────────────────────────────────────────── -function getScanPaths(): string[] { +function getScanDirs() { return getPlatform().paths.malwareScanDirs() } +function getScanPaths(): string[] { + return getScanDirs().map(d => d.path) +} + /** Validate that a file path is within one of the allowed scan directories or the quarantine dir */ function isPathInAllowedDirs(filePath: string, allowedDirs: string[]): boolean { const sep = process.platform === 'win32' ? '\\' : '/' @@ -1275,7 +1279,7 @@ export async function scanMalware( // ── Phase 2: File discovery ── updateCategory('discovering', { status: 'running' }) - const scanDirs = getScanPaths() + const scanDirs = getScanDirs() const exclusions = getSettings().exclusions const files: string[] = [] for (let si = 0; si < scanDirs.length; si++) { @@ -1350,7 +1354,10 @@ export async function scanMalware( const matches = yaraEngine.scanFile(filePath) for (const match of matches) { if (threats.some(t => t.path === filePath)) break - if (match.metadata.filenameOnly === 'true') continue + // filenameOnly rules should only match outside system dirs on Windows + if (match.metadata.filenameOnly === 'true') { + if (isInSystemDir || process.platform !== 'win32') continue + } const fields = yaraMatchToThreatFields(match) threats.push({ diff --git a/src/main/services/cloud-agent.ts b/src/main/services/cloud-agent.ts index 217ffbee..9509ddc5 100644 --- a/src/main/services/cloud-agent.ts +++ b/src/main/services/cloud-agent.ts @@ -3103,7 +3103,8 @@ class CloudAgentService { } if (app.isPackaged) { const host = parsed.hostname.toLowerCase() - if (host === 'localhost' || host === '127.0.0.1' || host === '[::1]' || host === '::1' || host === '0.0.0.0') { + // IPv4 loopback and private ranges + if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') { await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') return } @@ -3115,6 +3116,16 @@ class CloudAgentService { await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') return } + // IPv6 loopback, private, and link-local ranges + const bare = host.replace(/^\[|\]$/g, '') + if (bare === '::1' || bare.startsWith('fc') || bare.startsWith('fd') + || bare.startsWith('fe8') || bare.startsWith('fe9') || bare.startsWith('fea') || bare.startsWith('feb') + || bare.startsWith('::ffff:127.') || bare.startsWith('::ffff:10.') + || bare.startsWith('::ffff:192.168.') || bare.startsWith('::ffff:169.254.') + || /^::ffff:172\.(1[6-9]|2\d|3[01])\./.test(bare)) { + await this.postCommandResult(requestId, false, undefined, 'Private/loopback URLs not allowed') + return + } } } catch { await this.postCommandResult(requestId, false, undefined, 'Invalid URL format')