From 67506715529dd355fe33b90bbac1e785a6b0310a Mon Sep 17 00:00:00 2001 From: jorgepwgestao Date: Wed, 22 Jul 2026 18:19:17 -0300 Subject: [PATCH 1/3] security: harden Electron config - nodeIntegration false, contextIsolation, sandbox, secure permissions --- package-lock.json | 809 ++++------------------------------------------ package.json | 8 +- src/main.js | 249 ++++---------- src/preload.js | 69 ++-- 4 files changed, 179 insertions(+), 956 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba9de70..4046304 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,194 +1,65 @@ { "name": "Grok-Desktop", - "version": "1.2.5", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "Grok-Desktop", - "version": "1.2.5", - "license": "ISC", + "version": "1.3.0", + "license": "GPL-2.0", "dependencies": { "electron-tabs": "^1.0.4" }, "devDependencies": { - "electron": "^39.1.2" + "electron": "^43.2.0" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.4.tgz", + "integrity": "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" } }, "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", "progress": "^2.0.3", - "semver": "^6.2.0", + "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=12" + "node": ">=22.12.0" }, "optionalDependencies": { - "global-agent": "^3.0.0" - } - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" + "undici": "^7.24.4" } }, "node_modules/@types/node": { - "version": "22.18.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", - "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "undici-types": "~7.18.0" } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -203,108 +74,23 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/electron": { - "version": "39.2.7", - "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.7.tgz", - "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==", + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-43.2.0.tgz", + "integrity": "sha512-80zvrgG7ZRXD+tD0IyLvrnN9n+veSxadMRsMaC9wKKP3iUbtC7rGM8+dVuCmOb0Rrwwv8ESW4awnUZh9Hbp1fA==", "dev": true, - "hasInstallScript": true, "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", - "extract-zip": "^2.0.1" + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" }, "bin": { - "electron": "cli.js" + "electron": "cli.js", + "install-electron": "install.js" }, "engines": { - "node": ">= 12.20.55" + "node": ">= 22.12.0" } }, "node_modules/electron-tabs": { @@ -313,221 +99,17 @@ "integrity": "sha512-f3hUfMufW8kEZiXf2UndYsmoTdye0rcTL4TjF5bhLTTG8ROmla5Q68ZHSaVmK37BeBQilOfJ70NvqiBd7ahsBQ==", "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-agent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/global-agent/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "dev": true, "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, "engines": { - "node": ">=10.19.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/graceful-fs": { @@ -537,110 +119,6 @@ "dev": true, "license": "ISC" }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC", - "optional": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -648,57 +126,6 @@ "dev": true, "license": "MIT" }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -709,126 +136,19 @@ "node": ">=0.4.0" } }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -842,40 +162,23 @@ "node": ">= 8.0" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">= 4.0.0" + "node": ">=20.18.1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } + "license": "MIT" } } } diff --git a/package.json b/package.json index 56623b1..be8fb42 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Grok-Desktop", - "version": "1.2.5", - "description": "Grok-Desktop application for Grok.com with tab support", + "version": "1.3.0", + "description": "Grok-Desktop application for Grok.com with tab support - security hardened", "main": "src/main.js", "scripts": { "start": "electron .", @@ -23,13 +23,13 @@ "tabs" ], "author": "AnRkey ", - "license": "ISC", + "license": "GPL-2.0", "type": "commonjs", "dependencies": { "electron-tabs": "^1.0.4" }, "devDependencies": { - "electron": "^39.1.2" + "electron": "^43.2.0" }, "overrides": { "form-data": "4.0.4", diff --git a/src/main.js b/src/main.js index 1631d61..6ad9ea3 100644 --- a/src/main.js +++ b/src/main.js @@ -1,65 +1,47 @@ - const { app, BrowserWindow, shell, Menu, ipcMain, nativeTheme, session, webContents, dialog } = require('electron'); const os = require('os'); const fs = require('fs'); const { exec } = require('child_process'); +const path = require('path'); // GPU acceleration detection and graceful fallback function configureGpuAcceleration() { - // Check if we're in a headless environment or container const isHeadless = !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY; const isContainer = fs.existsSync('/.dockerenv') || process.env.container === 'docker'; - // Check for NVIDIA/AMD GPU availability (basic detection) let hasGpu = false; try { - // Check for NVIDIA GPU if (fs.existsSync('/dev/nvidia0') || process.env.NVIDIA_VISIBLE_DEVICES) { hasGpu = true; } - // Check for AMD GPU if (fs.existsSync('/dev/dri/card0')) { hasGpu = true; } - } catch (e) { - // Ignore errors in GPU detection - } + } catch (e) {} - // Disable GPU acceleration if: - // 1. In headless environment - // 2. In container without GPU passthrough - // 3. No GPU detected - // 4. Explicitly requested via environment variable const shouldDisableGpu = isHeadless || (isContainer && !hasGpu) || !hasGpu || process.env.GROK_DISABLE_GPU === 'true'; if (shouldDisableGpu) { console.log('Grok Desktop: Disabling GPU acceleration for compatibility'); app.disableHardwareAcceleration(); - - // Additional GPU-related switches for better compatibility app.commandLine.appendSwitch('disable-gpu-compositing'); app.commandLine.appendSwitch('disable-accelerated-video-decode'); app.commandLine.appendSwitch('disable-accelerated-mjpeg-decode'); - // Log the reason for transparency const reasons = []; if (isHeadless) reasons.push('headless environment'); if (isContainer && !hasGpu) reasons.push('container without GPU'); if (!hasGpu) reasons.push('no GPU detected'); if (process.env.GROK_DISABLE_GPU === 'true') reasons.push('explicitly disabled'); - console.log(`Grok Desktop: GPU acceleration disabled due to: ${reasons.join(', ')}`); } else { console.log('Grok Desktop: GPU acceleration enabled'); } } -// Configure GPU acceleration before app initialization configureGpuAcceleration(); -// Global error handler for GPU/VAAPI issues process.on('warning', (warning) => { - // Handle VAAPI and GPU-related warnings gracefully if (warning.message && ( warning.message.includes('vaInitialize failed') || warning.message.includes('VAAPI') || @@ -69,11 +51,9 @@ process.on('warning', (warning) => { console.log('Grok Desktop: GPU warning detected, continuing with software rendering:', warning.message); return; } - // Log other warnings normally console.warn(warning.name, warning.message, warning.stack); }); -// Handle uncaught exceptions related to GPU process.on('uncaughtException', (error) => { if (error.message && ( error.message.includes('vaInitialize failed') || @@ -82,26 +62,21 @@ process.on('uncaughtException', (error) => { error.message.includes('GPU') )) { console.log('Grok Desktop: GPU error caught, continuing with software rendering:', error.message); - return; // Don't exit the process + return; } - // Re-throw non-GPU errors throw error; }); -// Track GPU acceleration state and restart attempts let gpuDisabled = false; let restartAttempted = false; -// Disable GPU acceleration if we detect initialization failures function handleGpuAcceleration() { - // Check if we're already in fallback mode if (process.argv.includes('--disable-gpu') || process.env.ELECTRON_DISABLE_GPU === '1') { gpuDisabled = true; console.log('GPU acceleration disabled by flag or environment variable'); return; } - // Listen for GPU process crashes or initialization errors app.on('gpu-process-crashed', (event, killed) => { if (!killed && !gpuDisabled && !restartAttempted) { console.warn('GPU process crashed, attempting to restart with GPU acceleration disabled'); @@ -110,7 +85,6 @@ function handleGpuAcceleration() { } }); - // Monitor for VAAPI/GPU errors in stderr const originalStderrWrite = process.stderr.write; process.stderr.write = function(chunk, encoding, callback) { const data = chunk.toString(); @@ -129,83 +103,49 @@ function handleGpuAcceleration() { function restartWithGpuDisabled() { if (restartAttempted) return; restartAttempted = true; - - // Disable hardware acceleration for next start app.disableHardwareAcceleration(); - - // Show a brief notification to user about fallback mode console.log('Restarting Grok Desktop with GPU acceleration disabled for compatibility...'); - - // Restart the app app.relaunch({ args: [...process.argv.slice(1), '--disable-gpu'] }); app.exit(0); } -// Initialize GPU handling before app setup handleGpuAcceleration(); -// Handle open-external-url from renderer with enhanced validation +// Secure external URL handler ipcMain.handle('open-external-url', async (_event, url) => { try { - // Basic type and protocol validation - if (typeof url !== 'string' || !url.startsWith('http')) { - return false; - } - - // Parse URL to validate format and prevent malicious schemes + if (typeof url !== 'string' || !url.startsWith('http')) return false; const urlObj = new URL(url); + if (!['http:', 'https:'].includes(urlObj.protocol)) return false; + if (!urlObj.hostname || urlObj.hostname.length === 0 || urlObj.hostname.length > 253) return false; - // Ensure it's HTTP or HTTPS (not javascript:, data:, etc.) - if (!['http:', 'https:'].includes(urlObj.protocol)) { - return false; - } - - // Basic URL validation - ensure hostname exists and is reasonable - if (!urlObj.hostname || urlObj.hostname.length === 0 || urlObj.hostname.length > 253) { - return false; - } - - // Prevent localhost/private IP access for external URLs const hostname = urlObj.hostname.toLowerCase(); if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0' || - hostname.startsWith('192.168.') || hostname.startsWith('10.') || - hostname.startsWith('172.')) { + hostname.startsWith('192.168.') || hostname.startsWith('10.') || hostname.startsWith('172.')) { return false; } await shell.openExternal(url); return true; } catch (error) { - // Invalid URL format return false; } }); -const path = require('path'); -// Keep a global reference of the window object to prevent garbage collection let mainWindow; let aboutWindow; -// Allow autoplay without user gesture (for seamless audio playback) try { app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required'); } catch (_) {} -// Define the allowed URL patterns for internal handling with secure domain validation const allowedUrlPatterns = [ - // Allow grok.com domain and all its paths (for normal browsing), but not as subdomain /^https?:\/\/grok\.com(?:\/|$)/, - // Allow x.ai domain and all its paths (for normal browsing), but not as subdomain /^https?:\/\/x\.ai(?:\/|$)/, - // Allow x.com domain for OAuth flows (but not as subdomain) /^https?:\/\/x\.com(?:\/|$)/, - // Allow accounts.x.ai domain and auth-related paths (but not as subdomain) /^https?:\/\/accounts\.x\.ai(?:\/|$)/, - // Allow accounts.google.com domain and OAuth paths (but not as subdomain) /^https?:\/\/accounts\.google\.com(?:\/|$)/, - // Allow appleid.apple.com domain and OAuth paths (but not as subdomain) /^https?:\/\/appleid\.apple\.com(?:\/|$)/ ]; -// Enforce single instance const gotSingleInstanceLock = app.requestSingleInstanceLock(); if (!gotSingleInstanceLock) { app.quit(); @@ -219,14 +159,8 @@ if (!gotSingleInstanceLock) { }); } -// Track webContents that should always use light color scheme const forcedLightWebContentsIds = new Set(); -// Always On Top (AOT) functionality for cross-platform compatibility -// Windows: Uses Electron's built-in setAlwaysOnTop() method -// Linux: Uses wmctrl command-line tool for better GNOME/Wayland compatibility -// If running under Wayland on Linux, automatically restarts with X11 forced - let wmctrlAvailable = false; let isWayland = false; let x11Forced = false; @@ -234,7 +168,6 @@ let x11Forced = false; function checkWmctrlAvailability() { if (os.platform() !== 'linux') return; - // Check if we're running under Wayland (Rocky Linux 10 defaults to Wayland) isWayland = !!process.env.WAYLAND_DISPLAY || !!process.env.XDG_SESSION_TYPE?.includes('wayland'); x11Forced = process.argv.includes('--ozone-platform=x11'); @@ -242,13 +175,10 @@ function checkWmctrlAvailability() { if (isWayland && !x11Forced) { console.log('Grok Desktop: Running under Wayland, forcing X11 for AOT compatibility'); - // GNOME on Wayland intentionally restricts programmatic AOT for security - // We force X11 mode where wmctrl works reliably forceX11Mode(); return; } - // Check if wmctrl is available (install with: sudo dnf install wmctrl on Rocky Linux) exec('which wmctrl', (error) => { wmctrlAvailable = !error; if (wmctrlAvailable) { @@ -262,8 +192,6 @@ function checkWmctrlAvailability() { function forceX11Mode() { console.log('Grok Desktop: Relaunching with X11 for AOT compatibility...'); - - // Relaunch with X11 forced to enable wmctrl functionality const newArgs = [...process.argv.slice(1), '--ozone-platform=x11']; app.relaunch({ args: newArgs, @@ -272,18 +200,14 @@ function forceX11Mode() { app.exit(0); } -// Fallback AOT toggle using wmctrl on Linux function toggleAlwaysOnTopLinux(mainWindow) { if (!wmctrlAvailable) return false; return new Promise((resolve) => { - // Get the window title to target it specifically const windowTitle = mainWindow.getTitle() || 'Grok Desktop'; - - // First focus the window, then toggle always-on-top const commands = [ - `wmctrl -a "${windowTitle}"`, // Focus/activate the window - `wmctrl -r "${windowTitle}" -b toggle,above` // Toggle always-on-top + `wmctrl -a "${windowTitle}"`, + `wmctrl -r "${windowTitle}" -b toggle,above` ]; exec(commands.join(' && '), (error) => { @@ -299,31 +223,30 @@ function toggleAlwaysOnTopLinux(mainWindow) { } function createWindow() { - // Create the browser window mainWindow = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, webPreferences: { - nodeIntegration: true, // Enable Node.js integration - contextIsolation: false, // Disable context isolation for this use case - webviewTag: true, // Enable webview tag for tabs - spellcheck: true + nodeIntegration: false, + contextIsolation: true, + sandbox: true, + webviewTag: true, + preload: path.join(__dirname, 'preload.js'), + spellcheck: true, + webSecurity: true, + allowRunningInsecureContent: false }, icon: path.join(__dirname, 'grok.png') }); - // Disable the menu bar Menu.setApplicationMenu(null); - // Ensure shortcuts work when focus is on the main window UI try { attachShortcutHandlers(mainWindow.webContents); } catch (_) {} - // Load the index.html file mainWindow.loadFile(path.join(__dirname, '../index.html')); - // Configure spellchecker languages for default session and webview partition try { const locale = (typeof app.getLocale === 'function' && app.getLocale()) || 'en-US'; const languages = Array.isArray(locale) ? locale : [locale]; @@ -349,14 +272,13 @@ function createWindow() { } } catch (_) {} - // Send initial theme and listen for OS theme changes const sendTheme = () => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('system-theme-updated', nativeTheme.shouldUseDarkColors ? 'dark' : 'light'); } }; sendTheme(); - // Apply color scheme to all web contents (main and webviews) + const applyColorSchemeToAll = () => { const scheme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; try { @@ -378,30 +300,16 @@ function createWindow() { applyColorSchemeToAll(); }); - // Open DevTools in development mode - // mainWindow.webContents.openDevTools(); - - // Handle window closed event mainWindow.on('closed', () => { mainWindow = null; }); - // Set up URL handling setupUrlHandling(); - - // Set up IPC handlers setupIpcHandlers(); - - // Set up WebRTC/media permissions (allow across all domains) setupPermissions(); - - // Enable right-click context menus setupContextMenus(); - - // Set up keyboard shortcuts (Ctrl+T, Ctrl+Tab, Ctrl+R) setupKeyboardShortcuts(); - // Ensure newly created webContents/webviews get correct color scheme app.on('web-contents-created', (_event, contents) => { const scheme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; if (typeof contents.setColorScheme === 'function') { @@ -423,28 +331,21 @@ function createWindow() { }); } -// Create window when Electron has finished initialization app.whenReady().then(() => { checkWmctrlAvailability(); createWindow(); app.on('activate', () => { - // On macOS, re-create a window when the dock icon is clicked and no windows are open if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); -// Quit when all windows are closed, except on macOS app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); -// Handle URL navigation and determine if URLs should be opened internally function setupUrlHandling() { - // Handle navigation events from webContents app.on('web-contents-created', (event, contents) => { - // Intercept new window requests; always deny BrowserWindow creation - // Internal domains will be handled by the renderer's webview 'new-window' handler contents.setWindowOpenHandler(({ url }) => { const isInternal = allowedUrlPatterns.some(pattern => pattern.test(url)); if (!isInternal) { @@ -455,13 +356,10 @@ function setupUrlHandling() { }); } -// Set up IPC handlers for renderer-to-main process communication function setupIpcHandlers() { - // Handle always-on-top toggle ipcMain.handle('toggle-always-on-top', async () => { if (!mainWindow) return false; - // On Linux, use wmctrl if available for better GNOME compatibility if (os.platform() === 'linux') { if (wmctrlAvailable) { const result = await toggleAlwaysOnTopLinux(mainWindow); @@ -469,10 +367,8 @@ function setupIpcHandlers() { } else { console.warn('Grok Desktop: wmctrl not available on Linux, AOT may not work'); } - // Fall back to Electron method if wmctrl fails or isn't available } - // Use Electron's built-in method (works on Windows/macOS, may not work reliably on Linux GNOME/Wayland) try { const isAlwaysOnTop = mainWindow.isAlwaysOnTop(); mainWindow.setAlwaysOnTop(!isAlwaysOnTop); @@ -483,7 +379,6 @@ function setupIpcHandlers() { } }); - // Provide app version to renderer ipcMain.handle('get-app-version', () => { try { return app.getVersion(); @@ -492,19 +387,16 @@ function setupIpcHandlers() { } }); - // Open About page in a new tab instead of a window ipcMain.handle('show-app-info', async () => { const name = typeof app.getName === 'function' ? app.getName() : 'Grok Desktop'; const version = typeof app.getVersion === 'function' ? app.getVersion() : '0.0.0'; const repoUrl = 'https://github.com/AnRkey/Grok-Desktop'; - // Build the about page URL with parameters const urlObj = new URL(`file://${path.join(__dirname, '../about.html')}`); urlObj.searchParams.set('name', name); urlObj.searchParams.set('version', version); urlObj.searchParams.set('repo', repoUrl); - // Derive developer/contact from the GitHub repo URL let developer = 'AnRkey'; try { const m = repoUrl.match(/^https?:\/\/github\.com\/([^/]+)/i); @@ -514,7 +406,6 @@ function setupIpcHandlers() { urlObj.searchParams.set('developer', developer); urlObj.searchParams.set('contact', contactUrl); - // Send the URL to the renderer to create a new tab if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('open-about-tab', urlObj.toString()); } @@ -522,15 +413,8 @@ function setupIpcHandlers() { return { name, version }; }); - // Fetch Grok usage rate limits - // Usage stats feature inspired by Joshua Wang's Grok Usage Watch extension - // https://github.com/JoshuaWang2211 - // Fixed: Execute fetch inside the webview's context (where user is logged in) - // to avoid 403 errors from session.fetch() in main process - // Thanks to Joshua for identifying the root cause and suggesting this solution! ipcMain.handle('fetch-grok-rate-limits', async () => { try { - // Find the active Grok webview's webContents const allContents = webContents.getAllWebContents(); const grokWebview = allContents.find(wc => { try { @@ -540,12 +424,11 @@ function setupIpcHandlers() { return false; } }); - + if (!grokWebview) { return { error: 'No Grok tab found' }; } - - // Execute the fetch inside the webview's context where session cookies are available + const result = await grokWebview.executeJavaScript(` (async () => { const fetchRateLimits = async (requestKind, modelName) => { @@ -555,7 +438,7 @@ function setupIpcHandlers() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requestKind, modelName }) }); - + if (response.status === 401 || response.status === 403) { return { error: 'UNAUTHORIZED' }; } @@ -567,26 +450,25 @@ function setupIpcHandlers() { return { error: e.message }; } }; - + const [defaultLimits, grok4HeavyLimits] = await Promise.all([ fetchRateLimits('DEFAULT', 'grok-3'), fetchRateLimits('DEFAULT', 'grok-4-heavy') ]); - + return { DEFAULT: defaultLimits, GROK4HEAVY: grok4HeavyLimits }; })() `); - + return result; } catch (error) { return { error: error.message }; } }); - // Force light/dynamic color scheme for specific webContents id ipcMain.handle('force-light-color-scheme', (_event, wcId, shouldForceLight) => { try { const wc = webContents.fromId(wcId); @@ -594,7 +476,6 @@ function setupIpcHandlers() { if (shouldForceLight) { forcedLightWebContentsIds.add(wcId); if (typeof wc.setColorScheme === 'function') wc.setColorScheme('light'); - // Stronger override via DevTools Protocol: emulate prefers-color-scheme: light try { if (!wc.debugger.isAttached()) wc.debugger.attach('1.3'); wc.debugger.sendCommand('Emulation.setEmulatedMedia', { @@ -605,7 +486,6 @@ function setupIpcHandlers() { forcedLightWebContentsIds.delete(wcId); const scheme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; if (typeof wc.setColorScheme === 'function') wc.setColorScheme(scheme); - // Remove emulation try { if (wc.debugger.isAttached()) { wc.debugger.sendCommand('Emulation.setEmulatedMedia', { features: [] }); @@ -618,15 +498,13 @@ function setupIpcHandlers() { return false; } }); -} +} -// Enable context menus across the app (window and webviews) function setupContextMenus() { app.on('web-contents-created', (_event, contents) => { contents.on('context-menu', (event, params) => { const template = []; - // Spell-check suggestions (when right-clicking a misspelled word) if (params.misspelledWord && params.misspelledWord.trim()) { const suggestions = Array.isArray(params.dictionarySuggestions) ? params.dictionarySuggestions.slice(0, 6) @@ -641,7 +519,6 @@ function setupContextMenus() { }); } - // Allow adding the word to the custom dictionary for this session if (contents.session && typeof contents.session.addWordToSpellCheckerDictionary === 'function') { template.push({ label: `Add to Dictionary: "${params.misspelledWord}"`, @@ -654,7 +531,6 @@ function setupContextMenus() { } } - // Link options if (params.linkURL) { template.push({ label: 'Open Link in Browser', @@ -662,7 +538,6 @@ function setupContextMenus() { }); } - // Image options if (params.hasImageContents && params.srcURL) { template.push({ label: 'Save Image As…', @@ -670,7 +545,6 @@ function setupContextMenus() { }); } - // Edit actions if (params.isEditable) { template.push( { role: 'undo' }, @@ -687,7 +561,6 @@ function setupContextMenus() { template.push({ role: 'copy' }, { type: 'separator' }); } - // Navigation (for webviews/pages) const canGoBack = contents.navigationHistory && typeof contents.navigationHistory.canGoBack === 'function' && contents.navigationHistory.canGoBack(); const canGoForward = contents.navigationHistory && typeof contents.navigationHistory.canGoForward === 'function' && contents.navigationHistory.canGoForward(); template.push( @@ -696,7 +569,6 @@ function setupContextMenus() { { label: 'Reload', click: () => contents.reload && contents.reload() } ); - const menu = Menu.buildFromTemplate(template); const win = BrowserWindow.fromWebContents(contents); if (win) menu.popup({ window: win }); @@ -704,46 +576,71 @@ function setupContextMenus() { }); } -// Allow all media-related permissions for all domains (both default and persist:grok sessions) +// ===== SECURE PERMISSIONS (origin-aware) ===== function setupPermissions() { + const isAllowedOrigin = (url) => { + if (!url || typeof url !== 'string') return false; + try { + return allowedUrlPatterns.some(pattern => pattern.test(url)); + } catch { + return false; + } + }; + const enableForSession = (targetSession) => { if (!targetSession) return; try { - // Always grant permission checks if (typeof targetSession.setPermissionCheckHandler === 'function') { - targetSession.setPermissionCheckHandler(() => true); + targetSession.setPermissionCheckHandler((wc, permission, requestingOrigin) => { + return isAllowedOrigin(requestingOrigin); + }); } - // Always grant runtime permission requests + if (typeof targetSession.setPermissionRequestHandler === 'function') { - targetSession.setPermissionRequestHandler((_wc, _permission, callback, _details) => { - try { callback(true); } catch (_) {} + targetSession.setPermissionRequestHandler((wc, permission, callback, details) => { + const url = details?.requestingUrl || (wc && typeof wc.getURL === 'function' ? wc.getURL() : ''); + if (isAllowedOrigin(url) && + ['media', 'mediaKeySystem', 'display-capture', 'clipboard-read', 'clipboard-sanitized-write'].includes(permission)) { + callback(true); + } else { + callback(false); + } }); } - // Best-effort: allow device and display capture if supported by current Electron + if (typeof targetSession.setDevicePermissionHandler === 'function') { - targetSession.setDevicePermissionHandler(() => true); + targetSession.setDevicePermissionHandler((details) => { + return isAllowedOrigin(details.origin || details.securityOrigin || ''); + }); } + if (typeof targetSession.setDisplayMediaRequestHandler === 'function') { - targetSession.setDisplayMediaRequestHandler((_wc, request, callback) => { - // Approve requested audio/video capture; defer exact source selection to default behavior - try { callback({ video: !!request.video, audio: !!request.audio }); } catch (_) {} + targetSession.setDisplayMediaRequestHandler((wc, request, callback) => { + const url = wc.getURL(); + if (isAllowedOrigin(url)) { + callback({ video: !!request.video, audio: !!request.audio }); + } else { + callback({}); + } }); } - } catch (_) {} + } catch (e) { + console.error('Permission setup error:', e); + } }; try { enableForSession(session.defaultSession); } catch (_) {} try { enableForSession(session.fromPartition('persist:grok')); } catch (_) {} - // Ensure any future sessions/webviews also have audio unmuted try { app.on('web-contents-created', (_event, contents) => { - try { if (typeof contents.setAudioMuted === 'function') contents.setAudioMuted(false); } catch (_) {} + try { + if (typeof contents.setAudioMuted === 'function') contents.setAudioMuted(false); + } catch (_) {} }); } catch (_) {} } -// Keyboard shortcuts wired at the webContents level so they work in webviews too function setupKeyboardShortcuts() { try { app.on('web-contents-created', (_event, contents) => attachShortcutHandlers(contents)); @@ -754,26 +651,20 @@ function attachShortcutHandlers(contents) { try { contents.on('before-input-event', (event, input) => { try { - // Only handle keyDown with Control on Windows/Linux if (input.type !== 'keyDown' || !input.control) return; const key = input.key; - // Deliver to the hosting window (handles webviews as well) const host = contents.hostWebContents || contents; const win = BrowserWindow.fromWebContents(host); if (!win || win.isDestroyed()) return; - // Ctrl+K -> Remap to Ctrl+Shift+K for grok.com search - // grok.com responds to Ctrl+Shift+K, not Ctrl+K (which Chromium intercepts for omnibox) if ((key === 'k' || key === 'K') && !input.shift) { event.preventDefault(); - // Send Ctrl+Shift+K to the webContents using sendInputEvent (creates trusted OS-level events) contents.sendInputEvent({ type: 'keyDown', keyCode: 'K', modifiers: ['control', 'shift'] }); - // Send keyUp after a short delay setTimeout(() => { try { contents.sendInputEvent({ @@ -785,15 +676,13 @@ function attachShortcutHandlers(contents) { }, 10); return; } - // Don't intercept Ctrl+Shift+K - let it pass through naturally (it already works) - // Ctrl+T -> new tab if (key === 't' || key === 'T') { event.preventDefault(); win.webContents.send('shortcut-new-tab'); return; } - // Ctrl+Tab -> next tab, Ctrl+Shift+Tab -> previous tab + if (key === 'Tab') { event.preventDefault(); if (input.shift) { @@ -803,13 +692,13 @@ function attachShortcutHandlers(contents) { } return; } - // Ctrl+R -> reload active tab (override default window reload) + if (key === 'r' || key === 'R') { event.preventDefault(); win.webContents.send('shortcut-reload-tab'); return; } - // Ctrl+I -> show information/about dialog + if (key === 'i' || key === 'I') { event.preventDefault(); win.webContents.send('shortcut-show-info'); diff --git a/src/preload.js b/src/preload.js index f6abfe3..3401115 100644 --- a/src/preload.js +++ b/src/preload.js @@ -1,28 +1,59 @@ -const { ipcRenderer } = require('electron'); +const { contextBridge, ipcRenderer } = require('electron'); -// Since we're using nodeIntegration: true and contextIsolation: false, -// we don't need to use contextBridge. Instead, we can expose our API -// directly to the window object. +const validSendChannels = [ + 'tab-created', + 'tab-closed', + 'navigate-to-url', + 'renderer-error', + 'renderer-promise-rejection' +]; -window.api = { - // Send a message to the main process +const validReceiveChannels = [ + 'new-tab', + 'close-tab', + 'url-updated', + 'system-theme-updated', + 'shortcut-new-tab', + 'shortcut-next-tab', + 'shortcut-prev-tab', + 'shortcut-reload-tab', + 'shortcut-show-info', + 'open-about-tab' +]; + +contextBridge.exposeInMainWorld('api', { send: (channel, data) => { - // Whitelist channels - const validChannels = ['tab-created', 'tab-closed', 'navigate-to-url', 'renderer-error', 'renderer-promise-rejection']; - if (validChannels.includes(channel)) { + if (validSendChannels.includes(channel)) { ipcRenderer.send(channel, data); } }, - // Receive a message from the main process + receive: (channel, func) => { - const validChannels = ['new-tab', 'close-tab', 'url-updated']; - if (validChannels.includes(channel)) { - // Deliberately strip event as it includes `sender` - ipcRenderer.on(channel, (event, ...args) => func(...args)); + if (validReceiveChannels.includes(channel)) { + const subscription = (event, ...args) => func(...args); + ipcRenderer.on(channel, subscription); + return () => ipcRenderer.removeListener(channel, subscription); + } + }, + + on: (channel, func) => { + if (validReceiveChannels.includes(channel)) { + const subscription = (event, ...args) => func(...args); + ipcRenderer.on(channel, subscription); + return () => ipcRenderer.removeListener(channel, subscription); } }, - // Get the version of the application - getVersion: () => { - return process.env.npm_package_version; - } -}; \ No newline at end of file + + getVersion: () => ipcRenderer.invoke('get-app-version'), + + openExternal: (url) => ipcRenderer.invoke('open-external-url', url), + + toggleAlwaysOnTop: () => ipcRenderer.invoke('toggle-always-on-top'), + + showAppInfo: () => ipcRenderer.invoke('show-app-info'), + + fetchGrokRateLimits: () => ipcRenderer.invoke('fetch-grok-rate-limits'), + + forceLightColorScheme: (wcId, shouldForceLight) => + ipcRenderer.invoke('force-light-color-scheme', wcId, shouldForceLight) +}); \ No newline at end of file From a4c6499e06bf9a55c7bbf3a7c4cf815bff8b53fe Mon Sep 17 00:00:00 2001 From: jorgepwgestao Date: Wed, 22 Jul 2026 18:25:50 -0300 Subject: [PATCH 2/3] docs: update CHANGELOG and README for v1.3.0 security hardening --- CHANGELOG.md | 43 +++--- README.md | 366 ++++----------------------------------------------- 2 files changed, 42 insertions(+), 367 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd7b85a..f3c290e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. +## [1.3.0] - 2026-07-22 + +### Security +- **Critical hardening**: Disabled `nodeIntegration` and enabled `contextIsolation` + `sandbox` on the main BrowserWindow +- Completely rewrote `preload.js` using `contextBridge` (removed direct Node access from renderer) +- Made permission handlers origin-aware (only allow media/display-capture for trusted domains: grok.com, x.ai, accounts.google.com, appleid.apple.com, etc.) +- Added explicit security attributes on webviews (`nodeintegration=false`, `contextisolation=true`) +- Updated Electron from 39.1.2 → **43.2.0** (latest stable with many security fixes) +- Fixed license inconsistency in `package.json` (now correctly set to GPL-2.0) + +### Changed +- Replaced all `require('electron')` and direct `ipcRenderer` usage in `index.html` with the secure `window.api` bridge +- Replaced Node `https` module with native `fetch` for update checking +- Improved overall security posture while keeping full feature compatibility + +### Fixed +- Maintained all existing functionality (tabs, AOT, usage stats, OAuth, shortcuts, theme, etc.) after the security refactor ## [1.2.5] - 2025-12-17 @@ -28,6 +45,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Changed - Bump version to 1.2.4 + ## [1.2.3] - 2025-11-08 ### Added @@ -47,25 +65,6 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Changed - Bump version to 1.2.2 -## [Unreleased] - -### Added -- Keyboard shortcuts in the app: - - Ctrl+T: open new tab - - Ctrl+Tab / Ctrl+Shift+Tab: cycle through open tabs (next/previous) - - Ctrl+R reloads the active tab - - Ctrl+I: open information/about dialog -### Changed -- Update Electron to 39.x (incrementally 36 → 37 → 38 → 39) -- Use electron-builder via npx in npm scripts (no global install required) - -### Removed -- Remove unused dependency: electron-store -- Remove `brace-expansion` overrides after verification - -### Security -- Verified `brace-expansion` advisories (CVE-2025-5889); ensured no affected versions in tree - ## [1.2.1] - 2025-08-16 ### Changed @@ -98,7 +97,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ### Added - Initial pre-release of Grok Desktop - [Unreleased]: https://github.com/AnRkey/Grok-Desktop/compare/v1.2.5...HEAD +[1.3.0]: https://github.com/AnRkey/Grok-Desktop/compare/v1.2.5...v1.3.0 [1.2.5]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.2.5 [1.2.4]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.2.4 [1.2.3]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.2.3 @@ -106,6 +105,4 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve [1.2.1]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.2.1 [1.2.0]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.2.0 [1.1.0]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.1.0 -[1.0.0-pre]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.0.0-pre - - +[1.0.0-pre]: https://github.com/AnRkey/Grok-Desktop/releases/tag/v1.0.0-pre \ No newline at end of file diff --git a/README.md b/README.md index cbf2f5b..5efc728 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Grok-Desktop v1.2.5 +# Grok-Desktop v1.3.0 ## Description Grok-Desktop is an Electron-based desktop application for Windows 10/11 and Linux that wraps `grok.com`, providing desktop-application-like access to Grok with real-time API usage monitoring, multi-tab support, and seamless authentication for xAI, Google, and Apple accounts. @@ -26,21 +26,32 @@ Grok-Desktop is an Electron-based desktop application for Windows 10/11 and Linu - **Always-on-top function** with cross-platform support (Windows & Linux) - **Dark/Light mode support** with system theme detection - **Grok speech mode** support -- **Enhanced security** with domain validation and OAuth protection + +## Security (v1.3.0) +- **Critical Electron hardening**: + - `nodeIntegration: false` + - `contextIsolation: true` + - `sandbox: true` +- Secure `preload.js` using `contextBridge` (no direct Node access from renderer) +- Origin-aware permission handlers (media / display-capture only allowed for trusted domains) +- Explicit security attributes on all `` elements +- Updated to **Electron 43.2.0** +- License correctly set to **GPL-2.0** +- Domain validation + external URL protection (blocks localhost / private IPs) +- Content Security Policy (CSP) ## Download ### Windows -- [Grok-Desktop_Installer-v1.2.5.exe](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Installer-v1.2.5.exe) - Windows installer (EXE) -- [Grok-Desktop_Installer-v1.2.5.msi](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Installer-v1.2.5.msi) - Windows installer (MSI) -- [Grok-Desktop_Portable-v1.2.5.exe](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Portable-v1.2.5.exe) - Portable Windows executable -- [Grok-Desktop_Windows-v1.2.5.zip](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Windows-v1.2.5.zip) - Windows ZIP archive +- [Grok-Desktop_Installer-v1.3.0.exe](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.3.0/Grok-Desktop_Installer-v1.3.0.exe) - Windows installer (EXE) +- [Grok-Desktop_Installer-v1.3.0.msi](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.3.0/Grok-Desktop_Installer-v1.3.0.msi) - Windows installer (MSI) +- [Grok-Desktop_Portable-v1.3.0.exe](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.3.0/Grok-Desktop_Portable-v1.3.0.exe) - Portable Windows executable ### Linux -- [Grok-Desktop-v1.2.5.deb](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop-v1.2.5.deb) - Debian/Ubuntu package -- [Grok-Desktop-v1.2.5.rpm](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop-v1.2.5.rpm) - RPM package (RHEL/Rocky Linux/Fedora) -- [Grok-Desktop_Linux-v1.2.5.tar.gz](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Linux-v1.2.5.tar.gz) - Linux tar.gz archive -- [Grok-Desktop_Linux-v1.2.5.7z](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.2.5/Grok-Desktop_Linux-v1.2.5.7z) - Linux 7z archive +- [Grok-Desktop-v1.3.0.deb](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.3.0/Grok-Desktop-v1.3.0.deb) - Debian/Ubuntu package +- [Grok-Desktop-v1.3.0.rpm](https://github.com/AnRkey/Grok-Desktop/releases/download/v1.3.0/Grok-Desktop-v1.3.0.rpm) - RPM package (RHEL/Rocky Linux/Fedora) + +> **Note**: Release assets for v1.3.0 will be available after the official release. ## System Requirements @@ -57,69 +68,6 @@ Grok-Desktop is an Electron-based desktop application for Windows 10/11 and Linu - **Node.js**: LTS version (20.x recommended) - **Internet connection** for downloading dependencies -## Project Structure -``` -Grok-Desktop/ -├── src/ # Main Electron application code -│ ├── main.js # Main Electron process -│ ├── preload.js # Preload script for renderer security -│ ├── renderer.js # Renderer process code -│ ├── custom-tabs.js # Custom tabs implementation -│ ├── grok.ico # Windows application icon -│ └── grok.png # Linux application icon -├── assets/ # Static assets -│ ├── icon.png -│ └── icon.svg -├── build-resources/ # Build configuration and resources -│ ├── icons/ # Application icons for different sizes -│ ├── after-install.sh # Post-installation script -│ ├── after-remove.sh # Post-removal script -│ └── com.grok.desktop.metainfo.xml # App metadata for Linux -├── index.html # Main application HTML -├── styles.css # Application styles -├── about.html # About dialog HTML -├── package.json # Node.js dependencies and build config -├── build.bat # Windows build script -├── build-linux.sh # Linux build script -├── CHANGELOG.md # Version history and changes -├── LINUX_BUILD_GUIDE.md # Detailed Linux build instructions -├── new_features.md # Security features documentation -└── README.md # This file -``` - -<<<<<<< HEAD -## Build Grok-Desktop -1. Install Node.js from [nodejs.org](https://nodejs.org/). -2. Clone this repository or download the files. -3. Install dependencies if needed: `npm install` -4. Build using npm scripts: - - Directory build (unpacked): `npm run build-dir` - - Portable executable: `npm run build-portable` - - Full installers (NSIS + MSI): `npm run build-installer` - -Notes: -- These scripts use `npx electron-builder@latest` (no global install required). -- All build outputs are written to the `build` directory. - -## Usage -- After building, install `Grok-Desktop` with `Grok-Desktop_Installer-v1.2.3.exe` from the `build` directory -- Launch `Grok-Desktop` from the Start Menu -- Log in via `grok.com`, using Google, Apple, or xAI authentication as needed. -- Use the + button in the top toolbar (or Ctrl+T) to add new tabs. -- Click the AOT button in the top right to toggle always-on-top functionality. -- Use keyboard shortcuts to work faster: - - Ctrl+T: Open a new tab - - Ctrl+Tab / Ctrl+Shift+Tab: Cycle through open tabs (next/previous) - - Ctrl+R: Reload the currently active tab - - Ctrl+I: Show information/about dialog - -## Keyboard Shortcuts -- Ctrl+T: Open a new tab -- Ctrl+Tab: Switch to the next tab -- Ctrl+Shift+Tab: Switch to the previous tab -- Ctrl+R: Reload the active tab (does not reload the entire app window) -- Ctrl+I: Show information/about dialog -======= ## Building from Source ### Prerequisites @@ -129,7 +77,6 @@ Notes: 4. Install dependencies: `npm install` ### Build Commands ->>>>>>> development #### Windows ```bash @@ -140,273 +87,4 @@ npm run build-installer npm run build-portable # Directory build (unpacked) -npm run build-dir -``` - -#### Linux -```bash -# RPM + AppImage packages -npm run build-linux - -# RPM only for RHEL/Rocky Linux -npm run build-rhel - -# All platforms (Windows + Linux) -npm run build-all -``` - -**Notes:** -- All scripts use `npx electron-builder@latest` (no global installation required) -- Build outputs are written to the `build/` directory -- For detailed Linux build instructions, see [LINUX_BUILD_GUIDE.md](LINUX_BUILD_GUIDE.md) - -## Installation - -### Windows -1. Download and run `Grok-Desktop_Installer-v1.2.5.exe` from the releases page -2. Follow the installation wizard -3. Launch "Grok Desktop" from the Start Menu - -### Linux - -#### RPM Package (Recommended for RHEL/Rocky Linux) -```bash -cd build -sudo dnf install ./Grok-Desktop-v1.2.5.x86_64.rpm -``` - -## Usage Guide - -### Getting Started -1. Launch Grok-Desktop from your applications menu or desktop shortcut -2. Sign in to grok.com using your xAI, Google, or Apple account -3. The application opens with your first Grok tab - -### Interface Overview -- **Tab Bar**: Create and switch between multiple Grok conversations -- **Navigation Controls**: Back/forward buttons, reload, and URL bar -- **Toolbar Buttons**: - - **+**: Create new tab - - **↻**: Reload current tab - - **AOT**: Toggle always-on-top mode - - **s**: Toggle API usage statistics display - - **👁**: Toggle Grok-4-Heavy stats visibility (eye icon) - - **i**: Show about dialog - -### API Usage Monitoring -Grok-Desktop includes real-time monitoring of your Grok API usage limits: - -#### Usage Statistics Display -When enabled (click the **s** button), a status bar appears at the bottom showing: - -- **Low Effort**: `remaining / total` - Basic query limits -- **High Effort**: `remaining / total` - Advanced feature usage -- **Grok 4 Heavy**: `remaining / total` - Specialized model limits -- **Refill**: Time until limits reset (when applicable) - -#### Visual Indicators -- **Green**: Normal usage levels -- **Orange**: Warning (≤25% remaining) -- **Red**: Critical (≤10% remaining) - -#### Features -- **Auto-refresh**: Updates every 5 seconds when active -- **Persistent settings**: Remembers your display preferences -- **Toggle controls**: Hide/show specific statistics as needed -- **Login detection**: Shows "Login required" when not authenticated - -Thanks to Joshua Wang for his code. - -### Keyboard Shortcuts -| Shortcut | Action | -|----------|--------| -| `Ctrl+T` | Open a new tab | -| `Ctrl+Tab` | Switch to next tab | -| `Ctrl+Shift+Tab` | Switch to previous tab | -| `Ctrl+R` | Reload active tab | -| `Ctrl+I` | Show information/about dialog | - -### Always-on-Top Feature -- Click the **AOT** button to toggle always-on-top mode -- The button turns green when active -- On Linux, requires `wmctrl` package (automatically handled) - -## Support & Troubleshooting - -### Getting Help -Need help? Found a bug? Have a feature request? - -[![Submit an Issue](https://img.shields.io/github/issues/AnRkey/Grok-Desktop?style=for-the-badge)](https://github.com/AnRkey/Grok-Desktop/issues/new/choose) - -#### When Reporting Issues -Please include: -1. **Operating System & Version**: e.g., "Rocky Linux 10 with GNOME 49" -2. **Application Version**: Found in Help → About dialog -3. **Steps to Reproduce**: Detailed step-by-step instructions -4. **Expected vs Actual Behavior**: What should happen vs what actually happens -5. **Screenshots**: If applicable, especially for UI issues -6. **Error Messages**: Copy any error messages from console/terminal -7. **System Details**: GPU, display server (X11/Wayland), etc. - -Check [existing issues](https://github.com/AnRkey/Grok-Desktop/issues) first to avoid duplicates. - -### Common Issues & Solutions - -#### Always-on-Top Not Working on Linux -**Symptoms**: AOT button doesn't work on Rocky Linux/GNOME -**Solution**: Ensure `wmctrl` is installed: -```bash -# Rocky Linux/RHEL/Fedora -sudo dnf install wmctrl - -# Ubuntu/Debian -sudo apt install wmctrl -``` - -#### Usage Statistics Not Showing -**Symptoms**: Clicking 's' button doesn't show usage data -**Solution**: -1. Ensure you're logged into Grok.com in the application -2. The feature requires an active Grok session to fetch API limits -3. If you see "Login required", sign in through the Grok tab -4. Statistics should appear automatically once authenticated - -#### Application Won't Start -**Symptoms**: App fails to launch -**Solutions**: -- Check system requirements (4GB RAM minimum) -- Try running from terminal to see error messages -- On Linux, ensure required libraries are installed: - ```bash - sudo dnf install gtk3 libXScrnSaver alsa-lib nss - ``` - -#### Build Fails on Linux -**Symptoms**: `npm run build-linux` fails -**Solutions**: -- Install build dependencies: `sudo dnf groupinstall "Development Tools"` -- For RPM builds on Rocky Linux 10: `sudo dnf install libxcrypt-compat` -- See [LINUX_BUILD_GUIDE.md](LINUX_BUILD_GUIDE.md) for detailed instructions - -### Providing Feedback -Your feedback helps improve Grok-Desktop! Here's how to contribute: - -- **🐛 Bug Reports**: [Submit an issue](https://github.com/AnRkey/Grok-Desktop/issues/new?labels=bug&template=bug_report.md) -- **✨ Feature Requests**: [Submit an enhancement](https://github.com/AnRkey/Grok-Desktop/issues/new?labels=enhancement&template=feature_request.md) -- **💬 General Feedback**: Email anrkey@gmail.com with subject "Grok-Desktop Feedback" -- **📝 Documentation**: Help improve docs by submitting pull requests - -All feedback is reviewed and considered for future updates. Thank you for helping make Grok-Desktop better! - -## Technical Details - -### Architecture -Grok-Desktop is built with: -- **Electron**: Cross-platform desktop app framework -- **Node.js**: Backend runtime -- **Web technologies**: HTML, CSS, JavaScript for the UI -- **Custom tabs**: Multi-tab interface using Electron's webview - -### Security Features -- **Domain validation**: Prevents malicious subdomain attacks -- **OAuth protection**: Secure handling of Google/Apple/xAI authentication -- **External URL validation**: Blocks localhost/private IPs and validates protocols -- **CSP headers**: Content Security Policy for additional protection -- **Sandboxing**: Electron's security features enabled by default - -### API Usage Monitoring System -Real-time tracking of Grok API usage limits via authenticated API calls: - -#### Data Sources -- **Rate Limits API**: Fetches current usage data from `grok.com/rest/rate-limits` -- **Model-Specific Tracking**: Monitors both standard models and Grok-4-Heavy -- **Token-Based Calculations**: Displays remaining queries against total allowances - -#### Technical Implementation -- **Session-Based Authentication**: Uses persistent Grok session cookies for API access -- **Parallel Fetching**: Concurrently retrieves multiple model limits for performance -- **Error Handling**: Graceful fallbacks when API is unavailable or authentication fails -- **Local Storage**: Remembers user preferences for display settings -- **Auto-Refresh**: Background updates every 5 seconds when monitoring is active - -### Always-on-Top (AOT) Implementation -Cross-platform always-on-top functionality with automatic compatibility handling: - -#### Windows Implementation -- Uses Electron's built-in `BrowserWindow.setAlwaysOnTop()` method -- Native Windows API integration - -#### Linux Implementation -- **Primary**: Uses `wmctrl` command-line tool for reliable window management -- **Fallback**: Electron's `setAlwaysOnTop()` method when `wmctrl` unavailable -- **Wayland Compatibility**: Automatically detects Wayland and restarts with X11 forced -- **GNOME Support**: Works with GNOME Shell and Mutter window manager - -#### Linux Dependencies -```bash -# Required for AOT functionality -sudo dnf install wmctrl # Rocky Linux/RHEL/Fedora -sudo apt install wmctrl # Ubuntu/Debian -``` - -### Recent Changes (v1.2.5) -- **Bug Fix**: Fixed Usage Stats "Login required" error on Windows 11 (Issue #11) - - API calls now execute in webview context with proper authentication - - Thanks to Joshua Wang for identifying the root cause -- **Previous (v1.2.4)**: API Usage Monitoring, enhanced security, improved Linux AOT support - -For complete changelog, see [CHANGELOG.md](CHANGELOG.md). - -## Changelog Summary - -### v1.2.5 (Latest) -- 🐛 **Bug Fix**: Fixed Usage Stats "Login required" error on Windows 11 - - Resolved 403 API errors by executing fetch in authenticated webview context - - Thanks to Joshua Wang (@JoshuaWang2211) for the bug report and solution - -### Recent Versions -- **v1.2.4**: API Usage Monitoring, enhanced security, improved Linux AOT -- **v1.2.3**: Reload button, MSI installer, build improvements -- **v1.2.2**: Electron security update (CVE-2025-55305) -- **v1.2.1**: Better dark mode, voice mode support -- **v1.2.0**: WebRTC/audio support, CSP enhancements - -See [CHANGELOG.md](CHANGELOG.md) for complete version history. - -## Contributing - -We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for: -- Development setup instructions -- Coding standards and guidelines -- Pull request process -- Testing requirements - -### Quick Start for Contributors -```bash -git clone https://github.com/AnRkey/Grok-Desktop.git -cd Grok-Desktop -npm install -npm start # Run in development mode -``` - -## License -This project is licensed under the GNU General Public License version 2.0 (GPL-2.0). See the [LICENSE](LICENSE) file for details. - -## Contact & Support -- **Issues & Bug Reports**: [GitHub Issues](https://github.com/AnRkey/Grok-Desktop/issues) -- **Email**: anrkey@gmail.com -- **Releases**: [GitHub Releases](https://github.com/AnRkey/Grok-Desktop/releases) - -## Acknowledgments - -### Third-Party Components -- **Electron**: Cross-platform desktop app framework -- **electron-tabs**: Multi-tab interface library -- **wmctrl**: Linux window management (AOT functionality) - -### Assets & Artwork -The `grok.ico` application icon was sourced online and converted for use in this project. If you are from xAI or Grok.com and have concerns about the use of this artwork, please contact us and we'll address it promptly. - ---- - -**Grok-Desktop v1.2.5** - Making Grok accessible everywhere! 🚀 \ No newline at end of file +npm run build-dir \ No newline at end of file From 9e1479c54c68cab739797a4c4248139cd51dde35 Mon Sep 17 00:00:00 2001 From: jorgepwgestao Date: Wed, 22 Jul 2026 20:04:03 -0300 Subject: [PATCH 3/3] feat: local folder access + agentic capabilities (v1.3.0) - Secure folder selection with native dialog - Read folder structure (depth/file limits + ignore heavy dirs) - Secure clipboard via Electron main process - Read / write / search files (path-safe) - Visual folder indicator in toolbar (UX) - Improved context injection into Grok chat - DevTools button for debugging - Security: path validation, sandbox, contextIsolation --- index.html | 630 ++++++++++++++++++++++++------------------------- src/main.js | 213 ++++++++++++++++- src/preload.js | 17 +- 3 files changed, 535 insertions(+), 325 deletions(-) diff --git a/index.html b/index.html index 8e689bc..5101e93 100644 --- a/index.html +++ b/index.html @@ -38,7 +38,6 @@ } } - /* Also support JS-driven theme toggling via a class */ :root.dark { --color-text: #e6e6e6; --color-bg-toolbar: #1e1f22; @@ -54,7 +53,6 @@ --scrollbar-thumb-hover: #666b73; } - /* Basic styling for the tabs */ body { margin: 0; padding: 0; @@ -68,15 +66,15 @@ height: 50px; background-color: var(--color-bg-toolbar); display: flex; - overflow: hidden; /* keep controls visible; only tabs scroll */ + overflow: hidden; position: relative; } .tabs { display: flex; flex: 1 1 auto; - min-width: 0; /* allow flex child to shrink so it doesn't push controls */ - overflow-x: auto; /* scroll just the tabs area */ + min-width: 0; + overflow-x: auto; overflow-y: hidden; } @@ -91,7 +89,7 @@ height: 40px; cursor: pointer; align-self: center; - display: none; /* hidden by default; shown only when needed */ + display: none; } .tabs-scroll:hover { background-color: var(--color-hover); } .tabs-scroll-left { margin-right: 2px; } @@ -165,24 +163,27 @@ display: flex; align-items: center; padding: 0 5px; - flex: 0 0 auto; /* reserve space; never shrink */ + flex: 0 0 auto; white-space: nowrap; + overflow-x: auto; + max-width: 340px; } .aot-button { background: none; border: 1px solid var(--color-aot-border); border-radius: 4px; - margin-left: 5px; - padding: 2px 6px; + margin-left: 3px; + padding: 1px 5px; font-size: 12px; cursor: pointer; - height: 24px; + height: 22px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; color: var(--color-text); + min-width: 24px; } .aot-button:hover { @@ -199,16 +200,17 @@ background: none; border: 1px solid var(--color-aot-border); border-radius: 4px; - margin-right: 5px; - padding: 2px 6px; - font-size: 16px; + margin-right: 3px; + padding: 1px 5px; + font-size: 14px; cursor: pointer; - height: 24px; + height: 22px; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; color: var(--color-text); + min-width: 24px; } .reload-button:hover { @@ -331,10 +333,54 @@ display: none; } - /* Adjust views container height when status bar is active */ body.usage-bar-active .views-container { height: calc(100vh - 40px - 41px); } + + /* ===== Indicador de Pasta (UX) ===== */ + .folder-indicator { + display: none; + align-items: center; + gap: 6px; + background: #4CAF50; + color: white; + padding: 3px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + max-width: 260px; + margin-right: 8px; + cursor: default; + } + + .folder-indicator.active { + display: flex; + } + + .folder-indicator .folder-icon { + font-size: 14px; + } + + .folder-indicator .folder-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .folder-indicator .folder-close { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 14px; + padding: 0 2px; + opacity: 0.8; + line-height: 1; + } + + .folder-indicator .folder-close:hover { + opacity: 1; + } @@ -343,12 +389,22 @@
- - - - - - + + + +
+ 📁 + Nenhuma pasta + +
+ + + + + + + +
@@ -375,14 +431,12 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/main.js b/src/main.js index 6ad9ea3..f6a6fd7 100644 --- a/src/main.js +++ b/src/main.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, shell, Menu, ipcMain, nativeTheme, session, webContents, dialog } = require('electron'); +const { app, BrowserWindow, shell, Menu, ipcMain, nativeTheme, session, webContents, dialog, clipboard } = require('electron'); const os = require('os'); const fs = require('fs'); const { exec } = require('child_process'); @@ -135,6 +135,9 @@ ipcMain.handle('open-external-url', async (_event, url) => { let mainWindow; let aboutWindow; +// Pasta atualmente selecionada (para segurança) +let currentWorkingFolder = null; + try { app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required'); } catch (_) {} const allowedUrlPatterns = [ @@ -222,6 +225,18 @@ function toggleAlwaysOnTopLinux(mainWindow) { }); } +// ===== Segurança de path ===== +function isPathSafe(filePath) { + if (!currentWorkingFolder || !filePath) return false; + try { + const resolved = path.resolve(filePath); + const base = path.resolve(currentWorkingFolder); + return resolved.startsWith(base + path.sep) || resolved === base; + } catch { + return false; + } +} + function createWindow() { mainWindow = new BrowserWindow({ width: 1200, @@ -498,6 +513,201 @@ function setupIpcHandlers() { return false; } }); + + // ===== FOLDER ACCESS ===== + ipcMain.handle('select-folder', async () => { + try { + const result = await dialog.showOpenDialog(mainWindow, { + properties: ['openDirectory'], + title: 'Selecionar pasta para o Grok' + }); + + if (result.canceled || !result.filePaths || result.filePaths.length === 0) { + return null; + } + + currentWorkingFolder = result.filePaths[0]; + return currentWorkingFolder; + } catch (error) { + console.error('Error selecting folder:', error); + return null; + } + }); + + ipcMain.handle('read-folder-structure', async (_event, folderPath) => { + try { + if (!folderPath || typeof folderPath !== 'string') { + return { error: 'Caminho inválido' }; + } + + currentWorkingFolder = folderPath; + + const MAX_DEPTH = 3; + const MAX_FILES = 150; + + function walk(dir, depth = 0) { + if (depth > MAX_DEPTH) return []; + + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return []; + } + + const result = []; + let count = 0; + + for (const entry of entries) { + if (count >= MAX_FILES) { + result.push({ name: '...', type: 'truncated' }); + break; + } + + if (['node_modules', '.git', '.next', 'dist', 'build', '.cache', '__pycache__', '.vscode', '.idea'].includes(entry.name)) { + continue; + } + + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + result.push({ + name: entry.name, + type: 'directory', + children: walk(fullPath, depth + 1) + }); + } else { + result.push({ + name: entry.name, + type: 'file' + }); + } + count++; + } + + return result; + } + + const structure = walk(folderPath); + + return { + path: folderPath, + structure + }; + } catch (error) { + console.error('Error reading folder:', error); + return { error: error.message }; + } + }); + + // ===== CLIPBOARD ===== + ipcMain.handle('write-clipboard', (_event, text) => { + try { + clipboard.writeText(text); + return true; + } catch (e) { + console.error('Clipboard error:', e); + return false; + } + }); + + // ===== LER ARQUIVO ===== + ipcMain.handle('read-file', async (_event, filePath) => { + try { + if (!isPathSafe(filePath)) { + return { error: 'Acesso negado: arquivo fora da pasta de trabalho' }; + } + + if (!fs.existsSync(filePath)) { + return { error: 'Arquivo não encontrado' }; + } + + const stats = fs.statSync(filePath); + if (stats.size > 500 * 1024) { // limite de 500KB + return { error: 'Arquivo muito grande (máximo 500KB)' }; + } + + const content = fs.readFileSync(filePath, 'utf8'); + return { + path: filePath, + content, + size: stats.size + }; + } catch (error) { + return { error: error.message }; + } + }); + + // ===== ESCREVER ARQUIVO ===== + ipcMain.handle('write-file', async (_event, filePath, content) => { + try { + if (!isPathSafe(filePath)) { + return { error: 'Acesso negado: arquivo fora da pasta de trabalho' }; + } + + // Criar pasta se não existir + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(filePath, content, 'utf8'); + return { success: true, path: filePath }; + } catch (error) { + return { error: error.message }; + } + }); + + // ===== BUSCAR ARQUIVOS ===== + ipcMain.handle('search-files', async (_event, folderPath, query) => { + try { + if (!folderPath || !query) { + return { error: 'Pasta ou termo de busca inválido' }; + } + + const results = []; + const MAX_RESULTS = 50; + const q = query.toLowerCase(); + + function search(dir, depth = 0) { + if (depth > 4 || results.length >= MAX_RESULTS) return; + + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + + for (const entry of entries) { + if (results.length >= MAX_RESULTS) break; + + if (['node_modules', '.git', '.next', 'dist', 'build', '.cache', '__pycache__'].includes(entry.name)) { + continue; + } + + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (entry.name.toLowerCase().includes(q)) { + results.push({ path: fullPath, type: 'directory', name: entry.name }); + } + search(fullPath, depth + 1); + } else { + // Busca no nome do arquivo + if (entry.name.toLowerCase().includes(q)) { + results.push({ path: fullPath, type: 'file', name: entry.name }); + } + } + } + } + + search(folderPath); + return { results, query }; + } catch (error) { + return { error: error.message }; + } + }); } function setupContextMenus() { @@ -576,7 +786,6 @@ function setupContextMenus() { }); } -// ===== SECURE PERMISSIONS (origin-aware) ===== function setupPermissions() { const isAllowedOrigin = (url) => { if (!url || typeof url !== 'string') return false; diff --git a/src/preload.js b/src/preload.js index 3401115..0888c97 100644 --- a/src/preload.js +++ b/src/preload.js @@ -45,15 +45,20 @@ contextBridge.exposeInMainWorld('api', { }, getVersion: () => ipcRenderer.invoke('get-app-version'), - openExternal: (url) => ipcRenderer.invoke('open-external-url', url), - toggleAlwaysOnTop: () => ipcRenderer.invoke('toggle-always-on-top'), - showAppInfo: () => ipcRenderer.invoke('show-app-info'), - fetchGrokRateLimits: () => ipcRenderer.invoke('fetch-grok-rate-limits'), - forceLightColorScheme: (wcId, shouldForceLight) => - ipcRenderer.invoke('force-light-color-scheme', wcId, shouldForceLight) + ipcRenderer.invoke('force-light-color-scheme', wcId, shouldForceLight), + + // ===== Pasta e arquivos ===== + selectFolder: () => ipcRenderer.invoke('select-folder'), + readFolderStructure: (folderPath) => ipcRenderer.invoke('read-folder-structure', folderPath), + writeClipboard: (text) => ipcRenderer.invoke('write-clipboard', text), + + // Novas funções + readFile: (filePath) => ipcRenderer.invoke('read-file', filePath), + writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content), + searchFiles: (folderPath, query) => ipcRenderer.invoke('search-files', folderPath, query) }); \ No newline at end of file