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/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/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/package-lock.json b/package-lock.json index c577f181..205bc9f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "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", @@ -1787,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", diff --git a/package.json b/package.json index 19bb9459..52c9a26e 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", @@ -70,6 +71,7 @@ "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", diff --git a/scripts/fetch-yara-rules.js b/scripts/fetch-yara-rules.js new file mode 100644 index 00000000..2b3e0558 --- /dev/null +++ b/scripts/fetch-yara-rules.js @@ -0,0 +1,108 @@ +#!/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, readdirSync, unlinkSync } = 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 body + try { + // 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() + return + } finally { + clearTimeout(timeout) + } + + 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 < 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) { + console.error(`[fetch-yara-rules] SHA-256 mismatch — expected ${body.sha256}, got ${computed}`) + process.exit(1) + } + } + + // 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) { + 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, + // 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 => { + 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 c4ae2f1f..241d1585 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' @@ -10,7 +10,8 @@ import type { MalwareScanResult, MalwareScanProgress, MalwareActionResult, - QuarantinedItem + QuarantinedItem, + YaraRulesInfo } from '../../shared/types' import type { WindowGetter } from './index' import { getPlatform } from '../platform' @@ -18,8 +19,11 @@ 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 { 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)] @@ -225,6 +229,103 @@ const TRUSTED_PATHS = [ 'logitech', 'razer', 'corsair', 'steelseries', ] +// ─── YARA engine (lazy-initialized singleton) ──────────────── + +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 + _yaraInitPromise = _initYaraEngine() + 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() + _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.slice(0, 20)) + } + if (result.loaded === 0) { + console.warn('[yara] No rules loaded — falling back to regex patterns') + engine.dispose() + return null + } + _yaraEngine = engine + return engine + } catch (err) { + _yaraCompileProgress = null + console.warn('[yara] Init failed, falling back to regex patterns:', err) + return null + } +} + +/** + * 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 { + _yaraEngine = null + _yaraInitPromise = null + _yaraCompileProgress = null +} + +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' + else if (bundled.length > 0) source = 'bundled' + + const hasRules = cached.length > 0 || bundled.length > 0 + + return { + available, + engine: compiling ? 'compiling' : available ? 'yara' : hasRules ? 'yara' : 'regex-fallback', + rulesLoaded: engine?.rulesLoaded ?? 0, + version: meta?.version ?? null, + updatedAt: meta?.updatedAt ?? null, + source, + bundledRules: bundled.length, + cachedRules: cached.length, + compileProgress: _yaraCompileProgress, + } +} + // ─── Heuristic checks ───────────────────────────────────────── function calculateEntropy(buffer: Buffer): number { @@ -1028,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' ? '\\' : '/' @@ -1107,6 +1212,10 @@ export async function scanMalware( const scannableExts = getScannableExtensions() const systemDirs = getSystemDirs() + // 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' if (hasNativeAv) { @@ -1152,28 +1261,38 @@ 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') }) // ── Phase 2: File discovery ── updateCategory('discovering', { status: 'running' }) - const scanPaths = getScanPaths() + const scanDirs = getScanDirs() 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, 4, 5000, 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`) @@ -1182,14 +1301,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,7 +1324,7 @@ export async function scanMalware( if (!fileStat) return const fileSize = fileStat.size - // ── Check 1: Known malware filename patterns ── + // ── 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, '/'))) @@ -1228,27 +1348,77 @@ export async function scanMalware( } } - for (const mal of KNOWN_MALWARE_PATTERNS) { - if (mal.pattern.test(fileName) || mal.pattern.test(filePath)) { - if (!threats.some(t => t.path === filePath)) { + // ── 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.scanFile(filePath) + for (const match of matches) { + if (threats.some(t => t.path === filePath)) break + // 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({ + 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)) { + threats.push({ + id: randomUUID(), + path: filePath, + fileName, + size: fileSize, + detectionName: mal.name, + severity: mal.severity, + source: 'signature', + details: mal.details, + selected: true + }) + sigThreatsCount++ + } + break + } + } + + // 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({ 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 +1434,65 @@ 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 ── + // 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() + 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 ── @@ -1364,22 +1521,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' - ? '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' - }) - } + // 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 }) + 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 @@ -1843,4 +1999,20 @@ 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(`${CLOUD_SERVER_URL}${RULES_ENDPOINT}`) + if (result.success && result.stats) { + resetYaraEngine() + } + return result + }) + + // Start periodic YARA rule updates — runs for all users, not just cloud-linked + startPeriodicRuleChecks(CLOUD_SERVER_URL, () => resetYaraEngine()) + + // 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/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.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 45419086..af165738 100644 --- a/src/main/platform/darwin/paths.ts +++ b/src/main/platform/darwin/paths.ts @@ -36,23 +36,34 @@ export function createDarwinPaths(): PlatformPaths { return { ...cleanerPaths, - malwareScanDirs(): string[] { + malwareScanDirs() { return [ - join(HOME, 'Downloads'), - join(HOME, 'Desktop'), - join(HOME, 'Documents'), - '/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', + // 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/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..d15d9b7c 100644 --- a/src/main/platform/linux/paths.test.ts +++ b/src/main/platform/linux/paths.test.ts @@ -6,13 +6,13 @@ 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() 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) @@ -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..70d8fdd4 100644 --- a/src/main/platform/linux/paths.ts +++ b/src/main/platform/linux/paths.ts @@ -36,12 +36,26 @@ export function createLinuxPaths(): PlatformPaths { return { ...cleanerPaths, - malwareScanDirs(): string[] { + malwareScanDirs() { return [ - join(HOME, 'Downloads'), - join(HOME, 'Desktop'), - join(HOME, 'Documents'), - '/tmp', + // 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/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.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 52c06312..16e502b5 100644 --- a/src/main/platform/win32/paths.ts +++ b/src/main/platform/win32/paths.ts @@ -39,16 +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'), - join(LOCALAPPDATA, 'Temp'), - APPDATA, - LOCALAPPDATA, - PROGRAMDATA, + // 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 }, ] }, 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..9509ddc5 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,79 @@ 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: 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 (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 + } + if (app.isPackaged) { + const host = parsed.hostname.toLowerCase() + // 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 + } + 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 + } + // 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') + 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..38b3de47 --- /dev/null +++ b/src/main/services/yara-engine.test.ts @@ -0,0 +1,174 @@ +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[] +} + +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, + 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) + } + }) + + it('clamps invalid severity to high', () => { + const match: YaraMatch = { + ruleName: 'Test', + metadata: { severity: 'info' as any }, + matchedStrings: [], + } + expect(yaraMatchToThreatFields(match).severity).toBe('high') + }) +}) + +// ─── @litko/yara-x integration tests ──────────────────────────── + +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" + severity = "medium" + details = "Test detection" + strings: + $a = "malware_test" nocase + condition: + $a +}`) + 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('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 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) + }) + + 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('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 clean = scanner.scan(Buffer.from('nothing here')) + expect(clean.length).toBe(0) + + 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 new file mode 100644 index 00000000..03701195 --- /dev/null +++ b/src/main/services/yara-engine.ts @@ -0,0 +1,260 @@ +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[] +} + +/** 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 }[] +} + +/** @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 YaraXModule { + create(): YaraXScanner +} + +// ─── Engine ────────────────────────────────────────────────── + +export class YaraEngine { + private _scanner: YaraXScanner | null = null + private _ready = false + private _rulesLoaded = 0 + + /** Create the scanner instance. Call once before loading rules. */ + async initialize(): Promise { + try { + const yarax: YaraXModule = require('@litko/yara-x') + this._scanner = yarax.create() + this._ready = true + } catch (err) { + console.warn('[yara] @litko/yara-x initialization failed:', err) + this._ready = false + throw err + } + } + + isReady(): boolean { + return this._ready && this._scanner !== null + } + + /** + * Compile YARA rules from file paths and/or raw source strings. + * + * 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 + */ + 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 yarax: YaraXModule = require('@litko/yara-x') + const errors: string[] = [] + const total = ruleFilePaths.length + extraSources.length + + // 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, content: readFileSync(filePath, 'utf-8') }) + } catch (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 + 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...') + } + + // Slow path: validate each file individually, exclude broken ones + const validSources: string[] = [] + for (let i = 0; i < sources.length; i++) { + try { + yarax.compile(sources[i].content) + validSources.push(sources[i].content) + } catch (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 } + } + + this._rulesLoaded = validSources.length + return { loaded: validSources.length, errors } + } + + get rulesLoaded(): number { + return this._rulesLoaded + } + + /** + * Scan a buffer against all compiled rules. + * Fast — rules are already compiled, only pattern matching runs. + */ + scanBuffer(buffer: Buffer): YaraMatch[] { + if (!this._scanner) return [] + + try { + const results = this._scanner.scan(buffer) + return results.map(r => this._convertMatch(r)) + } catch (err) { + console.warn('[yara] Scan error:', err) + return [] + } + } + + /** + * Scan a file directly from disk (avoids reading into JS memory). + */ + scanFile(filePath: string): YaraMatch[] { + if (!this._scanner) return [] + + try { + const results = this._scanner.scanFile(filePath) + return results.map(r => this._convertMatch(r)) + } catch (err) { + console.warn('[yara] File scan error:', err) + return [] + } + } + + private _convertMatch(r: YaraXMatch): YaraMatch { + const metadata: YaraMatch['metadata'] = {} + 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, + metadata, + matchedStrings: r.matches.map(m => m.data), + } + } + + dispose(): void { + this._scanner = null + 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. + */ +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, + 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..17cce971 --- /dev/null +++ b/src/main/services/yara-rules-store.test.ts @@ -0,0 +1,347 @@ +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) + }) +}) + +// ─── 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', () => { + 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..56521a45 --- /dev/null +++ b/src/main/services/yara-rules-store.ts @@ -0,0 +1,323 @@ +import { readFileSync, writeFileSync, renameSync, unlinkSync, rmSync, 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 +export 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') +} + +// ─── Bundled rules (fetched at build time, shipped with the installer) ── + +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 { + if (!existsSync(dir)) return [] + return readdirSync(dir) + .filter(f => f.endsWith('.yar')) + .sort() + .map(f => join(dir, f)) + } catch { + return [] + } +} + +/** 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() + 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 + ) +} + +// ─── 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 { + // 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') +} + +// ─── 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 text: string + try { + const meta = getRulesMetadata() + const headers: Record = { 'Accept': 'application/json' } + if (meta) headers['X-Kudu-Rules-Version'] = meta.version + + // 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 } + } + + 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)' } + } + + // 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) + } + + 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) { + 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' } + } + + // 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-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` + + mkdirSync(stageDir, { recursive: true }) + + 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 + } + + 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 +} diff --git a/src/preload/index.ts b/src/preload/index.ts index f69260be..3d79a218 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -273,6 +273,15 @@ 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), + 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/.checksums.json b/src/renderer/src/locales/.checksums.json index 62d3b8f5..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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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": "d20e7fa2b3df73ed2963ce102357cf75d7b568a50f80154e63373f9ed032eb4d", + "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 8c9b30c6..7c15635b 100644 --- a/src/renderer/src/locales/ar/malware.json +++ b/src/renderer/src/locales/ar/malware.json @@ -1,12 +1,12 @@ { "pageTitle": "فاحص البرامج الضارة", - "pageDescription": "اكتشاف التهديدات متعدد المحركات — التوقيعات، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", + "pageDescription": "اكتشاف التهديدات بمحركات متعددة — التواقيع، والتحليل الاستدلالي، وتحليل البرامج النصية، وسلامة النظام، وفحص الاستمرارية", "scanButtonScanning": "جارٍ الفحص...", "scanButton": "فحص", "quarantineButton": "الحجر", "deleteButton": "حذف", "severityCritical": "حرج", - "severityHigh": "مرتفع", + "severityHigh": "عالٍ", "severityMedium": "متوسط", "severityLow": "منخفض", "sourceDefenderWindows": "مضاد فيروسات أصلي", @@ -20,9 +20,9 @@ "threatCountPlural": "{{count}} تهديدات", "threatFound": "تم العثور على {{count}}", "scanCategoryClean": "نظيف", - "scanCategoryNA": "غير متوفر", + "scanCategoryNA": "غير متاح", "scanSummarySystemClean": "النظام نظيف", - "scanSummaryThreatsDetected": "تم اكتشاف {{count}} من التهديدات", + "scanSummaryThreatsDetected": "تم اكتشاف {{count}} تهديد", "scanStatFiles": "الملفات", "scanStatDuration": "المدة", "scanStatEngines": "المحركات", @@ -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}} تهديدات", @@ -51,19 +51,42 @@ "threatDetailPath": "المسار", "confirmQuarantineTitle": "نقل التهديدات إلى الحجر", "confirmQuarantineDescription": "سيؤدي هذا إلى نقل {{count}} من التهديدات المكتشفة إلى الحجر. يمكن استعادة الملفات لاحقًا عند الحاجة.", - "confirmQuarantineLabel": "نقل إلى الحجر الآن", + "confirmQuarantineLabel": "النقل إلى الحجر الآن", "confirmDeleteTitle": "حذف التهديدات", "confirmDeleteDescription": "سيؤدي هذا إلى حذف {{count}} من التهديدات المكتشفة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", - "confirmDeleteLabel": "حذف نهائيًا", + "confirmDeleteLabel": "حذف نهائي", "toastScanFailed": "فشل فحص البرامج الضارة", "toastActionFailed": "فشل {{action}} التهديدات", "toastActionFailedDescription": "حاول التشغيل كمسؤول", "errorOperationFailed": "فشلت العملية — حاول التشغيل كمسؤول", "tabScanner": "الفاحص", "tabQuarantine": "الحجر", + "tabDatabase": "قاعدة البيانات", + "dbTitle": "قاعدة بيانات التواقيع", + "dbDescription": "قواعد YARA لاكتشاف البرامج الضارة المستخدمة بواسطة الفاحص", + "dbFetchLatest": "التحقق من وجود تحديثات", + "dbUpdating": "جارٍ التحديث...", + "dbEngine": "المحرك", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex احتياطي", + "dbEngineCompiling": "جارٍ التجميع...", + "dbCompiling": "جارٍ تجميع قواعد التواقيع ({{loaded}}/{{total}})...", + "dbRulesLoaded": "القواعد المحمّلة", + "dbVersion": "إصدار التواقيع", + "dbLastUpdated": "آخر تحديث", + "dbNever": "أبدًا", + "dbSource": "مصدر القواعد", + "dbSourceCloud": "السحابة (مخزنة مؤقتًا)", + "dbSourceBundled": "مضمنة مع التطبيق", + "dbSourceNone": "لم يتم تحميل أي قواعد", + "dbRuleFiles": "ملفات القواعد", + "dbRuleFilesCounts": "{{bundled}} مضمنة، {{cached}} من السحابة", + "dbUpdateSuccess": "تم التحديث إلى v{{version}} ({{count}} قاعدة)", + "dbAlreadyCurrent": "التواقيع محدّثة بالفعل", + "dbUpdateFailed": "فشل تحديث التواقيع", "quarantineEmptyTitle": "لا توجد عناصر في الحجر", - "quarantineEmptyDescription": "ستظهر هنا الملفات المنقولة إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", - "quarantineHeading": "الملفات المعزولة", + "quarantineEmptyDescription": "ستظهر هنا الملفات التي تم نقلها إلى الحجر. يمكنك استعادتها أو حذفها نهائيًا.", + "quarantineHeading": "الملفات الموجودة في الحجر", "quarantineCount": "{{count}} ملف", "quarantineCountPlural": "{{count}} ملفات", "quarantineColumnFile": "الملف", @@ -72,15 +95,15 @@ "quarantineColumnOriginal": "الموقع الأصلي", "quarantineOriginalUnknown": "غير معروف", "quarantineRestoreButton": "استعادة", - "quarantineDeleteButton": "حذف نهائيًا", + "quarantineDeleteButton": "حذف نهائي", "quarantineSelectAll": "تحديد الكل", "quarantineDeselectAll": "إلغاء تحديد الكل", "confirmRestoreTitle": "استعادة الملفات", "confirmRestoreDescription": "سيؤدي هذا إلى استعادة {{count}} من الملفات إلى موقعها الأصلي. استعد فقط الملفات التي تثق بها.", "confirmRestoreLabel": "استعادة الآن", "confirmDeleteQuarantineTitle": "حذف نهائي", - "confirmDeleteQuarantineDescription": "سيؤدي هذا إلى حذف {{count}} من الملفات المعزولة نهائيًا. لا يمكن التراجع عن هذا الإجراء.", - "confirmDeleteQuarantineLabel": "حذف نهائيًا", + "confirmDeleteQuarantineDescription": "سيؤدي هذا إلى حذف {{count}} من الملفات الموجودة في الحجر نهائيًا. لا يمكن التراجع عن هذا الإجراء.", + "confirmDeleteQuarantineLabel": "حذف نهائي", "quarantineLoading": "جارٍ تحميل الحجر...", "quarantineRestoring": "جارٍ استعادة الملفات...", "quarantineDeleting": "جارٍ حذف الملفات...", diff --git a/src/renderer/src/locales/cs/malware.json b/src/renderer/src/locales/cs/malware.json index 6b7cb638..f629bfbe 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,14 +30,14 @@ "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": "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", @@ -56,11 +56,34 @@ "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 dokončit", "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": "Kudu Cloud Signatures", + "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", + "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 +97,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 +107,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..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": "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,29 @@ "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": "Kudu Cloud-signaturer", + "dbEngineRegex": "Regex-reserve", + "dbEngineCompiling": "Kompilerer...", + "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", + "dbRulesLoaded": "Regler indlæst", + "dbVersion": "Signaturversion", + "dbLastUpdated": "Sidst 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 +99,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..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, Heuristiken, Skriptanalyse, Systemintegrität und Persistenzprüfung", - "scanButtonScanning": "Wird gescannt...", + "scanButtonScanning": "Scan wird ausgeführt...", "scanButton": "Scannen", "quarantineButton": "In Quarantäne verschieben", "deleteButton": "Löschen", @@ -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", @@ -61,6 +61,29 @@ "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex-Fallback", + "dbEngineCompiling": "Wird kompiliert...", + "dbCompiling": "Signaturregeln werden kompiliert ({{loaded}}/{{total}})...", + "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..3d7e4db1 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,29 @@ "errorOperationFailed": "Η λειτουργία απέτυχε — δοκιμάστε να εκτελέσετε ως διαχειριστής", "tabScanner": "Σαρωτής", "tabQuarantine": "Καραντίνα", + "tabDatabase": "Βάση δεδομένων", + "dbTitle": "Βάση δεδομένων υπογραφών", + "dbDescription": "Κανόνες ανίχνευσης κακόβουλου λογισμικού YARA που χρησιμοποιούνται από τον σαρωτή", + "dbFetchLatest": "Έλεγχος για ενημερώσεις", + "dbUpdating": "Γίνεται ενημέρωση...", + "dbEngine": "Μηχανή", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "Γίνεται μεταγλώττιση...", + "dbCompiling": "Μεταγλώττιση κανόνων υπογραφών ({{loaded}}/{{total}})...", + "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 +92,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/en/malware.json b/src/renderer/src/locales/en/malware.json index 33bce9a4..5c755479 100644 --- a/src/renderer/src/locales/en/malware.json +++ b/src/renderer/src/locales/en/malware.json @@ -62,6 +62,30 @@ "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "Compiling...", + "dbCompiling": "Compiling signature rules ({{loaded}}/{{total}})...", + "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/locales/es/malware.json b/src/renderer/src/locales/es/malware.json index 079f51c9..1bd5b44a 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", @@ -61,6 +61,29 @@ "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": "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 las 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 +99,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 +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 90fb6fc9..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 eheyden tarkistus ja pysyvyyden tarkistus", + "pageDescription": "Monimoottorinen uhkien tunnistus — allekirjoitukset, heuristiikka, skriptianalyysi, järjestelmän eheys ja pysyvyyden tarkistus", "scanButtonScanning": "Skannataan...", "scanButton": "Skannaa", "quarantineButton": "Siirrä karanteeniin", @@ -20,7 +20,7 @@ "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,44 @@ "threatDetailFile": "Tiedosto", "threatDetailSize": "Koko", "threatDetailPath": "Polku", - "confirmQuarantineTitle": "Siirrä uhat 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", "tabQuarantine": "Karanteeni", - "quarantineEmptyTitle": "Ei karanteeniin siirrettyjä kohteita", + "tabDatabase": "Tietokanta", + "dbTitle": "Allekirjoitustietokanta", + "dbDescription": "Skannerin käyttämät YARA-haittaohjelmien tunnistussäännöt", + "dbFetchLatest": "Tarkista päivitykset", + "dbUpdating": "Päivitetään...", + "dbEngine": "Moottori", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex-varajärjestelmä", + "dbEngineCompiling": "Käännetään...", + "dbCompiling": "Käännetään allekirjoitussääntöjä ({{loaded}}/{{total}})...", + "dbRulesLoaded": "Säännöt ladattu", + "dbVersion": "Allekirjoitusversio", + "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": "Allekirjoitukset ovat jo ajan tasalla", + "dbUpdateFailed": "Allekirjoitusten 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", @@ -75,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}} tiedostoa palautettu", - "toastRestoreFailed": "Joidenkin tiedostojen palauttaminen epäonnistui", + "toastRestoreFailed": "Joidenkin tiedostojen palautus epäonnistui", "toastDeleteQuarantineSuccess": "{{count}} tiedostoa poistettu pysyvästi", - "toastRestoreNoOriginal": "Palautus ei onnistu — alkuperäinen sijainti ei ole tiedossa" + "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 89488980..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", @@ -50,33 +50,56 @@ "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é", - "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", + "dbFetchLatest": "Rechercher des mises à jour", + "dbUpdating": "Mise à jour...", + "dbEngine": "Moteur", + "dbEngineYara": "Signatures cloud Kudu", + "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": "Incluses avec l'application", + "dbSourceNone": "Aucune règle chargée", + "dbRuleFiles": "Fichiers de règles", + "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", "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", "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.", @@ -87,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": "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..8b87d813 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": "נמוכה", @@ -50,10 +50,10 @@ "threatDetailSize": "גודל", "threatDetailPath": "נתיב", "confirmQuarantineTitle": "העבר איומים להסגר", - "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איום/איומים שזוהו להסגר. ניתן יהיה לשחזר קבצים מאוחר יותר במידת הצורך.", + "confirmQuarantineDescription": "פעולה זו תעביר {{count}} איומים שזוהו להסגר. ניתן יהיה לשחזר את הקבצים מאוחר יותר במידת הצורך.", "confirmQuarantineLabel": "העבר להסגר כעת", "confirmDeleteTitle": "מחק איומים", - "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איום/איומים שזוהו. לא ניתן לבטל פעולה זו.", + "confirmDeleteDescription": "פעולה זו תמחק לצמיתות {{count}} איומים שזוהו. לא ניתן לבטל פעולה זו.", "confirmDeleteLabel": "מחק לצמיתות", "toastScanFailed": "סריקת הנוזקות נכשלה", "toastActionFailed": "לא ניתן היה {{action}} את האיומים", @@ -61,8 +61,31 @@ "errorOperationFailed": "הפעולה נכשלה — נסה להפעיל כמנהל מערכת", "tabScanner": "סורק", "tabQuarantine": "הסגר", + "tabDatabase": "מסד נתונים", + "dbTitle": "מסד נתוני חתימות", + "dbDescription": "כללי זיהוי נוזקות של YARA שבהם הסורק משתמש", + "dbFetchLatest": "בדוק אם קיימים עדכונים", + "dbUpdating": "מעדכן...", + "dbEngine": "מנוע", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "מהדר...", + "dbCompiling": "מהדר כללי חתימות ({{loaded}}/{{total}})...", + "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,16 +99,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/hi/malware.json b/src/renderer/src/locales/hi/malware.json index 8e1e5fce..f485077a 100644 --- a/src/renderer/src/locales/hi/malware.json +++ b/src/renderer/src/locales/hi/malware.json @@ -1,9 +1,9 @@ { - "pageTitle": "Malware स्कैनर", - "pageDescription": "मल्टी-इंजन खतरा पहचान — signatures, heuristics, script analysis, system integrity और persistence scanning", + "pageTitle": "मैलवेयर स्कैनर", + "pageDescription": "मल्टी-इंजन खतरा पहचान — signatures, heuristics, script analysis, system integrity, और persistence scanning", "scanButtonScanning": "स्कैन किया जा रहा है...", "scanButton": "स्कैन", - "quarantineButton": "Quarantine", + "quarantineButton": "क्वारंटीन", "deleteButton": "हटाएँ", "severityCritical": "गंभीर", "severityHigh": "उच्च", @@ -19,9 +19,9 @@ "threatCount": "{{count}} खतरा", "threatCountPlural": "{{count}} खतरे", "threatFound": "{{count}} मिला", - "scanCategoryClean": "साफ़", + "scanCategoryClean": "स्वच्छ", "scanCategoryNA": "लागू नहीं", - "scanSummarySystemClean": "सिस्टम साफ़ है", + "scanSummarySystemClean": "सिस्टम स्वच्छ", "scanSummaryThreatsDetected": "{{count}} खतरे पाए गए", "scanStatFiles": "फ़ाइलें", "scanStatDuration": "अवधि", @@ -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 और संदिग्ध फ़ाइलों की जाँच करें।", + "noThreatsDetectedDescription": "{{duration}}s में {{engineCount}} इंजनों पर {{filesScanned}} फ़ाइलें स्कैन की गईं — आपका सिस्टम स्वच्छ है।", + "emptyStateTitle": "मैलवेयर स्कैनर", + "emptyStateDescription": "\"स्कैन\" पर क्लिक करके अपने सिस्टम में malware, adware, crypto miners, और संदिग्ध फ़ाइलों की जाँच करें।", "detectedThreatsHeading": "पाए गए खतरे", "detectedThreatsCount": "{{count}} खतरा", "detectedThreatsCountPlural": "{{count}} खतरे", @@ -49,21 +49,44 @@ "threatDetailFile": "फ़ाइल", "threatDetailSize": "आकार", "threatDetailPath": "पथ", - "confirmQuarantineTitle": "खतरों को Quarantine में भेजें", - "confirmQuarantineDescription": "इससे पाए गए {{count}} threat(s) को Quarantine में भेज दिया जाएगा। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", - "confirmQuarantineLabel": "अभी Quarantine में भेजें", + "confirmQuarantineTitle": "खतरों को क्वारंटीन करें", + "confirmQuarantineDescription": "इससे पाए गए {{count}} threat(s) क्वारंटीन में भेज दिए जाएँगे। आवश्यकता होने पर फ़ाइलों को बाद में पुनर्स्थापित किया जा सकता है।", + "confirmQuarantineLabel": "अभी क्वारंटीन करें", "confirmDeleteTitle": "खतरों को हटाएँ", "confirmDeleteDescription": "इससे पाए गए {{count}} threat(s) स्थायी रूप से हटा दिए जाएँगे। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteLabel": "स्थायी रूप से हटाएँ", - "toastScanFailed": "Malware स्कैन विफल हुआ", + "toastScanFailed": "मैलवेयर स्कैन विफल हुआ", "toastActionFailed": "खतरों को {{action}} करने में विफल", "toastActionFailedDescription": "व्यवस्थापक के रूप में चलाने का प्रयास करें", "errorOperationFailed": "क्रिया विफल हुई — व्यवस्थापक के रूप में चलाने का प्रयास करें", "tabScanner": "स्कैनर", - "tabQuarantine": "Quarantine", - "quarantineEmptyTitle": "कोई Quarantine आइटम नहीं", - "quarantineEmptyDescription": "Quarantine में भेजी गई फ़ाइलें यहाँ दिखाई देंगी। आप उन्हें पुनर्स्थापित कर सकते हैं या स्थायी रूप से हटा सकते हैं।", - "quarantineHeading": "Quarantine की गई फ़ाइलें", + "tabQuarantine": "क्वारंटीन", + "tabDatabase": "डेटाबेस", + "dbTitle": "Signature डेटाबेस", + "dbDescription": "स्कैनर द्वारा उपयोग किए जाने वाले YARA malware detection rules", + "dbFetchLatest": "अपडेट की जाँच करें", + "dbUpdating": "अपडेट किया जा रहा है...", + "dbEngine": "इंजन", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex Fallback", + "dbEngineCompiling": "कंपाइल किया जा रहा है...", + "dbCompiling": "Signature rules कंपाइल किए जा रहे हैं ({{loaded}}/{{total}})...", + "dbRulesLoaded": "नियम लोड किए गए", + "dbVersion": "Signature संस्करण", + "dbLastUpdated": "अंतिम अपडेट", + "dbNever": "कभी नहीं", + "dbSource": "नियम स्रोत", + "dbSourceCloud": "Cloud (cached)", + "dbSourceBundled": "ऐप के साथ शामिल", + "dbSourceNone": "कोई नियम लोड नहीं किए गए", + "dbRuleFiles": "नियम फ़ाइलें", + "dbRuleFilesCounts": "{{bundled}} शामिल, {{cached}} cloud से", + "dbUpdateSuccess": "v{{version}} में अपडेट किया गया ({{count}} नियम)", + "dbAlreadyCurrent": "Signatures पहले से अद्यतित हैं", + "dbUpdateFailed": "Signatures अपडेट करने में विफल", + "quarantineEmptyTitle": "कोई क्वारंटीन आइटम नहीं", + "quarantineEmptyDescription": "क्वारंटीन में भेजी गई फ़ाइलें यहाँ दिखाई देंगी। आप उन्हें पुनर्स्थापित कर सकते हैं या स्थायी रूप से हटा सकते हैं।", + "quarantineHeading": "क्वारंटीन की गई फ़ाइलें", "quarantineCount": "{{count}} फ़ाइल", "quarantineCountPlural": "{{count}} फ़ाइलें", "quarantineColumnFile": "फ़ाइल", @@ -76,16 +99,16 @@ "quarantineSelectAll": "सभी चुनें", "quarantineDeselectAll": "सभी का चयन हटाएँ", "confirmRestoreTitle": "फ़ाइलें पुनर्स्थापित करें", - "confirmRestoreDescription": "इससे {{count}} file(s) को उनके मूल स्थान पर पुनर्स्थापित किया जाएगा। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", + "confirmRestoreDescription": "इससे {{count}} file(s) उनके मूल स्थान पर पुनर्स्थापित कर दी जाएँगी। केवल उन्हीं फ़ाइलों को पुनर्स्थापित करें जिन पर आप भरोसा करते हैं।", "confirmRestoreLabel": "अभी पुनर्स्थापित करें", "confirmDeleteQuarantineTitle": "स्थायी रूप से हटाएँ", "confirmDeleteQuarantineDescription": "इससे {{count}} quarantined file(s) स्थायी रूप से हटा दी जाएँगी। इस क्रिया को पूर्ववत नहीं किया जा सकता।", "confirmDeleteQuarantineLabel": "स्थायी रूप से हटाएँ", - "quarantineLoading": "Quarantine लोड किया जा रहा है...", + "quarantineLoading": "क्वारंटीन लोड किया जा रहा है...", "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 2b504dca..67af93a7 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", @@ -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", + "noThreatsDetectedTitle": "Nem észleltünk fenyegetést", "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 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,14 +55,37 @@ "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 malware-vizsgálat sikertelen volt", - "toastActionFailed": "A fenyegetések {{action}} művelete sikertelen volt", + "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 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ú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": "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ója", + "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}} 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", "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", @@ -74,9 +97,9 @@ "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 a(z) {{count}} karanténba helyezett fájl(oka)t. Ez a művelet nem vonható vissza.", @@ -85,7 +108,7 @@ "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..1a10cbbf 100644 --- a/src/renderer/src/locales/id/malware.json +++ b/src/renderer/src/locales/id/malware.json @@ -9,7 +9,7 @@ "severityHigh": "Tinggi", "severityMedium": "Sedang", "severityLow": "Rendah", - "sourceDefenderWindows": "AV bawaan", + "sourceDefenderWindows": "AV Bawaan", "sourceDefenderMac": "Penandatanganan Kode", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "Analisis Heuristik", @@ -48,12 +48,12 @@ "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", "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,31 +61,54 @@ "errorOperationFailed": "Operasi gagal — coba jalankan sebagai administrator", "tabScanner": "Pemindai", "tabQuarantine": "Karantina", - "quarantineEmptyTitle": "Tidak Ada Item yang Dikarantina", + "tabDatabase": "Database", + "dbTitle": "Database Signature", + "dbDescription": "Aturan deteksi malware YARA yang digunakan oleh pemindai", + "dbFetchLatest": "Periksa Pembaruan", + "dbUpdating": "Memperbarui...", + "dbEngine": "Engine", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Fallback Regex", + "dbEngineCompiling": "Mengompilasi...", + "dbCompiling": "Mengompilasi aturan signature ({{loaded}}/{{total}})...", + "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 yang terbaru", + "dbUpdateFailed": "Gagal memperbarui signature", + "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 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 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", - "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 65f614c7..541b60a7 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": "Spostamento in quarantena delle minacce selezionate...", + "actingQuarantining": "Messa 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": "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,42 @@ "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 YARA di rilevamento malware utilizzate dallo scanner", + "dbFetchLatest": "Verifica aggiornamenti", + "dbUpdating": "Aggiornamento in corso...", + "dbEngine": "Motore", + "dbEngineYara": "Kudu Cloud Signatures", + "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", + "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,7 +99,7 @@ "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.", diff --git a/src/renderer/src/locales/ja/malware.json b/src/renderer/src/locales/ja/malware.json index b669dc7c..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": "{{engineCount}} 個のエンジンで {{filesScanned}} 個のファイルを {{duration}} 秒間スキャンしました — システムはクリーンです。", + "noThreatsDetectedDescription": "{{engineCount}} 個のエンジンで {{filesScanned}} 個のファイルを {{duration}} 秒でスキャンしました — システムはクリーンです。", "emptyStateTitle": "マルウェア スキャナー", - "emptyStateDescription": "\"スキャン\" をクリックして、システム内のマルウェア、アドウェア、暗号通貨マイナー、不審なファイルを確認します。", + "emptyStateDescription": "「スキャン」をクリックして、システム内のマルウェア、アドウェア、暗号資産マイナー、不審なファイルを確認します。", "detectedThreatsHeading": "検出された脅威", "detectedThreatsCount": "{{count}} 件の脅威", "detectedThreatsCountPlural": "{{count}} 件の脅威", @@ -61,8 +61,31 @@ "errorOperationFailed": "操作に失敗しました — 管理者として実行してみてください", "tabScanner": "スキャナー", "tabQuarantine": "隔離", + "tabDatabase": "データベース", + "dbTitle": "シグネチャ データベース", + "dbDescription": "スキャナーで使用される YARA マルウェア検出ルール", + "dbFetchLatest": "更新プログラムの確認", + "dbUpdating": "更新しています...", + "dbEngine": "エンジン", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex フォールバック", + "dbEngineCompiling": "コンパイルしています...", + "dbCompiling": "シグネチャ ルールをコンパイルしています ({{loaded}}/{{total}})...", + "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..4de471cc 100644 --- a/src/renderer/src/locales/ko/malware.json +++ b/src/renderer/src/locales/ko/malware.json @@ -1,6 +1,6 @@ { - "pageTitle": "악성코드 검사기", - "pageDescription": "멀티 엔진 위협 탐지 — 시그니처, 휴리스틱, 스크립트 분석, 시스템 무결성 및 지속성 검사", + "pageTitle": "멀웨어 스캐너", + "pageDescription": "다중 엔진 위협 탐지 — 시그니처, 휴리스틱, 스크립트 분석, 시스템 무결성 및 지속성 검사", "scanButtonScanning": "검사 중...", "scanButton": "검사", "quarantineButton": "격리", @@ -9,7 +9,7 @@ "severityHigh": "높음", "severityMedium": "보통", "severityLow": "낮음", - "sourceDefenderWindows": "기본 제공 AV", + "sourceDefenderWindows": "기본 AV", "sourceDefenderMac": "코드 서명", "sourceDefenderLinux": "ClamAV", "sourceHeuristic": "휴리스틱 분석", @@ -18,7 +18,7 @@ "filesScanned": "{{count}}개 파일 검사됨", "threatCount": "{{count}}개 위협", "threatCountPlural": "{{count}}개 위협", - "threatFound": "{{count}}개 발견", + "threatFound": "{{count}}개 발견됨", "scanCategoryClean": "정상", "scanCategoryNA": "해당 없음", "scanSummarySystemClean": "시스템 정상", @@ -39,9 +39,9 @@ "actionResultDeleted": "{{count}}개 삭제됨", "actionResultFailed": "{{count}}개 실패", "noThreatsDetectedTitle": "탐지된 위협 없음", - "noThreatsDetectedDescription": "{{duration}}초 동안 {{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 검사했습니다 — 시스템이 정상입니다.", - "emptyStateTitle": "악성코드 검사기", - "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 악성코드, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", + "noThreatsDetectedDescription": "{{engineCount}}개 엔진으로 {{filesScanned}}개 파일을 {{duration}}초 동안 검사했습니다 — 시스템이 정상입니다.", + "emptyStateTitle": "멀웨어 스캐너", + "emptyStateDescription": "\"검사\"를 클릭하여 시스템에서 멀웨어, 애드웨어, 암호화폐 채굴기 및 의심스러운 파일을 확인하세요.", "detectedThreatsHeading": "탐지된 위협", "detectedThreatsCount": "{{count}}개 위협", "detectedThreatsCountPlural": "{{count}}개 위협", @@ -55,14 +55,37 @@ "confirmDeleteTitle": "위협 삭제", "confirmDeleteDescription": "탐지된 위협 {{count}}개를 영구적으로 삭제합니다. 이 작업은 되돌릴 수 없습니다.", "confirmDeleteLabel": "영구 삭제", - "toastScanFailed": "악성코드 검사에 실패했습니다", - "toastActionFailed": "위협 {{action}}에 실패했습니다", + "toastScanFailed": "멀웨어 검사 실패", + "toastActionFailed": "위협 {{action}} 실패", "toastActionFailedDescription": "관리자 권한으로 실행해 보세요", - "errorOperationFailed": "작업에 실패했습니다 — 관리자 권한으로 실행해 보세요", - "tabScanner": "검사기", + "errorOperationFailed": "작업 실패 — 관리자 권한으로 실행해 보세요", + "tabScanner": "스캐너", "tabQuarantine": "격리", + "tabDatabase": "데이터베이스", + "dbTitle": "시그니처 데이터베이스", + "dbDescription": "스캐너에서 사용하는 YARA 멀웨어 탐지 규칙", + "dbFetchLatest": "업데이트 확인", + "dbUpdating": "업데이트 중...", + "dbEngine": "엔진", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex 대체", + "dbEngineCompiling": "컴파일 중...", + "dbCompiling": "시그니처 규칙 컴파일 중 ({{loaded}}/{{total}})...", + "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}}개 파일", @@ -87,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 47ef1e7f..4932a540 100644 --- a/src/renderer/src/locales/ms/malware.json +++ b/src/renderer/src/locales/ms/malware.json @@ -26,16 +26,16 @@ "scanStatFiles": "Fail", "scanStatDuration": "Tempoh", "scanStatEngines": "Enjin", - "severityHeading": "Tahap Keterukan", + "severityHeading": "Keterukan", "selectedHeading": "Dipilih", "selectedOfThreats": "daripada {{count}} ancaman", "selectAll": "Pilih Semua", "deselectAll": "Nyahpilih Semua", - "actingQuarantining": "Menguarantinkan ancaman yang dipilih...", + "actingQuarantining": "Mengkuarantin ancaman yang dipilih...", "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", @@ -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", @@ -61,8 +61,31 @@ "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Sandaran Regex", + "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": "Cloud (cache)", + "dbSourceBundled": "Disertakan dengan aplikasi", + "dbSourceNone": "Tiada peraturan dimuatkan", + "dbRuleFiles": "Fail Peraturan", + "dbRuleFilesCounts": "{{bundled}} disertakan, {{cached}} dari cloud", + "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..00aa0c6f 100644 --- a/src/renderer/src/locales/nl/malware.json +++ b/src/renderer/src/locales/nl/malware.json @@ -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,19 +50,42 @@ "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", "tabQuarantine": "Quarantaine", + "tabDatabase": "Database", + "dbTitle": "Handtekeningendatabase", + "dbDescription": "YARA-malwaredetectieregels die door de scanner worden gebruikt", + "dbFetchLatest": "Controleren op updates", + "dbUpdating": "Bijwerken...", + "dbEngine": "Engine", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex-terugvaloptie", + "dbEngineCompiling": "Compileren...", + "dbCompiling": "Handtekeningregels compileren ({{loaded}}/{{total}})...", + "dbRulesLoaded": "Regels geladen", + "dbVersion": "Handtekeningversie", + "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 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 naar de quarantaine zijn verplaatst, worden hier weergegeven. 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", @@ -85,7 +108,7 @@ "quarantineRestoring": "Bestanden herstellen...", "quarantineDeleting": "Bestanden verwijderen...", "toastRestoreSuccess": "{{count}} bestand(en) hersteld", - "toastRestoreFailed": "Kan sommige bestanden niet herstellen", + "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 254c1e5c..569e5c3a 100644 --- a/src/renderer/src/locales/no/malware.json +++ b/src/renderer/src/locales/no/malware.json @@ -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", @@ -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,29 @@ "errorOperationFailed": "Operasjonen mislyktes — prøv å kjøre som administrator", "tabScanner": "Skanner", "tabQuarantine": "Karantene", + "tabDatabase": "Database", + "dbTitle": "Signaturdatabase", + "dbDescription": "YARA-regler for deteksjon av skadelig programvare brukt av skanneren", + "dbFetchLatest": "Se etter oppdateringer", + "dbUpdating": "Oppdaterer...", + "dbEngine": "Motor", + "dbEngineYara": "Kudu Cloud-signaturer", + "dbEngineRegex": "Regex-reserve", + "dbEngineCompiling": "Kompilerer...", + "dbCompiling": "Kompilerer signaturregler ({{loaded}}/{{total}})...", + "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}} levert med appen, {{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", @@ -76,16 +99,16 @@ "quarantineSelectAll": "Velg alle", "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 f3d13b67..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", @@ -20,14 +20,14 @@ "threatCountPlural": "{{count}} zagrożenia", "threatFound": "Znaleziono: {{count}}", "scanCategoryClean": "czysty", - "scanCategoryNA": "N/D", + "scanCategoryNA": "Nie dotyczy", "scanSummarySystemClean": "System czysty", "scanSummaryThreatsDetected": "Wykryto zagrożenia: {{count}}", "scanStatFiles": "Pliki", "scanStatDuration": "Czas trwania", "scanStatEngines": "Silniki", "severityHeading": "Poziom zagrożenia", - "selectedHeading": "Zaznaczone", + "selectedHeading": "Zaznaczono", "selectedOfThreats": "z {{count}} zagrożeń", "selectAll": "Zaznacz wszystko", "deselectAll": "Odznacz wszystko", @@ -35,11 +35,11 @@ "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": "Niepowodzenie: {{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,18 +49,41 @@ "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}}” dla zagrożeń", + "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", "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Awaryjny Regex", + "dbEngineCompiling": "Kompilowanie...", + "dbCompiling": "Kompilowanie reguł sygnatur ({{loaded}}/{{total}})...", + "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 wersji v{{version}} ({{count}} reguł)", + "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 +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 a0d260b0..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í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ísticas, análise de scripts, integridade do sistema e análise de persistência", "scanButtonScanning": "A analisar...", "scanButton": "Analisar", "quarantineButton": "Colocar em quarentena", @@ -10,27 +10,27 @@ "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", + "selectAll": "Selecionar tudo", + "deselectAll": "Desselecionar tudo", "actingQuarantining": "A colocar as ameaças selecionadas em quarentena...", "actingDeleting": "A eliminar as ameaças selecionadas...", "actionCompleteQuarantine": "Quarentena concluída", @@ -38,54 +38,77 @@ "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", + "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", "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Fallback 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)", + "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 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", + "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": "Desselecionar 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)", "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 e149ddb5..885769ae 100644 --- a/src/renderer/src/locales/ro/malware.json +++ b/src/renderer/src/locales/ro/malware.json @@ -1,14 +1,14 @@ { "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ă", - "severityMedium": "Medie", - "severityLow": "Scăzută", + "severityCritical": "Critic", + "severityHigh": "Ridicat", + "severityMedium": "Mediu", + "severityLow": "Scăzut", "sourceDefenderWindows": "AV nativ", "sourceDefenderMac": "Semnare cod", "sourceDefenderLinux": "ClamAV", @@ -31,17 +31,17 @@ "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", "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", @@ -49,9 +49,9 @@ "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", @@ -61,6 +61,29 @@ "errorOperationFailed": "Operațiunea a eșuat — încercați să rulați ca administrator", "tabScanner": "Scanner", "tabQuarantine": "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": "Kudu Cloud Signatures", + "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", + "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": "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ă", @@ -78,14 +101,14 @@ "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.", "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...", "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 98e6120a..5bb315c8 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,30 @@ "errorOperationFailed": "Не удалось выполнить операцию — попробуйте запустить от имени администратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", - "quarantineEmptyTitle": "Нет объектов в карантине", + "tabDatabase": "База данных", + "dbTitle": "База сигнатур", + "dbDescription": "Правила обнаружения вредоносных программ YARA, используемые сканером", + "dbFetchLatest": "Проверить обновления", + "dbUpdating": "Обновление...", + "dbEngine": "Модуль", + "dbEngineYara": "Облачные сигнатуры Kudu", + "dbEngineRegex": "Резервный Regex", + "dbEngineCompiling": "Компиляция...", + "dbCompiling": "Компиляция правил сигнатур ({{loaded}}/{{total}})...", + "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 +102,7 @@ "confirmRestoreDescription": "Это действие восстановит {{count}} файл(ов) в исходное расположение. Восстанавливайте только те файлы, которым доверяете.", "confirmRestoreLabel": "Восстановить", "confirmDeleteQuarantineTitle": "Удалить безвозвратно", - "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Его нельзя отменить.", + "confirmDeleteQuarantineDescription": "Это действие безвозвратно удалит {{count}} файл(ов) из карантина. Это действие нельзя отменить.", "confirmDeleteQuarantineLabel": "Удалить безвозвратно", "quarantineLoading": "Загрузка карантина...", "quarantineRestoring": "Восстановление файлов...", @@ -87,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 20d625af..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", @@ -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.", "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 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", @@ -61,6 +61,29 @@ "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": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex-reserv", + "dbEngineCompiling": "Kompilerar...", + "dbCompiling": "Kompilerar signaturregler ({{loaded}}/{{total}})...", + "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", @@ -79,13 +102,13 @@ "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}} filer 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...", "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..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": "กักกัน", @@ -12,7 +12,7 @@ "sourceDefenderWindows": "โปรแกรมป้องกันไวรัสในระบบ", "sourceDefenderMac": "การลงนามโค้ด", "sourceDefenderLinux": "ClamAV", - "sourceHeuristic": "การวิเคราะห์เชิงฮิวริสติก", + "sourceHeuristic": "การวิเคราะห์เชิงพฤติกรรม", "sourceSignature": "ลายเซ็นที่รู้จัก", "initializingScanEngines": "กำลังเริ่มต้นเอนจินการสแกน...", "filesScanned": "สแกนแล้ว {{count}} ไฟล์", @@ -33,13 +33,13 @@ "deselectAll": "ยกเลิกการเลือกทั้งหมด", "actingQuarantining": "กำลังกักกันภัยคุกคามที่เลือก...", "actingDeleting": "กำลังลบภัยคุกคามที่เลือก...", - "actionCompleteQuarantine": "กักกันเสร็จสิ้น", - "actionCompleteDeletion": "ลบเสร็จสิ้น", + "actionCompleteQuarantine": "กักกันเสร็จสมบูรณ์", + "actionCompleteDeletion": "ลบเสร็จสมบูรณ์", "actionResultQuarantined": "กักกันแล้ว {{count}} รายการ", "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,6 +61,29 @@ "errorOperationFailed": "การดำเนินการล้มเหลว — ลองเรียกใช้ในฐานะผู้ดูแลระบบ", "tabScanner": "ตัวสแกน", "tabQuarantine": "กักกัน", + "tabDatabase": "ฐานข้อมูล", + "dbTitle": "ฐานข้อมูลลายเซ็น", + "dbDescription": "กฎการตรวจจับมัลแวร์ YARA ที่ตัวสแกนใช้", + "dbFetchLatest": "ตรวจหาการอัปเดต", + "dbUpdating": "กำลังอัปเดต...", + "dbEngine": "เอนจิน", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex สำรอง", + "dbEngineCompiling": "กำลังคอมไพล์...", + "dbCompiling": "กำลังคอมไพล์กฎลายเซ็น ({{loaded}}/{{total}})...", + "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 +99,16 @@ "quarantineSelectAll": "เลือกทั้งหมด", "quarantineDeselectAll": "ยกเลิกการเลือกทั้งหมด", "confirmRestoreTitle": "กู้คืนไฟล์", - "confirmRestoreDescription": "การดำเนินการนี้จะกู้คืนไฟล์ {{count}} รายการไปยังตำแหน่งเดิม กู้คืนเฉพาะไฟล์ที่คุณเชื่อถือเท่านั้น", + "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/tr/malware.json b/src/renderer/src/locales/tr/malware.json index 96f91c52..8baef03e 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", @@ -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...", @@ -39,7 +39,7 @@ "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ı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", @@ -56,11 +56,34 @@ "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ı", "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": "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": "Asla", + "dbSource": "Kural Kaynağı", + "dbSourceCloud": "Cloud (önbelleğe alınmış)", + "dbSourceBundled": "Uygulamayla birlikte gelir", + "dbSourceNone": "Hiç kural yüklenmedi", + "dbRuleFiles": "Kural Dosyaları", + "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ınmış Dosyalar", @@ -76,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 a1a8a9b5..99b4373f 100644 --- a/src/renderer/src/locales/uk/malware.json +++ b/src/renderer/src/locales/uk/malware.json @@ -28,20 +28,20 @@ "scanStatEngines": "Рушії", "severityHeading": "Рівень небезпеки", "selectedHeading": "Вибрано", - "selectedOfThreats": "з {{count}} загроз", + "selectedOfThreats": "із {{count}} загроз", "selectAll": "Вибрати все", "deselectAll": "Зняти вибір з усього", "actingQuarantining": "Переміщення вибраних загроз до карантину...", "actingDeleting": "Видалення вибраних загроз...", "actionCompleteQuarantine": "Переміщення до карантину завершено", "actionCompleteDeletion": "Видалення завершено", - "actionResultQuarantined": "До карантину переміщено: {{count}}", + "actionResultQuarantined": "Переміщено до карантину: {{count}}", "actionResultDeleted": "Видалено: {{count}}", "actionResultFailed": "Не вдалося: {{count}}", "noThreatsDetectedTitle": "Загроз не виявлено", "noThreatsDetectedDescription": "Проскановано {{filesScanned}} файлів за допомогою {{engineCount}} рушіїв за {{duration}} с — ваша система чиста.", "emptyStateTitle": "Сканер шкідливого ПЗ", - "emptyStateDescription": "Натисніть \"Сканувати\", щоб перевірити систему на шкідливе ПЗ, рекламне ПЗ, криптомайнери та підозрілі файли.", + "emptyStateDescription": "Натисніть \"Сканувати\", щоб перевірити систему на наявність шкідливого ПЗ, рекламного ПЗ, криптомайнерів і підозрілих файлів.", "detectedThreatsHeading": "Виявлені загрози", "detectedThreatsCount": "{{count}} загроза", "detectedThreatsCountPlural": "{{count}} загроз", @@ -50,19 +50,42 @@ "threatDetailSize": "Розмір", "threatDetailPath": "Шлях", "confirmQuarantineTitle": "Перемістити загрози до карантину", - "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(у) до карантину. За потреби файли можна буде відновити пізніше.", + "confirmQuarantineDescription": "Буде переміщено {{count}} виявлених загроз(и) до карантину. За потреби файли можна буде відновити пізніше.", "confirmQuarantineLabel": "Перемістити до карантину", "confirmDeleteTitle": "Видалити загрози", - "confirmDeleteDescription": "Буде назавжди видалено {{count}} виявлених загроз(у). Цю дію неможливо скасувати.", - "confirmDeleteLabel": "Видалити назавжди", + "confirmDeleteDescription": "Буде остаточно видалено {{count}} виявлених загроз(и). Цю дію неможливо скасувати.", + "confirmDeleteLabel": "Видалити остаточно", "toastScanFailed": "Не вдалося виконати сканування на шкідливе ПЗ", "toastActionFailed": "Не вдалося {{action}} загрози", "toastActionFailedDescription": "Спробуйте запустити від імені адміністратора", - "errorOperationFailed": "Операцію не виконано — спробуйте запустити від імені адміністратора", + "errorOperationFailed": "Операцію не вдалося виконати — спробуйте запустити від імені адміністратора", "tabScanner": "Сканер", "tabQuarantine": "Карантин", - "quarantineEmptyTitle": "Елементів у карантині немає", - "quarantineEmptyDescription": "Файли, переміщені до карантину, з’являться тут. Ви зможете відновити їх або видалити назавжди.", + "tabDatabase": "База даних", + "dbTitle": "База сигнатур", + "dbDescription": "Правила виявлення шкідливого ПЗ YARA, які використовує сканер", + "dbFetchLatest": "Перевірити наявність оновлень", + "dbUpdating": "Оновлення...", + "dbEngine": "Рушій", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Резервний Regex", + "dbEngineCompiling": "Компіляція...", + "dbCompiling": "Компіляція правил сигнатур ({{loaded}}/{{total}})...", + "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}} файлів", @@ -72,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 ac41765e..26d0e3c4 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ả", @@ -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.", @@ -61,9 +61,32 @@ "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", - "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", + "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": "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}})...", + "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": "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ừ cloud", + "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 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", "quarantineColumnFile": "Tệp", @@ -76,16 +99,16 @@ "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.", "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", "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..1daca481 100644 --- a/src/renderer/src/locales/zh-CN/malware.json +++ b/src/renderer/src/locales/zh-CN/malware.json @@ -24,7 +24,7 @@ "scanSummarySystemClean": "系统干净", "scanSummaryThreatsDetected": "检测到 {{count}} 个威胁", "scanStatFiles": "文件", - "scanStatDuration": "耗时", + "scanStatDuration": "时长", "scanStatEngines": "引擎", "severityHeading": "严重级别", "selectedHeading": "已选择", @@ -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,31 @@ "errorOperationFailed": "操作失败——请尝试以管理员身份运行", "tabScanner": "扫描器", "tabQuarantine": "隔离区", + "tabDatabase": "数据库", + "dbTitle": "签名数据库", + "dbDescription": "扫描器使用的 YARA 恶意软件检测规则", + "dbFetchLatest": "检查更新", + "dbUpdating": "正在更新...", + "dbEngine": "引擎", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex 回退", + "dbEngineCompiling": "正在编译...", + "dbCompiling": "正在编译签名规则 ({{loaded}}/{{total}})...", + "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 +94,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..d4f93840 100644 --- a/src/renderer/src/locales/zh-TW/malware.json +++ b/src/renderer/src/locales/zh-TW/malware.json @@ -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": "詳細資料", @@ -50,10 +50,10 @@ "threatDetailSize": "大小", "threatDetailPath": "路徑", "confirmQuarantineTitle": "隔離威脅", - "confirmQuarantineDescription": "這會將偵測到的 {{count}} 個威脅移至隔離區。如有需要,之後可還原檔案。", + "confirmQuarantineDescription": "這將把偵測到的 {{count}} 個威脅移至隔離區。如有需要,之後可還原檔案。", "confirmQuarantineLabel": "立即隔離", "confirmDeleteTitle": "刪除威脅", - "confirmDeleteDescription": "這會永久刪除偵測到的 {{count}} 個威脅。此動作無法復原。", + "confirmDeleteDescription": "這將永久刪除偵測到的 {{count}} 個威脅。此動作無法復原。", "confirmDeleteLabel": "永久刪除", "toastScanFailed": "惡意軟體掃描失敗", "toastActionFailed": "無法{{action}}威脅", @@ -61,8 +61,31 @@ "errorOperationFailed": "作業失敗 — 請嘗試以系統管理員身分執行", "tabScanner": "掃描器", "tabQuarantine": "隔離區", + "tabDatabase": "資料庫", + "dbTitle": "特徵碼資料庫", + "dbDescription": "掃描器使用的 YARA 惡意軟體偵測規則", + "dbFetchLatest": "檢查更新", + "dbUpdating": "更新中...", + "dbEngine": "引擎", + "dbEngineYara": "Kudu Cloud Signatures", + "dbEngineRegex": "Regex 備援", + "dbEngineCompiling": "編譯中...", + "dbCompiling": "正在編譯特徵碼規則 ({{loaded}}/{{total}})...", + "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,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/pages/MalwareScannerPage.tsx b/src/renderer/src/pages/MalwareScannerPage.tsx index 4686f4f3..2a55c66b 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,20 @@ export function MalwareScannerPage() { useEffect(() => { if (viewMode === 'quarantine') loadQuarantineItems() - }, [viewMode, loadQuarantineItems]) + 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' @@ -335,6 +379,12 @@ export function MalwareScannerPage() { )} + {/* ════════════════════ SCANNER TAB ════════════════════ */} @@ -765,6 +815,21 @@ export function MalwareScannerPage() { icon={Shield} title={t('emptyStateTitle')} description={t('emptyStateDescription')} + action={ + + } /> )} @@ -1064,6 +1129,124 @@ export function MalwareScannerPage() { )} + {/* ════════════════════ DATABASE TAB ════════════════════ */} + {viewMode === 'database' && ( +
+
+ + {/* Header + update button */} +
+
+
+ +
+
+

{t('dbTitle')}

+

{t('dbDescription')}

+
+
+ +
+ + {/* Compile progress banner */} + {yaraInfo?.engine === 'compiling' && yaraInfo.compileProgress && ( +
+
+ + + {t('dbCompiling', { loaded: yaraInfo.compileProgress.loaded, total: yaraInfo.compileProgress.total })} + +
+
+
+
+
+ )} + + {/* Info grid */} + {yaraInfo ? ( +
+ {/* Engine status */} +
+

{t('dbEngine')}

+
+
+ + {yaraInfo.engine === 'compiling' ? t('dbEngineCompiling') : yaraInfo.engine === 'yara' ? 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 ════════════════════ */}