mesh-cli: forward the line's standard input to the controller, with the line alone
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/a-terminal-line-takes-standard-input stopped: a member was stopped
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/a-terminal-line-takes-standard-input stopped: a member was stopped
A secret given at the controller's terminal through mesh-cli (secret accept … --from -) never reached the line: the engine forwarded the words only. It now forwards what mesh-cli's standard input held (at most 64 KiB, the SDK's bound) on the same core request to the controller, says it nowhere, and sends none with a follow of the same call. mesh-sdk moves to f047d0a.
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.9
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.11-0.20261009143344-f047d0a4a970
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/sys v0.48.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.9 h1:2zq9tbwoXEYi+dDBxPoeNmn39KdRRKRp8CK33F4LWd0=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.9/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.11-0.20261009143344-f047d0a4a970 h1:9tFDQsgmI+4X7/BpZGXIr+HemPKE7YddYGqWV0lINAI=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.11-0.20261009143344-f047d0a4a970/go.mod h1:GFuZUElBZ9A++mxgIKo97aXXo+kV0uJ/UkbhQPPIbrY=
|
||||
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||
|
||||
@@ -41,6 +41,10 @@ type Asked struct {
|
||||
// a process another account's session started. Only a login session is a person at a terminal (novox/hq ADR
|
||||
// 0272): the controller makes nothing else the terminal.
|
||||
Session string `json:"session,omitempty"`
|
||||
// Stdin is what mesh-cli's standard input held, at most cli.MaxStdin: carried to the controller with the line
|
||||
// and nowhere else. The engine says it in no journal, keeps it in no state, and never sends it again: a follow
|
||||
// carries none. The controller gives it to a line that runs as its terminal alone.
|
||||
Stdin []byte `json:"stdin,omitempty"`
|
||||
}
|
||||
|
||||
// Forward asks the controller and follows the line to what came of it (AskTheController): what it printed, that
|
||||
@@ -279,5 +283,5 @@ func answer(ctx context.Context, conn *net.UnixConn, held *slots, forward Forwar
|
||||
_ = conn.SetDeadline(time.Now().Add(cli.AskWithin))
|
||||
asking, cancel := context.WithTimeout(ctx, cli.AskWithin-30*time.Second)
|
||||
defer cancel()
|
||||
reply(forward(asking, Asked{Line: req.Line, Account: account, UID: uid, Session: session}))
|
||||
reply(forward(asking, Asked{Line: req.Line, Account: account, UID: uid, Session: session, Stdin: req.Stdin}))
|
||||
}
|
||||
|
||||
@@ -135,6 +135,10 @@ func TestTheRequestToTheControllerKeepsItsFieldNames(t *testing.T) {
|
||||
if got := keysOf(t, body); got != "account line uid" {
|
||||
t.Fatalf("the request's fields are %q", got)
|
||||
}
|
||||
body, _ = json.Marshal(Asked{Line: []string{"secret"}, Account: "a", UID: 1, Stdin: []byte("x")})
|
||||
if got := keysOf(t, body); got != "account line stdin uid" {
|
||||
t.Fatalf("a request with standard input has the fields %q", got)
|
||||
}
|
||||
body, _ = json.Marshal(Asked{Follow: "call-1", Account: "a", UID: 1})
|
||||
if got := keysOf(t, body); got != "account follow uid" {
|
||||
t.Fatalf("a follow's fields are %q", got)
|
||||
@@ -346,3 +350,62 @@ func TestTheSessionIsReadBeforeTheLine(t *testing.T) {
|
||||
t.Fatalf("read in the order %v, sent %q", order, got.Session)
|
||||
}
|
||||
}
|
||||
|
||||
// mesh-cli's standard input reaches the controller with its line, whole, and only with the line: a follow of the
|
||||
// same call carries none, and the engine says it nowhere (novox/hq ADR 0259 §10, ADR 0272).
|
||||
func TestStandardInputGoesToTheControllerWithTheLineAndNowhereElse(t *testing.T) {
|
||||
secret := []byte("123456789:the-bot-token-given-at-the-terminal\n")
|
||||
var said []string
|
||||
var mu sync.Mutex
|
||||
var asked [][]byte
|
||||
ask := func(_ context.Context, body []byte) ([]byte, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
asked = append(asked, append([]byte(nil), body...))
|
||||
if len(asked) == 1 {
|
||||
return []byte(`{"result":{"running":true,"call":"call-7"}}`), nil
|
||||
}
|
||||
return []byte(`{"result":{"exit":0,"terminal":true}}`), nil
|
||||
}
|
||||
was := followEvery
|
||||
followEvery = time.Millisecond
|
||||
t.Cleanup(func() { followEvery = was })
|
||||
socket := filepath.Join(t.TempDir(), "run", "cli.sock")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- Serve(ctx, socket, func(ctx context.Context, a Asked) cli.Answer { return AskTheController(ctx, ask, a) },
|
||||
func(s string) { mu.Lock(); said = append(said, s); mu.Unlock() })
|
||||
}()
|
||||
defer func() { cancel(); <-done }()
|
||||
for i := 0; i < 200; i++ {
|
||||
if _, err := os.Stat(socket); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
a, err := cli.AskWith(context.Background(), socket, cli.Request{Line: []string{"secret", "accept", "anchor",
|
||||
"telegram", "telegram-token", "--from", "-"}, Stdin: secret})
|
||||
if err != nil || a.Exit != 0 {
|
||||
t.Fatalf("%v %+v", err, a)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(asked) != 2 {
|
||||
t.Fatalf("asked %d times, want the line and one follow", len(asked))
|
||||
}
|
||||
var first, follow Asked
|
||||
_ = json.Unmarshal(asked[0], &first)
|
||||
_ = json.Unmarshal(asked[1], &follow)
|
||||
if string(first.Stdin) != string(secret) {
|
||||
t.Fatalf("the controller was not given the standard input whole: %q", first.Stdin)
|
||||
}
|
||||
if len(follow.Stdin) != 0 || strings.Contains(string(asked[1]), "stdin") {
|
||||
t.Fatalf("a follow carried the standard input again: %s", asked[1])
|
||||
}
|
||||
for _, s := range said {
|
||||
if strings.Contains(s, "bot-token") {
|
||||
t.Fatalf("the engine said the standard input: %s", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user