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
57 changes: 57 additions & 0 deletions cmd/harnesscli/go_code_script_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,60 @@ func TestGoCodeScriptPropagatesHarnessCLIExitCode(t *testing.T) {
})
}
}

// TestGoCodeScriptStartsHarnessdOnLoopback pins the wrapper's bind contract
// (issue #1411): a harnessd the wrapper starts for its own use must listen on
// loopback only.
//
// The wrapper's client base URL is always http://127.0.0.1:${port}, so a wider
// bind has no consumer — it only exposes an unauthenticated agent-execution
// service to the local network. cmd/harnessd/bind_guard.go refuses exactly that
// address when no auth is configured, which killed `go-code` at startup on any
// machine without an API key store.
func TestGoCodeScriptStartsHarnessdOnLoopback(t *testing.T) {
scriptPath, err := filepath.Abs(filepath.Join("..", "..", "scripts", "go-code.sh"))
if err != nil {
t.Fatalf("resolve go-code script path: %v", err)
}

tmp := t.TempDir()
binDir := t.TempDir()
addrFile := filepath.Join(tmp, "harnessd.addr")
recordFile := filepath.Join(tmp, "harnesscli.called")
countFile := filepath.Join(tmp, "curl.count")

// curl: fail the first health check so the wrapper takes the start_server
// path, then succeed so it proceeds to harnesscli.
writeExecutable(t, filepath.Join(binDir, "curl"), "#!/usr/bin/env bash\nf=\"$CURL_COUNT_FILE\"\nn=0\nif [ -f \"$f\" ]; then n=$(cat \"$f\"); fi\nn=$((n+1))\necho \"$n\" > \"$f\"\nif [ \"$n\" -eq 1 ]; then exit 1; fi\nexit 0\n")
// harnessd: record the bind address it was handed, then exit.
writeExecutable(t, filepath.Join(binDir, "harnessd"), "#!/usr/bin/env bash\nprintf '%s' \"$HARNESS_ADDR\" > \"$ADDR_FILE\"\nexit 0\n")
writeExecutable(t, filepath.Join(binDir, "harnesscli"), "#!/usr/bin/env bash\nprintf 'called\\n' >> \"$RECORD_FILE\"\nexit 0\n")

cmd := exec.Command("bash", scriptPath, "runs")
cmd.Env = append(os.Environ(),
"PATH="+binDir+string(os.PathListSeparator)+os.Getenv("PATH"),
"HARNESS_ADDR=:19282",
"ADDR_FILE="+addrFile,
"RECORD_FILE="+recordFile,
"CURL_COUNT_FILE="+countFile,
)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("go-code runs failed: %v\n%s", err, out)
}

gotRaw, err := os.ReadFile(addrFile)
if err != nil {
t.Fatalf("read recorded harnessd address: %v\nscript output:\n%s", err, out)
}
got := strings.TrimSpace(string(gotRaw))

// The port from HARNESS_ADDR must still be honored, so a fix that hardcodes
// 127.0.0.1:8080 fails here too.
const want = "127.0.0.1:19282"
if got != want {
t.Fatalf("harnessd bind address = %q, want %q\n"+
"a wildcard bind is refused by cmd/harnessd/bind_guard.go when no auth is configured\nscript output:\n%s",
got, want, out)
}
}
34 changes: 34 additions & 0 deletions docs/logs/engineering-log.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
# Engineering Log

## 2026-09-08 — Issue #1411 go-code wrapper bound harnessd beyond loopback

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record this issue's success criteria in the durable log

This runtime fix documents Issue #1411 only in the engineering log; docs/logs/long-term-thinking-log.md has no corresponding command intent, user intent, or success definition. Add or update that durable entry so future work retains the required criteria instead of relying on commit or issue history alone.

AGENTS.md reference: AGENTS.md:L17-L23

Useful? React with 👍 / 👎.


