Is Private Grammar Checker - Harper safe?
We analyzed Private Grammar Checker - Harper version 2.11.0's actual code and gave it a 65/100 trust score. Harper appears purpose-aligned, but its broad…
Bottom line
Harper appears purpose-aligned, but its broad page reach and unauthenticated Google Docs replacement bridge create meaningful security concerns.
What it can access
It can inspect editable writing areas across websites and Google Docs, which fits grammar checking but is broader than necessary. [Q1][Q2][Q3][Q8]
Security weaknesses
Google Docs pages can trigger text replacement through predictable events without caller or user-gesture checks, allowing unwanted document changes. [Q16][Q21]
Signs of misuse
No evidence shows ordinary writing is sent outside the browser; vendor submissions are user-initiated and no browsing tracking was found. [Q6][Q12][Q14]
Code findings
What access does this extension ask for, and does that access make sense for what it claims to do?
It can run an isolated page access in every frame on every URL and has tabs and storage permissions. A separate main-world script runs only in Google Docs.
Declares an all-URLs, all-frames content script and a Google Docs-only main-world script. — manifest.json:22-45
Requests storage, tabs, and writewithharper.com host access. — manifest.json:78-87
Defines and locally enables a finite set of writing-heavy domains by default. — assets/chunk-CaF1VrzQ.js:74-158
Which websites can it run on, and can it read or change what appears on those pages?
On enabled sites it reads text from writing fields and Google Docs so it can show grammar suggestions. It targets editable writing surfaces rather than arbitrary page text.
Selects textareas, spellchecked text inputs, and supported contenteditable editors as lint targets. — assets/chunk-D2kYtfWB.js:1332-1415
Reads Google Docs annotated document text into a hidden bridge node. — google-docs-bridge.js:146-195
Checks the requesting domain against local per-site settings before linting. — assets/chunk-CaF1VrzQ.js:266-290,597-624
Can it see sensitive information you type, such as passwords, payment details, messages, or form entries?
Writing fields can contain private emails, chats, documents, or work material. Harper reads those fields on enabled sites, but the normal lint path keeps their text inside the extension and packaged local engine.
Passes selected editor text to the extension lint client and supports error examples with limited surrounding context. — assets/chunk-D2kYtfWB.js:1275-1301,1332-1415
Sends text through chrome.runtime messaging to the extension service worker, not to a web endpoint. — assets/chunk-D6eCUBw7.js:1469-1489
Processes the text in the local linter and returns lints. — assets/chunk-CaF1VrzQ.js:231-253
Can it access private browser data such as cookies, signed-in sessions, browsing history, bookmarks, or downloads?
It stores local domain choices, lint settings, dictionary words, ignored lints, optional rule packs, install date, and popup state. It uses tabs metadata to manage its popup and domain checks, but does not collect cookies, history, bookmarks, or saved passwords.
Reads and writes the described extension-local settings and rule data. — assets/chunk-CaF1VrzQ.js:40-63,64-73,341-344,495-703
Requests storage and tabs but not cookies, history, bookmarks, or password-related access. — manifest.json:78-87
Can it access anything else on your device, such as files, location, camera, microphone, clipboard, or installed applications?
No evidence of this behavior was found in the reviewed package.
Declares none of the device, clipboard, downloads, or native-integration permissions. — manifest.json:78-87
Provides an explicit .weirpack file picker and reads only files selected there. — assets/chunk-Ch2LeZQA.js:934-944,1163-1181
Does it send information outside your browser? If so, what information is sent, where does it go, and why?
Normal drafts and documents are not sent to a server. Install/uninstall pages and a popup version check contact writewithharper.com, while domain reviews and problematic-lint examples are uploaded only after the user submits forms that disclose the transfer.
Opens fixed install/uninstall URLs and provides the vendor-only form POST helper. — assets/chunk-CaF1VrzQ.js:27-35,423-438
Discloses and submits user-entered review/report fields and fetches only a latest-version string. — assets/chunk-B7szcRjt.js:3779-3797,4108-4125,4224-4233
Processes ordinary lint text with the packaged local linter. — assets/chunk-CaF1VrzQ.js:231-253,549-563
Does it download or run code, settings, or instructions supplied by a remote server?
The popup downloads a version string from the vendor to show an update warning. It does not download remote lint rules, selectors, scripts, or operational configuration; optional Weirpacks come only from files the user selects.
Fetches /latestversion and uses it only to compare versions and offer a fixed help page. — assets/chunk-B7szcRjt.js:4221-4254
Loads optional rule packs from a local settings-page file picker. — assets/chunk-Ch2LeZQA.js:934-944,1163-1181
Can it take actions without a clear user request, such as opening pages, redirecting searches, downloading files, or sending requests?
It automatically scans and watches eligible writing fields on enabled sites, sending their current text to the local service worker as the user types. A finite domain list is enabled by default; other sites require user opt-in unless the user enables the global default.
Continuously scans, observes page mutations, and registers eligible editor targets. — assets/chunk-D2kYtfWB.js:1303-1308,1332-1431
Defines default-enabled domains and local per-domain/global controls. — assets/chunk-CaF1VrzQ.js:74-158,597-624
Exposes an Enable on New Sites by Default option and warns that it can affect apps. — assets/chunk-Ch2LeZQA.js:934-935
Can it change important browser settings, including your proxy, search engine, homepage, privacy settings, or other extensions?
No evidence of this behavior was found in the reviewed package.
Requests none of the browser-setting, proxy, management, or policy permissions. — manifest.json:78-87
Is any important behavior difficult to inspect because the code is hidden, heavily obfuscated, dynamically generated, or supplied by third parties?
The package includes a roughly 15 MB compiled WebAssembly grammar engine and generated browser bundles. That limits manual readability, but the permission, editor, messaging, network, and Google Docs bridge code remains readable; concealment was not demonstrated.
Lists the large packaged WebAssembly module and generated JavaScript chunks. — inventory.json:1-145
Keeps background message, local linter, network, and storage behavior readable. — assets/chunk-CaF1VrzQ.js:159-703
Provides readable Google Docs access and replacement logic. — google-docs-bridge.js:1-786
Does it use eval(), new Function(), or string-based timers to turn text into executable code?
No evidence of this behavior was found in the reviewed package.
Contains the fixed Lodash global-object lookup Function("return this")(). — assets/chunk-Ch2LeZQA.js:130
Allows packaged scripts and WebAssembly, but not unsafe-eval; connections are restricted to self and writewithharper.com. — manifest.json:82-87
Does it track the websites, pages, searches, or links you visit?
No evidence of this behavior was found in the reviewed package.
Uses activation/update events and sender.tab.url for local popup cleanup and parent-domain enablement. — assets/chunk-CaF1VrzQ.js:51-63,266-290,579-624
Lint messages include the page domain and text for local processing. — assets/chunk-D6eCUBw7.js:1481-1489
The only POST helper is used by explicit fixed vendor feedback forms. — assets/chunk-CaF1VrzQ.js:423-438
Does it create a persistent ID or browser fingerprint to recognize you?
No evidence of this behavior was found in the reviewed package.
Stores installedOn and reviewed locally without a network submission. — assets/chunk-CaF1VrzQ.js:643-660
The vendor POST helper sends only form fields supplied by its caller. — assets/chunk-CaF1VrzQ.js:423-438
Does it send cookies, session details, authentication data, or page contents elsewhere?
No evidence of this behavior was found in the reviewed package.
Does not request cookies or authentication-related permissions. — manifest.json:78-87
Targets textareas, spellchecked text inputs, and contenteditable editors, not password inputs. — assets/chunk-D2kYtfWB.js:1332-1415
Does it record typing, form entries, clipboard contents, or screenshots?
It continuously reads writing-field content on enabled sites to provide grammar feedback. This is not raw keylogging: it observes current editor text, excludes many disabled/read-only/opt-out fields, keeps linting local, and does not capture screenshots or the clipboard.
Scans eligible editors and watches DOM/focus changes to keep targets current. — assets/chunk-D2kYtfWB.js:1303-1431
Excludes invisible, disabled, read-only, and several opt-out editor surfaces. — assets/chunk-D2kYtfWB.js:1309-1329,1361-1413
Runs received text through packaged WebAssembly locally. — assets/chunk-CaF1VrzQ.js:231-253,549-563
Can a website or remote server send commands that make the extension take actions?
A script running in Google Docs can dispatch Harper's public custom request or legacy replace event and trigger document-text replacement. The bridge validates message shape but not sender or user gesture.
Listens for document CustomEvents and validates only protocol, request ID, and request kind. — google-docs-bridge-request-handler.js:43-88,124-140
Handles replaceText and a legacy replacement event without origin, token, or user-gesture validation. — google-docs-bridge.js:647-728,730-756
Loads the main-world bridge only on Google Docs document pages. — manifest.json:23-33
Does it alter links, referral codes, advertisements, shopping carts, or search results?
No evidence of this behavior was found in the reviewed package.
Applies accepted text replacements through input/beforeinput events. — assets/chunk-3E6bXvt6.js:654-739
Selects a requested document range and inserts replacement text through insertText or a plain-text paste event. — google-docs-bridge.js:647-728
Exposes the legacy replacement event directly to scripts on the Docs page. — google-docs-bridge.js:753-756
Does it redirect or modify web traffic, request headers, downloads, or proxy settings?
It contacts writewithharper.com for install/uninstall pages, version checking, and user-submitted feedback. It does not intercept page traffic, rewrite headers, redirect requests, control the proxy, or manage downloads.
Uses fixed vendor install/uninstall URLs and a feedback POST helper. — assets/chunk-CaF1VrzQ.js:27-35,423-438
Calls the helper only for submitted feedback and performs a simple version GET. — assets/chunk-B7szcRjt.js:3779-3797,4108-4125,4224-4233
Has no webRequest, declarativeNetRequest, proxy, or downloads permission and restricts extension-page connections to the vendor. — manifest.json:78-87
Can it communicate with software on the computer or services running on localhost?
No evidence of this behavior was found in the reviewed package.
Declares no nativeMessaging permission and restricts extension network access to self and writewithharper.com. — manifest.json:78-87
Loads the grammar engine from the packaged extension WebAssembly URL. — assets/chunk-CaF1VrzQ.js:549-563
Does it decode, decrypt, or unpack hidden data and then execute or inject it?
No evidence of this behavior was found in the reviewed package.
Dynamically imports a fixed chrome.runtime.getURL packaged chunk. — assets/index.ts-loader-CH77K5yT.js:1-13
Validates, stores, and locally loads user-selected Weirpacks and packaged WebAssembly. — assets/chunk-CaF1VrzQ.js:461-485,549-563,692-703
Permits packaged WebAssembly and restricts extension connections to self and the vendor. — manifest.json:82-87
Can ordinary websites access privileged extension features through messaging or exposed files?
The Google Docs page bridge is reachable through predictable document CustomEvent names and does not authenticate the caller. A page script can request geometry or replacement operations, but cannot pass arbitrary commands into the extension service worker or access other sites.
Defines predictable event names and accepts structurally valid getRects or replaceText requests from the document. — google-docs-bridge-request-handler.js:11-14,43-88,124-140
Implements the two exposed operations with bounds checks and text-realignment safeguards. — google-docs-bridge.js:585-645,647-728
Confines the main-world bridge to Google Docs documents. — manifest.json:23-33
Does suspicious behavior activate only for certain users, websites, dates, or remote settings?
No evidence of this behavior was found in the reviewed package.
Stores all lint, domain, dictionary, and Weirpack state locally. — assets/chunk-CaF1VrzQ.js:495-703
Uses the remote latest-version response only for a version mismatch indicator and fixed help link. — assets/chunk-B7szcRjt.js:4221-4254
Does it perform hidden resource-intensive work, such as cryptocurrency mining?
No evidence of this behavior was found in the reviewed package.
Ties WebAssembly work to local lint requests. — assets/chunk-CaF1VrzQ.js:231-253,549-563
Ties recurring page work to editor scanning and Google Docs synchronization. — assets/chunk-D2kYtfWB.js:1303-1431
Does it change form fields or where a form sends its information?
It reads eligible writing inputs and can replace their contents when a suggestion is applied. It excludes password inputs and does not change form actions or submit forms.
Targets textareas, spellchecked text inputs, and supported contenteditable editors while excluding disabled/read-only surfaces. — assets/chunk-D2kYtfWB.js:1332-1415
Applies chosen replacement text through input events rather than changing a form destination. — assets/chunk-3E6bXvt6.js:654-739
Provides the Google Docs document replacement path. — google-docs-bridge.js:647-728