From 46aea254d06622534a2275a4ca0d39d400ed62d1 Mon Sep 17 00:00:00 2001 From: imnyang Date: Thu, 5 Mar 2026 03:06:24 +0900 Subject: [PATCH] first commit --- .gitignore | 1 + README.md | 57 +++++++++++++++++++ background.firefox.js | 29 ++++++++++ build-firefox.sh | 25 +++++++++ content.js | 6 ++ dist/figma-windows-ua-firefox-unsigned.xpi | Bin 0 -> 2218 bytes dist/firefox/.amo-upload-uuid | 1 + dist/firefox/background.firefox.js | 29 ++++++++++ dist/firefox/content.js | 6 ++ dist/firefox/injected.js | 62 +++++++++++++++++++++ dist/firefox/manifest.json | 38 +++++++++++++ dist/signed/d168a70cb6a24fe4a478-1.0.0.xpi | Bin 0 -> 9777 bytes flake.lock | 61 ++++++++++++++++++++ flake.nix | 32 +++++++++++ injected.js | 62 +++++++++++++++++++++ manifest.firefox.json | 38 +++++++++++++ manifest.json | 45 +++++++++++++++ rules.json | 30 ++++++++++ sign-firefox.sh | 47 ++++++++++++++++ 19 files changed, 569 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 background.firefox.js create mode 100755 build-firefox.sh create mode 100644 content.js create mode 100644 dist/figma-windows-ua-firefox-unsigned.xpi create mode 100644 dist/firefox/.amo-upload-uuid create mode 100644 dist/firefox/background.firefox.js create mode 100644 dist/firefox/content.js create mode 100644 dist/firefox/injected.js create mode 100644 dist/firefox/manifest.json create mode 100644 dist/signed/d168a70cb6a24fe4a478-1.0.0.xpi create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 injected.js create mode 100644 manifest.firefox.json create mode 100644 manifest.json create mode 100644 rules.json create mode 100755 sign-firefox.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f2fc7a --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Figma Windows UA Spoofer (Chrome + Firefox) + +`figma.com` / `*.figma.com`에서만 User-Agent를 Windows로 바꾸고, +페이지 JS의 `navigator.userAgent`, `navigator.platform`도 Windows 값으로 오버라이드합니다. + +## 빠른 사용 + +### Chrome / Edge + +1. `chrome://extensions` (Edge: `edge://extensions`) 접속 +2. 개발자 모드 ON +3. 압축해제된 확장 프로그램 로드 +4. 프로젝트 폴더 선택 + +### Firefox unsigned 빌드 + +```bash +./build-firefox.sh +``` + +- 출력: `dist/figma-windows-ua-firefox-unsigned.xpi` +- 개발/테스트용 unsigned 패키지 + +### Firefox 서명 빌드 (실사용) + +1. AMO(Add-ons Mozilla)에서 API Key/Secret 발급 +2. `.env`에 값 저장 (자동 로드) + +```dotenv +AMO_JWT_ISSUER="" +AMO_JWT_SECRET="" +``` + +3. Nix로 서명 실행 (추천) + +```bash +nix run .#sign-firefox +``` + +- 출력: `dist/signed/*.xpi` (서명 완료) + +## Firefox 설치 + +- 임시 로드: `about:debugging#/runtime/this-firefox` → 임시 부가 기능 로드 → `dist/firefox/manifest.json` +- 실제 배포/설치: `dist/signed/*.xpi` 사용 + +## 확인 + +`figma.com`에서 콘솔 실행: + +```js +navigator.userAgent +navigator.platform +navigator.userAgentData?.platform +``` + +Windows 계열 값이면 정상입니다. diff --git a/background.firefox.js b/background.firefox.js new file mode 100644 index 0000000..5a31bf3 --- /dev/null +++ b/background.firefox.js @@ -0,0 +1,29 @@ +const WINDOWS_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.7444.163 Safari/537.36'; + +browser.webRequest.onBeforeSendHeaders.addListener( + (details) => { + const headers = details.requestHeaders || []; + let found = false; + + for (const header of headers) { + if (header.name && header.name.toLowerCase() === 'user-agent') { + header.value = WINDOWS_UA; + found = true; + break; + } + } + + if (!found) { + headers.push({ name: 'User-Agent', value: WINDOWS_UA }); + } + + return { requestHeaders: headers }; + }, + { + urls: [ + 'https://figma.com/*', + 'https://*.figma.com/*' + ] + }, + ['blocking', 'requestHeaders'] +); diff --git a/build-firefox.sh b/build-firefox.sh new file mode 100755 index 0000000..2d648d1 --- /dev/null +++ b/build-firefox.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DIST_DIR="$ROOT_DIR/dist/firefox" +XPI_PATH="$ROOT_DIR/dist/figma-windows-ua-firefox-unsigned.xpi" + +rm -rf "$DIST_DIR" +mkdir -p "$DIST_DIR" + +rm -f "$ROOT_DIR/dist/figma-windows-ua-firefox.xpi" +rm -f "$ROOT_DIR/dist/figma-windows-ua-firefox.zip" + +cp "$ROOT_DIR/manifest.firefox.json" "$DIST_DIR/manifest.json" +cp "$ROOT_DIR/background.firefox.js" "$DIST_DIR/background.firefox.js" +cp "$ROOT_DIR/content.js" "$DIST_DIR/content.js" +cp "$ROOT_DIR/injected.js" "$DIST_DIR/injected.js" + +rm -f "$XPI_PATH" +( + cd "$DIST_DIR" + zip -r "$XPI_PATH" . +) + +echo "Firefox build created: $XPI_PATH" diff --git a/content.js b/content.js new file mode 100644 index 0000000..bd9c07f --- /dev/null +++ b/content.js @@ -0,0 +1,6 @@ +(() => { + const script = document.createElement('script'); + script.src = chrome.runtime.getURL('injected.js'); + script.onload = () => script.remove(); + (document.head || document.documentElement).appendChild(script); +})(); diff --git a/dist/figma-windows-ua-firefox-unsigned.xpi b/dist/figma-windows-ua-firefox-unsigned.xpi new file mode 100644 index 0000000000000000000000000000000000000000..5295266917dbee5ae966400e6f4d0d1c23c9226b GIT binary patch literal 2218 zcmWIWW@Zs#U|`^2I4zbMv*&Z4?qntghQ(|Q4BQMd44HXZsmUd&DSBDOp&^_M%;ySM zWP)&M1vdjD%L`@(29^{C28OvIxASfr@YKE!|8Umqfu-l;WzJ1}9A%js@3C$1mhZAV z!8dtDp{nz=$yKfYYrpuGZ7X^GtldS4U-Q!kk8N)coMf-CWZ+opVUn|R#_8NowOr~6 zv8Uv1x+316c->qZUisja7+dXndq$(4Kb$uGHSEg@Wgg11J&*a`*LM6~0n4|aH{U*O z0H@*^dnE`p`{vKFU1}T zoI3D?ZR(?9l?l4d?&8;G?A(wT*tN`(VeO_W)&g0Zq!oKD^>!S{_d2$sX1HaGPrLSpl~yZdCU3dUta$(A4yKFwao;YL zB*t&#jW^PrDz3e9>A9qmgI>ZRmsPoPcfRd8HTj>=jY%F8-#*h_Sa{@%(<*hYEge4| zF8n%8V`fWXRQn#a8A^hwkGMCqdL~8vIbxFiW0l*SO^4i=^E`M&gg>Y7d{2|ppPI`P zrfD=!EH!^s+DVT|n-$8xAMjrq;}+pv?C&G{>B14alMUj^@f$P@t1?zd&EB^5n5&T0 zCayu>Y#k6Jd7I~M%^d2lqyUlp$ zzW6qFrG1-1Z=du_sTFD5{cVn1-?LBCe{XBHT5#%yLXXteKUKxGSN9l~ML&J7^*?>? z|MpAPQT3sg)hbEz_BX%#bMMz0(eJapR_RrFCe=-_Hd|ZA6o8Vhqqv?uh-PG9_{zk< zz{?=Rkeis7nU-2yqL)>ipNB7H*9JQJ9X1fy^IbHoPt=p!ylRo{2L^#h4O-dR`9a%1 zTHWAd4gG7~Cp+nav;MR_o2`wltLGU`%Tx$m^!X3>+TgZLqSZTIGcff&oNT+=i@7>n ztYRgbN@Q@ee(%*Y0@~KsoFwLExP5%H zFGB2eNb9ndQycp3u)dJVS{Sv^V%9@xF0ZoUiH7#7FZZ0z3Q)w3t3NN@*gYTw0QWA)&90X?0L(TIZf9B1HZPv*_(eS zYoo-rxHIb)RvNN0Z|l8eEpYtQtZ7dq9K33_d|I!2AwuVDd0|0j@7oP+22T#_{`mSw zU$OZWa8<`h~ zbUQ43qt+h4Jr!4YsUKwM2W8X~YUB>&Ge}ry`nSA@ ze_da`F?0zd1H&CA1_n_E8HS|9y|m1t)U^Bx0%dFLq}cph1|n_mKWm@M zd$lBquV;#vOURgMuUhad3xNou6A4^?U#wIs!!S$tgC1Mf;j()J}V3Nq+&Xhjm zPN8(%7yZn7hJSb8UjDm;xoN#%UE;1V#ye}a+)B97U3WomZ_lERZ)~+QHqFV5`#$l; zXQ8(}0heXf`hToEFPQ;WIwfqOpUog`1VOXqvV>g3C z6yMfWukN~(vR<8#MQV;RF(6I z74FTs8^a%0TE%;N`=*yEA^Z;8TO)r~TR8DG+Q`p5rW(xd)spx-S*B^n+SM1uq7KQ~ z>6iZ4`0enB;Zu%_MxMt1WS&0(QYIY`?gwFmjr(~bn`AqBBH1Xb7b5xv` zX6sGon_ja_f&2W$f9wI?j7)OOxT;YJ1`uFmU|?9%2x6gBs;rPo6|F)=HxRY_LN@RQ z69al_Mx2qRC>ajf$SM{Fl { + const headers = details.requestHeaders || []; + let found = false; + + for (const header of headers) { + if (header.name && header.name.toLowerCase() === 'user-agent') { + header.value = WINDOWS_UA; + found = true; + break; + } + } + + if (!found) { + headers.push({ name: 'User-Agent', value: WINDOWS_UA }); + } + + return { requestHeaders: headers }; + }, + { + urls: [ + 'https://figma.com/*', + 'https://*.figma.com/*' + ] + }, + ['blocking', 'requestHeaders'] +); diff --git a/dist/firefox/content.js b/dist/firefox/content.js new file mode 100644 index 0000000..bd9c07f --- /dev/null +++ b/dist/firefox/content.js @@ -0,0 +1,6 @@ +(() => { + const script = document.createElement('script'); + script.src = chrome.runtime.getURL('injected.js'); + script.onload = () => script.remove(); + (document.head || document.documentElement).appendChild(script); +})(); diff --git a/dist/firefox/injected.js b/dist/firefox/injected.js new file mode 100644 index 0000000..f6be54c --- /dev/null +++ b/dist/firefox/injected.js @@ -0,0 +1,62 @@ +(() => { + const windowsUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.7444.163 Safari/537.36'; + const appVersion = '5.0 (Windows)'; + + const override = (obj, key, value) => { + try { + Object.defineProperty(obj, key, { + get: () => value, + configurable: true + }); + } catch (error) { + // no-op + } + }; + + override(Navigator.prototype, 'userAgent', windowsUA); + override(Navigator.prototype, 'appVersion', appVersion); + override(Navigator.prototype, 'platform', 'Win32'); + override(Navigator.prototype, 'oscpu', 'Windows NT 10.0; Win64; x64'); + + if (navigator.userAgentData) { + const uaData = { + brands: [ + { brand: 'Chromium', version: '142' }, + { brand: 'Google Chrome', version: '142' } + ], + mobile: false, + platform: 'Windows', + getHighEntropyValues: async (hints) => { + const result = { + architecture: 'x86', + bitness: '64', + mobile: false, + model: '', + platform: 'Windows', + platformVersion: '10.0.0', + uaFullVersion: '142.0.7444.163', + wow64: false + }; + + if (Array.isArray(hints)) { + return hints.reduce((acc, hint) => { + if (hint in result) acc[hint] = result[hint]; + return acc; + }, {}); + } + + return result; + }, + toJSON: () => ({ + brands: [ + { brand: 'Chromium', version: '142' }, + { brand: 'Google Chrome', version: '142' } + ], + mobile: false, + platform: 'Windows' + }) + }; + + override(Navigator.prototype, 'userAgentData', uaData); + } +})(); diff --git a/dist/firefox/manifest.json b/dist/firefox/manifest.json new file mode 100644 index 0000000..59bf5ae --- /dev/null +++ b/dist/firefox/manifest.json @@ -0,0 +1,38 @@ +{ + "manifest_version": 2, + "name": "Figma Windows UA Spoofer", + "description": "Spoof Windows User-Agent and platform on figma.com", + "version": "1.0.0", + "applications": { + "gecko": { + "id": "figma-windows-ua-spoofer@imnyang.local", + "strict_min_version": "109.0" + } + }, + "permissions": [ + "webRequest", + "webRequestBlocking", + "https://figma.com/*", + "https://*.figma.com/*" + ], + "background": { + "scripts": [ + "background.firefox.js" + ] + }, + "content_scripts": [ + { + "matches": [ + "https://figma.com/*", + "https://*.figma.com/*" + ], + "js": [ + "content.js" + ], + "run_at": "document_start" + } + ], + "web_accessible_resources": [ + "injected.js" + ] +} diff --git a/dist/signed/d168a70cb6a24fe4a478-1.0.0.xpi b/dist/signed/d168a70cb6a24fe4a478-1.0.0.xpi new file mode 100644 index 0000000000000000000000000000000000000000..612aae7be59e554a0ee7d6b8501d239365c0d3a1 GIT binary patch literal 9777 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 L#K7=G8N>qsZgJF> literal 0 HcmV?d00001 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c993e94 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1772542754, + "narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..df25787 --- /dev/null +++ b/flake.nix @@ -0,0 +1,32 @@ +{ + description = "Figma Windows UA Spoofer - Firefox signing environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + bash + zip + web-ext + nodejs + ]; + }; + + apps.sign-firefox = { + type = "app"; + program = toString (pkgs.writeShellScript "sign-firefox" '' + set -euo pipefail + exec ${pkgs.bash}/bin/bash ./sign-firefox.sh + ''); + }; + }); +} \ No newline at end of file diff --git a/injected.js b/injected.js new file mode 100644 index 0000000..f6be54c --- /dev/null +++ b/injected.js @@ -0,0 +1,62 @@ +(() => { + const windowsUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.7444.163 Safari/537.36'; + const appVersion = '5.0 (Windows)'; + + const override = (obj, key, value) => { + try { + Object.defineProperty(obj, key, { + get: () => value, + configurable: true + }); + } catch (error) { + // no-op + } + }; + + override(Navigator.prototype, 'userAgent', windowsUA); + override(Navigator.prototype, 'appVersion', appVersion); + override(Navigator.prototype, 'platform', 'Win32'); + override(Navigator.prototype, 'oscpu', 'Windows NT 10.0; Win64; x64'); + + if (navigator.userAgentData) { + const uaData = { + brands: [ + { brand: 'Chromium', version: '142' }, + { brand: 'Google Chrome', version: '142' } + ], + mobile: false, + platform: 'Windows', + getHighEntropyValues: async (hints) => { + const result = { + architecture: 'x86', + bitness: '64', + mobile: false, + model: '', + platform: 'Windows', + platformVersion: '10.0.0', + uaFullVersion: '142.0.7444.163', + wow64: false + }; + + if (Array.isArray(hints)) { + return hints.reduce((acc, hint) => { + if (hint in result) acc[hint] = result[hint]; + return acc; + }, {}); + } + + return result; + }, + toJSON: () => ({ + brands: [ + { brand: 'Chromium', version: '142' }, + { brand: 'Google Chrome', version: '142' } + ], + mobile: false, + platform: 'Windows' + }) + }; + + override(Navigator.prototype, 'userAgentData', uaData); + } +})(); diff --git a/manifest.firefox.json b/manifest.firefox.json new file mode 100644 index 0000000..59bf5ae --- /dev/null +++ b/manifest.firefox.json @@ -0,0 +1,38 @@ +{ + "manifest_version": 2, + "name": "Figma Windows UA Spoofer", + "description": "Spoof Windows User-Agent and platform on figma.com", + "version": "1.0.0", + "applications": { + "gecko": { + "id": "figma-windows-ua-spoofer@imnyang.local", + "strict_min_version": "109.0" + } + }, + "permissions": [ + "webRequest", + "webRequestBlocking", + "https://figma.com/*", + "https://*.figma.com/*" + ], + "background": { + "scripts": [ + "background.firefox.js" + ] + }, + "content_scripts": [ + { + "matches": [ + "https://figma.com/*", + "https://*.figma.com/*" + ], + "js": [ + "content.js" + ], + "run_at": "document_start" + } + ], + "web_accessible_resources": [ + "injected.js" + ] +} diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..60e96f0 --- /dev/null +++ b/manifest.json @@ -0,0 +1,45 @@ +{ + "manifest_version": 3, + "name": "Figma Windows UA Spoofer", + "description": "Spoof Windows User-Agent and platform on figma.com", + "version": "1.0.0", + "permissions": [ + "declarativeNetRequest" + ], + "host_permissions": [ + "https://figma.com/*", + "https://*.figma.com/*" + ], + "declarative_net_request": { + "rule_resources": [ + { + "id": "ruleset_1", + "enabled": true, + "path": "rules.json" + } + ] + }, + "content_scripts": [ + { + "matches": [ + "https://figma.com/*", + "https://*.figma.com/*" + ], + "js": [ + "content.js" + ], + "run_at": "document_start" + } + ], + "web_accessible_resources": [ + { + "resources": [ + "injected.js" + ], + "matches": [ + "https://figma.com/*", + "https://*.figma.com/*" + ] + } + ] +} diff --git a/rules.json b/rules.json new file mode 100644 index 0000000..9831ff3 --- /dev/null +++ b/rules.json @@ -0,0 +1,30 @@ +[ + { + "id": 1, + "priority": 1, + "action": { + "type": "modifyHeaders", + "requestHeaders": [ + { + "header": "User-Agent", + "operation": "set", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.7444.163 Safari/537.36" + } + ] + }, + "condition": { + "regexFilter": "^https:\\/\\/([a-zA-Z0-9-]+\\.)*figma\\.com\\/", + "resourceTypes": [ + "main_frame", + "sub_frame", + "xmlhttprequest", + "script", + "image", + "font", + "stylesheet", + "media", + "other" + ] + } + } +] diff --git a/sign-firefox.sh b/sign-firefox.sh new file mode 100755 index 0000000..d4b54c2 --- /dev/null +++ b/sign-firefox.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SOURCE_DIR="$ROOT_DIR/dist/firefox" +SIGNED_DIR="$ROOT_DIR/dist/signed" + +if [ -f "$ROOT_DIR/.env" ]; then + set -a + # shellcheck disable=SC1091 + . "$ROOT_DIR/.env" + set +a +fi + +if [ -z "${AMO_JWT_ISSUER:-}" ] || [ -z "${AMO_JWT_SECRET:-}" ]; then + echo "AMO_JWT_ISSUER / AMO_JWT_SECRET 값이 필요합니다." + echo "(.env 또는 환경변수에서 읽습니다)" + echo "예:" + echo " export AMO_JWT_ISSUER=\"\"" + echo " export AMO_JWT_SECRET=\"\"" + exit 1 +fi + +"$ROOT_DIR/build-firefox.sh" +mkdir -p "$SIGNED_DIR" + +if command -v web-ext >/dev/null 2>&1; then + web-ext sign \ + --source-dir "$SOURCE_DIR" \ + --artifacts-dir "$SIGNED_DIR" \ + --api-key "$AMO_JWT_ISSUER" \ + --api-secret "$AMO_JWT_SECRET" \ + --channel unlisted +elif command -v npx >/dev/null 2>&1; then + npx --yes web-ext sign \ + --source-dir "$SOURCE_DIR" \ + --artifacts-dir "$SIGNED_DIR" \ + --api-key "$AMO_JWT_ISSUER" \ + --api-secret "$AMO_JWT_SECRET" \ + --channel unlisted +else + echo "web-ext 또는 npx가 필요합니다." + echo "Nix 사용 시: nix develop -c ./sign-firefox.sh" + exit 1 +fi + +echo "Signed XPI generated under: $SIGNED_DIR"