Bind Agent execution to task providers and SIP revision

This commit is contained in:
2026-09-29 20:54:32 +08:00
parent d8a0c29e8f
commit 0d191fa464
2 changed files with 80 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
package configread
import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
)
// ExecutionBindingSHA256 binds the exact immutable task and provider bytes
// to the SIP revision delivered to an Agent. Length prefixes prevent
// concatenation collisions; no credential content is returned or logged.
func ExecutionBindingSHA256(taskJSON, providersJSON []byte, sipRevision int64) (string, error) {
if !json.Valid(taskJSON) || !json.Valid(providersJSON) || sipRevision <= 0 {
return "", errors.New("execution snapshot has invalid JSON or SIP revision")
}
h := sha256.New()
var number [8]byte
binary.BigEndian.PutUint64(number[:], uint64(len(taskJSON)))
_, _ = h.Write(number[:])
_, _ = h.Write(taskJSON)
binary.BigEndian.PutUint64(number[:], uint64(len(providersJSON)))
_, _ = h.Write(number[:])
_, _ = h.Write(providersJSON)
binary.BigEndian.PutUint64(number[:], uint64(sipRevision))
_, _ = h.Write(number[:])
return hex.EncodeToString(h.Sum(nil)), nil
}
+51
View File
@@ -0,0 +1,51 @@
package configread
import (
"os"
"strings"
"testing"
)
func TestExecutionBindingSHA256CoversTaskProvidersAndSIPRevision(t *testing.T) {
task, err := os.ReadFile("../../contracts/local/examples/config-read-task-full.json")
if err != nil {
t.Fatal(err)
}
providers, err := os.ReadFile("../../contracts/local/examples/config-read-providers.json")
if err != nil {
t.Fatal(err)
}
original, err := ExecutionBindingSHA256(task, providers, 8)
if err != nil || len(original) != 64 {
t.Fatalf("binding hash: %q %v", original, err)
}
changedTask := append(append([]byte(nil), task...), ' ')
changedProviders := append(append([]byte(nil), providers...), ' ')
for _, tc := range []struct {
name string
task []byte
providers []byte
revision int64
}{
{"task", changedTask, providers, 8},
{"providers", task, changedProviders, 8},
{"sip-revision", task, providers, 9},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := ExecutionBindingSHA256(tc.task, tc.providers, tc.revision)
if err != nil || got == original {
t.Fatalf("snapshot field not bound: %q %v", got, err)
}
})
}
for _, tc := range []struct {
task, providers []byte
revision int64
}{
{nil, providers, 8}, {task, nil, 8}, {[]byte(`not-json`), providers, 8}, {task, providers, 0},
} {
if _, err := ExecutionBindingSHA256(tc.task, tc.providers, tc.revision); err == nil || strings.Contains(err.Error(), "example-only-not-a-real-secret") {
t.Fatalf("invalid binding must be rejected without printing provider credentials: %v", err)
}
}
}