first commit

This commit is contained in:
암냥 2026-03-05 03:06:24 +09:00
commit 46aea254d0
No known key found for this signature in database
19 changed files with 569 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

57
README.md Normal file
View file

@ -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="<your-api-key>"
AMO_JWT_SECRET="<your-api-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 계열 값이면 정상입니다.

29
background.firefox.js Normal file
View file

@ -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']
);

25
build-firefox.sh Executable file
View file

@ -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"

6
content.js Normal file
View file

@ -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);
})();

Binary file not shown.

1
dist/firefox/.amo-upload-uuid vendored Normal file
View file

@ -0,0 +1 @@
{"uploadUuid":"b4b16a82abdb4ce5850b5d0deb76ef3b","channel":"unlisted","xpiCrcHash":"e5567d47cfb9f6133110396308a37ed42afc5be51f6b047ae4643d3f5f063591"}

29
dist/firefox/background.firefox.js vendored Normal file
View file

@ -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']
);

6
dist/firefox/content.js vendored Normal file
View file

@ -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);
})();

62
dist/firefox/injected.js vendored Normal file
View file

@ -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);
}
})();

38
dist/firefox/manifest.json vendored Normal file
View file

@ -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"
]
}

Binary file not shown.

61
flake.lock generated Normal file
View file

@ -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
}

32
flake.nix Normal file
View file

@ -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
'');
};
});
}

62
injected.js Normal file
View file

@ -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);
}
})();

38
manifest.firefox.json Normal file
View file

@ -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"
]
}

45
manifest.json Normal file
View file

@ -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/*"
]
}
]
}

30
rules.json Normal file
View file

@ -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"
]
}
}
]

47
sign-firefox.sh Executable file
View file

@ -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=\"<your-api-key>\""
echo " export AMO_JWT_SECRET=\"<your-api-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"