Load default addons through launcher

- Default addons are now downloaded through the launcher to avoid redownloading when new profiles are created
- Updated README
This commit is contained in:
daijro 2024-08-13 22:14:43 -05:00
parent ad87cec317
commit 8aadd3e1f2
9 changed files with 247 additions and 37 deletions

View file

@ -17,8 +17,8 @@ Camoufox aims to be a minimalistic browser for robust fingerprint injection & an
## Features ## Features
- Fingerprint injection (override properties of `navigator`, `window`, `screen`, etc) ✅ - Fingerprint injection (override properties of `navigator`, `window`, `screen`, etc) ✅
- Patches to avoid Playwright detection ✅ - Patches to avoid bot detection ✅
- Custom Playwright Juggler implementation with minimal leaks - Custom Playwright Juggler implementation for the latest Firefox
- Font spoofing & anti-fingerprinting ✅ - Font spoofing & anti-fingerprinting ✅
- Patches from LibreWolf & Ghostery to remove Mozilla services ✅ - Patches from LibreWolf & Ghostery to remove Mozilla services ✅
- Optimized for memory and speed ✅ - Optimized for memory and speed ✅
@ -34,6 +34,7 @@ Camoufox is built on top of Firefox/Juggler instead of Chromium because:
### What's planned? ### What's planned?
- Continue research on potential leaks
- Built in TLS fingerprinting protection using [Hazetunnel](https://github.com/daijro/hazetunnel) - Built in TLS fingerprinting protection using [Hazetunnel](https://github.com/daijro/hazetunnel)
- Create integration tests - Create integration tests
- Chromium port (long-term) - Chromium port (long-term)
@ -198,6 +199,25 @@ Spoofing document.body has been implemented, but it is more advicable to set `wi
</details> </details>
<details>
<summary>
HTTP Headers
</summary>
Camoufox can override the following network headers:
| Property | Status |
| ----------------------- | ------ |
| headers.User-Agent | ✅ |
| headers.Accept-Language | ✅ |
| headers.Accept-Encoding | ✅ |
**Notes:**
- If `headers.User-Agent` is not set, it will fall back to `navigator.userAgent`.
</details>
<details> <details>
<summary> <summary>
@ -214,6 +234,14 @@ Example:
Camoufox will automatically download and use the latest uBlock Origin with custom privacy/adblock filters, and B.P.C. by default to help with scraping. Camoufox will automatically download and use the latest uBlock Origin with custom privacy/adblock filters, and B.P.C. by default to help with scraping.
You can also exclude default addons with the `--exclude-addons` flag:
```bash
./launcher --exclude-addons '["uBO", "BPC"]'
```
</details>
</details> </details>
<details> <details>
@ -285,6 +313,7 @@ Miscellaneous (WebGl spoofing, battery status, etc)
- Added B.P.C. - Added B.P.C.
- Addons are not allowed to open tabs - Addons are not allowed to open tabs
- Addons are automatically enabled in Private Browsing mode - Addons are automatically enabled in Private Browsing mode
- Addons are automatically pinned to the toolbar
## Stealth Performance ## Stealth Performance
@ -319,7 +348,7 @@ Camoufox performs well against every major WAF I've tested. (Test sites from [Bo
| [**BrowserScan**](https://browserscan.net/) | ✔️ | | [**BrowserScan**](https://browserscan.net/) | ✔️ |
| [**Bet365**](https://www.bet365.com/#/AC/B1/C1/D1002/E79147586/G40/) | ✔️ | | [**Bet365**](https://www.bet365.com/#/AC/B1/C1/D1002/E79147586/G40/) | ✔️ |
Camoufox does **not** fully support injecting Chromium fingerprints. Some websites (such as Cloudflare [Interstitial](https://nopecha.com/demo/cloudflare)) look for the Gecko webdriver underneath. Camoufox does **not** fully support injecting Chromium fingerprints. Some WAFs (such as [Interstitial](https://nopecha.com/demo/cloudflare)) look for the Gecko webdriver underneath.
--- ---
@ -334,11 +363,11 @@ graph TD
FFSRC[Firefox Source] -->|make fetch| REPO FFSRC[Firefox Source] -->|make fetch| REPO
subgraph REPO[Camoufox Repository] subgraph REPO[Camoufox Repository]
MASKING[Camoufox masking module] PATCHES[Fingerprint masking patches]
PATCHES[Debloat/optimizations]
ADDONS[uBlock & B.P.C.] ADDONS[uBlock & B.P.C.]
SYSTEM_FONTS[Win, Mac, Linux System fonts] DEBLOAT[Debloat/optimizations]
JUGGLER[Playwright Juggler] SYSTEM_FONTS[Win, Mac, Linux fonts]
JUGGLER[Patched Juggler]
end end
subgraph Local subgraph Local

View file

@ -5,6 +5,12 @@ import (
"strings" "strings"
) )
// Default addons to extract to /addons
var DefaultAddons = map[string]string{
"uBO": "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi",
"BPC": "https://gitflic.ru/project/magnolia1234/bpc_uploads/blob/raw?file=bypass_paywalls_clean-latest.xpi",
}
// Exclude lines from output // Exclude lines from output
var ExclusionRules = []string{ var ExclusionRules = []string{
// Ignore search related warnings // Ignore search related warnings
@ -17,6 +23,7 @@ var ExclusionRules = []string{
"^console\\.error:\\ \\(\\{\\}\\)$", "^console\\.error:\\ \\(\\{\\}\\)$",
"^console\\.error:\\ \"Could\\ not\\ record\\ event:\\ \"\\ \\(\\{\\}\\)$", "^console\\.error:\\ \"Could\\ not\\ record\\ event:\\ \"\\ \\(\\{\\}\\)$",
"^\\s*?$", "^\\s*?$",
"Rejected by Camoufox\\.$",
// Ignore missing urlbar provider errors // Ignore missing urlbar provider errors
"^JavaScript\\ error:\\ resource:///modules/UrlbarProvider", "^JavaScript\\ error:\\ resource:///modules/UrlbarProvider",
} }

View file

@ -4,14 +4,12 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"net"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"time"
) )
func getExecutableName() string { func getExecutableName() string {
@ -68,23 +66,6 @@ func filterOutput(r io.Reader, w io.Writer) {
} }
} }
func tryLoadAddons(debugPortInt int, addonsList []string) {
// Wait for the server to be open
for {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", debugPortInt))
if err == nil {
conn.Close()
break
}
time.Sleep(10 * time.Millisecond)
}
// Load addons
for _, addon := range addonsList {
loadFirefoxAddon(debugPortInt, addon)
}
}
// Run Camoufox // Run Camoufox
func runCamoufox(execName string, args []string, addonsList []string) { func runCamoufox(execName string, args []string, addonsList []string) {
// If addons are specified, get the debug port // If addons are specified, get the debug port

View file

@ -8,8 +8,10 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// Gets the debug port from the args, or creates a new one if not provided
func getDebugPort(args *[]string) int { func getDebugPort(args *[]string) int {
debugPort := parseArgs("-start-debugger-server", "", args, false) debugPort := parseArgs("-start-debugger-server", "", args, false)
@ -30,8 +32,8 @@ func getDebugPort(args *[]string) int {
return debugPortInt return debugPortInt
} }
// Confirm paths are valid
func confirmPaths(paths []string) { func confirmPaths(paths []string) {
// Confirm paths are valid
for _, path := range paths { for _, path := range paths {
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
fmt.Printf("Error: %s is not a valid addon path.\n", path) fmt.Printf("Error: %s is not a valid addon path.\n", path)
@ -40,8 +42,8 @@ func confirmPaths(paths []string) {
} }
} }
// Generate an open port
func getOpenPort() int { func getOpenPort() int {
// Generate an open port
ln, err := net.Listen("tcp", ":0") // listen on a random port ln, err := net.Listen("tcp", ":0") // listen on a random port
if err != nil { if err != nil {
return 0 return 0
@ -52,6 +54,24 @@ func getOpenPort() int {
return addr.Port return addr.Port
} }
// Waits for the server to start, then loads the addons
func tryLoadAddons(debugPortInt int, addonsList []string) {
// Wait for the server to be open
for {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", debugPortInt))
if err == nil {
conn.Close()
break
}
time.Sleep(10 * time.Millisecond)
}
// Load addons
for _, addon := range addonsList {
go loadFirefoxAddon(debugPortInt, addon)
}
}
// Firefox addon loader // Firefox addon loader
// Ported from this Nodejs implementation: // Ported from this Nodejs implementation:
// https://github.com/microsoft/playwright/issues/7297#issuecomment-1211763085 // https://github.com/microsoft/playwright/issues/7297#issuecomment-1211763085

View file

@ -17,11 +17,16 @@ func main() {
configPath := parseArgs("--config", "{}", &args, true) configPath := parseArgs("--config", "{}", &args, true)
addons := parseArgs("--addons", "[]", &args, true) addons := parseArgs("--addons", "[]", &args, true)
excludeAddons := parseArgs("--exclude-addons", "[]", &args, true)
//*** PARSE CONFIG ***//
// Read and parse the config file // Read and parse the config file
var configMap map[string]interface{} var configMap map[string]interface{}
parseJson(configPath, &configMap) parseJson(configPath, &configMap)
//*** PARSE ADDONS ***//
// If addons are passed, handle them // If addons are passed, handle them
var addonsList []string var addonsList []string
parseJson(addons, &addonsList) parseJson(addons, &addonsList)
@ -29,10 +34,21 @@ func main() {
// Confirm addon paths are valid // Confirm addon paths are valid
confirmPaths(addonsList) confirmPaths(addonsList)
userAgentOS := determineUserAgentOS(configMap) // Determine the user agent OS // Add the default addons, excluding the ones specified in --exclude-addons
var excludeAddonsList []string
parseJson(excludeAddons, &excludeAddonsList)
// OS specific font config addDefaultAddons(excludeAddonsList, &addonsList)
//*** FONTS ***//
// Determine the target OS
userAgentOS := determineUserAgentOS(configMap)
// Add OS specific fonts
updateFonts(configMap, userAgentOS) updateFonts(configMap, userAgentOS)
//*** LAUNCH ***//
setEnvironmentVariables(configMap, userAgentOS) setEnvironmentVariables(configMap, userAgentOS)
// Run the Camoufox executable // Run the Camoufox executable
@ -44,6 +60,7 @@ func main() {
runCamoufox(execName, args, addonsList) runCamoufox(execName, args, addonsList)
} }
// Parses & removes an argument from the args list
func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs bool) string { func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs bool) string {
for i := 0; i < len(*args); i++ { for i := 0; i < len(*args); i++ {
if (*args)[i] != param { if (*args)[i] != param {
@ -62,6 +79,7 @@ func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs
return defaultValue return defaultValue
} }
// Parses a JSON string or file into a map
func parseJson(argv string, target interface{}) { func parseJson(argv string, target interface{}) {
// Unmarshal the config input into a map // Unmarshal the config input into a map
var data []byte var data []byte
@ -84,6 +102,7 @@ func parseJson(argv string, target interface{}) {
} }
} }
// Determines the target OS from the user agent string if provided
func determineUserAgentOS(configMap map[string]interface{}) string { func determineUserAgentOS(configMap map[string]interface{}) string {
// Determine the OS from the user agent string if provided // Determine the OS from the user agent string if provided
defaultOS := normalizeOS(runtime.GOOS) defaultOS := normalizeOS(runtime.GOOS)
@ -96,8 +115,8 @@ func determineUserAgentOS(configMap map[string]interface{}) string {
return defaultOS return defaultOS
} }
// Get the OS name as {macos, windows, linux}
func normalizeOS(osName string) string { func normalizeOS(osName string) string {
// Get the OS name as {macos, windows, linux}
osName = strings.ToLower(osName) osName = strings.ToLower(osName)
switch { switch {
case osName == "darwin" || strings.Contains(osName, "mac"): case osName == "darwin" || strings.Contains(osName, "mac"):
@ -109,8 +128,8 @@ func normalizeOS(osName string) string {
} }
} }
// Add fonts associated with the OS to the config map
func updateFonts(configMap map[string]interface{}, userAgentOS string) { func updateFonts(configMap map[string]interface{}, userAgentOS string) {
// Add fonts associated with the OS to the config map
fonts, ok := configMap["fonts"].([]interface{}) fonts, ok := configMap["fonts"].([]interface{})
if !ok { if !ok {
fonts = []interface{}{} fonts = []interface{}{}
@ -129,8 +148,8 @@ func updateFonts(configMap map[string]interface{}, userAgentOS string) {
configMap["fonts"] = fonts configMap["fonts"] = fonts
} }
// Update the config map with the fonts and environment variables
func setEnvironmentVariables(configMap map[string]interface{}, userAgentOS string) { func setEnvironmentVariables(configMap map[string]interface{}, userAgentOS string) {
// Update the config map with the fonts and environment variables
updatedConfigData, err := json.Marshal(configMap) updatedConfigData, err := json.Marshal(configMap)
if err != nil { if err != nil {
fmt.Printf("Error updating config: %v\n", err) fmt.Printf("Error updating config: %v\n", err)

158
launcher/xpi-dl.go Normal file
View file

@ -0,0 +1,158 @@
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
// Downloads and extracts the default addons
func addDefaultAddons(excludeList []string, addonsList *[]string) {
// Build a map from DefaultAddons, excluding keys found in excludeAddonsList
addonsMap := make(map[string]string)
for name, url := range DefaultAddons {
if len(excludeList) == 0 || !contains(excludeList, name) {
addonsMap[name] = url
}
}
// Download if not already downloaded
maybeDownloadAddons(addonsMap, addonsList)
}
// Downloads and extracts the addon
func downloadAndExtract(url, extractPath string) error {
// Create a temporary file to store the downloaded zip
tempFile, err := os.CreateTemp("", "camoufox-addon-*.zip")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
defer os.Remove(tempFile.Name()) // Clean up the temp file when done
// Download the zip file
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to download addon: %w", err)
}
defer resp.Body.Close()
// Write the body to the temp file
_, err = io.Copy(tempFile, resp.Body)
if err != nil {
return fmt.Errorf("failed to write addon to temp file: %w", err)
}
// Close the file before unzipping
tempFile.Close()
// Extract the zip file
err = unzip(tempFile.Name(), extractPath)
if err != nil {
return fmt.Errorf("failed to extract addon: %w", err)
}
return nil
}
// Extracts the zip file
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// Checks if a slice contains a string
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// Returns the absolute path to the target addon location
func getAddonPath(addonName string) (string, error) {
execPath, err := os.Executable()
if err != nil {
fmt.Printf("Error getting executable path: %v\n", err)
return "", err
}
execDir := filepath.Dir(execPath)
addonPath := filepath.Join(execDir, "addons", addonName)
return addonPath, nil
}
// Downloads and extracts the addons
func maybeDownloadAddons(addons map[string]string, addonsList *[]string) {
for addonName, url := range addons {
// Get the addon path
addonPath, err := getAddonPath(addonName)
if err != nil {
fmt.Printf("Error getting addon path: %v\n", err)
continue
}
// Check if the addon is already extracted
if _, err := os.Stat(addonPath); !os.IsNotExist(err) {
// Add the existing addon path to addonsList
*addonsList = append(*addonsList, addonPath)
continue
}
// Addon doesn't exist, create directory and download
err = os.MkdirAll(addonPath, 0755)
if err != nil {
fmt.Printf("Failed to create directory for %s: %v\n", addonName, err)
continue
}
err = downloadAndExtract(url, addonPath)
if err != nil {
fmt.Printf("Failed to download and extract %s: %v\n", addonName, err)
} else {
fmt.Printf("Successfully downloaded and extracted %s\n", addonName)
// Add the new addon directory path to addonsList
*addonsList = append(*addonsList, addonPath)
}
}
}

View file

@ -35,10 +35,6 @@
"Exceptions": ["https://localhost/*"] "Exceptions": ["https://localhost/*"]
}, },
"Extensions": { "Extensions": {
"Install": [
"https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi",
"https://github.com/bpc-clone/bpc_updates/releases/download/latest/bypass_paywalls_clean-latest.xpi"
],
"Uninstall": [ "Uninstall": [
"google@search.mozilla.org", "google@search.mozilla.org",
"bing@search.mozilla.org", "bing@search.mozilla.org",