ARMANOS

Security and trust

Security and trust

Everything that makes an account an account stays on your computer: cookies, logins, site data. This page says what is written where, what our server is told, and what it is never told. Where a protection has an edge, the edge is named here rather than left for you to find.
0600
Permissions on every file that holds a secret, on macOS and Linux
AES-256-GCM
How a session is encrypted before it enters your own cloud folder
24 hours
How long the app keeps working with no server before it asks again
2233
Automated checks: 440 on the server, 1602 in the app, 191 on this site

Your accounts live on your disk

Cookies, local storage, IndexedDB, saved logins and the browser's own databases live inside the profile directory on your machine. On ARMANOS Browser that is fork-profiles/<profile id> inside the app's data folder: ~/Library/Application Support/@armanos/desktop on macOS, %APPDATA%\@armanos\desktop on Windows, ~/.config/@armanos/desktop on Linux. Nothing there is uploaded by itself.

The app's own files sit next to that directory: profiles.json, proxies.json, auth.json, session-sync.json and about a dozen more. Each is written with permissions for the owner alone, and written through a temporary neighbour that is then renamed into place. A power cut leaves the old file whole rather than half of a new one. A file that will not parse is copied aside as .corrupt-<time> before anything writes over it, because an unreadable list of profiles is not the same thing as an empty one.

Passwords inside those files are sealed with the operating system keychain and stored behind an enc1: prefix. That covers the profile's own login password, the proxy password copied into a profile, and the passphrase for the session folder. If the system cannot seal, the app writes the value as before and does not pretend otherwise. A constant key in the source code would look like encryption and protect nothing.

DataOn your diskOn our server
Cookies, local storage, site sessionsใช่
A profile's login and passwordใช่
A profile's one time code keyใช่
Browsing history, tabs, downloadsใช่
Profile settings: name, group, scenario, template, proxyใช่ใช่
The session archive for a second machineIn your own cloud folder, encrypted

chmod does nothing on Windows

There file permissions belong to access lists that Node cannot reach, so the 0600 mode is real on macOS and Linux only. On Windows the protection is the user profile itself: %APPDATA% is not readable by another account without administrator rights. We would rather write that down than let the number imply more than it does.

The server is told settings, never sessions

When you are signed in, profile settings are mirrored to the server so a second machine and your teammates see the same list. The whole payload is short: name, group, scenario, fingerprint template, the fingerprint generation, proxy, tags and notes. A change sends only the fields you actually touched, so moving a profile into a folder cannot drag a stale proxy along with it.

What is absent from that list matters more. Cookies never leave. Neither does the profile's login pair, nor its one time code key, nor a single byte of the Chromium databases. The fingerprint itself is stored nowhere at all: it is derived from the profile id and its template at launch, which is why the same profile is the same device on your second machine.

The proxy password does travel, because the proxy belongs to the workspace and the second machine needs it to connect. It is sealed before it is written to the database, with a key that lives in the server's environment rather than in the database beside the data. The proxy list does not hand passwords back: a row says a password is set, and taking the working value is a separate action that lands in the audit log.

  • Your email and a bcrypt hash of your password

    Cost 10. The plain password is stored nowhere, and changing it revokes every refresh token you hold.

  • Your machines

    Name, operating system, last seen. Unlinking is remembered, so a machine cannot quietly register itself again.

  • The audit log of your workspace

    A new machine's registration records the address it came from, so a sign-in you did not make is visible to you.

  • One sync row per profile and machine

    When the session last went out or came in, and whether it worked. No file names and no contents.

  • Who holds a profile open

    The user, the machine name and the time the lock expires. This is what stops two live sessions of one account.

  • Plan, status and renewal date

    Card details never reach our database. They are held by the payment provider.

A session travels through a folder you own

Cookies stay on the machine, so sitting down at a second computer would mean logging into thirty accounts again. Session sync solves that without us holding your data. You point the app at a folder that something already synchronises, a cloud client or a network drive, and it puts one encrypted file per profile there. We never see that folder and do not want to.

The archive is encrypted with AES-256-GCM, and the key is stretched from your passphrase by scrypt with N=32768, r=8, p=1. A wrong passphrase gives an honest refusal rather than garbage. The passphrase itself stays on your machine, sealed by the system keychain, and never enters the folder. That is said on the screen too, because automatic upload cannot ask you at every profile close.

