Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions Dockerfile.relay
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ COPY packages/keystore/package.json packages/keystore/
COPY packages/sdk/package.json packages/sdk/
COPY packages/cli/package.json packages/cli/
COPY packages/relay/package.json packages/relay/
COPY packages/shell/package.json packages/shell/
# packages/shell was consolidated into packages/cli

# Stub missing workspace dirs to satisfy bun install
RUN mkdir -p demo landpage && echo '{"name":"demo","private":true}' > demo/package.json && echo '{"name":"landpage","private":true}' > landpage/package.json
Expand All @@ -33,12 +33,25 @@ FROM oven/bun:1.3-slim

WORKDIR /app

# Only copy the built dist files — relay has zero external runtime deps
# Copy workspace config + package.jsons for workspace resolution
COPY --from=build /app/package.json /app/bun.lock ./
COPY --from=build /app/packages/core/package.json packages/core/
COPY --from=build /app/packages/core/dist packages/core/dist
COPY --from=build /app/packages/relay/package.json packages/relay/
COPY --from=build /app/packages/relay/dist packages/relay/dist

# Stub other workspace packages so bun can resolve the workspace
RUN mkdir -p packages/keystore packages/sdk packages/cli && \
echo '{"name":"@authmesh/keystore","private":true}' > packages/keystore/package.json && \
echo '{"name":"@authmesh/sdk","private":true}' > packages/sdk/package.json && \
echo '{"name":"@authmesh/cli","private":true}' > packages/cli/package.json && \
mkdir -p demo landpage && \
echo '{"name":"demo","private":true}' > demo/package.json && \
echo '{"name":"landpage","private":true}' > landpage/package.json

# Install to create workspace links
RUN bun install

EXPOSE 3001

ENV PORT=3001
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ app.use(amesh.verify());
| Package | Description |
|---------|-------------|
| [`@authmesh/sdk`](./packages/sdk) | Signing fetch client + Express verification middleware |
| [`@authmesh/cli`](./packages/cli) | CLI: `init`, `listen`, `invite`, `list`, `revoke`, `provision` |
| [`@authmesh/cli`](./packages/cli) | CLI: `init`, `listen`, `invite`, `list`, `revoke`, `provision`, `grant`, `shell` |
| [`@authmesh/agent`](./packages/agent) | Agent daemon + full CLI: all CLI commands + `agent start` |
| [`@authmesh/core`](./packages/core) | Crypto primitives: sign, verify, canonical string, nonce, HMAC, HKDF, ECDH |
| [`@authmesh/keystore`](./packages/keystore) | Key storage drivers: Secure Enclave, macOS Keychain, TPM 2.0, encrypted file |
| [`@authmesh/relay`](./packages/relay) | WebSocket relay for device pairing handshakes |
Expand Down
62 changes: 32 additions & 30 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions docs/architecture-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,17 @@ The controller CLI displays this code; the target CLI prompts the operator to en

---

## ADR-011: Remote shell as separate package with explicit shell permission
## ADR-011: Remote shell in the CLI with explicit shell permission

**Decision:** The remote shell feature ships as `@authmesh/shell`, a separate npm package with separate binaries (`amesh-agent`, `amesh-shell`). Shell access requires explicit `amesh grant --shell` after pairing.
**Decision:** The remote shell feature is part of `@authmesh/cli` — one package, one binary. `amesh shell` connects to a remote target. `amesh agent start` runs the daemon. Shell access requires explicit `amesh grant --shell` after pairing.

**Why:**

1. **Security boundary:** Installing `@authmesh/sdk` for HTTP API auth must never pull in PTY code or an agent daemon. The attack surface for API signing and shell access are fundamentally different.
1. **One install:** Developers install one thing (`@authmesh/cli`) and get everything — identity management, pairing, API auth, shell client, and agent daemon.

2. **Explicit consent:** Pairing for API authentication (`amesh invite`) does not grant shell access. A `permissions.shell` flag in the allow list defaults to `false`. The target admin must explicitly run `amesh grant <device-id> --shell`. This prevents implicit privilege escalation.
2. **Explicit consent:** Pairing for API authentication (`amesh invite`) does not grant shell access. A `permissions.shell` flag in the allow list defaults to `false`. The target admin must explicitly run `amesh grant <device-id> --shell`. This is the security boundary, not the package boundary.

