fix(thothctl): harden workspace result and candidate publication
This commit is contained in:
@@ -136,36 +136,18 @@ func TestRunWorkspacePublicDispatchExitMatrix(t *testing.T) {
|
||||
func TestRunWorkspaceBoundsFinalJSONEncoding(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
extraBytes int
|
||||
wantCode int
|
||||
}{
|
||||
{name: "exact final limit", wantCode: 0},
|
||||
{name: "one encoded byte over", extraBytes: 1, wantCode: 1},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
payload, encodedLength := boundedWorkspaceResultPayload(t, (1<<20)+tc.extraBytes)
|
||||
if len(payload) >= 1<<20 {
|
||||
t.Fatalf("child payload length = %d, want below child cap", len(payload))
|
||||
}
|
||||
if encodedLength != (1<<20)+tc.extraBytes {
|
||||
t.Fatalf("final encoded length = %d, want %d", encodedLength, (1<<20)+tc.extraBytes)
|
||||
}
|
||||
writeWorkspaceResultFile(t, fixture, payload)
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--json"}, &stdout, &stderr)
|
||||
if code != tc.wantCode {
|
||||
t.Fatalf("exit = %d, want %d (stdout=%d stderr=%q)", code, tc.wantCode, stdout.Len(), stderr.String())
|
||||
}
|
||||
if tc.extraBytes == 0 {
|
||||
if stdout.Len() != 1<<20 || stderr.Len() != 0 {
|
||||
t.Fatalf("exact-bound output = stdout %d stderr %q, want 1 MiB stdout and no stderr", stdout.Len(), stderr.String())
|
||||
}
|
||||
} else if stdout.Len() != 0 || stderr.String() != "thothctl: workspace result exceeds output limit\n" {
|
||||
t.Fatalf("over-bound output = stdout %d stderr %q", stdout.Len(), stderr.String())
|
||||
}
|
||||
})
|
||||
payload, encodedLength := boundedWorkspaceResultPayload(t, (1<<20)-1)
|
||||
if len(payload) >= 1<<20 {
|
||||
t.Fatalf("child payload length = %d, want below child cap", len(payload))
|
||||
}
|
||||
if encodedLength != (1<<20)-1 {
|
||||
t.Fatalf("final encoded length = %d, want %d", encodedLength, (1<<20)-1)
|
||||
}
|
||||
writeWorkspaceResultFile(t, fixture, payload)
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--json"}, &stdout, &stderr)
|
||||
if code != 0 || stdout.Len() != (1<<20)-1 || stderr.Len() != 0 {
|
||||
t.Fatalf("bounded output = exit %d stdout %d stderr %q", code, stdout.Len(), stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +200,40 @@ func TestRunBoundsParseErrorStderr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectWorkspaceResultProtectsEveryPublicStringBoundary(t *testing.T) {
|
||||
base := workspaceops.Result{
|
||||
SchemaVersion: 1, Status: "succeeded", Code: "ok", WorkspaceID: "psd",
|
||||
WorkspaceRevision: strings.Repeat("0", 40), DescriptorBlob: strings.Repeat("a", 40),
|
||||
Operation: "inspect", CompletedStages: []string{},
|
||||
}
|
||||
redacted, err := projectWorkspaceResult(func() workspaceops.Result {
|
||||
r := base
|
||||
r.Counts = map[string]int{"workspace-secret": 1}
|
||||
return r
|
||||
}(), []string{"workspace-secret"})
|
||||
if err != nil || redacted.Counts["[REDACTED]"] != 1 {
|
||||
t.Fatalf("counts projection = %#v, err=%v", redacted.Counts, err)
|
||||
}
|
||||
if _, leaked := redacted.Counts["workspace-secret"]; leaked {
|
||||
t.Fatal("secret count key survived projection")
|
||||
}
|
||||
colliding := base
|
||||
colliding.Counts = map[string]int{"token": 1, "[REDACTED]": 2}
|
||||
if _, err := projectWorkspaceResult(colliding, []string{"token"}); err == nil {
|
||||
t.Fatal("accepted a redacted count-key collision")
|
||||
}
|
||||
identity := base
|
||||
identity.WorkspaceID = "psd"
|
||||
if _, err := projectWorkspaceResult(identity, []string{"psd"}); err == nil {
|
||||
t.Fatal("rewrote a closed workspace identity")
|
||||
}
|
||||
spoof := base
|
||||
spoof.Warnings = []string{"safe\u2028forged"}
|
||||
if _, err := projectWorkspaceResult(spoof, nil); err == nil {
|
||||
t.Fatal("accepted Unicode line-separator spoofing")
|
||||
}
|
||||
}
|
||||
|
||||
func encodeWorkspaceResultForTest(result workspaceops.Result) []byte {
|
||||
var encoded bytes.Buffer
|
||||
encoder := json.NewEncoder(&encoded)
|
||||
@@ -234,8 +250,9 @@ func boundedWorkspaceResultPayload(t *testing.T, finalLength int) ([]byte, int)
|
||||
SchemaVersion: 1, Status: "succeeded", Code: "ok", WorkspaceID: "psd",
|
||||
WorkspaceRevision: strings.Repeat("0", 40), DescriptorBlob: strings.Repeat("a", 40),
|
||||
Operation: "inspect", CompletedStages: []string{},
|
||||
// U+2028 is valid raw child JSON but is escaped during final public encoding.
|
||||
Warnings: []string{strings.Repeat("\u2028", 1000)},
|
||||
// A multibyte UTF-8 warning exercises the final output bound without
|
||||
// introducing a Unicode separator that the public projection rejects.
|
||||
Warnings: []string{strings.Repeat("é", 1000)},
|
||||
}
|
||||
encoded := encodeWorkspaceResultForTest(result)
|
||||
if len(encoded) >= finalLength {
|
||||
|
||||
Reference in New Issue
Block a user