# Redirecting the agent's Nous auth store out of a Syncthing shared folder

**Symptom (what you might see):**

- `/root/.hermes/shared/nous_auth.json` and `nous_auth.lock` exist on the VPS
- Same files appear in the Windows mirror of `hermes-shared` (e.g. `C:\Users\Rob\Hermes Shared\nous_auth.json`)
- File contains a JSON object with `access_token`, `refresh_token`, `client_id`, `expires_at`, `scope: "inference:invoke"`
- The `.stignore` in the shared folder may already list `nous_auth.json` and `nous_auth.lock` — ignore rules only stop NEW scans, not already-indexed files

**Root cause:**

The agent writes its OAuth refresh token to `<hermes-root>/shared/nous_auth.json` by default (see `hermes_cli/auth.py:4596` and `_nous_shared_auth_dir()` at line 4614). This is intentional — it lets named profiles share a single OAuth session. The env var `HERMES_SHARED_AUTH_DIR` exists to override it. On this VPS the path happens to be `/root/.hermes/shared/`, which is also the default Syncthing `hermes-shared` folder. Result: refresh tokens get broadcast to every paired device on every token refresh (every ~6h, via the `nous_auth_keepalive` thread).

**Execution split (added 2026-08-06): steps 1–5 + 7 are agent-runnable from a gateway session; step 6 is NOT.** A runtime guard blocks `systemctl restart/stop` of `hermes-gateway.service` from inside the gateway process (SIGTERM would propagate to the restart command itself): `Blocked: cannot restart or stop the gateway from inside the gateway process`. There is no in-session workaround. The correct flow when running from a gateway session:

1. Agent runs steps 1–5 + verification itself (all safe; the env var only takes effect on next gateway start, so nothing breaks mid-session)
2. Agent hands the user a one-line block for an outside shell (Hostinger web terminal):

   ```bash
   systemctl restart hermes-gateway.service && sleep 3 && systemctl is-active hermes-gateway.service
   ```

   Expected: `active`. Desktop clients drop ~5s and reconnect; conversation state survives (DB-backed).
3. After the user confirms, agent runs post-restart verification: token refresh (every ~6h, `nous_auth_keepalive` thread) writes to `/root/.hermes/auth-store/` and does NOT recreate the file in `/root/.hermes/shared/`.

The combined block below is for **user-run / outside-shell execution only** (e.g. the original one-shot Hostinger paste). Agents in a gateway session must use the split flow above.

```bash
set -e

# 1. Create a non-synced location for the auth store
mkdir -p /root/.hermes/auth-store
chmod 700 /root/.hermes/auth-store

# 2. Move the existing file there (preserves it, removes it from the synced folder)
if [ -f /root/.hermes/shared/nous_auth.json ]; then
  mv /root/.hermes/shared/nous_auth.json /root/.hermes/auth-store/nous_auth.json
fi
if [ -f /root/.hermes/shared/nous_auth.lock ]; then
  mv /root/.hermes/shared/nous_auth.lock /root/.hermes/auth-store/nous_auth.lock
fi
chmod 600 /root/.hermes/auth-store/nous_auth.json
chmod 600 /root/.hermes/auth-store/nous_auth.lock 2>/dev/null || true

# 3. Persist the override so it survives restarts
ENV_FILE=/root/.hermes/.env
if [ -f "$ENV_FILE" ] && ! grep -q "HERMES_SHARED_AUTH_DIR" "$ENV_FILE"; then
  printf '\n# Redirect Nous auth store out of Syncthing hermes-shared (security: stop broadcasting OAuth tokens to paired devices)\nHERMES_SHARED_AUTH_DIR=/root/.hermes/auth-store\n' >> "$ENV_FILE"
elif [ ! -f "$ENV_FILE" ]; then
  printf '# Redirect Nous auth store out of Syncthing hermes-shared (security: stop broadcasting OAuth tokens to paired devices)\nHERMES_SHARED_AUTH_DIR=/root/.hermes/auth-store\n' > "$ENV_FILE"
fi

# 4. Make sure .stignore covers the moved filename (belt-and-braces)
STIGNORE=/root/.hermes/shared/.stignore
touch "$STIGNORE"
grep -q '^nous_auth' "$STIGNORE" || echo "nous_auth*" >> "$STIGNORE"

# 5. Trigger a rescan so the deletion propagates to paired devices (Connie + Surface)
API=$(grep -oP 'apikey>\K[^<]+' /root/.local/state/syncthing/config.xml)
curl -s -X POST -H "X-API-Key: $API" "http://127.0.0.1:8384/rest/db/scan?folder=hermes-shared" && echo

# 6. Restart the gateway so the new env var is loaded for the next token refresh
systemctl restart hermes-gateway.service
sleep 2
systemctl is-active --quiet hermes-gateway.service && echo "gateway: active" || echo "gateway: FAILED to restart"

# 7. Verify
echo
echo "=== VERIFY ==="
ls -la /root/.hermes/auth-store/
ls -la /root/.hermes/shared/ | grep -i nous_auth || echo "shared/nous_auth*: GONE (good)"
grep "HERMES_SHARED_AUTH_DIR" /root/.hermes/.env
```

**Expected output on success:**

- `{"success":true}` from the Syncthing rescan API
- `gateway: active`
- `shared/nous_auth*: GONE (good)`
- `HERMES_SHARED_AUTH_DIR=/root/.hermes/auth-store` line in `/root/.hermes/.env`

**After-the-fact cleanup (manual, on each paired device):**

Within ~30 seconds of the rescan, the deleted files should disappear from:

- `C:\Users\Rob\Hermes Shared\nous_auth.json` and `.lock` on Connie
- `C:\Users\rkbla\Hermes Shared\nous_auth.json` and `.lock` on Surface (if Surface is paired to `hermes-shared`)

If they don't disappear automatically, right-click the file in Explorer and pick "Revert" via the Syncthing tray menu, or just delete them manually — they're gone from the source-of-truth and Syncthing will eventually reconcile.

**Token rotation (separate decision):**

Moving the file stops *future* leaks. The refresh_token that already got broadcast is still valid until the OAuth server's own expiry (typically 1h for access_token, 30d for refresh_token, but depends on the provider). If you want to treat the leak as a real exposure:

1. Log into portal.nousresearch.com (or whichever provider) and revoke all active sessions for the `hermes-cli` client
2. Re-run the device-code auth flow: `hermes auth add nous --type oauth` (or whatever the current command is for this install)
3. The new token will land in the redirected path and stay there

For the Nous refresh token observed on 2026-07-26: the access_token expired within 40 min, but the refresh_token had a longer window. The user's call whether to rotate immediately or let normal expiry handle it.

**Prevention for future setups:**

Apply this redirect as step 0 of any `hermes-shared`-style folder setup, BEFORE the first rescan. The order matters: if you sync the shared folder first and the agent writes `nous_auth.json` after, you have to do the move + rescan dance. If you set the env var first, the file never lands in the synced path.
