> For the complete documentation index, see [llms.txt](https://docs.keeper.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keeper.io/release-notes/developer-tools/automator/automator-18.0.0.md).

# Automator 18.0.0

Released on October 29, 2026

This is a major, security-focused release of Keeper Automator. It adds cryptographic verification of every request sent from Keeper to Automator, per-configuration encryption keys, and support for running the Automator container as a non-root user. All Automator deployments should be upgraded to version 18.0.0 as soon as possible, or during the next scheduled maintenance window.

{% hint style="danger" %}
As documented, ensure all Automator deployments adhere to our recommended [Ingress Requirements](/sso-connect-cloud/device-approvals/automator/ingress-requirements.md), ensuring all inbound traffic is restricted to Keeper's infrastructure.

**Important:** Review the **Critical Upgrade Instructions** below before upgrading, especially if Automator sits behind a reverse proxy, load balancer, or web application firewall.
{% endhint %}

### New Features

* **KAA-191: Optional non-root container operation.** The Automator Docker image now includes a dedicated `automator` account (UID/GID `10001:10001`), so customers can choose to run the JVM as a non-root user. The image still runs as root by default, so existing deployments can upgrade without changing their user, port, or storage configuration. An optional `docker-compose.nonroot.yml` override is now included in the distribution ZIP. See "Running Automator as a non-root user (optional)" below.

### Improvements

* **KAA-188: Thread-safe endpoints.** Automator endpoints now handle concurrent requests safely.
* **KAA-197: Clearer permission logging.** Logging now states clearly when a non-root Automator process cannot read a root-owned configuration folder, which makes permission problems easier to diagnose.
* **KAA-199: Smaller base image.** The `musl-utils` and `java-common` packages have been removed from the Alpine base image.

### Security Updates

* **KAA-183: Keeper request signature verification.** Automator now verifies that every incoming request, on all endpoints except `/health`, was signed by Keeper. Verification uses a built-in Keeper public key and supports key rotation. Each request carries a signature, key ID, timestamp, and nonce. Requests that are unsigned, changed in transit, older than the allowed time window, or replayed are rejected. See "Request signature headers" below.
* **KAA-174: Per-configuration transmission keys.** Each Automator configuration now gets its own unique AES transmission key, replacing the single key that was shared across the whole instance. Existing enrolled configurations are migrated automatically to keep their current key, so traffic is not interrupted. New configurations, including deleted and re-created ones, get a newly generated unique key.
* **KAA-193: Signed Automator ID enforcement.** Automator endpoints now ignore any Automator ID in the request URL. They use only the ID inside the signed request.
* **KAA-187: Test mode hardening.** Device approval endpoints can no longer switch Automator into test mode.
* **KAA-192 / KAA-196: Third-party library and container image updates.** Library dependencies and native image packages have been upgraded according to our regular SDLC process. Docker image vulnerability scanning has been added to the release pipeline.

### Critical Upgrade Instructions

#### 1. Request signature headers

Starting with version 18.0.0, every request from Keeper to Automator includes the following headers:

* `X-Automator-Request-Signature`
* `X-Automator-Request-Key-Id`
* `X-Automator-Request-Timestamp`
* `X-Automator-Request-Nonce`

**Passing requests through a reverse proxy**

A reverse proxy, load balancer, or web application firewall between Keeper and Automator must pass all four headers and the request body without changing their values or contents. Removing a required header, replacing the signature, or changing data covered by the signature causes verification to fail.

Keeper and Automator must have accurate clocks so that requests remain within the 60-second window. Each retry must use a new nonce, a current timestamp, and a new signature. Resending a previously accepted request with the same headers is rejected while its nonce remains in the cache.

#### 2. Running Automator as a non-root user (optional)

The Docker image defaults to root for compatibility. This section describes the opt-in non-root configuration for customers who want the JVM to run as UID/GID `10001:10001`.

The non-root process needs write access to `/usr/mybin/config` to persist state and `/usr/mybin/logs` to write rolling logs. Packaged settings, libraries, and the application JAR remain root-owned and read-only.

**Option 1: Keep the internal listener on port 443**

Use `NET_BIND_SERVICE` to allow the non-root JVM to bind port `443`. For direct Docker, a named config volume can be initialized and mounted as follows:

```
$ docker volume create automatordata
$ docker run --rm -u 0:0 -v automatordata:/config alpine:3.20 \
    chown -R 10001:10001 /config
$ docker run -d \
    --user 10001:10001 \
    --cap-drop=ALL \
    --cap-add=NET_BIND_SERVICE \
    -p443:443/tcp \
    -v automatordata:/usr/mybin/config \
    --name "Keeper-Automator" \
    --restart on-failure:3 \
    keeper/automator
```

For Docker Compose, use the optional `docker-compose.nonroot.yml` override included in the distribution ZIP:

```
$ docker compose -f docker-compose.yml -f docker-compose.nonroot.yml up -d
```

The override sets UID/GID `10001`, drops all capabilities, adds `NET_BIND_SERVICE`, and runs a one-shot `config-permissions` service to initialize the named config volume before Automator starts.

For bind-mounted config directories, make the host path writable by UID/GID `10001` before starting the container:

```
$ sudo chown -R 10001:10001 /path/to/automator/config
```

If logs are also mounted from the host, update that directory too:

```
$ sudo chown -R 10001:10001 /path/to/automator/logs
```

`docker cp` creates files in the container as root by default. For the direct Docker example above, stop Automator after copying the certificate files and use a temporary root helper against the mounted config volume to set ownership and private file permissions:

```
$ docker stop Keeper-Automator
$ docker run --rm -u 0:0 -v automatordata:/config alpine:3.20 sh -c \
    'chown 10001:10001 /config/ssl-certificate.pfx && chmod 0600 /config/ssl-certificate.pfx'
$ docker start Keeper-Automator
```

If a password file is used, include it in the helper command as well:

```
$ docker stop Keeper-Automator
$ docker run --rm -u 0:0 -v automatordata:/config alpine:3.20 sh -c \
    'chown 10001:10001 /config/ssl-certificate.pfx /config/ssl-certificate-password.txt && chmod 0600 /config/ssl-certificate.pfx /config/ssl-certificate-password.txt'
$ docker start Keeper-Automator
```

For Compose, restart the optional root-owned initializer after copying the files; it fixes ownership and sets the PFX/password file modes to `0600`:

```
$ docker compose -f docker-compose.yml -f docker-compose.nonroot.yml stop automator
$ docker compose -f docker-compose.yml -f docker-compose.nonroot.yml run --rm --no-deps config-permissions
$ docker compose -f docker-compose.yml -f docker-compose.nonroot.yml up -d automator
```

Verify the opt-in process identity and runtime directory permissions:

```
$ docker exec Keeper-Automator id
$ docker exec Keeper-Automator ls -ldn /usr/mybin/config /usr/mybin/logs
```

The process should report UID/GID `10001`, and both runtime directories must be writable by that user.

**Option 2: Capability-free fallback on port 8443**

If the deployment platform cannot provide `NET_BIND_SERVICE`, the non-root JVM can listen on unprivileged port `8443`. Change the internal/container target and `AUTOMATOR_PORT`; the public listener can remain on `443`:

```
$ docker run -d \
    --user 10001:10001 \
    --cap-drop=ALL \
    -e AUTOMATOR_PORT=8443 \
    -p443:8443/tcp \
    -v automatordata:/usr/mybin/config \
    --name "Keeper-Automator" \
    --restart on-failure:3 \
    keeper/automator
```

For Compose, set `AUTOMATOR_PORT=8443` and map the existing host port to container port `8443`, for example `8089:8443`. Update backend targets and health probes to `8443` as well. Keep the public listener and Commander URL unchanged when the external port remains unchanged. Drop all capabilities; do not add `NET_BIND_SERVICE` for port `8443`. If adapting the `docker-compose.nonroot.yml` override, remove its `NET_BIND_SERVICE` addition for this fallback.

Example Compose service settings (combine with the config-volume ownership initialization described above):

```
services:
  automator:
    user: "10001:10001"
    environment:
      AUTOMATOR_PORT: 8443
    cap_drop:
      - ALL
    ports:
      - "8089:8443"
    volumes:
      - automatordata:/usr/mybin/config
```

**Kubernetes non-root deployment**

Merge the following relevant fields into the existing Deployment and Service manifests. This example shows non-root binding port `443` with `NET_BIND_SERVICE`. For the capability-free fallback, set `AUTOMATOR_PORT`, `containerPort`, Service `targetPort`, and probe ports to `8443`, and omit the `NET_BIND_SERVICE` addition. The Service's public `port` can remain `443`. Keep the pod non-root and ensure its config volume is writable by UID/GID `10001`:

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: automator-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: automator
  template:
    metadata:
      labels:
        app: automator
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        fsGroupChangePolicy: OnRootMismatch
      containers:
        - name: automator
          image: keeper/automator:latest
          env:
            - name: AUTOMATOR_PORT
              value: "443" # set to "8443" for the capability-free fallback
          ports:
            - name: https
              containerPort: 443 # set to 8443 for the capability-free fallback
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop:
                - ALL
              # Include this add only when AUTOMATOR_PORT/containerPort is 443.
              add:
                - NET_BIND_SERVICE
---
apiVersion: v1
kind: Service
metadata:
  name: automator-service
spec:
  type: NodePort
  selector:
    app: automator
  ports:
    - name: https
      port: 443
      targetPort: 443 # set to 8443 for the capability-free fallback
      protocol: TCP
      nodePort: 30000
```

Configure readiness/liveness probes to the selected container port. Check storage-driver behavior: `fsGroup` is not honored by every driver, and existing PVC contents may require a one-time ownership migration. Ensure certificate Secret files are readable by UID/GID `10001` without making private key material world-readable.

### General Update Instructions

* For container deployments, update the container and restart the service. Then run "automator setup" followed by "automator init" as described in the relevant section "Installation Method" for your type of deployment.
* For other deployment methods, see the Automator documentation to update.

See the [Keeper Automator Documentation](/sso-connect-cloud/device-approvals/automator.md) for full deployment and configuration management instructions.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.keeper.io/release-notes/developer-tools/automator/automator-18.0.0.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
