Is Copy Text Easily safe?
Copy Text Easily is medium risk. When Copy Text Easily's paid clipboard-history mode is active with system-wide scope, the offscreen document polls the system clipboard every 700ms. New text is sent to the save-history handler, so text copied in other apps can be recorded.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
System Clipboard Polled Every 700 ms
When Copy Text Easily's paid clipboard-history mode is active with system-wide scope, the offscreen document polls the system clipboard every 700ms.
New text is sent to the save-history handler, so text copied in other apps can be recorded.
You enable the paid clipboard-history mode with system-wide clipboard scope.
The gate checks the paid flag, save-all-copies setting, and the system-wide scope value.
The extension repeatedly reads the system clipboard and forwards new text to its history handler.
The polling loop runs every 700 ms and ignores duplicate recent browser-copy values.
While the gated mode is active, the offscreen document performs an immediate clipboard check and then repeats the check every 700 ms.
| Field | Value | Why it matters | |
|---|---|---|---|
Paid-mode flag | a: true (illustrative) | The loop only runs after the extension sees the paid-mode flag as active. | |
System-wide setting | saveAllCopies: true, listenScope: system-wide | The loop requires a setting that tells the extension to save all clipboard copies, not only browser-originated copies. | |
Clipboard text | QuarterlyPayroll.xlsx password: S3cure-2026 (illustrative) | The value read from your system clipboard can include text copied from another application. | |
History message | action: saveHistory, text: copied value | New clipboard text is forwarded to the background handler that writes the local history entry. |
The offscreen document gates and polls system clipboard text
function u() {
return i(this, void 0, void 0, function*() {
const e = yield function(e) {
return i(this, void 0, void 0, function*() {
try {
if (chrome.storage && chrome.storage.sync) return yield new Promise(t => chrome.storage.sync.get(e, t))
} catch (e) {}
return new Promise(t => {
chrome.runtime.sendMessage({
action: "storage:getSync",
keys: e
}, e => {
t((null == e ? void 0 : e.data) || {})
})
})
})
}(["a", "historySettings"]), t = Boolean(e.a), o = e.historySettings || {
saveAllCopies: !1,
listenScope: "respect-extension"
};
return t && o.saveAllCopies && "system-wide" === o.listenScope
})
}
function v() {
return i(this, void 0, void 0, function*() {
if (r) return;
if (r = !0, !(yield u())) return void(r = !1);
const e = () => i(this, void 0, void 0, function*() {
try {
if (!(yield u())) return;
const e = yield function() {
var e;
return i(this, void 0, void 0, function*() {
try {
const {
cteSuppressUntil: t
} = yield d(["cteSuppressUntil"]);
if ("number" == typeof t && Date.now() < t) return "";
if (null === (e = navigator.clipboard) || void 0 === e ? void 0 : e.readText) {
const e = yield navigator.clipboard.readText();
if (null == e ? void 0 : e.trim()) return e.trim()
}
} catch (e) {}
return new Promise(e => {
try {
const t = document.createElement("textarea");
if (t.style.cssText = "position:fixed;left:-9999px;top:0;opacity:0", document.body.appendChild(t), t.focus(), document.execCommand("paste")) {
const o = t.value.trim();
t.remove(), e(o)
} else t.remove(), e("")
} catch (t) {
e("")
}
})
})
}();
e && e !== s && (s = e, function(e) {
i(this, void 0, void 0, function*() {
e === c && Date.now() - l < 2e3 || chrome.runtime.sendMessage({
action: "saveHistory",
text: e
})
})
}(e))
} catch (e) {}
});
yield e(), a = setInterval(e, 700)
})
}e && "saveHistory" === e.action && "string" == typeof e.text && function(e) {
const t = e.trim();
if (!t) return;
const o = Date.now();
chrome.storage.local.get(["history", "lastBrowserCopyText", "lastBrowserCopyTs"], e => {
let n = Array.isArray(e.history) ? e.history : [];
if (n.length && n[0].text === t && o - n[0].timestamp < 2e3) return;
if (t === e.lastBrowserCopyText && o - (e.lastBrowserCopyTs || 0) < 2e3) return;
n = n.filter(e => !(e.text === t && (!e.site || "" === e.site)));
const s = `${o}-${Math.random().toString(36).slice(2,8)}`;
n.unshift({
id: s,
text: t,
timestamp: o,
site: "",
isFavorite: !1,
source: "system"
}), n.length > 100 && (n = n.slice(0, 100)), chrome.storage.local.set({
history: n
})
})
}(e.text)What it can do
Permissions this extension asks for, as declared in version 2.7.1. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 2.7.2, which we have not unpacked yet.
Read and change your data on every site you visit
<all_urls>
Store data in your browser
storage
See the address and title of every tab you have open
tabs
Store an unlimited amount of data in your browser
unlimitedStorage
Run hidden pages in the background
offscreen
Read whatever you have copied to your clipboard
clipboardRead
Add items to the right-click menu
contextMenus