Changing the passphrase forgets what has already been sent, so every profile goes again under the new one. Otherwise the folder would hold a mix of two passphrases: the second machine would get an honest refusal while the first stayed silent.

  1. 1

    The profile closes

    Chromium keeps writing its databases for a couple of seconds, so the app waits four seconds and checks that the profile is still closed. An open profile is never copied.

  2. 2

    The archive is written next door, then renamed

    Half a file would mean a session that cannot be decrypted sitting on top of one that worked. The cloud picks up the renamed file the same instant.

  3. 3

    A small meta file records the size and the time

    The other machine compares the size on disk with the size in the meta. A bundle that is still downloading, or evicted by a storage saver, counts as not ready and nothing is overwritten.

  4. 4

    The second machine pulls before launch

    Same profile id, same derived fingerprint, and the cookies that came with it. The account sees the machine it saw yesterday.

  5. 5

    Both sides changed since last time? Nothing is touched.

    That means two machines worked in parallel. The app says so and lets you choose, instead of quietly overwriting someone's logins.

The cloud makes copies of its own

When two machines write at once, iCloud, Dropbox and OneDrive each leave a second file under their own naming instead of choosing a winner. Those copies used to be invisible, so a person believed the exchange had gone through while a colleague's work lay beside it. They are recognised now and reported. Leftover temporary files older than an hour are swept as well, because each one is a full copy of a session in space you pay for.

One profile does not open in two places

Two people on a team could open the same profile at once, and the site then saw one account live from two machines at the same moment. That is the exact behaviour accounts get blocked for, so the product would have been doing the thing it is bought to prevent. The lock lives on the server, because two machines cannot see each other.

Launching claims the lock, and the lock is held by time rather than by promise. The app renews it every 60 seconds, and it expires 3 minutes after the last renewal. A machine unplugged from the wall therefore frees its profile on its own, with no call to support. Closing the last window releases it at once, so a colleague does not wait out the timer for nothing.

A busy profile answers with a refusal that names the machine holding it and since when, and you can take it over deliberately. If someone takes it from you, your next renewal fails, the app closes the windows and tells you what happened. The same door refuses a launch when your machine has been unlinked, when the team seat is gone, or when the profile is beyond the quota you pay for. All three are checked again at every renewal, so a window opened before a downgrade cannot outlive it.

Lock lifetime
3 minutes from the last renewal
Renewal
every 60 seconds while the profile is open
Release
immediately when the last window closes
A start is refused when
the profile is open elsewhere, the machine is unlinked, the seat is gone, or the quota is exceeded
Without an account
there is no lock, and profiles are local to this machine

The identifier of an installation is random, created once and kept in the data folder. It used to be derived from the host name and user name, and two fresh laptops both called MacBook-Pro were then treated as one machine.

The server is not a permission to work

If our server is unreachable, the app launches the profile anyway rather than turning an outage into a work stoppage. The other half of that honesty: a machine that cannot reach the server cannot know that a colleague has the profile open. The lock defends you from a second window, not from a bad network.

Two steps at the door, and a list of machines

Your password is stored as a bcrypt hash at cost 10 and never in plain form. Ten wrong attempts close the account for 15 minutes, and the same counter covers the second step. Six digits are a million options, so a leaked password plus patient guessing would otherwise have been a full sign-in.

The second step is an ordinary authenticator code: RFC 6238, thirty seconds, six digits, readable by any of the usual phone apps. The arithmetic is written here rather than pulled from a library, because a library on the road to your password is code that updates itself one day. Turning the step on gives you ten recovery codes, of which the server keeps only SHA-256 hashes, compared in constant time. The shared secret is sealed in the database.

Between the two steps there is a short ticket, and it is signed with its own key. A ticket signed with the access key would itself be an access token, and one password would then be enough to get in. It lives five minutes and carries a mark of its own.

Sessions: the access token lives 15 minutes, the refresh token 30 days and rotates on every use. A refresh token presented twice revokes the whole family, because reuse means a copy is loose. Signing out revokes the token on the server as well as on disk, which is what matters when the file sits on a laptop you sold.

Password
bcrypt, cost 10
Wrong attempts
10, then 15 minutes closed
Second step
TOTP, 30 seconds, 6 digits
Recovery codes
10, stored as SHA-256 only
Ticket between the steps
5 minutes, its own signing key
Access token
15 minutes
Refresh token
30 days, rotated, reuse revokes the family
Sign-in handed from the site to the app
one code, 2 minutes
Entitlement re-check
every 5 minutes while online
Offline grace
24 hours
Renewal grace
3 days past the stored date

A clock wound backwards does not extend the offline window: a check dated in the future counts as no check at all.

