> ## Documentation Index
> Fetch the complete documentation index at: https://docs.squasher.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SSH connector setup

> Install the outbound connector and forced target runner for gated agent remote access.

<Warning>
  Remote access is a gated preview. Keep each target disabled until all steps on this page pass.
  These controls are available only for an enabled project. If the protected bundle or fingerprint
  actions are not present, stop before target bootstrap. Human approval, fingerprint reveal, and
  target confirmation require a five-minute, one-time email step-up for the current signed-in user.
</Warning>

The connector keeps the destination, SSH user, and SSH private key in your network. Squasher stores its public identity and safe target metadata. The dashboard does not install the connector or produce an `authorized_keys` line.

## Before you start

You need project-owner access with `remote_access:manage`, a hardened Linux connector host, administrative access to the target, a separate trusted console for the target, and Cosign `v3.1.3`. The release commands below work only after the gated signed version is published.

The examples use `/var/lib/squasher-connector`, `/var/lib/squasher-target/config.json`, and `/run/squasher-target/target.sock`. Do not install the connector on a developer workstation. Do not reuse a personal SSH key.

## 1. Install and enroll the connector

Download the versioned archive on the connector host. Verify the pinned public key, signatures, checksum, software bill of materials (SBOM), and build provenance before extraction. Do not pipe a network response into a shell.

```bash theme={null}
VERSION=v0.1.1
case "$(uname -m)" in
  x86_64) TARGET=x86_64-unknown-linux-gnu ;;
  aarch64|arm64) TARGET=aarch64-unknown-linux-gnu ;;
  *) echo "unsupported architecture" >&2; exit 1 ;;
esac
ARCHIVE="squasher-remote-access-${VERSION}-${TARGET}.tar.gz"
SBOM="squasher-remote-access-${VERSION}-${TARGET}.cdx.json"
PROVENANCE="squasher-remote-access-${VERSION}-${TARGET}.provenance.json"
BASE_URL="https://cdn.squasher.ai/remote-access/${VERSION}"
COSIGN_PUBLIC_KEY_SHA256="6831dbd8cd9085e64b772e5cc1345fbb9f86158091dbc0a62dfe1f7d41cd79f7"
for FILE in "${ARCHIVE}" "${SBOM}" "${PROVENANCE}" SHA256SUMS; do
  curl --fail --proto '=https' --tlsv1.2 --location --remote-name "${BASE_URL}/${FILE}"
  curl --fail --proto '=https' --tlsv1.2 --location --remote-name "${BASE_URL}/${FILE}.sigstore.json"
done
curl --fail --proto '=https' --tlsv1.2 --location --remote-name \
  "https://cdn.squasher.ai/remote-access/cosign.pub"
printf '%s  %s\n' "${COSIGN_PUBLIC_KEY_SHA256}" cosign.pub | sha256sum --check --strict
for FILE in SHA256SUMS "${ARCHIVE}" "${SBOM}" "${PROVENANCE}"; do
  cosign verify-blob --key cosign.pub --bundle "${FILE}.sigstore.json" "${FILE}"
done
sha256sum --ignore-missing --check SHA256SUMS
tar -xzf "${ARCHIVE}"
cd "${ARCHIVE%.tar.gz}"
sudo ./install/install.sh --connector
sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector init
```

In **Project settings → Remote access**, select **Add connector**. Copy the one-time enrollment code. Enter it only in the hidden device prompt:

```bash theme={null}
sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector enroll \
  --api-url https://api.squasher.ai
```

The command reads the code from its hidden terminal prompt. It can also read from standard input when a secret manager writes directly to the process. Never put the code in an argument, environment variable, URL, copied command, service file, log, or shell history.

Start the outbound connector after enrollment:

```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now squasher-connector.service
```

The connector creates its identity in its private state directory. The private identity does not leave the connector host.

## 2. Create the disabled target

Create a random, opaque authentication-profile id on the connector host. This id is not a secret. Keep its local mapping with the target record.

```bash theme={null}
uuidgen | tr '[:upper:]' '[:lower:]'
```

