recursive-agent 0.8.2

A Rust coding-agent platform: kernel, tools, multi-agent orchestration, HTTP API, MCP, and TUI
Documentation
name: "HTTP API Authentication (RECURSIVE_HTTP_AUTH_KEYS)"
description: |
  Verify the HTTP server's inbound API-key authentication (src/http/auth.rs).
  Uses the same in-container curl pattern as 08-http-api.yaml because the
  server is started inside the container and is only reachable from within.

    - When RECURSIVE_HTTP_AUTH_KEYS is set, requests without X-API-Key return 401.
    - Requests with the correct key are accepted (200).
    - Requests with a wrong key are rejected (401).
    - /health and /metrics bypass auth (liveness probes must work unauthenticated).
sequential: true

setup:
  - name: "Kill any leftover server on port 9097"
    ignoreError: true
    exec:
      container: recursive-e2e
      command: |
        # Allocate a free port for this suite (host-mode port randomization).
        PORT=$(python3 -c "import socket; s=socket.socket(); s.bind(('',0)); p=s.getsockname()[1]; s.close(); print(p)")
        echo "$PORT" > /tmp/e2e-host-port-39.txt
        echo "suite-port=$PORT"
        export PORT=$(cat /tmp/e2e-host-port-39.txt)
        pkill -f 'recursive.*http --addr' 2>/dev/null || true
        sleep 0.5
        echo cleaned

  - name: "Start HTTP server on 9097 with auth key 'test-secret-key'"
    exec:
      container: recursive-e2e
      command: |
        export PORT=$(cat /tmp/e2e-host-port-39.txt)
        nohup sh -c "RECURSIVE_HTTP_AUTH_KEYS=test-secret-key \
          recursive \
            --api-base http://aimock:4010/v1 \
            --api-key mock-key -m mock-chat \
            http --addr 0.0.0.0:$PORT" \
          > /tmp/http-$PORT.log 2>&1 &
        for i in $(seq 1 20); do
          curl -sf http://localhost:$PORT/health > /dev/null 2>&1 && echo "server-ready" && break
          sleep 0.5
        done

cases:
  # ── /health and /metrics bypass auth ─────────────────────────────────────────
  - name: "GET /health without key returns 200 (auth-exempt endpoint)"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        http://localhost:$PORT/health
    expect:
      exitCode: 0
      output:
        contains: "status=200"

  - name: "GET /metrics without key returns 200 (auth-exempt endpoint)"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        http://localhost:$PORT/metrics
    expect:
      exitCode: 0
      output:
        contains: "status=200"

  # ── Auth-protected endpoints without key ─────────────────────────────────────
  - name: "GET /sessions without X-API-Key returns 401"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        http://localhost:$PORT/sessions
    expect:
      exitCode: 0
      output:
        contains: "status=401"

  - name: "POST /sessions without key returns 401"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        -X POST http://localhost:$PORT/sessions
        -H 'Content-Type: application/json'
        -d '{}'
    expect:
      exitCode: 0
      output:
        contains: "status=401"

  - name: "POST /run without key returns 401"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        -X POST http://localhost:$PORT/run
        -H 'Content-Type: application/json'
        -d '{"goal":"test"}'
    expect:
      exitCode: 0
      output:
        contains: "status=401"

  # ── Wrong key ────────────────────────────────────────────────────────────────
  - name: "GET /sessions with wrong X-API-Key returns 401"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        -H 'X-API-Key: wrong-key'
        http://localhost:$PORT/sessions
    expect:
      exitCode: 0
      output:
        contains: "status=401"

  # ── Correct key grants access ─────────────────────────────────────────────────
  - name: "GET /sessions with correct X-API-Key returns 200"
    exec:
      container: recursive-e2e
      command: >
        export PORT=$(cat /tmp/e2e-host-port-39.txt); curl -s -o /dev/null -w 'status=%{http_code}'
        -H 'X-API-Key: test-secret-key'
        http://localhost:$PORT/sessions
    expect:
      exitCode: 0
      output:
        contains: "status=200"

  - name: "POST /sessions with correct key creates a session (200 or 201)"
    exec:
      container: recursive-e2e
      command: |
        export PORT=$(cat /tmp/e2e-host-port-39.txt)
        OUT=$(curl -sf \
          -H 'X-API-Key: test-secret-key' \
          -X POST http://localhost:$PORT/sessions \
          -H 'Content-Type: application/json' \
          -d '{"system_prompt":"auth test"}')
        echo "$OUT" | grep -q '"id"' && echo "has-id" || echo "no-id"
    expect:
      exitCode: 0
      output:
        contains: "has-id"

teardown:
  - name: "Stop HTTP server on 9097"
    ignoreError: true
    exec:
      container: recursive-e2e
      command: |
        export PORT=$(cat /tmp/e2e-host-port-39.txt)
        pkill -f 'recursive.*http --addr' 2>/dev/null || true
        rm -f /tmp/http-$PORT.log
        echo stopped