3. **Separate binaries:** `amesh-agent` and `amesh-shell` are distinct from `amesh` (the CLI). Users opt into shell capability by installing a separate package.
3. **The daemon is opt-in by invocation:** `amesh agent start` must be explicitly run. It doesn't auto-start, doesn't install as a service, and refuses to run as root without `--allow-root`.

**Security design choices:**

Expand All @@ -188,7 +188,7 @@ The controller CLI displays this code; the target CLI prompts the operator to en
- **Per-controller session limits** — prevents DoS by authorized-but-misbehaving peers

**Rejected alternatives:**
- Bundling in `@authmesh/cli` — mixes API auth tooling with shell daemon, implicit capability creep
- Separate `@authmesh/agent` package — adds install confusion without meaningful security benefit; the permission gate (`amesh grant --shell`) is the real security boundary, not the package boundary
- Auto-granting shell on pairing — violates principle of least privilege
- Reusing pairing handshake's random-nonce encryption — birthday-bound risk over long sessions
- Session resumption — complexity and nonce-reuse risk outweigh the latency benefit
22 changes: 10 additions & 12 deletions landpage/src/routes/docs/remote-shell/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@
<!-- Install -->
<section class="py-8">
<h2 id="install" class="scroll-mt-20 text-xl font-semibold text-zinc-50">Install</h2>
<p class="mt-2 text-zinc-400">The shell feature is a separate package from the CLI.</p>
<p class="mt-2 text-zinc-400">Two binaries: <code class="text-emerald-400">amesh</code> for the controller (your laptop), <code class="text-emerald-400">amesh-agent</code> for the server.</p>
<div class="mt-4">
<CodeBlock code={`<span class="text-zinc-500"># Install the shell package (agent + shell client)</span>
brew install ameshdev/tap/amesh-shell
<span class="text-zinc-500"># or</span>
npm install -g @authmesh/shell
<CodeBlock code={`<span class="text-zinc-500"># On your laptop (controller)</span>
brew install ameshdev/tap/amesh

<span class="text-zinc-500"># You also need the CLI for pairing and permissions</span>
brew install ameshdev/tap/amesh`} />
<span class="text-zinc-500"># On the server (target) — includes all CLI commands + daemon</span>
brew install ameshdev/tap/amesh-agent`} />
</div>
</section>

Expand Down Expand Up @@ -85,10 +83,10 @@ amesh list
<h3 class="mt-6 text-sm font-semibold uppercase tracking-wide text-zinc-500">3. Start the agent</h3>
<div class="mt-3">
<CodeBlock code={`<span class="text-zinc-500"># On the target (server) — start the agent daemon</span>
amesh-agent start
amesh agent start

<span class="text-zinc-500"># Or with options</span>
amesh-agent start --relay wss://relay.authmesh.dev/ws --idle-timeout 60`} />
amesh agent start --relay wss://relay.authmesh.dev/ws --idle-timeout 60`} />
</div>
</section>

Expand All @@ -98,7 +96,7 @@ amesh-agent start --relay wss://relay.authmesh.dev/ws --idle-timeout 60`} />

