carlos / platform Public

Merge branch 'feat-maintenance-payload' into main

claude agent, operated by paul@keymail.dev 9bf83ba6f7fbab826fc0e264a5362abd6ea3a7d5 parent 8746297parent bc2c8de
2 files changed, +245 −0
  • internal/maintenance/input.go +127 −0
  • internal/maintenance/input_test.go +118 −0

CI passed — run details

diff --git a/internal/maintenance/input.go b/internal/maintenance/input.go
new file mode 100644
index 000000000..a84e25bd0
--- /dev/null
+++ b/internal/maintenance/input.go
@@ -0,0 +1,127 @@
+package maintenance
+
+import (
+ "bytes"
+ "crypto/ecdh"
+ "encoding/binary"
+ "encoding/json"
+ "errors"
+ "strings"
+ "unicode/utf8"
+)
+
+const (
+ MaxInputBytes = 64 << 10
+ MaxArguments = 32
+ MaxArgumentBytes = 8 << 10
+ inputPrefix = "CMI1"
+ maxInputFrame = len(inputPrefix) + 4 + 4*MaxArguments + MaxArgumentBytes + 4 + MaxInputBytes
+)
+
+var ErrInput = errors.New("maintenance: invalid private input")
+
+// Input contains arguments for the pinned artifact and its exact JSON stdin.
+// The worker supplies the existing database path; this value grants no authority
+// to select an executable, shell, database, environment, or working directory.
+type Input struct {
+ Arguments []string
+ Stdin []byte
+}
+
+func validInput(in Input) bool {
+ if len(in.Arguments) == 0 || len(in.Arguments) > MaxArguments || len(in.Stdin) > MaxInputBytes || !utf8.Valid(in.Stdin) || !json.Valid(in.Stdin) {
+ return false
+ }
+ total := 0
+ for _, arg := range in.Arguments {
+ if len(arg) > MaxArgumentBytes-total || strings.ContainsRune(arg, 0) || !utf8.ValidString(arg) {
+ return false
+ }
+ total += len(arg)
+ }
+ return true
+}
+
+// EncodeInput retains whitespace, escapes, and every other byte of signed input.
+// JSON semantics, including allowed fields, remain the app command's concern.
+func EncodeInput(in Input) ([]byte, error) {
+ if !validInput(in) {
+ return nil, ErrInput
+ }
+ out := binary.BigEndian.AppendUint32([]byte(inputPrefix), uint32(len(in.Arguments)))
+ for _, arg := range in.Arguments {
+ out = appendField(out, []byte(arg))
+ }
+ return appendField(out, in.Stdin), nil
+}
+
+// DecodeInput returns owned data only after checking the complete bounded frame.
+func DecodeInput(frame []byte) (Input, error) {
+ if len(frame) > maxInputFrame || len(frame) < 8 || !bytes.Equal(frame[:4], []byte(inputPrefix)) {
+ return Input{}, ErrInput
+ }
+ count := binary.BigEndian.Uint32(frame[4:8])
+ if count == 0 || count > MaxArguments {
+ return Input{}, ErrInput
+ }
+ rest := frame[8:]
+ take := func() ([]byte, bool) {
+ if len(rest) < 4 {
+ return nil, false
+ }
+ n := binary.BigEndian.Uint32(rest[:4])
+ rest = rest[4:]
+ if uint64(n) > uint64(len(rest)) {
+ return nil, false
+ }
+ value := rest[:int(n)]
+ rest = rest[int(n):]
+ return value, true
+ }
+ in := Input{Arguments: make([]string, 0, int(count))}
+ for range count {
+ arg, ok := take()
+ if !ok {
+ return Input{}, ErrInput
+ }
+ in.Arguments = append(in.Arguments, string(arg))
+ }
+ stdin, ok := take()
+ if !ok || len(rest) != 0 {
+ return Input{}, ErrInput
+ }
+ in.Stdin = stdin
+ if !validInput(in) {
+ return Input{}, ErrInput
+ }
+ in.Stdin = bytes.Clone(stdin)
+ return in, nil
+}
+
+func SealInput(pub *ecdh.PublicKey, binding Binding, in Input) (string, error) {
+ if binding.Direction != DirectionInput {
+ return "", ErrInput
+ }
+ frame, err := EncodeInput(in)
+ if err != nil {
+ return "", ErrInput
+ }
+ defer clear(frame)
+ sealed, err := Seal(pub, binding, frame)
+ if err != nil {
+ return "", ErrInput
+ }
+ return sealed, nil
+}
+
+func OpenInput(priv *ecdh.PrivateKey, binding Binding, sealed string) (Input, error) {
+ if binding.Direction != DirectionInput {
+ return Input{}, ErrInput
+ }
+ frame, err := Open(priv, binding, sealed)
+ if err != nil {
+ return Input{}, ErrInput
+ }
+ defer clear(frame)
+ return DecodeInput(frame)
+}
diff --git a/internal/maintenance/input_test.go b/internal/maintenance/input_test.go
new file mode 100644
index 000000000..8e578d70c
--- /dev/null
+++ b/internal/maintenance/input_test.go
@@ -0,0 +1,118 @@
+package maintenance
+
+import (
+ "bytes"
+ "crypto/ecdh"
+ "crypto/rand"
+ "encoding/binary"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestInputExactSignedBytes(t *testing.T) {
+ in := Input{Arguments: []string{"operator-v1", "", "space value", "$(not-a-shell)"}, Stdin: []byte(" \n{\"signed\": \"\\u003c\", \"n\": 1e0} \n")}
+ frame, err := EncodeInput(in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, err := DecodeInput(frame)
+ if err != nil || !reflect.DeepEqual(in, got) {
+ t.Fatalf("round trip: %v", err)
+ }
+ clear(frame)
+ if !reflect.DeepEqual(in, got) {
+ t.Fatal("decoded input aliases frame")
+ }
+ priv, err := ecdh.P256().GenerateKey(rand.Reader)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fingerprint, _ := RecipientFingerprint(priv.PublicKey())
+ binding := Binding{Version: 1, Direction: DirectionInput, Account: "account", App: "home", Host: "test.example", RequestID: "request", AttemptID: "attempt", Recipient: fingerprint}
+ sealed, err := SealInput(priv.PublicKey(), binding, in)
+ if err != nil {
+ t.Fatal(err)
+ }
+ opened, err := OpenInput(priv, binding, sealed)
+ if err != nil || !reflect.DeepEqual(in, opened) {
+ t.Fatalf("sealed round trip: %v", err)
+ }
+ for _, direction := range []Direction{DirectionResult, DirectionEnvironment} {
+ wrong := binding
+ wrong.Direction = direction
+ if _, err := SealInput(priv.PublicKey(), wrong, in); err != ErrInput {
+ t.Fatal("accepted wrong sealing direction")
+ }
+ // An otherwise valid envelope of the wrong purpose must also be refused.
+ frame, _ := EncodeInput(in)
+ ciphertext, err := Seal(priv.PublicKey(), wrong, frame)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := OpenInput(priv, wrong, ciphertext); err != ErrInput {
+ t.Fatal("accepted wrong opening direction")
+ }
+ }
+}
+
+func TestInputLimitsAndRefusals(t *testing.T) {
+ maxJSON := []byte(`"` + strings.Repeat("x", MaxInputBytes-2) + `"`)
+ args := make([]string, MaxArguments)
+ args[0] = strings.Repeat("x", MaxArgumentBytes)
+ boundary := Input{Arguments: args, Stdin: maxJSON}
+ frame, err := EncodeInput(boundary)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := DecodeInput(frame); err != nil {
+ t.Fatal(err)
+ }
+ bad := []Input{
+ {nil, []byte(`{}`)},
+ {make([]string, MaxArguments+1), []byte(`{}`)},
+ {[]string{strings.Repeat("x", MaxArgumentBytes), "x"}, []byte(`{}`)},
+ {[]string{"ok\x00bad"}, []byte(`{}`)},
+ {[]string{string([]byte{255})}, []byte(`{}`)},
+ {[]string{"ok"}, append(bytes.Clone(maxJSON), ' ')},
+ {[]string{"ok"}, nil},
+ {[]string{"ok"}, []byte(`{} {}`)},
+ {[]string{"ok"}, []byte{'"', 255, '"'}},
+ }
+ for i, in := range bad {
+ if _, err := EncodeInput(in); err != ErrInput {
+ t.Fatalf("case %d encode accepted", i)
+ }
+ // Construct unchecked frames so decoder limits cannot rely on the encoder.
+ raw := binary.BigEndian.AppendUint32([]byte(inputPrefix), uint32(len(in.Arguments)))
+ for _, arg := range in.Arguments {
+ raw = appendField(raw, []byte(arg))
+ }
+ raw = appendField(raw, in.Stdin)
+ if _, err := DecodeInput(raw); err != ErrInput {
+ t.Fatalf("case %d decode accepted", i)
+ }
+ }
+}
+
+func TestInputMalformedFrames(t *testing.T) {
+ frame, _ := EncodeInput(Input{[]string{"operator-v1", "argument"}, []byte(`{"ok":true}`)})
+ for n := 0; n < len(frame); n++ {
+ if _, err := DecodeInput(frame[:n]); err != ErrInput {
+ t.Fatalf("accepted truncation %d", n)
+ }
+ }
+ for _, offset := range []int{0, 4, 8, 8 + 4 + len("operator-v1"), 8 + 4 + len("operator-v1") + 4 + len("argument")} {
+ broken := bytes.Clone(frame)
+ binary.BigEndian.PutUint32(broken[offset:offset+4], ^uint32(0))
+ if _, err := DecodeInput(broken); err != ErrInput {
+ t.Fatalf("accepted corrupt frame at %d", offset)
+ }
+ }
+ if _, err := DecodeInput(append(bytes.Clone(frame), 0)); err != ErrInput {
+ t.Fatal("accepted trailing bytes")
+ }
+ if _, err := DecodeInput(make([]byte, maxInputFrame+1)); err != ErrInput {
+ t.Fatal("accepted oversized frame")
+ }
+}