fix: make command reconciliation binary safe
Some checks failed
tamq-ci / test (push) Failing after 6s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
tegwick 2026-08-25 21:43:37 +02:00
parent 92881b6b56
commit fbb56f5981
7 changed files with 95 additions and 10 deletions

View file

@ -1,18 +1,22 @@
.PHONY: install uninstall test check
.DEFAULT_GOAL := help
.PHONY: help install uninstall test check
install:
help: ## List available targets
@awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z0-9_-]+:.*## / {printf " %-12s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
install: ## Install or refresh tamq as a user-level uv tool
@command -v uv >/dev/null || { echo "tamq: uv is required; install it from https://docs.astral.sh/uv/" >&2; exit 1; }
uv tool install --force --reinstall --refresh .
@command -v tamq >/dev/null || { echo "tamq: installed, but the uv tool bin directory is not on PATH; run 'uv tool update-shell'" >&2; exit 1; }
tamq --version
uninstall:
uninstall: ## Uninstall the user-level tamq tool
uv tool uninstall tmux-amq
test:
test: ## Run the test suite
uv run pytest
check:
check: ## Run tests and repository consistency checks
uv run pytest
git diff --check
python3 -m compileall -q src

View file

@ -103,7 +103,7 @@ Not yet suitable:
and stronger process-supervision evidence.
- Cross-host messaging or use as a general-purpose broker.
The suite currently has 133 passing tests. It includes atomic counter races,
The suite currently has 136 passing tests. It includes atomic counter races,
pseudo-terminal normalization, real tmux pushy/trigger behavior, operator-echo
suppression, and an isolated installed-package workflow with deterministic gita
fixtures. Forgejo CI installs tmux and uv and retains CLI help/version smoke

View file

@ -26,6 +26,7 @@
| workplan | TAMQ-WP-0014 | finished | — | workplans/TAMQ-WP-0014-readable-duplex-messaging-and-limits.md |
| task | TAMQ-WP-ADHOC-2026-08-24-T01 | done | — | workplans/ADHOC-2026-08-24.md |
| task | TAMQ-WP-ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25.md |
| task | TAMQ-WP-ADHOC-2026-08-25-T02 | done | — | workplans/ADHOC-2026-08-25.md |
| task | TAMQ-WP-0001-T01 | done | — | workplans/TAMQ-WP-0001-statehub-bootstrap.md |
| task | TAMQ-WP-0001-T02 | done | — | workplans/TAMQ-WP-0001-statehub-bootstrap.md |
| task | TAMQ-WP-0001-T03 | done | — | workplans/TAMQ-WP-0001-statehub-bootstrap.md |

View file

@ -80,11 +80,15 @@ class TmuxManager:
if not os.access(self.command_dir, os.W_OK | os.X_OK):
raise TmuxError(f"address command directory is not writable: {self.command_dir}")
for existing in self.command_dir.iterdir():
if not existing.is_file() or existing.is_symlink():
if (
not existing.name.startswith("@")
or not existing.is_file()
or existing.is_symlink()
):
continue
try:
content = existing.read_text(encoding="utf-8")
except OSError:
except (OSError, UnicodeError):
continue
if "# tamq-address-command v1" in content:
existing.unlink()
@ -106,7 +110,7 @@ class TmuxManager:
raise TmuxError(f"refusing to replace existing command: {destination}")
try:
existing = destination.read_text(encoding="utf-8")
except OSError as exc:
except (OSError, UnicodeError) as exc:
raise TmuxError(f"cannot inspect existing command: {destination}") from exc
if "# tamq-protocol-command v2" not in existing:
raise TmuxError(f"refusing to replace existing command: {destination}")

21
tests/test_makefile.py Normal file
View file

@ -0,0 +1,21 @@
import subprocess
from pathlib import Path
def test_plain_make_lists_available_targets():
project = Path(__file__).resolve().parents[1]
result = subprocess.run(
["make", "--no-print-directory"],
cwd=project,
text=True,
capture_output=True,
check=True,
)
assert result.stdout.splitlines() == [
" help List available targets",
" install Install or refresh tamq as a user-level uv tool",
" uninstall Uninstall the user-level tamq tool",
" test Run the test suite",
" check Run tests and repository consistency checks",
]

View file

@ -184,6 +184,37 @@ def test_protocol_commands_absorb_forwarded_shell_lines(tmp_path):
assert result.stdout == ""
def test_protocol_reconciliation_skips_unrelated_binary_executables(tmp_path):
command_dir = tmp_path / "commands"
command_dir.mkdir()
binary = command_dir / "other-tool"
binary.write_bytes(b"\x7fELF\x8e\xff\x00")
legacy = command_dir / "@audit-core"
legacy.write_text(
"#!/bin/sh\n# tamq-address-command v1\nexit 0\n",
encoding="utf-8",
)
manager = tmux.TmuxManager("tamq-test", command_dir=command_dir)
manager._install_address_commands(["audit-core"])
assert binary.read_bytes() == b"\x7fELF\x8e\xff\x00"
assert not legacy.exists()
assert (command_dir / "To:audit-core:").is_file()
def test_protocol_reconciliation_refuses_undecodable_command_collision(tmp_path):
command_dir = tmp_path / "commands"
command_dir.mkdir()
collision = command_dir / "To:audit-core:"
collision.write_bytes(b"\x8e\xff")
manager = tmux.TmuxManager("tamq-test", command_dir=command_dir)
with pytest.raises(tmux.TmuxError, match="cannot inspect existing command"):
manager._install_address_commands(["audit-core"])
assert collision.read_bytes() == b"\x8e\xff"
def test_address_commands_reject_unsafe_repository_names(tmp_path):
manager = tmux.TmuxManager("tamq-test", command_dir=tmp_path / "commands")
with pytest.raises(tmux.TmuxError, match="cannot be exposed"):

View file

@ -1,7 +1,7 @@
---
id: TAMQ-WP-ADHOC-2026-08-25
type: workplan
title: "Recognize enhanced terminal submission keys"
title: "Terminal and local operator fixes"
domain: communication
repo: tmux-amq
status: finished
@ -42,3 +42,27 @@ fix, and update the operator documentation.
the target's queued follow-up inputs.
- Full checks and installed-package verification passed. No user session or
durable user message was changed during diagnosis.
## Make target help and binary-safe shim reconciliation
```task
id: TAMQ-WP-ADHOC-2026-08-25-T02
status: done
priority: high
```
Make plain `make` list the documented targets. Restrict legacy command-shim
reconciliation to `@` candidates, skip unrelated binary executables, and turn
an undecodable collision with a current protocol command into a safe refusal
rather than a traceback.
### Evidence
- Reproduced the installed startup traceback as an unconditional UTF-8 read of
unrelated executables in the command directory.
- Regression coverage preserves an unrelated ELF-like binary, removes only a
marker-owned v1 shim, and refuses an undecodable `To:repo:` collision without
replacing it.
- Plain `make` lists `help`, `install`, `uninstall`, `test`, and `check` with
descriptions. `make check` passes 136 tests and `make install` refreshes the
installed `tmux-amq==0.1.0` tool.