<h3 class="mt-4 text-sm font-semibold uppercase tracking-wide text-zinc-500">Interactive shell</h3>
<div class="mt-3">
<CodeBlock code={`<span class="text-zinc-500">$</span> amesh-shell prod-api
<CodeBlock code={`<span class="text-zinc-500">$</span> amesh shell prod-api
Connecting to prod-api (am_7f2e8a1b)...
Connected. Shell session started.

Expand All @@ -110,7 +108,7 @@ user

<h3 class="mt-6 text-sm font-semibold uppercase tracking-wide text-zinc-500">Single command</h3>
<div class="mt-3">
<CodeBlock code={`<span class="text-zinc-500">$</span> amesh-shell prod-api -c "df -h"
<CodeBlock code={`<span class="text-zinc-500">$</span> amesh shell prod-api -c "df -h"
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 12G 35G 26% /`} />
</div>
Expand Down Expand Up @@ -170,7 +168,7 @@ Filesystem Size Used Avail Use% Mounted on
</div>
<div class="border-l-2 border-red-400/60 pl-4 py-1">
<div class="text-sm font-semibold text-zinc-50">"Handshake failed" / connection timeout</div>
<div class="mt-1 text-sm text-zinc-400">The agent is not running on the target. Start it with <code class="text-emerald-400">amesh-agent start</code>.</div>
<div class="mt-1 text-sm text-zinc-400">The agent is not running on the target. Start it with <code class="text-emerald-400">amesh agent start</code>.</div>
</div>
<div class="border-l-2 border-red-400/60 pl-4 py-1">
<div class="text-sm font-semibold text-zinc-50">"Refusing to run as root"</div>
Expand Down
4 changes: 2 additions & 2 deletions landpage/src/routes/use-cases/remote-shell/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@
]}
codeTabs={[
{ filename: 'Terminal (target)', code: `<span class="text-zinc-500"># On the server — start the agent daemon</span>
<span class="text-zinc-500">$</span> <span class="text-zinc-50">amesh-agent start</span>
<span class="text-zinc-500">$</span> <span class="text-zinc-50">amesh agent start</span>

amesh agent listening on relay.authmesh.dev
Device: <span class="text-emerald-400">am_7f2e8a1b</span> (prod-api)
Authorized controllers: 2

Waiting for shell requests...` },
{ filename: 'Terminal (controller)', code: `<span class="text-zinc-500"># On your laptop — open a shell</span>
<span class="text-zinc-500">$</span> <span class="text-zinc-50">amesh-shell prod-api</span>
<span class="text-zinc-500">$</span> <span class="text-zinc-50">amesh shell prod-api</span>

Connecting to prod-api (<span class="text-emerald-400">am_7f2e8a1b</span>)...
Connected. Shell session started.
Expand Down
42 changes: 42 additions & 0 deletions packages/agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# @authmesh/agent

Agent daemon for [amesh](https://github.com/ameshdev/amesh) remote shell --- secure remote access using device-bound identity. Includes all CLI commands plus the agent daemon. One install on the server.

## Install

```bash
brew install ameshdev/tap/amesh-agent
# or
npm install -g @authmesh/agent
```

## Setup

```bash
amesh-agent init --name "prod-api"
amesh-agent listen
# Controller runs: amesh invite <code>

amesh-agent grant am_3d9f1a2e --shell
amesh-agent agent start
```

## Commands

All CLI commands plus the agent daemon:

```bash
amesh-agent init --name "prod-api" # Create device identity
amesh-agent listen # Start pairing (target side)
amesh-agent invite <code> # Join pairing
amesh-agent list # Show paired devices
amesh-agent revoke <device-id> # Remove a device
amesh-agent grant <device-id> --shell # Grant shell access
amesh-agent provision # Generate bootstrap tokens
amesh-agent shell <device> # Open remote shell
amesh-agent agent start # Start the agent daemon
```

## License

[MIT](https://github.com/ameshdev/amesh/blob/main/LICENSE)
29 changes: 15 additions & 14 deletions packages/shell/package.json → packages/agent/package.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"name": "@authmesh/shell",
"name": "@authmesh/agent",
"version": "0.2.0",
"description": "Secure remote shell for ameshSSH-like access with device-bound identity",
"description": "amesh agent daemon + CLIremote shell target with device-bound identity",
"type": "module",
"license": "MIT",
"author": "Yair Etzion",
"repository": {
"type": "git",
"url": "https://github.com/ameshdev/amesh.git",
"directory": "packages/shell"
"directory": "packages/agent"
},
"homepage": "https://github.com/ameshdev/amesh",
"keywords": [
"authentication",
"agent",
"remote-shell",
"ssh-alternative",
"device-identity",
"pty",
"encrypted-shell"
"daemon"
],
"publishConfig": {
"access": "public"
},
"bin": {
"amesh-agent": "./dist/commands/agent-start.js",
"amesh-shell": "./dist/commands/shell.js"
"amesh-agent": "./dist/index.js"
},
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
"oclif": {
"commands": "./dist/commands",
"bin": "amesh-agent"
},
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"build": "tsc -b",
"test": "bun test src/__tests__/",
Expand All @@ -43,9 +43,10 @@
"dependencies": {
"@authmesh/core": "workspace:*",
"@authmesh/keystore": "workspace:*",
"@noble/curves": "2.0.1",
"@noble/ciphers": "2.1.1",
"@noble/hashes": "2.0.1"
"@noble/curves": "2.0.1",
"@noble/hashes": "2.0.1",
"@oclif/core": "^4.0.0"
},
"devDependencies": {
"@eslint/js": "^9.0.0",
Expand Down
Loading
Loading