Skip to content

Security model ​

Walis executes arbitrary shell. That single fact drives every decision below.

Who can run what ​

LayerControl
Sign-inon by default. The first administrator needs the setup code the server prints at startup (a development server skips it on the machine itself). Administrator actions that outlive the session (API keys, password and OTP resets, granting admin, turning sign-in or auditing off) ask for the password again
Rolesadmin, operator, viewer
Per jobeach job has a view role and a run role
Pluginsinstalling requires admin — a plugin is code

A job you cannot view returns 404, not 403, so its existence is not disclosed.

Transport ​

A fresh install answers plain HTTP from any address, so that the first connection works without a certificate. Session cookies are HttpOnly and SameSite=Strict either way, and are marked Secure when the request really arrived over TLS.

Once TLS is in place, set Security__RequireHttps=true. A request to /api, /mcp or /metrics from another machine that did not arrive over HTTPS is then refused with 426, and every cookie is Secure. TLS terminated at a reverse proxy counts only when the proxy is listed in Security__TrustedProxies (127.0.0.1,::1 for a proxy on the same machine); forwarded headers from anywhere else are ignored.

AllowedHosts limits the host names Walis answers to. The default accepts any.

Build isolation ​

Builds run as the service account, which means they could otherwise read Walis's own database — where password hashes, API key hashes and cloud credentials live.

The build environment is an allow-list. A build inherits PATH, USER, LOGNAME, SHELL, locale, temp directories and the TLS certificate variables, and nothing else. The service's connection strings and secrets are not filtered out of the build environment — they were never put in it.

On macOS, every step is wrapped in sandbox-exec, denying reads and writes to the data directory, the database directory, backup destinations, the installation directory (current, releases, var), the service account's home directory and any path you add, before re-allowing the job's working directory; it also denies sending signals to other processes, Mach lookups and reading other processes' info. Individual jobs can opt into Deny network, or be marked by an administrator as Trusted job: run without sandbox (for self-update).

Everywhere else, a build runs unisolated by default. Walis has no equivalent sandbox on Windows or Linux, and a remote runner applies none of its own, so there a script can read whatever the account running it can — on the built-in runner, that includes the data directory. The build log records it, and the settings page warns about it. This default exists so that a first build works on every platform; it assumes the people who can edit jobs are trusted (when started in Production listening on a non-loopback address, a brand-new database starts with Refuse to run without an isolation boundary turned on until an administrator explicitly acknowledges the risk).

Where they are not, an administrator turns on Refuse to run without an isolation boundary. Then the built-in runner on Windows or Linux refuses to start builds, and a remote runner is handed work only when it is isolated externally (a container, a VM), declares so, and an administrator lists its name under externally isolated runners. A runner that merely claims external isolation in its request is refused; the approval has to come from the server's own settings.

You can add more denied paths, and restrict jobs to an allow-list of working directories.

Even with the sandbox, this is not full isolation — same user, same machine. It stops the realistic problem (a build script casually reading the database) rather than a determined attacker who already runs code as you.

Automatic triggers ​

A downstream job is triggered by a build, not by a person, so there is no session to check. Walis uses the upstream job's run role as the caller's authority: a job that an operator can run cannot trigger a job that requires an administrator. Without that, adding one line to a low-privilege job would be a side door onto a privileged one.

Chains stop at ten deep, so a cycle terminates.

Keys and secrets ​

API keys are walis_<prefix>_<124-bit secret>. The database keeps only a domain-separated SHA-256 lookup hash — a fixed-cost hash is correct here because the secret is high-entropy and machine generated, unlike a password. Plaintext is shown once.

Each key has a scope. New keys default to mcp, which reaches /mcp and nothing else. A runner key is bound to one runner name and can only register, heartbeat, claim and report for it. No key of any scope can touch sign-in, accounts, or key lifecycle. Both /mcp as a whole and individual API keys can be pinned to source IPv4/IPv6 addresses or CIDR ranges.

Secrets stored in the database (AI provider keys, secret environment variables, passwords and cloud credentials in settings, password build parameters and TOTP secrets) are encrypted at rest with AES-256-GCM (enc:v2:, bound to the table and column) using Security:EncryptionKey or <data directory>/secret.key (0600). password build parameters are stored in a column marked [JsonIgnore], so they cannot leave through an API response, and their values are added to the log masking list.

Plugins ​

Plugins return a string; Walis runs it. The sandbox (Jint, no I/O, four resource limits) matters, but the real control is that you can read the produced script before installing, and that capabilities must be declared and approved.

Backups ​

Archives contain password hashes and cloud keys, so a passphrase is mandatory as soon as backups are enabled or any destination is configured. Encryption is authenticated (PBKDF2-SHA256, AES-256-GCM chunks with an authenticated end marker), so a modified or truncated archive is rejected before anything is restored; the restore endpoint accepts only this format.

Security center & posture monitoring ​

Walis includes an admin-only Security Center (/security) and an aggregated endpoint (GET /api/security-center) for continuous posture evaluation:

  1. Posture Assessment: 10 standard checks covering enforced sign-in, OTP coverage, security alert dispatch, build isolation boundaries, working directory restrictions, webhook secrets, MCP firewalling, master key integrity, and stored secret decryptability.
  2. Privileged & Risk Inventory: Real-time auditing of unsandboxed or network-enabled jobs, trusted plugins with external egress privileges, high-risk unconstrained API keys, account permissions, and runner isolation states.
  3. Threats & Lockout Management: Tracks accounts and source IPs temporarily blocked by brute-force throttling rules, providing one-click manual release (DELETE /api/security-center/lockouts?key=...).
  4. Security Policies & Audit Trail: Real-time policy adjustments coupled with multi-criteria filtering and CSV export for all operational audit records.

Released under the MIT License.