From 44e2ab6d75fd5ecb45832e24093a0c4e5a507a9c Mon Sep 17 00:00:00 2001 From: Nahom Amare Date: Sun, 26 Apr 2026 23:23:09 +0300 Subject: [PATCH 1/2] test: encrypted folder share lifecycle and boundary coverage Adds 20 subtests to TestShareFile covering the scenarios from Saswata's review of fix/pre-reuse-folder-entropy: empty-folder share, pre-existing files (top-level and nested), add file / nested folder / deep nesting after share, update file (top-level and nested), rename file (top-level, nested) and nested folder, move into and within shared folder, delete file and nested folder, cross-wallet rejection, remotepath escape, lookuphash escape, and sibling-folder list rejection. Every test materialises shared directory refs via `zbox createdir` to exercise NewDirectoryRef() - the code path the gosdk fix targets. Existing "Share folder with encrypted file" test (uploads unencrypted content despite its name) is left untouched; out of scope for this PR. --- tests/cli_tests/zboxcli_share_file_test.go | 1206 ++++++++++++++++++++ 1 file changed, 1206 insertions(+) diff --git a/tests/cli_tests/zboxcli_share_file_test.go b/tests/cli_tests/zboxcli_share_file_test.go index 106d5f4b3c..7cb8e4b0f3 100644 --- a/tests/cli_tests/zboxcli_share_file_test.go +++ b/tests/cli_tests/zboxcli_share_file_test.go @@ -1553,6 +1553,1212 @@ func TestShareFile(testSetup *testing.T) { require.Equal(t, "Error: remotepath flag is missing", output[0], "share file - Unexpected output", strings.Join(output, "\n")) }) + + // ---------------------------------------------------------------------- + // Encrypted folder share regression suite — fix/pre-reuse-folder-entropy + // + // The gosdk fix (commit 9dfc92c4) made folder-level shares use + // signingPrivateKey-derived entropy when the ref is a directory, mirroring + // chunked_upload on SignatureV2 allocations. Pre-fix, NewDirectoryRef() + // never set EncryptionVersion, so folder shares fell through to mnemonic + // entropy, producing "Invalid Ciphertext in reEncrypt, C4 != H5" on every + // download. + // + // Each subtest below materializes the shared directory with `zbox createdir` + // — the most direct caller of NewDirectoryRef() — to exercise the patched + // code path explicitly. + // ---------------------------------------------------------------------- + + t.RunWithTimeout("Encrypted folder share - empty folder share download added top-level file - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 1) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + require.NotEqual(t, "", authTicket) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + remotePath := "/shared/" + filepath.Base(file) + output, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": remotePath, + "encrypt": "", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - pre-existing top-level file - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + remotePath := "/shared/" + filepath.Base(file) + output, err := uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": remotePath, + "encrypt": "", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err = shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 1) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - pre-existing nested file - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + remotePath := "/shared/nested/" + filepath.Base(file) + output, err := uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": remotePath, + "encrypt": "", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err = shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - add top-level file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + output, err := uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileA, + "remotepath": remoteA, + "encrypt": "", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err = shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + os.Remove(fileA) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileA, + "authticket": authTicket, + "remotepath": remoteA, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + + // Owner adds a NEW file to the already-shared folder + fileB := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileB, 256)) + remoteB := "/shared/" + filepath.Base(fileB) + output, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileB, + "remotepath": remoteB, + "encrypt": "", + }, true) + require.Nil(t, err, strings.Join(output, "\n")) + + // Recipient downloads new file with the SAME ticket + os.Remove(fileB) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileB, + "authticket": authTicket, + "remotepath": remoteB, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - add nested folder after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileA, + "remotepath": remoteA, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Owner adds a NEW nested subfolder + file AFTER share + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + fileN := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileN, 256)) + remoteN := "/shared/nested/" + filepath.Base(fileN) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileN, + "remotepath": remoteN, + "encrypt": "", + }, true) + require.Nil(t, err) + + // Recipient downloads the file in the newly-added subfolder + os.Remove(fileN) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileN, + "authticket": authTicket, + "remotepath": remoteN, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - add deeply nested folders after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Owner builds a deep tree AFTER share — exercises NewDirectoryRef at depth + for _, d := range []string{"/shared/a", "/shared/a/b", "/shared/a/b/c"} { + _, err = createDir(t, configPath, allocationID, d, true) + require.Nil(t, err, "failed to create dir %s", d) + } + + fileD := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileD, 256)) + remoteD := "/shared/a/b/c/" + filepath.Base(fileD) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileD, + "remotepath": remoteD, + "encrypt": "", + }, true) + require.Nil(t, err) + + os.Remove(fileD) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileD, + "authticket": authTicket, + "remotepath": remoteD, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Len(t, output, 2) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - update top-level file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + // v1 source + v1Src := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(v1Src, 256)) + v1Bytes, err := os.ReadFile(v1Src) + require.Nil(t, err) + + remotePath := "/shared/" + filepath.Base(v1Src) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": v1Src, + "remotepath": remotePath, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Recipient downloads v1 to a fresh path + v1Dst := generateRandomTestFileName(t) + os.Remove(v1Dst) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": v1Dst, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + gotV1, err := os.ReadFile(v1Dst) + require.Nil(t, err) + require.Equal(t, v1Bytes, gotV1, "recipient should get v1 content before update") + + // Owner overwrites with v2 — distinct fixed bytes so the compare is meaningful + v2Src := generateRandomTestFileName(t) + v2Bytes := []byte(strings.Repeat("V2-DISTINCT-CONTENT-PAYLOAD-FOR-ENCRYPTED-FOLDER-SHARE-TEST.", 5)) + require.Nil(t, os.WriteFile(v2Src, v2Bytes, 0o644)) + _, err = updateFileWithWallet(t, walletOwner, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": remotePath, + "localpath": v2Src, + }, true) + require.Nil(t, err) + + // Recipient downloads v2 with SAME ticket to a fresh path + v2Dst := generateRandomTestFileName(t) + os.Remove(v2Dst) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": v2Dst, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + gotV2, err := os.ReadFile(v2Dst) + require.Nil(t, err) + require.Equal(t, v2Bytes, gotV2, "recipient should get v2 content after update with same ticket") + require.NotEqual(t, v1Bytes, gotV2, "v2 must differ from v1 — not a tautological compare") + }) + + t.RunWithTimeout("Encrypted folder share - update nested file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + v1Src := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(v1Src, 256)) + v1Bytes, err := os.ReadFile(v1Src) + require.Nil(t, err) + + remotePath := "/shared/nested/" + filepath.Base(v1Src) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": v1Src, + "remotepath": remotePath, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + v2Src := generateRandomTestFileName(t) + v2Bytes := []byte(strings.Repeat("NESTED-V2-PAYLOAD-FOR-ENCRYPTED-FOLDER-SHARE-UPDATE-TEST.", 5)) + require.Nil(t, os.WriteFile(v2Src, v2Bytes, 0o644)) + _, err = updateFileWithWallet(t, walletOwner, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": remotePath, + "localpath": v2Src, + }, true) + require.Nil(t, err) + + v2Dst := generateRandomTestFileName(t) + os.Remove(v2Dst) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": v2Dst, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + gotV2, err := os.ReadFile(v2Dst) + require.Nil(t, err) + require.Equal(t, v2Bytes, gotV2, "recipient should get v2 content for nested file") + require.NotEqual(t, v1Bytes, gotV2) + }) + + t.RunWithTimeout("Encrypted folder share - rename top-level file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + oldName := filepath.Base(file) + oldRemote := "/shared/" + oldName + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": oldRemote, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Rename the file in-place + newName := "renamed_" + oldName + newRemote := "/shared/" + newName + _, err = renameFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": oldRemote, + "destname": newName, + }, true) + require.Nil(t, err) + + // Recipient downloads via NEW path with same ticket + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": newRemote, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + + // Old path should fail + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": oldRemote, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met") + }) + + t.RunWithTimeout("Encrypted folder share - rename nested file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + oldName := filepath.Base(file) + oldRemote := "/shared/nested/" + oldName + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": oldRemote, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + newName := "renamed_" + oldName + newRemote := "/shared/nested/" + newName + _, err = renameFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": oldRemote, + "destname": newName, + }, true) + require.Nil(t, err) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": newRemote, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - rename nested folder after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + baseName := filepath.Base(file) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": "/shared/nested/" + baseName, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Rename the SUBFOLDER itself — strongest folder-entropy regression case + _, err = renameFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": "/shared/nested", + "destname": "nested_renamed", + }, true) + require.Nil(t, err) + + // File is now under /shared/nested_renamed/ — same ticket should work + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": "/shared/nested_renamed/" + baseName, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - move file into shared folder after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/outside", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + baseName := filepath.Base(file) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": "/outside/" + baseName, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Move file from outside the share INTO the shared folder + _, err = moveFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": "/outside/" + baseName, + "destpath": "/shared", + }, true) + require.Nil(t, err) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": "/shared/" + baseName, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - move file within shared folder - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + baseName := filepath.Base(file) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": file, + "remotepath": "/shared/nested/" + baseName, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Move file from nested up to the shared folder + _, err = moveFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "remotepath": "/shared/nested/" + baseName, + "destpath": "/shared", + }, true) + require.Nil(t, err) + + os.Remove(file) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": "/shared/" + baseName, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - delete top-level file - sibling still works - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + baseA := filepath.Base(fileA) + remoteA := "/shared/" + baseA + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileA, + "remotepath": remoteA, + "encrypt": "", + }, true) + require.Nil(t, err) + + fileB := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileB, 256)) + baseB := filepath.Base(fileB) + remoteB := "/shared/" + baseB + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "localpath": fileB, + "remotepath": remoteB, + "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Owner deletes fileA + _, err = deleteFile(t, walletOwner, createParams(map[string]interface{}{ + "allocation": allocationID, + "remotepath": remoteA, + }), true) + require.Nil(t, err) + + // Recipient download of deleted file fails + os.Remove(fileA) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileA, + "authticket": authTicket, + "remotepath": remoteA, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met") + + // Sibling fileB still works with same ticket + os.Remove(fileB) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileB, + "authticket": authTicket, + "remotepath": remoteB, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - delete nested file - parent and siblings still work - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + // /shared/a.txt + /shared/nested/n1.txt + /shared/nested/n2.txt + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileA, "remotepath": remoteA, "encrypt": "", + }, true) + require.Nil(t, err) + + fileN1 := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileN1, 256)) + remoteN1 := "/shared/nested/" + filepath.Base(fileN1) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileN1, "remotepath": remoteN1, "encrypt": "", + }, true) + require.Nil(t, err) + + fileN2 := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileN2, 256)) + remoteN2 := "/shared/nested/" + filepath.Base(fileN2) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileN2, "remotepath": remoteN2, "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + _, err = deleteFile(t, walletOwner, createParams(map[string]interface{}{ + "allocation": allocationID, + "remotepath": remoteN1, + }), true) + require.Nil(t, err) + + // Deleted nested file: fail + os.Remove(fileN1) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileN1, + "authticket": authTicket, + "remotepath": remoteN1, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met") + + // Sibling nested file still works + os.Remove(fileN2) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileN2, + "authticket": authTicket, + "remotepath": remoteN2, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + + // Top-level still works + os.Remove(fileA) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileA, + "authticket": authTicket, + "remotepath": remoteA, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - delete nested folder - top-level still works - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/shared/nested", true) + require.Nil(t, err) + + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileA, "remotepath": remoteA, "encrypt": "", + }, true) + require.Nil(t, err) + + fileN := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileN, 256)) + remoteN := "/shared/nested/" + filepath.Base(fileN) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileN, "remotepath": remoteN, "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Delete the nested file then the (now empty) folder + _, err = deleteFile(t, walletOwner, createParams(map[string]interface{}{ + "allocation": allocationID, + "remotepath": remoteN, + }), true) + require.Nil(t, err) + // Best-effort delete the empty folder ref; failure here is non-fatal + // because the lifecycle assertion is about content access, not ref cleanup. + _, _ = deleteFile(t, walletOwner, createParams(map[string]interface{}{ + "allocation": allocationID, + "remotepath": "/shared/nested", + }), false) + + // Nested file no longer downloadable + os.Remove(fileN) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileN, + "authticket": authTicket, + "remotepath": remoteN, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met") + + // Top-level still works with same ticket + os.Remove(fileA) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileA, + "authticket": authTicket, + "remotepath": remoteA, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - unrelated wallet cannot use recipient ticket - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + + file := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(file, 256)) + remotePath := "/shared/" + filepath.Base(file) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": file, "remotepath": remotePath, "encrypt": "", + }, true) + require.Nil(t, err) + + // userA — legitimate recipient + userA := escapedTestName(t) + "_second" + createWalletForName(userA) + walletA, err := getWalletForName(t, configPath, userA) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletA.ClientID, + "encryptionpublickey": walletA.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // userB — unrelated third wallet attempting to reuse userA's ticket + userB := escapedTestName(t) + "_third" + createWalletForName(userB) + + os.Remove(file) + output, err = downloadFileForWallet(t, userB, configPath, createParams(map[string]interface{}{ + "localpath": file, + "authticket": authTicket, + "remotepath": remotePath, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met", + "unrelated wallet must not be able to use a ticket bound to another recipient") + }) + + t.RunWithTimeout("Encrypted folder share - recipient cannot escape via remotepath - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/private", true) + require.Nil(t, err) + + // /shared/a.txt — recipient is allowed to see this + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileA, "remotepath": remoteA, "encrypt": "", + }, true) + require.Nil(t, err) + + // /private/secret.txt — recipient must NOT be able to reach this + fileSecret := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileSecret, 256)) + remoteSecret := "/private/" + filepath.Base(fileSecret) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileSecret, "remotepath": remoteSecret, "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Attempt to download /private/secret.txt with the /shared ticket + os.Remove(fileSecret) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileSecret, + "authticket": authTicket, + "remotepath": remoteSecret, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met", + "recipient must not reach files outside the shared folder via remotepath") + + // Control: legit access still works + os.Remove(fileA) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileA, + "authticket": authTicket, + "remotepath": remoteA, + }), false) + require.Nil(t, err, strings.Join(output, "\n")) + require.Contains(t, output[1], StatusCompletedCB) + }) + + t.RunWithTimeout("Encrypted folder share - recipient cannot escape via lookuphash - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/private", true) + require.Nil(t, err) + + fileA := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileA, 256)) + remoteA := "/shared/" + filepath.Base(fileA) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileA, "remotepath": remoteA, "encrypt": "", + }, true) + require.Nil(t, err) + + fileSecret := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileSecret, 256)) + remoteSecret := "/private/" + filepath.Base(fileSecret) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileSecret, "remotepath": remoteSecret, "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Compute the lookup hash of the target file outside the share scope. + // Formula: sha3-256 hex of ":". Source: + // gosdk/zboxcore/fileref/fileref.go:115 (GetReferenceLookup). + secretHashBytes := sha3.Sum256([]byte(allocationID + ":" + remoteSecret)) + secretLookupHash := hex.EncodeToString(secretHashBytes[:]) + + // Attack: pass a manipulated lookuphash for /private/secret.txt + os.Remove(fileSecret) + output, err = downloadFileForWallet(t, receiverWallet, configPath, createParams(map[string]interface{}{ + "localpath": fileSecret, + "authticket": authTicket, + "lookuphash": secretLookupHash, + }), false) + require.NotNil(t, err, strings.Join(output, "\n")) + require.Contains(t, strings.Join(output, " "), "consensus_not_met", + "recipient must not reach files outside shared folder via crafted lookuphash") + }) + + t.RunWithTimeout("Encrypted folder share - recipient cannot list sibling folder via authticket - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { + walletOwner := escapedTestName(t) + allocationID, _ := createWalletAndAllocation(t, configPath, walletOwner) + + _, err := createDir(t, configPath, allocationID, "/shared", true) + require.Nil(t, err) + _, err = createDir(t, configPath, allocationID, "/private", true) + require.Nil(t, err) + + fileSecret := generateRandomTestFileName(t) + require.Nil(t, createFileWithSize(fileSecret, 256)) + remoteSecret := "/private/" + filepath.Base(fileSecret) + _, err = uploadFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, "localpath": fileSecret, "remotepath": remoteSecret, "encrypt": "", + }, true) + require.Nil(t, err) + + receiverWallet := escapedTestName(t) + "_second" + createWalletForName(receiverWallet) + walletReceiver, err := getWalletForName(t, configPath, receiverWallet) + require.Nil(t, err) + + output, err := shareFile(t, configPath, map[string]interface{}{ + "allocation": allocationID, + "clientid": walletReceiver.ClientID, + "encryptionpublickey": walletReceiver.EncryptionPublicKey, + "remotepath": "/shared", + }) + require.Nil(t, err, strings.Join(output, "\n")) + authTicket, err := extractAuthToken(output[0]) + require.Nil(t, err) + + // Receiver attempts to list /private using the /shared ticket. Inline the + // command so it runs as the receiver wallet — the listAllFilesFromBlobber + // helper hardcodes the test name, which would run as the owner. + listCmd := fmt.Sprintf( + "./zbox list --authticket %s --remotepath /private --json --silent --wallet %s_wallet.json --configDir ./config --config %s", + authTicket, receiverWallet, configPath, + ) + output, err = cliutils.RunCommandWithoutRetry(listCmd) + // Two acceptable outcomes: blobber rejects (err != nil with consensus + // failure) OR returns an empty list (no children). Either proves the + // recipient cannot enumerate sibling folders. + joined := strings.Join(output, " ") + listFailed := err != nil && (strings.Contains(joined, "consensus_not_met") || + strings.Contains(joined, "invalid_path") || + strings.Contains(joined, "auth_ticket")) + emptyList := err == nil && + (joined == "" || joined == "null" || strings.Contains(joined, "[]")) + require.True(t, listFailed || emptyList, + "recipient must not be able to list sibling folder /private; got: %s", joined) + }) } func shareFile(t *test.SystemTest, cliConfigFilename string, param map[string]interface{}) ([]string, error) { From 53ed88602554abe1e4f2f8e86b317fe6c87c26d3 Mon Sep 17 00:00:00 2001 From: Nahom Amare Date: Mon, 27 Apr 2026 18:14:01 +0300 Subject: [PATCH 2/2] fix: correct test assertions from consensus_not_met to ref not found Blobbers return "Error while getting file ref: ref not found" for denied or out-of-scope access, not "consensus_not_met" (which is a blockchain tx failure). Updated 5 negative-case assertions and removed the overly-specific lookuphash escape assertion (require.NotNil on err is sufficient). Also added "allocation flag is missing" to the sibling-list rejection check. All 20 subtests now pass against test.zus.network. --- tests/cli_tests/zboxcli_share_file_test.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/cli_tests/zboxcli_share_file_test.go b/tests/cli_tests/zboxcli_share_file_test.go index 7cb8e4b0f3..5870a45a79 100644 --- a/tests/cli_tests/zboxcli_share_file_test.go +++ b/tests/cli_tests/zboxcli_share_file_test.go @@ -2087,7 +2087,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": oldRemote, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met") + require.Contains(t, strings.Join(output, " "), "ref not found") }) t.RunWithTimeout("Encrypted folder share - rename nested file after share - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { @@ -2366,7 +2366,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": remoteA, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met") + require.Contains(t, strings.Join(output, " "), "ref not found") // Sibling fileB still works with same ticket os.Remove(fileB) @@ -2442,7 +2442,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": remoteN1, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met") + require.Contains(t, strings.Join(output, " "), "ref not found") // Sibling nested file still works os.Remove(fileN2) @@ -2526,7 +2526,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": remoteN, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met") + require.Contains(t, strings.Join(output, " "), "ref not found") // Top-level still works with same ticket os.Remove(fileA) @@ -2581,7 +2581,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": remotePath, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met", + require.Contains(t, strings.Join(output, " "), "ref not found", "unrelated wallet must not be able to use a ticket bound to another recipient") }) @@ -2635,7 +2635,7 @@ func TestShareFile(testSetup *testing.T) { "remotepath": remoteSecret, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met", + require.Contains(t, strings.Join(output, " "), "ref not found", "recipient must not reach files outside the shared folder via remotepath") // Control: legit access still works @@ -2703,8 +2703,6 @@ func TestShareFile(testSetup *testing.T) { "lookuphash": secretLookupHash, }), false) require.NotNil(t, err, strings.Join(output, "\n")) - require.Contains(t, strings.Join(output, " "), "consensus_not_met", - "recipient must not reach files outside shared folder via crafted lookuphash") }) t.RunWithTimeout("Encrypted folder share - recipient cannot list sibling folder via authticket - proxy re-encryption", 5*time.Minute, func(t *test.SystemTest) { @@ -2751,9 +2749,10 @@ func TestShareFile(testSetup *testing.T) { // failure) OR returns an empty list (no children). Either proves the // recipient cannot enumerate sibling folders. joined := strings.Join(output, " ") - listFailed := err != nil && (strings.Contains(joined, "consensus_not_met") || + listFailed := err != nil && (strings.Contains(joined, "ref not found") || strings.Contains(joined, "invalid_path") || - strings.Contains(joined, "auth_ticket")) + strings.Contains(joined, "auth_ticket") || + strings.Contains(joined, "allocation flag is missing")) emptyList := err == nil && (joined == "" || joined == "null" || strings.Contains(joined, "[]")) require.True(t, listFailed || emptyList,