Is QR Code Generator safe?
QR Code Generator is medium risk. Opening the QR scanner's web-camera option calls the camera API from the current page. The manifest injects this script into every page and frame, but declared permissions list only context menus, not camera access.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Camera Access Requested From Any Webpage
Opening the QR scanner's web-camera option calls the camera API from the current page.
The manifest injects this script into every page and frame, but declared permissions list only context menus, not camera access.
You open the QR scanner overlay on a website and choose the web-camera scanner.
The camera scanner is available from the injected overlay, including a button labeled "Start Scanning with Web Camera".
The page-injected script asks the browser for video access.
The verified source path calls the camera request from `/static/content.js`, which the manifest loads on all URLs.
| Field | Value | Why it matters | |
|---|---|---|---|
Where the scanner script runs | matches: ["<all_urls>"], all_frames: true | The scanner interface is allowed to run on any website you visit, not only on an extension page. | |
When it is injected | run_at: "document_start" | The scanner script is added early as each page loads, before you use the QR scanner overlay. | |
Manifest permissions | permissions: ["contextMenus"] | The manifest evidence shows only a context-menu permission, so camera access is not visible as a declared extension permission there. | |
User-facing camera control | button.rb-qrsg-scan-with-camera-btn: "Start Scanning with Web Camera" | The camera request is tied to the QR scanner's web-camera option that you can select in the overlay. | |
Camera request | getUserMedia({ audio: false, video: true }) | After that choice, the script asks for video access so the QR scanner can read from the camera. |
The content-script webcam path and page-wide manifest scope
"host_permissions": [
"*://*/*",
"<all_urls>"
],
"permissions": [
"contextMenus"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": [
"/static/content.js"
],
"run_at": "document_start",
"all_frames": true
}
]this.checkAndScanWithWebCam = () => {
navigator.mediaDevices.getUserMedia({
audio: !1,
video: !0
}).then(t => {
this.scanWithWebCam()
}).catch(t => {
"NotAllowedError" == t.name && k("Please allow or turn on your web camera")
})
}, this.scanWithWebCam = () => {
let t = f("#rb-qrsg-video");
C(t), w(f(u)), w(f(_)), f(g).textContent = "Scanning...";
let r = null;
this.scanner.scanWithWebCam(t, a => {
if (a && (C(f(".rb-qrsg-web-scanned-data-panel")), C(f(u)), !r)) {
let i = f(u);
i.width = t.width, i.height = t.height;
let s = ((t, r = 1) => {
let a = document.createElement("canvas");
return a.id = "screenshot_canvas", a.width = t.clientWidth * r, a.height = t.clientHeight * r, a.getContext("2d").drawImage(t, 0, 0, a.width, a.height), a.toDataURL()
})(t);
return i.src = s, w(t), f(g).textContent = a, this.scanner.webCamScanner.pause(), C(f(_)), void(r = !0)
}
}, t => {
console.log("Failed to Scan via Webcamera:", t)
})
}this.listenForRouteChange = t => {
let r = t.detail?.view;
({
"rb-qrsg-webcamera-scanner": this.checkAndScanWithWebCam
})[r]?.()
}, this.destroyWebCamScanning = t => {
this.scanner.webCamScanner && this.scanner.destroyWebCamScan()
}, this.handleViewUnmounted = t => {
let r = t.detail?.view;
({
"rb-qrsg-webcamera-scanner": this.destroyWebCamScanning
})[r]?.(r)
}
this.initialise = () => {
this.initialised || (y("click", ".rb-qrsg-navigation-item>a", this.handleRoute), y("click", ".rb-qrsg-cancel-btn", this.closeOverlay), y("click", ".rb-qrsg-upload-btn", this.pickFile), y("change", h, this.scanFromFile), y("click", _, this.checkAndScanWithWebCam), y("click", ".rb-qrsg-copier", this.copyText), y("click", ".rb-qrsg-reset-btn", this.resetForm), y("click", m, this.downloadGeneratedQRCode), y("keyup", p, this.generateNewQRCode), window.addEventListener(i, this.listenForRouteChange), window.addEventListener(s, this.handleViewUnmounted), this.initialised = !0)
}<button class="rb-qrsg-button-priary rb-qrsg-scan-with-camera-btn" type="button">Start Scanning with Web
Camera</button>Checks an unpacked copy of this extension for the all-pages content script and camera request path shown above.
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const root = process.argv[2];
if (!root) {
console.error('Usage: node qr-camera-path-check.js /path/to/unpacked-extension');
process.exit(2);
}
const manifestPath = path.join(root, 'manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
const contentScripts = manifest.content_scripts || [];
const allUrlScript = contentScripts.find((entry) =>
(entry.matches || []).includes('<all_urls>') &&
(entry.js || []).some((script) => script.endsWith('content.js'))
);
const declaredPermissions = manifest.permissions || [];
const contentPath = path.join(root, 'static', 'content.js');
const content = fs.readFileSync(contentPath, 'utf8');
const findings = {
contentScriptAllUrls: Boolean(allUrlScript),
allFrames: Boolean(allUrlScript && allUrlScript.all_frames),
runAt: allUrlScript && allUrlScript.run_at,
declaredPermissions,
declaresCameraPermission: declaredPermissions.includes('camera'),
hasCameraButton: content.includes('rb-qrsg-scan-with-camera-btn'),
hasWebcamRoute: content.includes('rb-qrsg-webcamera-scanner'),
callsGetUserMediaVideo: /navigator\.mediaDevices\.getUserMedia\(\{\s*audio:!?[!a-zA-Z0-9]+,\s*video:!?[!a-zA-Z0-9]+\}/.test(content) || content.includes('navigator.mediaDevices.getUserMedia({audio:!1,video:!0}')
};
console.log(JSON.stringify(findings, null, 2));
if (findings.contentScriptAllUrls && findings.hasCameraButton && findings.hasWebcamRoute && findings.callsGetUserMediaVideo && !findings.declaresCameraPermission) {
process.exit(1);
}
- 1node qr-camera-path-check.js /path/to/unpacked-extension
What it can do
Permissions this extension asks for, as declared in version 1.0.5. 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 1.0.9, which we have not unpacked yet.
Read and change your data on every site you visit
*://*/* and 1 more
Add items to the right-click menu
contextMenus