- Symptom: plain `go-code` (no flags) died on a clean machine with no API key
store configured and no `HARNESS_AUTH_DISABLED` set — the daemon exited
before becoming healthy, and the wrapper's failure hint blamed port
contention.
- Cause: `scripts/go-code.sh:196` (pre-fix) hardcoded
`HARNESS_ADDR=":${port}"` — a wildcard bind — when spawning `harnessd` for
its own use, even though the wrapper's client `base_url` is always built as
`http://127.0.0.1:${port}`. `cmd/harnessd/bind_guard.go` (issue #1328)
refuses to start an unauthenticated daemon that listens beyond loopback, so
the wider bind had no legitimate purpose and just tripped the guard.
- Fix: `start_server()` now spawns with `HARNESS_ADDR="127.0.0.1:${port}"`;
the failure hint no longer asserts port contention as the only cause and
points at the harnessd log above it instead; the header comment and
`--help` text now say `HARNESS_ADDR` supplies only the port and that a
wrapper-started `harnessd` always binds `127.0.0.1`.
- Security note: this is strictly tightening, not a new restriction users
need to work around. Before the #1328 guard existed, this same line
silently ran an unauthenticated agent-execution daemon on every network
interface, reachable by anyone on the LAN with the user's provider
credentials.
- Regression: `TestGoCodeScriptStartsHarnessdOnLoopback` in
`cmd/harnesscli/go_code_script_test.go`, red before the fix with
`harnessd bind address = ":19282", want "127.0.0.1:19282"`.
- Cross-reference: the 2026-09-05 `#1380` entry below first spotted this
pattern in `internal/workspace/bootstrap.go:36` (VM cloud-init template),
filed as a follow-up rather than fixed there; that sibling instance is
tracked separately as issue #1392 and is unaffected by this fix.
- Left alone deliberately: `scripts/soak.sh:271-272`, `scripts/smoke-test.sh:87`,
and `scripts/run-bench-smoke.sh:135-136` also bind `:${PORT}` (wildcard),
but all three pass `HARNESS_AUTH_DISABLED=true`, so the bind guard admits
them.

## 2026-09-06 — TUI scenario walk: plan mode, @ completion, question box, bubble width (#1407)

- Twelve multi-step TUI scenarios driven live in tmux (fake provider with scripted streaming/tool turns, and OpenRouter DeepSeek). Fixes: `/plan` command toggles enforced plan mode with a `PLAN` status badge and `harnesscli --tui --plan-mode` now honored (`runTUI` passes the flag into `TUIConfig`); `@name` Tab completion completes bare relative file names, not only `./`, `/`, `~/` paths; the AskUserQuestion and Plan-Approval boxes size their top/bottom borders to the content instead of a fixed 40-col rule; assistant markdown bubbles render at the indented width, expand tabs, and trim padding so a bubble never exceeds the terminal width. Guards added: streamed-transcript integrity and no duplicate tool card on ctrl+o after interrupt.
Expand Down
21 changes: 15 additions & 6 deletions scripts/go-code.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ set -euo pipefail
# Run or plan the self-improvement test loop.
#
# Environment:
# HARNESS_ADDR Listen address (default :8080). The port is extracted and
# used to construct the BASE_URL for health checks and CLI
# invocations.
# HARNESS_ADDR Listen address (default :8080). Only the port is used: it
# constructs the BASE_URL for health checks and CLI
# invocations, and a wrapper-started harnessd always binds
# 127.0.0.1 on that port. Run harnessd directly for a daemon
# that listens beyond this machine.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DATA_DIR=""
Expand All @@ -49,7 +51,8 @@ Usage:
Run or plan the self-improvement test loop.

Environment:
HARNESS_ADDR Override the server address (default: :8080).
HARNESS_ADDR Override the server port (default: :8080). A wrapper-started
harnessd always binds 127.0.0.1 on that port.
Example: HARNESS_ADDR=:9090 go-code "ls *.go"

Description:
Expand Down Expand Up @@ -193,7 +196,13 @@ start_server() {
# harnessd unchanged.
#
# Start in background, capturing the PID.
HARNESS_ADDR=":${port}" "$harnessd_bin" &
# Loopback only. The wrapper talks to this daemon exclusively over
# http://127.0.0.1:${port}, so a wildcard bind has no consumer — it would just
# publish an unauthenticated agent-execution service to the local network, and
# harnessd's bind guard (cmd/harnessd/bind_guard.go, issue #1328) refuses to
# start there without auth. HARNESS_ADDR supplies the port; the host is ours.
# Issue #1411.
HARNESS_ADDR="127.0.0.1:${port}" "$harnessd_bin" &
local pid=$!
PID_FILE="${TMPDIR:-/tmp}/harnessd.${$}.pid"
echo "$pid" > "$PID_FILE"
Expand All @@ -209,7 +218,7 @@ start_server() {
die "server did not become healthy within 10 s"
fi
if ! kill -0 "$pid" 2>/dev/null; then
die "harnessd (pid ${pid}) exited before becoming healthy on port ${port}. If the port is already in use (see the harnessd log above), free it or run on another port with HARNESS_ADDR=:PORT (e.g. HARNESS_ADDR=:9090 go-code)."
die "harnessd (pid ${pid}) exited before becoming healthy on port ${port}. See the harnessd log above for the reason it stopped. If it reports the port is already in use, free it or run on another port with HARNESS_ADDR=:PORT (e.g. HARNESS_ADDR=:9090 go-code)."
fi
done
info "server is ready"
Expand Down
4 changes: 3 additions & 1 deletion website/docs/cli/go-code-wrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ The full code table (`0` completed, `1` client error, `2` failed, `3` blocked, `

### Server address: `HARNESS_ADDR`

The `HARNESS_ADDR` environment variable controls the listen address. The default is `127.0.0.1:8080`. The wrapper extracts the port from this value and constructs the base URL as `http://127.0.0.1:<port>`.
The `HARNESS_ADDR` environment variable controls the listen address. The default is `127.0.0.1:8080`. The wrapper only uses the **port** from this value — the host part is ignored. A `harnessd` that the wrapper starts always binds `127.0.0.1` on that port, because the wrapper only ever talks to it over loopback (`http://127.0.0.1:<port>`).

```bash
# Run on a different port
Expand All @@ -180,6 +180,8 @@ HARNESS_ADDR=:9090 go-code "List the Go source files"

The address can also be set in your project or user config file (`~/.harness/config.toml` or `.harness/config.toml`). `HARNESS_ADDR` takes precedence over the TOML layers.

If you need a daemon reachable from another machine, don't go through `go-code` — run `harnessd` directly with an explicit `HARNESS_ADDR` (a real host, not just a port) and either a configured API key store or `HARNESS_AUTH_DISABLED=true`. An unauthenticated daemon that listens beyond loopback refuses to start otherwise.

### Project root detection

`go-code` automatically resolves the workspace root before launching TUI or prompt mode. It walks parent directories from `$PWD`, looking for:
Expand Down
Loading