# Reported issues for infrabroker

Pod holds 17 of 45 problems reported by people outside the maintainer team. Issues filed by the project's own owners, members and collaborators are excluded entirely — a maintainer's release checklist is not a warning to a prospective user.

Back to [infrabroker](/mcp/infrabroker).

## Most discussed

### merge the three broker frontends (broker, mcp-broker, mcp-broker-http) into one binary with transport subcommands

*File references are against `a28ca75`.*

## Context

Three of the seven binaries are thin wrappers around the **same** `broker.Engine` + `internal/mcpserver`, differing only in transport:

- `cmd/broker` (~140 lines) — HTTP + mTLS, `POST /v1/ssh_run`
- `cmd/mcp-broker` (~66 lines) — MCP stdio
- `cmd/mcp-broker-http` (~166 lines) — MCP Streamable HTTP + OAuth/OIDC

Each `main` repeats the same wiring (`LoadConfig` → `NewEngine` → `monitor.Serve` → serve/run loop), and every release artifact, Doc

[Read the thread](https://github.com/luisgf/infrabroker/issues/180) · 2026-07-09 · closed · 2 comments

### install.sh --bindir is ignored by the shipped systemd units

audit-id: a409f4c31c61

**Category:** logic · **Severity:** low — see description

**Location:** deploy/install.sh:48

**Description:** --bindir DIR installs signer/control-plane/mcp-broker-http to $BINDIR, but the units copied in step 5 hard-code ExecStart=/usr/local/bin/.... A non-default bindir yields enabled units that exec missing binaries. install-shim.sh handles --bindir correctly.

**Proposed fix & verification:** Rewrite ExecStart to ${BINDIR}/<binary> when installing the units, or reje

[Read the thread](https://github.com/luisgf/infrabroker/issues/382) · 2026-08-13 · closed · 1 comment

### install.sh never heals existing *.env or broker-ctl.json modes

audit-id: e406c27d8510

**Category:** security · **Severity:** medium — see description

**Location:** deploy/install.sh:176

**Description:** The installer claims re-run heals, never widens. Service JSON is converged to 0640 on every run, but two secret-adjacent files are left as-is:
- /etc/infrabroker/{signer,control-plane,mcp-http}.env (EnvironmentFile for AZURE_* / OIDC / webhook tokens). A 0644 file is readable by every infrabroker-* user because /etc/infrabroker is 0750 root:infrabroker — 

[Read the thread](https://github.com/luisgf/infrabroker/issues/381) · 2026-08-13 · closed · 1 comment

### reload() is not serialized with writeMu, so a concurrent policy narrowing can be reverted in memory

audit-id: c805ca866a5d

**Category:** security · **Severity:** medium — see description

**Location:** cmd/signer/main.go:771

**Description:** writeMu serializes POST/DELETE /v1/policy mutations (mutateAllow reads, edits, writes, then swaps s.local). reload() — SIGHUP, POST /v1/reload, and the mtime watcher — reads signer.json and later swaps s.local under s.mu only. buildState can take seconds (CA/AKV). Sequence: reload reads v1 → mutateAllow writes and applies v2 (DELETE last dangerous allow)

[Read the thread](https://github.com/luisgf/infrabroker/issues/378) · 2026-08-13 · closed · 1 comment

### Command-policy wrapper gate misses versioned interpreters, ash, and /usr/bin/time

audit-id: 1427d5fdc887

**Category:** security · **Severity:** high — see description

**Location:** internal/signer/cmdpolicy.go:351

**Description:** After #371 the wrapper set is still an exact path.Base map. A live Decide() against denylist ^rm  still allows:
- python3.12 -c / python3.11 -c / /usr/bin/python3.12 -c (Ubuntu/Debian default interpreter name; python3 is listed, python3.12 is not)
- ash -c 'rm …' (Alpine's shell when invoked by that name; sh/dash/bash are listed)
- /usr/bin/time 

[Read the thread](https://github.com/luisgf/infrabroker/issues/377) · 2026-08-13 · closed · 1 comment

### Deploy skill still teaches obsolete empty _default callers and omits agent CA custody

audit-id: dcafd283e94a

**Category:** documentation · **Severity:** medium — see description

**Location:** .agents/skills/deploy/SKILL.md:76

**Description:** #338 fixed install.sh/checklist: a non-empty callers table is already default-deny without requiring \"_default\": {\"allowed_groups\": []}. The deploy skill still requires empty _default and later expects {} when callers._default is default-deny — wrong post-v2.0.0. Skill CA custody section also lists only akv|pem and says those are the 

[Read the thread](https://github.com/luisgf/infrabroker/issues/358) · 2026-08-12 · closed · 1 comment

### main is red: the go-sdk v1.7.0 bump (#314) breaks in-conversation approvals — SEP-2322 forbids server-initiated elicitation during tools/call

audit-id: d43e5a27e54a

**Category:** logic · **Severity:** high — see description

**Location:** internal/mcpserver/tools.go:454

**Description:** `chore(deps): bump github.com/modelcontextprotocol/go-sdk` (de4bba1, #314) moved the SDK from v1.6.1 to v1.7.0 and landed on main with a FAILING "Build Go Project" check — main has been red since 2026-08-01T07:05Z and `build` is a required check, so every PR is blocked.

`go test -race ./internal/mcpserver` fails:

    --- FAIL: TestApprovalDeclinedV

[Read the thread](https://github.com/luisgf/infrabroker/issues/318) · 2026-08-01 · closed · 1 comment

### Control plane drops allow_file_transfer when forwarding GET /v1/hosts, so every host behind it advertises file_transfer=false

audit-id: f848933a1873

**Category:** logic · **Severity:** medium — see description

**Location:** cmd/control-plane/main.go:660

**Description:** The signer serves `allow_file_transfer` on GET /v1/hosts (cmd/signer/main.go:1115), and `signer.Remote.FetchHosts` maps it into `HostInfo.AllowFileTransfer` (internal/signer/remote.go:435). But when a broker talks to the SIGNER THROUGH THE CONTROL PLANE, `handleHosts` re-marshals the result into a fresh `signer.WireHostInfo` and copies only Addr/User

[Read the thread](https://github.com/luisgf/infrabroker/issues/315) · 2026-08-01 · closed · 1 comment

## Most recent

### docs/USAGE.md opens with 'the seven MCP tools' but documents thirteen (the six k8s_* tools have their own section)

audit-id: b83a3a7006c9

**Category:** documentation · **Severity:** low — see description

**Location:** docs/USAGE.md:3

**Description:** The guide's first sentence says it 'covers practical usage of the seven MCP tools exposed by cmd/mcp-broker (stdio) and cmd/mcp-broker-http'. Thirteen tools ship and the same file documents all of them: the seven ssh_* tools plus the six k8s_* tools (k8s_list_clusters, k8s_get, k8s_list, k8s_logs, k8s_apply, k8s_delete) in section 10, added with the Kubernete

[Read the thread](https://github.com/luisgf/infrabroker/issues/309) · 2026-07-24 · closed · 1 comment

### Elicitation (#118) approve/decline outcome is not distinctly audited; an elicitation-approved exec is indistinguishable from a non-gated one

audit-id: e8f2340c7124

**Category:** logic · **Severity:** low — see description

**Location:** internal/mcpserver/tools.go:238

**Description:** With approval_via_elicitation, a require_approval command triggers an in-conversation elicitation. The approval REQUEST is audited (the first Execute withholds the cert and audits Outcome:error,'requires human approval'), but: (1) the DECLINE path (tools.go:238) returns 'approval declined' with NO audit write — the log cannot distinguish 'human asked 

[Read the thread](https://github.com/luisgf/infrabroker/issues/280) · 2026-07-14 · closed · 1 comment

### Redaction can inflate an audit entry past the 256 KiB reader buffer, bricking fail-closed startup (no write-time entry-size cap)

audit-id: 0503a78eb63b

**Category:** security · **Severity:** high — see description

**Location:** internal/audit/log.go:396

**Description:** Redaction runs on every Append (log.go:392-401) and EXPANDS free-text: the env-assignment default rule (internal/redact/defaults.go) turns AUTH=a (6 bytes) into AUTH=[REDACTED:env-assignment] (30 bytes). The sign request body is capped at 64 KiB (cmd/signer/main.go:589) but there is NO cap on command length, and no cap on the serialized entry size. Ever

[Read the thread](https://github.com/luisgf/infrabroker/issues/278) · 2026-07-14 · closed · 1 comment

### Command-policy deny/require_approval bypass via shell quoting/encoding: extractCommands matches the re-printed (quote-preserving) command, not its decoded literal

audit-id: 0f3773740cd1

**Category:** security · **Severity:** high — see description

**Location:** internal/signer/cmdpolicy.go:197

**Description:** extractCommands re-prints each parsed simple command with printer.Print (cmdpolicy.go:197), which PRESERVES quoting/encoding, and PolicySet.decideOne matches deny/require_approval regexes against that printed form (policyset.go:145,157). On a denylist host (no allowlist) a non-matching command default-allows (policyset.go:187-189). The target she

[Read the thread](https://github.com/luisgf/infrabroker/issues/277) · 2026-07-14 · closed · 1 comment

### infrabroker init --register-mcp writes a broker config with CWD-relative paths, so the registered MCP server never starts

audit-id: c82cfe6f18b8

**Category:** logic · **Severity:** medium — see description

**Location:** internal/initcmd/config.go:122

**Description:** `infrabroker init` emits config.json (and signer.json) with paths relative to the init directory: the broker's mTLS client cert/key/CA (`pki/broker.crt`, `pki/broker.key`, `pki/mtls_ca.crt`), the audit key (`pki/audit.seed`) and audit log (`audit.log`). The broker resolves these against the PROCESS working directory: broker.LoadConfig (internal/brok

[Read the thread](https://github.com/luisgf/infrabroker/issues/271) · 2026-07-14 · closed · 1 comment

### GET /v1/revocations returns Reason and FrozenBy to any authenticated caller, unfiltered by callers-RBAC (a default-denied broker still reads the whole freeze ledger)

audit-id: b7e69dd303ca

**Category:** security · **Severity:** low — see description

**Location:** cmd/signer/main.go:1002

**Description:** handleRevocations authenticates the mTLS caller then returns s.freezes.List() unconditionally. List() includes each entry's Value (broker CNs / end-user ids), Reason (free-text operator notes, e.g. 'employee under investigation'), FrozenBy (admin CN) and FrozenAt. The handler comment justifies this as 'like GET /v1/hosts', but /v1/hosts is group-filtered (

[Read the thread](https://github.com/luisgf/infrabroker/issues/221) · 2026-07-10 · closed · 1 comment

### Broker revocation poll (kill switch) degrades silently: repeated FetchRevocations failures emit only a log line, no metric/liveness signal

audit-id: a9f984f658f9

**Category:** security · **Severity:** medium — see description

**Location:** internal/broker/engine.go:687

**Description:** startRevocationPoll is the broker half of the kill switch (#126): it force-closes live sessions matching the signer freeze set. On FetchRevocations error it does 'log.Printf(warning: revocation poll failed ...); continue' with NO counter, gauge, or last-success timestamp. Every frontend exposes Prometheus via monitor.Serve, so /metrics is the moni

[Read the thread](https://github.com/luisgf/infrabroker/issues/217) · 2026-07-10 · closed · 1 comment

### k8s path: audit dry-run decisions and stop leaking the API-server URL to the model

## Summary
Two independent gaps on the Kubernetes broker path:
1. **Dry-run decisions are not audited.** The SSH dry-run path audits `dry_run_allowed`/`dry_run_denied`, but `K8sExecute` returns on dry-run with no audit entry — an agent can enumerate the whole k8s ActionPolicy surface (allowed/approval-gated verbs/resources/namespaces) leaving no trace in the broker audit log.
2. **API-server URL leaks to the model on transport errors**, violating the stated invariant that the broker never expose

[Read the thread](https://github.com/luisgf/infrabroker/issues/204) · 2026-07-10 · closed · 0 comments

### Kill-switch freeze coverage gaps: forwarder on_behalf_of bypass and unchecked /v1/clusters

## Summary
The signer's freeze check runs on the **resolved** caller, so a trusted forwarder acting via `on_behalf_of` is never freeze-checked on its **own** mTLS CN — freezing a compromised forwarder/control-plane is a near no-op. Separately, `GET /v1/clusters` has no freeze check at all.

## Where
- `cmd/signer/main.go:610` reassigns `caller` to the `on_behalf_of` value (via `resolveCaller`, `main.go:506`) **before** the freeze check at `cmd/signer/main.go:630` (`s.freezes.Frozen(caller, req.E

[Read the thread](https://github.com/luisgf/infrabroker/issues/203) · 2026-07-10 · closed · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/luisgf/infrabroker/issues).