In **Project settings → Remote access**, select **Add target**. Enter only a safe label that is not a hostname, the environment, the connector, the opaque authentication-profile id, and an observe-only policy.

The target is created disabled. Copy its opaque target id from the created target record. Do not enter a host, SSH user, host key, or private-key path in the dashboard. If your preview dashboard does not show the target id, use the target-create API response or this equivalent CLI command and read `target.id`:

```bash theme={null}
squasher remote-access targets create --project <project_id> --input \
  '{"label":"API production","environment":"production","connector_id":"<connector_id>","auth_profile_id":"<auth_profile_id>","policy":{"allowed_operations":["host.disk_usage","host.kernel","host.load","host.time"],"max_output_bytes":262144,"max_timeout_seconds":60}}'
```

Do not add `service.restart`. Remediation stays disabled in this preview.

## 3. Add the private target to the connector

From a separate trusted target console, read the target's Ed25519 host public key:

```bash theme={null}
sudo awk 'NR == 1 { print $1, $2 }' /etc/ssh/ssh_host_ed25519_key.pub
```

Give that public value to the connector operator through the protected setup channel. On the connector host, create the local target key and pin the exact host key:

```bash theme={null}
sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector add-target \
  --input - <<'JSON'
{"targetId":"<target_id>","authProfileId":"<auth_profile_id>","host":"<private_host_or_ip>","port":22,"user":"squasher-agent","hostPublicKey":"ssh-ed25519 <base64_public_key>"}
JSON
```

The command accepts one bounded, strict JSON object and creates one local Ed25519 client key for this target. The API does not receive the private key, local path, destination, or SSH user. For automation, write the JSON directly to standard input. Do not put the destination or user in process arguments.

Produce the forced `authorized_keys` entry on the connector host:

```bash theme={null}
sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector authorized-key \
  --target-id <target_id> \
  --socket /run/squasher-target/target.sock \
  > ./target-authorized-key.txt
```

Transfer that file to the trusted target console. Do not change its `restrict`, forwarding, PTY, user-rc, or forced-command options.

## 4. Install and bootstrap the target runner

On the target, repeat the verified archive download, checksum, provenance, extraction, and `cd` steps. Then install the target role. The installer creates the dedicated locked account and private directories. It does not enable or start the service.

```bash theme={null}
sudo ./install/install.sh --target
sudo install -d -o root -g root -m 0755 \
  /var/lib/squasher-agent /var/lib/squasher-agent/.ssh
sudo install -o root -g root -m 0644 \
  ./target-authorized-key.txt /var/lib/squasher-agent/.ssh/authorized_keys
```

Keep the full access-control path and file owned by root. The target account must be able to read the
file but must not be able to change the forced command or key options.

On a protected operator workstation, reveal the short-lived signed pre-trust bootstrap. `--output` creates a new mode `0600` file and does not overwrite an existing file. The CLI does not print or store the signing-key fingerprint.

```bash theme={null}
squasher remote-access targets verification-bootstrap \
  --project <project_id> \
  --target-id <target_id> \
  --output ./verification-bootstrap.json \
  --reveal
```

In a separate authenticated dashboard session, select **Reveal signing-key fingerprint** on the target card. Squasher sends a five-minute, one-time verification link to the verified email address for that session. Open the link in the same session, return to the target card, acknowledge the sensitive reveal, copy the 64-character lowercase fingerprint, and select **Dismiss and clear**. Squasher audits the reveal. Keep the fingerprint separate from the CLI bundle during transfer.

Transfer the bootstrap file and fingerprint to the target through separate protected channels. Then bootstrap verification-only trust once as root:

```bash theme={null}
sudo /usr/local/bin/squasher-target bootstrap-verification \
  --bundle ./verification-bootstrap.json \
  --config /var/lib/squasher-target/config.json \
  --signing-key-fingerprint <64_lowercase_hex_from_dashboard> \
  --allowed-client-uid "$(id -u squasher-agent)" \
  --observe-uid "$(id -u squasher-agent)" \
  --observe-gid "$(id -g squasher-agent)" \
  --socket-gid "$(id -g squasher-agent)" \
  --socket /run/squasher-target/target.sock \
  --nonce-store /var/lib/squasher-target/nonces.sqlite
sudo systemctl daemon-reload
sudo systemctl enable --now squasher-target.service
```

