1. Design posture
No remote code
No eval, no new Function, no dynamically loaded or remotely hosted scripts. All code ships inside the signed package.
socket.io-client bundled locally
The Deal Feed compiles socket.io-client into the bundle at build time — not fetched from a CDN at runtime.
MV3 isolation
Manifest V3 service worker with isolated content scripts. No persistent background page; privileged work delegated to short-lived offscreen/hidden contexts.
Local-first data
All config, credentials, and history in chrome.storage.local — no sync, no account. Never uploaded. The only first-party contact is the opt-in, receive-only Deal Feed (§4).
Strict Content Security Policy (extension_pages)
script-src 'self'; object-src 'self'; img-src 'self' data: https://api.qrserver.com https://tg-forwarded-deals.deals.group; connect-src 'self' https: wss:; frame-src https: http:; style-src 'self' 'unsafe-inline'
script-src 'self' means the browser refuses to execute any inline or remote script in extension pages. No eval/unsafe-inline for scripts. Zero runtime npm dependencies in the shipped bundle other than the locally-bundled socket.io-client.
2. Threat model
| Asset | Threat | Mitigation |
|---|---|---|
| User credentials (Telegram tokens, shortener keys, webhook secrets) | Exfiltration via export/share, analytics, or a malicious page | Export/share secret redaction; analytics allowlist + value scrubbing; cookie allowlist |
| Internal/localhost/cloud-metadata services | SSRF via user-supplied endpoints | isPublicHttpUrl() guard on every endpoint and redirect hop |
| The extension origin / rendered UI | XSS via crafted link text, store responses, or Deal Feed messages | Strict CSP; client-side re-sanitization to an allow-listed tag set; dangerous-scheme blocking |
| Third-party sites you browse | The mobile-preview DNR rule stripping their framing/CSP during normal browsing | DNR rule scoped via initiatorDomains; registered only while preview is open |
| Stored configuration | Poisoning via a malicious import file | Per-key shape validation + SSRF check + atomic write with rollback |
| The runtime message channel | Spoofed messages from web pages into the service worker | Message-type validation + sender.id check + payload size caps |
| Third-party endpoints / local network | Self-inflicted flood from a many-link conversion | Outbound concurrency gate (cap 6) + 429/503 backoff |
| Users | IDN homograph (phishing lookalike) links getting tagged | isSuspiciousIdnHost() rejection |
| User cookies/session | Leaking cookies while following redirects or loading feed images | credentials:'omit' on redirect & feed-image fetches; cookie allowlist for shorteners |
3. Concrete mitigations (in code)
SSRF guard — isPublicHttpUrl()
src/utils/url.ts. Rejects any URL that is not routable-public HTTP(S): loopback, link-local, cloud-metadata, RFC1918 private ranges, IPv6 ULA, localhost/.local/.internal, and non-HTTP(S) schemes. Decodes IPv4 literal forms (dotted, hex, octal, inet_aton short) and expands IPv6. Applied to every user-supplied endpoint and every redirect hop.
Cookie allowlist for shorteners
Cookies sent only to a hardcoded affiliate-shortener host allowlist (the Amazon family, amzn.to, flipkart.com, fkrt.it, fkrt.cc, dl.flipkart.com). Every other request uses credentials:'omit'. See src/services/ShortenerService.ts.
Credentialless redirect & feed-image fetches
The redirect resolver issues all fetches with credentials:'omit', so following a link never attaches your cookies to arbitrary hosts. Deal Feed images are likewise fetched with credentials:'omit'.
IDN homograph rejection — isSuspiciousIdnHost()
Flags hosts whose labels punycode to xn--… (e.g. Cyrillic-а flipkаrt.com). Cleaning rules and affiliate templates are not applied to such hosts.
Dangerous-scheme blocking
Anything rendered into an href is constrained to HTTP(S); javascript:, data:, vbscript:, file:, chrome: are blocked from becoming clickable links.
Secret redaction in export/share
exportAll() redacts credential-shaped fields by default unless you choose Include credentials. Single-item community shares always strip secrets. Error messages are scrubbed.
Runtime message validation + sender check + size caps
Inbound messages are validated against known types; a sender.id !== chrome.runtime.id guard rejects messages not from this extension; payload size caps bound accepted data.
Import shape validation + rollback
importAll() performs per-key shape validation, re-runs the SSRF guard on webhook/shortener URLs, and writes inside an atomic try/catch with snapshot rollback. Storage is never clear()-ed first.
Mobile-preview DNR rule scoped to own frames
The single dynamic rule pins initiatorDomains to the extension id and scopes to sub_frame, so it applies only to requests initiated by the extension's own pages. Registered only when you open the preview and removed afterward.
webRequest is read-only
Reads the Location header on cross-origin 3xx redirects so the tracer/resolver can follow the chain. It never modifies traffic; listeners added/removed per hop.
Outbound-fetch concurrency gate
src/utils/netGate.ts. Caps in-flight gated requests at 6 and applies exponential backoff with jitter on 429/503, honoring a numeric Retry-After. All redirect/shortener fetches go through gatedFetch.
4. Deal Feed security
The Deal Feed is the extension's one first-party network feature, designed to be safe by construction:
Opt-in
Nothing connects automatically. You must click Connect (a confirmation prompt). No account, no sign-in.
Receive-only
A Socket.io WSS connection to receive a stream of deals. The client never sends user data beyond a standard WebSocket; the server reports an anonymous online-client count.
No browser cookies
The WebSocket sends no cookies; feed images fetched with credentials:'omit'.
Re-sanitized before render
Every incoming message is re-sanitized into an allow-listed tag subset (sanitizeEditorHtml) before assignment to the DOM. Plain text is HTML-escaped. XSS-safe.
Locally bundled client
socket.io-client compiled into the bundle (no remote script load).
Auto-disconnect
Auto-disconnects after 5 minutes of inactivity; manual Disconnect anytime. Live-only with no stored backlog.
5. Permissions
The extension requests exactly: storage, tabs, scripting, contextMenus, clipboardWrite, notifications, alarms, webNavigation, webRequest, sidePanel, offscreen, declarativeNetRequest, plus host_permissions: <all_urls>. There is no activeTab. The content script over <all_urls> does not read or exfiltrate page content; its optional auto-detect is off by default.
6. Analytics
Delivered to GA4 (www.google-analytics.com/g/collect) only while enabled, with non_personalized_ads:true on every payload. The setting is presented as a pre-enabled toggle in the first-run wizard, so completing onboarding without changing it resolves to on — the Chrome data disclosure marks user-activity analytics as Yes (event counts only). Turn it off any time in Settings → Behavior.
It sends only an allowlisted set of non-personal params plus a random client id and per-session id; a denylist regex drops any URL/email/20+ char token; strings capped at 40 chars. It never sends URLs, domains, tags, tokens, secrets, message bodies, page content, config, user_id, or user_properties — by construction. Events batched (≤25/request) and rate-limited (30/min).
7. Data storage and limits
Data lives in chrome.storage.local only (quota ~10 MB). chrome.storage.session holds only transient values. No chrome.storage.sync, no unlimitedStorage.
Credentials are stored locally only and never uploaded to any DealsGroup server.
History is capped (default 500, configurable 100–5000), oldest pruned. The redirect cache is TTL-bounded (default 24h) with a hard cap of 1000 entries.
8. Responsible disclosure
If you find a security issue, please report it privately before public disclosure. Email [email protected] with the subject line SECURITY: DealsGroup Affiliate Engine.
Please include: the extension version (currently 1.0.12), your Chrome version and OS, a description and reproduction steps, expected vs. actual behavior, and any proof-of-concept. Do not include real credentials. We aim to acknowledge reports promptly and will coordinate a fix and disclosure timeline with you.
Report a vulnerability9. Verifying these claims yourself
Inspect the cited source files — every mitigation maps to a concrete file.
Confirm the manifest permissions and CSP in src/manifest.json.
Review the SSRF test coverage in test/url.ssrf.test.ts.
Project home and contact: deals.group.