The proxy password never reaches the command line

A separate browser process cannot take credentials the way an embedded one can, so they used to go into the launch string as --proxy-server=http://user:pass@host:port. Every process running as you can read another process's command line, and residential traffic is bought by the gigabyte. Worse, it did not even work: measured against the shipped 153.0.7978.0 build, that form made zero requests and loaded nothing, while the plain address made 13.

Now a small bridge runs on 127.0.0.1. It speaks to the browser without a password and to your proxy with one, so the command line carries only http://127.0.0.1:<port> and the real password never leaves the app's memory. The bridge accepts connections from loopback alone, and it asks for a one time pass that the profile's built-in extension supplies, so another program on the same machine cannot ride your paid channel. The bridge belongs to the profile, not to a single launch: a failed second launch used to kill it and leave the live window going out directly.

A proxy rule has to cover everything, and that took a measurement to notice. Written as http=host:port, a Chromium proxy rule applies to http:// addresses only. The https request went out directly, with your real address, while the app displayed the profile as proxied. The rule is a full address now, and the same measurement shows the CONNECT for the https request passing through the proxy.

WebRTC is the other classic path around a proxy. The engine launches with --force-webrtc-ip-handling-policy=disable_non_proxied_udp, so the browser does not open its own UDP route past the proxy while collecting addresses. You can check the result on your own machine with the leak tool on this site.

The Proxies screen of ARMANOS with five saved proxies and Test, Edit and Show profiles buttons on each row
The library holds a proxy once and links profiles to it. A row shows the address and the login. The password is not displayed here and is not returned by the server's list.

An exit through your own server is never bridged

An SSH exit always has a login, so the old rule considered the bridge necessary and raised it on port 22 of your server. Two things followed, and both were serious: the bridge address overwrote the tunnel address, and the SSH account password was sent in clear text as a Proxy-Authorization header to a port where no encryption exists yet. Those credentials belong to ssh, which presents them inside its own encrypted handshake.

The automation door stays shut until you open it

The app can be driven by scripts through a local HTTP API. It is off by default. When you turn it on it binds to 127.0.0.1 only, never to a public interface, and every call except status requires the token shown on that screen, passed as the token parameter, the x-api-token header or a bearer.

Loopback alone is not enough, and this is the part that is easy to miss. A page you visit can reach 127.0.0.1 from your own browser, and under a rebound DNS name it reaches it as same origin and can read the answers. For an anti-detect product that is the worst possible leak: status names the product and its version, and would tie every profile on the machine to one installation. So a request carrying browser-only headers, an Origin or a Sec-Fetch-Site other than none, or a Host that is not this loopback address, is refused.

The debugging channel that automation needs is opened with the same care. The port is 0 so the system picks a free one, the address is loopback, and the flag that would announce automation to every site is switched off. Measured on this engine: no port gives navigator.webdriver false, opening the port sets it true, and the flag brings it back to false.

The local automation API screen showing an enable toggle, the address http://127.0.0.1:50327 and an access token
Off until you switch it on. The address is loopback, and every call except status carries the token.

What we do not collect

There is no analytics in the app and none on this site. No tag manager, no session recorder, no crash reporter phoning home, no third party script of any kind. Search the repository for the usual names and you find them inside the tracker blocking rules, where they are the things being blocked.

The problem report is assembled locally: version, platform, chosen engine, the app's own notifications and a count of profiles. Anything that looks like a secret is removed by field name rather than by guessing at the value, because a password can look like an ordinary word. Password, token, cookie, seed, passphrase and the rest become [hidden], and the field name stays so you can see it was there. The report goes nowhere until you press send.

The free tools on this site measure inside your browser and make no requests at all. The leak tool has an empty ICE server list, because pointing it at someone's STUN server would tell that server about the visitor. Two stands fail the build if fetch, XMLHttpRequest, sendBeacon, WebSocket or EventSource ever appear in those files, and they refuse localStorage and cookies there as well.

  • The exit country check

    When a profile launches or you test a proxy, the app asks a public IP service over HTTPS, through that profile's own proxy. The answer sets the timezone and the language. Four providers are tried, so one refusal cannot decide a profile's identity.

  • The engine feed

    Update checks and engine downloads go over HTTPS, refuse redirects, and install only when the SHA-256 of the downloaded file matches the entry in the feed.

  • Our own server

    Profile settings, entitlement, team and support, and only while you are signed in.

  • Support, once you press send

    Your message travels with the scrubbed report. Nothing is sent in the background.

  • Script export drops the values

    An exported automation script keeps variable names and leaves their contents behind, because those contents are logins, passwords and invite codes.