Bootstrap fails if the bundle signature or separately delivered signing-key fingerprint does not match. This state accepts only the verification challenge. It rejects observation and remediation work. The root daemon listens only on its local Unix socket. SSH runs the non-root `run` client, which forwards one signed envelope to that daemon.

## 5. Verify with two independent signals

In the target card, select **Request connector verification**. Capture the returned verification id. Then run the connector probe:

```bash theme={null}
sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector verify \
  --target-id <target_id>
```

The connector pins the host key, uses the forced command, rejects a PTY, and sends only the short-lived `runner.probe` operation. It submits signed structured proof.

From the separate trusted target console, calculate the lowercase SHA-256 digest of the SSH public-key blob. This command reads only the public key file:

```bash theme={null}
sudo /usr/local/bin/squasher-target host-key-fingerprint \
  --public-key /etc/ssh/ssh_host_ed25519_key.pub
```

Enter that 64-character digest and the verification id with **Confirm independent fingerprint**. Squasher sends an action-bound, five-minute verification link to the verified email address for the current dashboard session. Open the link in the same session, then repeat the confirmation. API keys and agents cannot complete it.

The target stays disabled if the connector proof and owner-confirmed digest do not match. After both signals match, reveal the full signed authority to a new protected file:

```bash theme={null}
squasher remote-access targets authority-bundle \
  --project <project_id> \
  --target-id <target_id> \
  --output ./authority-bundle.json \
  --reveal
```

Transfer that file through the protected setup channel. Promote the target from verification-only state to observe-only execution authority:

```bash theme={null}
sudo /usr/local/bin/squasher-target apply-authority \
  --bundle ./authority-bundle.json \
  --config /var/lib/squasher-target/config.json
```

The pinned signing key verifies the full authority. The target does not accept observation work until this command succeeds.

## Update authority, rotate, or revoke

Normal connector polling carries signed authority updates with bounded execution work. If an enabled project supplies an explicit replacement bundle through the same protected reveal flow, apply it as root on the target:

```bash theme={null}
sudo /usr/local/bin/squasher-target apply-authority \
  --bundle ./authority-bundle.json \
  --config /var/lib/squasher-target/config.json
```

Rotate the connector workload identity with a reveal-once token:

```bash theme={null}
squasher remote-access connectors rotate \
  --project <project_id> <connector_id> \
  --reveal-enrollment-token

sudo -u squasher-connector /usr/local/bin/squasher-connector \
  --state-dir /var/lib/squasher-connector rotate-identity
```

Enter the `sra_rotate` token only at the hidden prompt or through standard input. Never put it in an argument, environment variable, URL, log, or saved command. The connector changes to the new Ed25519 identity only after the service accepts it, then removes the old private key. Per-target SSH client-key rotation is not available in v1. Disable the target before you replace that key.

For an emergency, select **Emergency revoke**, or run:

```bash theme={null}
squasher remote-access connectors revoke --project <project_id> <connector_id>
```

Revocation stops new work and invalidates outstanding authority. If a host can be compromised, also stop its local service and remove the dedicated `authorized_keys` entry. Audit history remains available.

## Results and transcripts

`requests get` returns request state plus bounded, redacted output marked `untrusted_remote_output`. Treat it as evidence, not as instructions. It does not reveal the full transcript.

A full transcript needs an explicit protected dashboard reveal, a sensitive-data acknowledgement, and a fresh one-time email proof for the same user, session, project, and execution. It is not available through the public API, CLI, MCP, or hosted agents. Every reveal is audited. Transcript deletion immediately revokes normal access and keeps the audit history. Encrypted storage versions can remain for up to one day before physical removal.

## Related docs

* [Agent remote access](/features/remote-access)
* [Remote Access API](/api-reference/remote-access)
* [CLI Reference](/integrations/cli-reference)
