Skip to content
AgentDock

Using AgentDock

Users and roles

Admins, operators and viewers, registration and audit.

AgentDock has local accounts (email and password), three roles, and per-project membership. Every request and every runner command is checked against the caller's role on the project, and everything that changes state is written to a tamper-evident audit log.

There is no SSO or OAuth login.

The first admin

A fresh install has no users: the database seed creates none. Create the first admin from the command line.

From a source checkout:

Terminal
bun run admin:create

It prompts for the email, an optional name and the password (twice). For a non-interactive setup, pass them as environment variables (and remove them afterwards):

Terminal
ADMIN_EMAIL=[email protected] ADMIN_PASSWORD='a long passphrase' ADMIN_NAME='You' \
  bun run admin:create

Inside the Docker stack, run the compiled CLI in the API container:

Terminal
docker compose exec api node dist/src/cli/admin-create.js

The command creates an active admin and refuses an email that already exists. See Docker Compose.

Registration

Registration is closed by default. While it is closed, the sign-up form answers registration_closed. An admin opens it under Admin → Settings → Open registration.

Even with registration open, nobody gets in on their own:

  1. A person registers at /register with an email, an optional name and a password (12 to 256 characters).
  2. The account is pending. Signing in sends them to /pending, which says they are waiting for an administrator.
  3. An admin opens Admin → Users → Pending and clicks Approve, which activates the account and assigns its role in one step. Or Reject.

A rejected email cannot register again until an admin deletes that account. Open registration only while you are onboarding people, then close it.

Account statuses are pending, active, rejected and disabled. Only active accounts can sign in.

Roles

Roles are global, and an admin picks one per user: admin, operator or viewer.

ViewerOperatorAdmin
Projects visibleWhere they are a memberWhere they are a memberAll, no membership needed
Fleet, queue, approvals list, history, activity, sessions, usageReadReadRead
Live worker paneYesYesYes
Start and stop the orchestrator, stop a slot, message a workerNoYesYes
Choose bypassPermissionsNoNoYes
Approve or request changes on a pull requestNoYesYes
Create issues, refresh the queue or the projectNoYesYes
Search, inspect, install (to the project) and run skillsNoYesYes
Create and manage schedulesNoYesYes
Terminal attachNoNoYes
Install skills into a runtime profileNoNoYes
Connect and remove projects, project settings, membersNoNoYes
Machine-wide sessions with no projectNoNoYes
Admin pages: runners, users, settings, integrations, budgets, prices, schedules, auditNoNoYes

In short: viewers read, operators steer the projects they belong to, admins run the instance.

Per-project overrides

Membership is per project (Project → Settings → Members). A member can carry a Role override, and the effective role on that project is the lower of the global role and the override. An override can only lower a role, never raise it:

  • an operator with a viewer override is a viewer on that project;
  • a viewer with an operator override is still a viewer.

Admins are admins on every project, whatever their membership row says.

Hidden projects return 404

A project you are not a member of is invisible. Its pages and API routes answer 404, not 403, so its existence is not revealed. Admin-only routes answer 403 to non-admins whether or not the project exists.

Changing roles and accounts

On Admin → Users:

  • A change to or from admin asks for confirmation.
  • Disable signs the person out everywhere at once and keeps everything they own. Enable brings them back.
  • Delete removes the account with its memberships, notification settings, Telegram link, personal budgets and the schedules they created. Their work (sessions, runs, approvals, audit records) stays.
  • You cannot disable or delete your own account; another admin can.
  • The last active admin cannot be demoted, disabled or deleted.

Sign-in and sessions

RuleValue
Password hashingargon2id
Session cookiead_session, httpOnly, SameSite=Lax, Secure when APP_ENV=production
CSRFad_csrf cookie echoed in the X-CSRF-Token header on every write
Idle expiry7 days
Absolute expiry30 days
Rate limit10 requests per minute per IP on login and register
Lockout10 failed logins in a row lock the account for 15 minutes

A wrong email and a wrong password give the same error, so the login form does not reveal which accounts exist. Changing your password signs out your other sessions. Disabling an account or changing its role signs that person out everywhere. Account lists your signed-in devices and lets you revoke any of them.

Behind a reverse proxy, set TRUST_PROXY so the rate limit sees the real client IP. See Configuration reference.

Audit log

Every state-changing action is recorded: sign-ins and failed sign-ins, user and role changes, project and member changes, every runner command (start, stop, message, skill install and run, approvals, terminal attach), schedule and budget changes, integration changes and more. A record holds the actor, action, target, project, before and after values, the result (ok, denied or error), and the IP and user agent for actions a person took.

Secrets never enter the log: fields such as password, token, secret and code are replaced with [redacted] before the record is written. The full text of a message to a worker is stored only as a hash.

Tamper evidence

The audit_records table is append-only twice over:

  • A hash chain. Each record stores hash = sha256(prevHash ‖ canonical(record)), with the first record chained to 64 zeros. Changing or deleting any row breaks every hash after it.
  • A database trigger rejects UPDATE, DELETE and TRUNCATE on the table. Anyone who bypasses it (a superuser dropping the trigger) still breaks the chain.

No role can edit or delete audit records through the API, and no retention job ever removes them.

Verification

A nightly job walks the whole chain and recomputes every hash. It runs at AUDIT_VERIFY_CRON (default 0 3 * * *) and stores the result. Admin → Audit shows the last verification and a Verify now button. A broken chain names the first broken record and explains what it means.

The same page filters records by time range, action, result, actor, target and project, and Export CSV downloads the matching rows (up to 100,000 per export, with spreadsheet formula injection neutralised).

Next steps