This site is served with the doors closed

The dashboard lives on the same domain as these pages, so the site's headers matter as much as the server's. They are set in one place and go out on every response.

Fonts are part of that. They are downloaded at build time and served from our own domain, so opening a page does not tell a font host that you were here. There is no external stylesheet and no external script anywhere, which is why the policy can name our API as the only destination and refuse everything else.

The policy is not the strictest form available, and that should be said plainly. script-src still allows inline scripts, because the framework puts its own inline scripts into every page. Foreign scripts, foreign frames and foreign destinations are blocked; our own inline code is not. That is a real gap between what we ship and the ideal, and we would rather write it here than let the header imply more.

HeaderValueWhat it prevents
Strict-Transport-Securitymax-age=31536000; includeSubDomainsA first visit over http that someone on the path could intercept
Content-Security-Policydefault-src 'self', connect-src our APIA foreign script, a foreign frame, or data sent to a foreign address
X-Frame-Options and frame-ancestorsDENY and 'none'Your dashboard shown inside someone's page with your clicks landing on top
X-Content-Type-OptionsnosniffThe browser guessing a file's type and running it as a script
Referrer-Policystrict-origin-when-cross-originThe full address of a dashboard page leaking to sites you click through to
Permissions-Policycamera=(), microphone=(), geolocation=()A request for hardware this site has no reason to want

Deleting is deleting

Deleting a profile stops its browser first. A running Chromium keeps browsing and keeps writing into a directory that no longer has an owner, and only the system's task manager could stop it. It is also why erasing the data used to fail on Windows, where a live process holds its files open.

Then everything keyed to that profile goes with it: the Chromium directory, its extensions, its schedules, the counters in stats.json and pace.json, and the launch entries in notifications.json. That last one was the quiet part. Every successful launch appended the profile's name to a file that survives restarts, so an anti-detect browser was keeping a permanent history of profiles you believed were gone.

Delete permanently also clears the encrypted archive from your sync folder and the marks that point at it. Otherwise a file with live cookies stayed in the cloud folder, any second machine could bring the profile back, and the window was promising that nothing could be restored.

Signing out revokes the refresh token on the server, not only on disk: the tokens live in a file, and a laptop gets sold or repaired. Unlinking a machine in the dashboard frees its seat and is remembered, so that machine cannot silently re-register itself on the next heartbeat.

What this does not do

  • It does not promise that you cannot be identified. What is built here is isolation and a device that stays consistent. A site can still judge you by behaviour, by the account's own history, or by the reputation of the address you come out of.
  • The builds are not code signed or notarized yet. macOS and Windows warn on the first run, and you have to allow the app by hand.
  • The 0600 file mode is real on macOS and Linux only. On Windows chmod touches the read only attribute and nothing else, and the protection there is the user profile itself.
  • The passphrase for the session folder is kept on your machine, sealed by the system keychain where the system allows it. Automatic upload cannot ask you at every profile close, and the screen says so.
  • The site's policy still allows our own inline scripts, because the framework inlines them. It is not the strictest form of that header.
  • Without an account there is no lock between machines, and an unreachable server does not stop a launch. The lock protects you from a second window, not from a bad network.

How to check

Every claim above has a file that implements it and a stand that fails when it stops being true.

Files holding secrets are written for the owner alone, and atomically
apps/desktop/src/lib/secureFile.js · apps/desktop/test/secrets-on-disk.js
Cookies and profile logins never go to the server
apps/desktop/src/lib/profiles.js · apps/desktop/test/login-stays-local.js
The proxy password never enters the command line, and an authenticated proxy works
apps/desktop/src/lib/proxyAuthBridge.js · apps/desktop/test/proxy-auth-bridge.js · apps/desktop/test/мост-принадлежит-профилю.js
A session travels encrypted, and deleting clears it from the folder too
apps/desktop/src/lib/sessionSync.js · apps/desktop/test/session-sync-folder.js · apps/desktop/test/purge-clears-cloud.js
One profile, one open window
apps/server/src/profiles/profiles.service.ts · apps/server/test/profile-lock.js · apps/desktop/test/profile-busy.js
The free tools on this site make no network calls
apps/web/components/tools/FingerprintCheck.tsx · apps/web/test/инструмент-отпечатка.js · apps/web/test/тест-утечки.js

Check it on your own machine

Install the app, open a profile, and look at the permissions on the files and the address in the command line. Two profiles are free and no card is asked for.