Self-hosted anti-detect browser profiles. Open-source AdsPower alternative for Puppeteer & Playwright.
npm install @aitofy/browser-profiles> ð Self-hosted anti-detect browser profiles. The open-source alternative to AdsPower & Multilogin.
> Run locally, own your data, no subscriptions.




Like n8n for automation or Affine for notes, this is AdsPower for developers â self-hosted, open-source, and privacy-first.
| â AdsPower/Multilogin | â
browser-profiles |
|------------------------|---------------------|
| $99+/month | Free forever |
| Cloud storage (data not yours) | Local storage (your data) |
| GUI only | Code-first (Puppeteer/Playwright) |
| Vendor lock-in | Open source (MIT) |
| No customization | Full control |
- ð Self-hosted - Data stays on your machine, no cloud dependency
- ð Privacy-first - Zero telemetry, zero tracking
- ðĄïļ Anti-detect - WebRTC, Canvas, WebGL, Audio fingerprint protection
- ð Proxy support - HTTP, HTTPS, SOCKS5 with auto timezone detection
- ðĶ Profile management - Create, update, delete browser profiles
- ð Puppeteer & Playwright - First-class integration
- ⥠Zero config - Works out of the box
- ðŠķ Lightweight - No extensions, pure CDP injection
``bashRecommended: rebrowser-puppeteer-core (better anti-detect!)
npm install @aitofy/browser-profiles rebrowser-puppeteer-core
$3
| Site | Score | Notes |
|------|-------|-------|
| browserleaks.com | â
100% | All checks passed |
| pixelscan.net | â
100% | Hardware fingerprint passed |
| browserscan.net | â ïļ 95% | Bot Control -5% (Puppeteer limitation) |
| creepjs | â ïļ 85% | Minor deductions |
> Note: 95% is the best achievable with Puppeteer/Playwright. 100% requires modified Chromium (like AdsPower).
ðŧ CLI Tool
Install globally to use the CLI:
`bash
npm install -g @aitofy/browser-profiles
`$3
`bash
List all profiles
browser-profiles listCreate a new profile
browser-profiles create my-account
browser-profiles create my-account --proxy http://user:pass@proxy.com:8080Open browser with a profile
browser-profiles open Quick launch (no profile needed)
browser-profiles launch
browser-profiles launch --proxy http://proxy.com:8080Show profile details
browser-profiles info Delete a profile
browser-profiles delete Show storage path
browser-profiles path
`ð Quick Start
$3
`typescript
import { quickLaunch } from '@aitofy/browser-profiles';// Launch anti-detect browser with proxy (timezone auto-detected!)
const { page, close } = await quickLaunch({
proxy: { type: 'http', host: 'your-proxy.com', port: 8080 },
});
await page.goto('https://browserscan.net');
await page.screenshot({ path: 'proof.png' });
await close();
`That's it! ð Browser fingerprint is protected, IP is hidden, timezone matches proxy location.
---
$3
`typescript
import { BrowserProfiles } from '@aitofy/browser-profiles';const profiles = new BrowserProfiles();
// Create a profile (saved to ~/.aitofy/browser-profiles/)
const profile = await profiles.create({
name: 'My Profile',
proxy: {
type: 'http',
host: 'proxy.example.com',
port: 8080,
},
});
// Launch browser
const { wsEndpoint, close } = await profiles.launch(profile.id);
// ... automation work ...
await close();
`$3
You can define your own custom profile IDs instead of using auto-generated ones:
`typescript
// Create profile with custom ID
const profile = await profiles.create({
id: 'google-main', // Custom ID (alphanumeric + hyphen/underscore)
name: 'Google Account',
});// Launch by custom ID directly
const { wsEndpoint } = await profiles.launch('google-main');
`Custom ID rules:
- 1-64 characters
- Alphanumeric with hyphens and underscores only (a-z, A-Z, 0-9, -, _)
- Must be unique
$3
You can now launch browsers using the profile name instead of ID:
`typescript
// Create profile
await profiles.create({ name: 'Facebook Account' });// Launch by name (case-insensitive)
const { wsEndpoint } = await profiles.launchByName('Facebook Account');
// Or use launchByIdOrName (works with both ID and name)
await profiles.launchByIdOrName('google-main'); // By ID
await profiles.launchByIdOrName('Facebook Account'); // By name
`$3
`typescript
import { withPuppeteer } from '@aitofy/browser-profiles/puppeteer';const { browser, page, close } = await withPuppeteer({
profile: 'my-profile-id', // or profile name
});
await page.goto('https://whoer.net');
await page.screenshot({ path: 'screenshot.png' });
await close();
`$3
`typescript
import { quickLaunch } from '@aitofy/browser-profiles/puppeteer';const { browser, page, close } = await quickLaunch({
proxy: {
type: 'http',
host: 'proxy.example.com',
port: 8080,
},
// timezone is now AUTO-DETECTED from proxy IP!
// No need to specify manually. If no proxy, system timezone is used.
fingerprint: {
platform: 'Win32', // Spoof as Windows
hardwareConcurrency: 8, // Spoof CPU cores
language: 'en-US',
},
});
// Output: [browser-profiles] ð Auto-detected timezone: America/New_York (New York, United States)
await page.goto('https://browserscan.net');
await close();
`$3
`typescript
import { withPlaywright } from '@aitofy/browser-profiles/playwright';const { browser, page, close } = await withPlaywright({
profile: 'my-profile-id',
});
await page.goto('https://example.com');
await close();
`$3
#### Temporary Sessions (No Profile Persistence)
`typescript
import { createSession } from '@aitofy/browser-profiles/puppeteer';// Quick session with random fingerprint - perfect for scraping
const session = await createSession({
temporary: true,
randomFingerprint: true, // Random platform, language, CPU cores
proxy: { type: 'http', host: 'proxy.com', port: 8080 },
});
await session.page.goto('https://example.com');
await session.close(); // Cleanup
`#### Patch Existing Pages
`typescript
import { patchPage } from '@aitofy/browser-profiles/puppeteer';// Apply anti-detect patches to any page
await patchPage(page, {
webdriver: true, // Hide webdriver flag
plugins: true, // Spoof Chrome plugins
chrome: true, // Fix chrome object
webrtc: true, // WebRTC leak protection
fingerprint: { platform: 'Win32', hardwareConcurrency: 8 },
});
`#### Generate Fingerprints On-Demand
`typescript
import { generateFingerprint, getFingerprintScripts } from '@aitofy/browser-profiles';// Generate a realistic fingerprint
const fp = generateFingerprint({
platform: 'macos',
gpu: 'apple',
screen: 'retina',
language: 'ja-JP',
});
console.log(fp.userAgent); // Mozilla/5.0 (Macintosh...
console.log(fp.webgl.renderer); // ANGLE (Apple, Apple M1 Pro...
// Use with any page
const scripts = getFingerprintScripts(fp);
await page.evaluateOnNewDocument(scripts);
`#### Standalone Chrome Launch
`typescript
import { launchChromeStandalone } from '@aitofy/browser-profiles';
import puppeteer from 'puppeteer-core';// Launch Chrome without profile management
const { wsEndpoint, close } = await launchChromeStandalone({
headless: false,
proxy: { type: 'http', host: 'proxy.com', port: 8080 },
});
const browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint });
await close();
`#### Inject Your Own Puppeteer
`typescript
import puppeteer from 'rebrowser-puppeteer-core';
import { withPuppeteer } from '@aitofy/browser-profiles/puppeteer';// Use your own puppeteer instance
const { browser, page } = await withPuppeteer({
profile: 'my-profile',
puppeteer, // â Inject here
});
`$3
Profiles are saved locally to
~/.aitofy/browser-profiles/ and persist between sessions.`typescript
import { BrowserProfiles } from '@aitofy/browser-profiles';const profiles = new BrowserProfiles();
// ===== CREATE =====
const profile = await profiles.create({
name: 'Facebook Account 1',
proxy: { type: 'http', host: 'proxy.example.com', port: 8080 },
tags: ['facebook', 'marketing'],
});
// ===== LIST ALL =====
const allProfiles = await profiles.list();
console.log(
Total: ${allProfiles.length} profiles);// ===== LIST BY TAG =====
const fbProfiles = await profiles.list({ tags: ['facebook'] });
// ===== GET BY ID =====
const myProfile = await profiles.get(profile.id);
// ===== UPDATE =====
await profiles.update(profile.id, {
name: 'Facebook Account 1 - Updated',
proxy: { type: 'socks5', host: 'new-proxy.com', port: 1080 },
});
// ===== LAUNCH BROWSER =====
const { wsEndpoint, close } = await profiles.launch(profile.id);
// ... do automation ...
await close();
// ===== DUPLICATE =====
const cloned = await profiles.duplicate(profile.id, 'Facebook Account 2');
// ===== EXPORT / IMPORT =====
const json = await profiles.export(profile.id);
const imported = await profiles.import(json);
// ===== DELETE =====
await profiles.delete(profile.id);
`$3
`typescript
// Create groups
const group = await profiles.createGroup('TikTok Shop', 'All TikTok accounts');// Move profile to group
await profiles.moveToGroup(profile.id, group.id);
// List groups
const groups = await profiles.listGroups();
// List profiles in group
const tikTokProfiles = await profiles.list({ groupId: group.id });
`$3
`typescript
import { ExTowerClient } from '@aitofy/browser-profiles/extower';
import puppeteer from 'puppeteer-core';const client = new ExTowerClient({
baseUrl: 'http://localhost:50325', // default
});
// Create profile in ExTower
const { id } = await client.createProfile({
name: 'TikTok Shop 1',
proxy: {
type: 'http',
host: 'proxy.example.com',
port: 8080,
},
});
// Launch browser
const { puppeteer: wsEndpoint } = await client.launchBrowser(id);
// Connect Puppeteer
const browser = await puppeteer.connect({
browserWSEndpoint: wsEndpoint,
});
const page = await browser.newPage();
await page.goto('https://seller-us.tiktok.com');
`ð API Reference
$3
Main class for managing browser profiles.
`typescript
const profiles = new BrowserProfiles({
storagePath: '~/.aitofy/browser-profiles', // Default: ~/.aitofy/browser-profiles
chromePath: '/path/to/chrome', // Custom Chrome path (optional)
defaultTimezone: 'UTC', // Default timezone for new profiles
});
`Default storage locations:
- macOS:
~/.aitofy/browser-profiles
- Linux: ~/.aitofy/browser-profiles
- Windows: C:\Users\#### Methods
| Method | Description |
|--------|-------------|
|
create(config) | Create a new profile (supports custom ID via config.id) |
| get(id) | Get profile by ID |
| getByName(name) | Get profile by name (case-insensitive) |
| getByIdOrName(idOrName) | Get profile by ID or name |
| list(options?) | List all profiles |
| update(id, updates) | Update profile |
| delete(id) | Delete profile |
| launch(id, options?) | Launch browser by profile ID |
| launchByName(name, options?) | Launch browser by profile name |
| launchByIdOrName(idOrName, options?) | Launch browser by ID or name |
| close(id) | Close running browser |
| closeAll() | Close all browsers |
| duplicate(id) | Duplicate profile |
| export(id) | Export to JSON |
| import(json) | Import from JSON |$3
`typescript
interface ProfileConfig {
id?: string; // Custom profile ID (auto-generated if omitted)
name: string; // Profile name
proxy?: ProxyConfig; // Proxy settings
timezone?: string; // e.g., "America/New_York"
cookies?: ProfileCookie[]; // Cookies to inject
fingerprint?: FingerprintConfig; // Fingerprint settings
startUrls?: string[]; // URLs to open on launch
tags?: string[]; // Tags for organization
groupId?: string; // Group for organization
}
`$3
`typescript
interface ProxyConfig {
type: 'http' | 'https' | 'socks5';
host: string;
port: number | string;
username?: string;
password?: string;
}
`$3
`typescript
interface LaunchOptions {
headless?: boolean; // Run headless (default: false)
chromePath?: string; // Custom Chrome path
args?: string[]; // Additional Chrome args
extensions?: string[]; // Extension paths to load
defaultViewport?: { width: number; height: number } | null;
slowMo?: number; // Slow down by ms
timeout?: number; // Launch timeout
}
`ð Anti-Detect Features
All fingerprint protections are hardcoded and injected via CDP - no extensions required!
$3
Comprehensive protection against bot detection:
| Check | Status | Description |
|-------|--------|-------------|
|
navigator.webdriver | â
Hidden | Returns undefined |
| window.chrome | â
Faked | Complete chrome object |
| chrome.runtime | â
Faked | Runtime object present |
| chrome.csi() | â
Faked | Timing function |
| chrome.loadTimes() | â
Faked | Page load metrics |
| navigator.plugins | â
Faked | 3 default Chrome plugins |
| navigator.connection | â
Faked | 4G connection info |
| navigator.getBattery() | â
Faked | Battery status API |
| navigator.permissions | â
Mocked | Permission query handling |$3
Automatically prevents WebRTC from leaking your real IP address. Works even when using proxy!
$3
Adds random noise to canvas data to prevent tracking.
$3
Spoofs WebGL parameters and adds noise to buffer data:
- Randomizes vendor/renderer strings
- Spoofs GPU parameters
- Adds noise to WebGL buffer data
$3
Adds tiny noise to audio data without affecting audio quality.
$3
`typescript
const profile = await profiles.create({
name: 'US Profile',
timezone: 'America/New_York', // Browser reports this timezone
});
`$3
Customize browser navigator properties:
`typescript
const profile = await profiles.create({
name: 'Custom Profile',
fingerprint: {
language: 'en-US',
platform: 'Win32',
hardwareConcurrency: 8,
deviceMemory: 16,
},
});
`$3
Handles authenticated proxies transparently:
`typescript
const profile = await profiles.create({
name: 'With Auth Proxy',
proxy: {
type: 'http',
host: 'proxy.example.com',
port: 8080,
username: 'user', // â
Auth handled automatically
password: 'password',
},
});
`â
Verified Test Results
Tested on 2026-01-08 with the following fingerprint testing sites:
| Property | Spoofed Value | Verification |
|----------|---------------|--------------|
| Timezone | America/New_York | â
BrowserScan |
| Platform | Win32 | â
BrowserLeaks |
| CPU Cores | 8 | â
PixelScan |
| Device Memory | 16GB | â
PixelScan |
| Language | en-US | â
All sites |
| WebRTC IP | Hidden | â
BrowserLeaks |
| Webdriver | Hidden | â
All sites |
| Plugins | 5 | â
BrowserLeaks |
| Connection API | 4g | â
Verified |
| Chrome Object | Complete | â
Verified |
| Chrome.csi | Present | â
Verified |
| Chrome.loadTimes | Present | â
Verified |
Sites tested:
- â
browserscan.net
- â
browserleaks.com
- â
pixelscan.net
- â
creepjs
ð Comparison
| Feature | browser-profiles | AdsPower | Multilogin | puppeteer-extra |
|---------|-----------------|----------|------------|-----------------|
| Open Source | â
MIT | â Paid | â Paid | â
MIT |
| Price | FREE | $9-50/mo | $99-199/mo | Free |
| Anti-Detect Score | 95% | 100% | 100% | ~80% |
| Profile Storage | â
| â
| â
| â |
| Proxy Auth | â
| â
| â
| â |
| Auto Timezone | â
| â
| â
| â |
| WebRTC Protection | â
| â
| â
| â ïļ Basic |
| Canvas Noise | â
| â
| â
| â ïļ Basic |
| TypeScript | â
| â | â | â ïļ Partial |
| npm Package | â
| â | â | â
|
| Puppeteer Integration | â
| â
| â ïļ | â
|
| Playwright Integration | â
| â | â ïļ | â
|
| Cloud Sync | ð Coming | â
| â
| â |
| GUI | â | â
| â
| â |
$3
AdsPower and Multilogin achieve 100% by using modified Chromium binaries. Our library uses standard Chrome with JS injection, which has a fundamental ~5% detection limitation on advanced sites like BrowserScan.
For most use cases (social media, e-commerce, scraping), 95% is sufficient.
ð Security
$3
- All data stored locally at ~/.aitofy/browser-profiles/
- Zero telemetry - no data sent to any server
- Profile configs stored as JSON files
- Chrome user data (passwords, autofill) encrypted by Chrome itself$3
`typescript
const profiles = new BrowserProfiles({
encryption: {
enabled: true,
masterKey: 'your-secret-key',
}
});
`
- AES-256-GCM encryption for sensitive data
- Optional cloud sync with end-to-end encryption
- Only you (or people you share the key with) can decryptð v0.2.5: Full Type Support
$3
Starting from v0.2.5,
PuppeteerPage and PlaywrightPage are native types re-exported from puppeteer-core and playwright. You now have access to ALL APIs directly:`typescript
import { withPuppeteer, PuppeteerPage } from '@aitofy/browser-profiles';const { page, close } = await withPuppeteer({ profile: 'my-profile' });
// â
Full API access - no workarounds needed!
await page.setRequestInterception(true);
page.on('request', (req) => {
if (req.resourceType() === 'image') {
req.abort();
} else {
req.continue();
}
});
const cookies = await page.cookies();
await page.setCookie({ name: 'session', value: '123', domain: '.example.com' });
await close();
`For Playwright:
`typescript
import { withPlaywright, PlaywrightPage } from '@aitofy/browser-profiles';const { page, close } = await withPlaywright({ profile: 'my-profile' });
// â
Full Playwright API!
await page.route('*/', (route) => {
if (route.request().resourceType() === 'image') {
route.abort();
} else {
route.continue();
}
});
await page.reload();
await page.waitForTimeout(1000);
await close();
`$3
All commonly used types are re-exported for convenience:
| Puppeteer Types | Playwright Types |
|-----------------|------------------|
|
PuppeteerPage | PlaywrightPage |
| PuppeteerBrowser | PlaywrightBrowser |
| HTTPRequest | PlaywrightContext |
| HTTPResponse | PlaywrightRequest |
| Cookie | PlaywrightResponse, Route` |Contributions are welcome! Please read our Contributing Guide.
MIT ÂĐ Aitofy
---
Made with âĪïļ by Aitofy