Arc: a secrets manager that encrypts everything in the browser
A manager for your secrets.
At a glance
- What is it?
- Arc is a self-hosted password and secrets manager made of a Go REST server and an HTML5 client that performs all encryption in the browser with AES256 in GCM mode, so the server stores ciphertext and never holds your key. It is a single-binary, single-user design aimed at isolated hardware rather than at team vaults, and its last release predates the last commit by a wide margin.
- Who is it for?
- Arc is a good fit for a single person who wants their passwords, notes and files in one encrypted place on hardware they control, and for a self-hosted setup on an isolated device reachable only over a private network. It is a poor fit for a team that needs shared access, audit trails or role-based permissions, and it is a poor fit if you would rather not hold a master encryption key that cannot be recovered.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 97 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A server that only ever holds ciphertext
Arc has two halves and the split is the whole design. The server is a RESTful API written in Go that exposes read and write primitives for encrypted records. The client is an HTML5 and JavaScript application that runs in every modern browser and is served by that same server. The README describes the project as a manager for your secrets, and the intent is a single storage and manager for passwords, encrypted notes, files and all the other secret things.
The cryptographic part is stated in detail, which is unusual and welcome. Records are generated, encrypted and decrypted client side. The algorithm is AES256 in GCM mode, the key derivation function is PBKDF2 with 10000 iterations, and the whole thing is built on WebCrypto, the browser's own cryptography API. The consequence is architectural: what the server stores is AES256 encrypted and compressed raw data, and the server never has the key that opens it.
That design has a clear appeal. A compromised server hands an attacker a database of blobs. It also has a clear cost, and the cost is the key itself. The server has to authenticate you before it will return ciphertext, and the key you use to decrypt lives in your browser, not in the server's configuration, which means the whole system depends on one secret that exists nowhere else. The README is explicit that this second key is not used to log in to the system itself, but to encrypt and decrypt your records, and that you may specify different keys on different logins as long as you remember which key encrypted which record. That sentence is the honest warning label on the whole project: a forgotten key means a lost record, and a mismatched key means a record that will not open.
The Go module is small and tells you the shape of the thing. It requires go 1.22.0 with a go1.22.2 toolchain, and the dependencies are gin for the HTTP layer, golang-jwt for token authentication, pelletier/go-toml for the configuration file, theckman/go-flock for file locking, golang.org/x/crypto, gomail for email, and tailscale.com for the private network integration.
The record types and the widgets around them
The client is described as an intuitive management system equipped with a set of UI widgets, and the list is more than a row of text fields.
Plain inputs and text areas cover short values and long text. There are custom file attachments, so a record can carry a file rather than a string. There is a markdown editor with preview and full screen mode, and a separate HTML editor with the same two modes, which makes Arc a place to keep a rendered document rather than just a blob of text. There are custom lists, so a record can be structured. There is a password field with strength estimation and a random password generator, which is the expected behaviour for a password manager and the reason the bcrypt hash used for the login is separate from the record contents.
Two of the widgets are unusual. One is a Bitcoin wallet address with an auto updating balance, which the client queries and displays. The other is a manager for Time-based One-time Password codes, implemented as the TOTP algorithm per the TOTP RFC draft, and the README says the component produces the same codes as the Google Authenticator app and can be used for 2FA. Storing a TOTP seed in a password manager is a defensible choice for some people and a debatable one for others, since it collapses the second factor into the first.
The records themselves are created with optional expiration dates, and they can be arranged and edited. The server tracks that expiry, which is where the notification events come in.
Behind the widgets, the repository shows a conventional Go web application: a cmd directory for the server entry point, controllers, a db package, middlewares, an events package, a pgp package, a scheduler, a tls package, an updater, utils, and a webui directory that holds the client and is compiled into the binary. The Makefile has an assets target that regenerates webui/compiled.go with go-bindata, which is how the client ends up served by the server itself rather than from a separate web server.
Running Arc with Docker and a hand-written config.toml
The README calls the Docker container the simplest way to run arc, and the sequence is: copy sample_config.toml into your own config.toml, change the important fields, then start the server.
The three fields that matter are the secret, which is a key used for token authentication, the username, and the password, which is the bcrypt hash of the authentication password. The hash is not written by hand, it is generated with the password subcommand of the image:
docker run -it evilsocket/arc:latest password "your-new-password" <optional-cost>The optional cost is an integer in the interval 4 to 31 that determines the bcrypt cost. Leaving it unset means the tool's default, and setting it higher buys resistance to offline cracking of the hash at the price of a slower login.
With the configuration written, the server starts as a container with three volumes:
docker run -it --network host \
-v /path/to/your/config.toml:/etc/arc/config.toml \
-v /path/to/data:/arc \
-v $HOME/.config/tsnet-arc:/root/.config/tsnet-arc \
evilsocket/arc:latestThe first mount is your configuration, the second is the data directory that holds the records, and the third is the Tailscale state, which is what lets the container join a tailnet. The network mode is host, so the container uses the host's network stack directly.
You then browse to https://localhost:8443/, or whatever address and port you configured, and log in with the configured credentials. The README tells you to add the generated HTTPS certificate as an exception in your browser, which tells you the server presents a certificate the browser does not trust by default. The Dockerfile confirms the shape of the service: it exposes 8443 and 443 and sets the binary as the entry point.
There are two other routes. Binary releases are published for several operating systems and architectures, including ARM, ARM64 and MIPS, and from source you install the server with a single command, which needs Go 1.22 or newer:
go install github.com/evilsocket/arc/cmd/arc@latestTwo keys, one login: the design that decides the risk
Most of the confusion people have with Arc comes from a single design decision, so it is worth spelling out what happens at login.
There are two secrets. The first is the login password, stored on the server as a bcrypt hash, used with the username to obtain a session. The second is the encryption key, supplied at login time, never stored on the server, and used in the browser to encrypt and decrypt records. The README notes the second key is not used to log in to the system itself but to encrypt and decrypt your records client side, and that you can use a different key each time you log in provided you remember which key belongs to which record.
That flexibility is unusual and it cuts both ways. Using one key for everything is simpler, and the UI presumably makes it straightforward to remember which is which, but the documentation does not describe a key label, a key identifier stored with the record, or a key recovery mechanism. There is also no envelope encryption here, where a random data key is wrapped by a master key, and no key escrow.
For the person running the server, the operational consequence is that the login password protects the ciphertext, and the encryption key protects the plaintext. Losing the encryption key loses the records, and no support path exists for that. A backup of the data directory is therefore only useful in combination with a record of which encryption key encrypted which record, and the README does not tell you how to keep that mapping safely. This is a real operational cost of the design, not a defect in the implementation.
The other side of the design is the one that makes the tool interesting. The server holds ciphertext, so a stolen disk image is not immediately a list of passwords, and a compromised API returns encrypted blobs. The server does hold a bcrypt hash of the login password, a token signing secret, and the JWTs it issues, and the repository includes an updater package, which means the server can phone home to report a new version being available, an event the README lists as update. That updater is worth reading before you deploy, because it is outbound network activity from a device that holds your secrets.
Tailscale, Headscale and the isolated hardware ambition
Arc's stated deployment picture is unusual. The README argues that arc should ideally run on dedicated portable hardware such as a Raspberry Pi Zero, where it can be reached over Bluetooth through a modern browser once configured with btnap, and that the more isolated the hardware, the better. Precompiled binaries exist for several operating systems and architectures including ARM, ARM64 and MIPS, so the README says it can run on pretty much anything with a CPU, from a smartphone to a router to a Mac or a Windows computer.
The network side has first class support. A tailscale configuration block can make the instance a node on a tailnet and invisible outside it:
# .... snippet ....
[tailscale]
# If true, will run this as a tailscale server node and won't be visible outside the tailscale network.
# In order to authenticate the node, set the TS_AUTHKEY environment variable or follow the onscreen instructions.
enabled = false
# Tailscale hostname, if left empty the system hostname will be used.
# NOTE: Make sure that HTTPS certificates are enabled for this tailscale host and that the hostname
# matches the certificate.
hostname = "stevie"
# If set, it overrides the Tailscale control URL, can be used for Headscale and alikes.
url = ''
# .... snippet ....Three details in that block matter operationally. Enabling it makes the server a tailscale node, so it is only reachable from inside the tailnet. The TS_AUTHKEY environment variable is how the node authenticates, which means that variable is a credential to protect. And the url field overrides the control API URL, which is what allows a self-hosted Headscale control server to be used instead of the hosted one, for people who do not want to depend on an external coordination service.
The hostname note is a caveat: HTTPS certificates have to be enabled for that host and the hostname has to match the certificate, otherwise the client you open will not trust the server.
The whole shape is a personal appliance: one device, one user, one vault, reachable only over a private network. That is a coherent design for the stated use case, and it also rules out the multi-user scenarios that most team password managers are built for.
Lifecycle events, notifications and the parts the README does not cover
The server tracks a small set of lifecycle events. A successful authentication is login_ok, a failed one is login_ko, an invalid JWT token presented to the API is token_ko, a new version being available is update, and a record reaching its expiration date is record_expired.
If configured to do so, the server creates brief reports of these events and sends them to the user and the client over three different channels. The events package in the repository, along with the gomail dependency in the module file, suggests email is one of them, but the documentation does not confirm it, and it would be wrong to assume the other two.
That is the pattern worth noticing across this README. It is precise about cryptography, about configuration keys, about Docker mounts and about flags, and thin about everything else. There is no documented API reference beyond the fact that the server exposes read and write primitives for encrypted records, no description of the record schema, no list of the configuration keys beyond the ones the walkthrough touches, and no statement of how many users the system supports. The events in the repository, the controller layer and the scheduler are the only hints about the rest.
The scheduler directory, alongside the events and updater packages, suggests some recurring work happens server side, most likely the expiry checks that produce record_expired and the version check that produces update. The README does not say so, and the difference matters: a version check that reaches the internet is a network dependency on a device that may be deliberately isolated, and an expiry job that runs on a schedule is a component whose timing you would want to know about before relying on expiry as a security control.
Licence, packaging and a release history that has slowed down
The licence is GPL-3.0, which is a strong copyleft licence. For a server and web client that you deploy for your own use, that is not a practical problem. It does become one if you distribute a modified version, because the copyleft obligation extends to the derived work, and this description covers the published terms rather than legal advice.
The packaging is built for a small device. The Dockerfile is a single-stage build on golang:1.22.2 that compiles cmd/arc and exposes 8443 and 443, with no runtime base image, so the container holds a Go binary and the config and data you mount. The Makefile adds the pieces a small server usually needs: a build target, a test target running go test over the tree, a run_dev target that uses a dev_config.toml, and an install target that copies the binary into /usr/local/bin, grants it the cap_net_bind_service capability so it can bind a low port, copies an arc.service unit into /etc/systemd/system and enables the service with systemctl. The go.mod also lists elazarl/go-bindata-assetfs, which is how the compiled web UI is served as assets.
The release record is the thing to weigh. Version 1.4.0 was published on 2024-04-07, and before that the project published v1.3.6 and v1.3.5 on the same day in January 2021. The repository is not archived and the last push was on 2026-07-18, so work has continued after the last tag, but the tag list has been quiet for more than two years. The application version and the released binary therefore need not match, and the updater event exists precisely because a running instance may be behind.
For a self-hosted appliance you run on a device you own, that is an acceptable posture. For anything that has to keep pace with a security advisory in its Go dependencies, a project whose release tag is two years old deserves a closer look at the commit log before you rely on it.
Editorial conclusion
Arc is a good fit for a single person who wants their passwords, notes and files in one encrypted place on hardware they control, and for a self-hosted setup on an isolated device reachable only over a private network. It is a poor fit for a team that needs shared access, audit trails or role-based permissions, and it is a poor fit if you would rather not hold a master encryption key that cannot be recovered. Before deploying it, generate the bcrypt hash with the password subcommand, set the secret and username in config.toml, and confirm that the volume mounted at /arc is on persistent storage that you back up, because the server stores the only copy of the encrypted records.
Frequently asked questions
How does Arc encrypt my passwords and notes?
Records are generated, encrypted and decrypted in the browser using AES256 in GCM mode with a PBKDF2 derived key using 10000 iterations, all through WebCrypto. The server stores the encrypted and compressed data and never receives the encryption key.
What is the difference between the login password and the encryption key in Arc?
The login password is stored on the server as a bcrypt hash and is used to authenticate, while the encryption key is supplied at login and stays in the browser to encrypt and decrypt records. The README says you can use a different key per login as long as you remember which key encrypted which record.
How do I run the Arc server with Docker?
Copy sample_config.toml to config.toml, set the secret, the username and a bcrypt password hash generated with the password subcommand, then start the container with your config, your data directory and the tsnet-arc state mounted. The interface is served over HTTPS on port 8443.
Can Arc be reached only over a private network?
Yes. The tailscale configuration block turns the instance into a tailscale node so it is not visible outside the tailnet, and the url field can override the control API URL to point at a self-hosted Headscale server instead.
What record types can Arc store?
Text inputs, text areas, file attachments, a markdown editor with preview, an HTML editor with preview, password fields with a random generator, custom lists, a Bitcoin wallet address with auto updating balance, and TOTP codes that match the Google Authenticator app.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/evilsocket-arc)Was this useful?