Bound download expansion and escape control chars on the terminal (closes #164)
Objects fetched from the store are untrusted; several decode paths let one expand or print without limit. - blobgen.LimitReader errors past a byte cap (not io.LimitReader silent EOF). DecodeManifest reads through caps on both compressed input and decompressed output, far above any real manifest, so json.Decode cannot buffer a compressible bomb. FetchAndDecryptBlob bounds decompression to the blob recorded uncompressed_size (not the restoring host blob_size_limit). - downloadSnapshotDB streams straight from storage to its temp file with io.Copy, replacing two ReadAll calls that held the whole database twice. - FetchBlob drops the per-blob Stat round-trip, its expectedSize parameter and returned size, all of which only fed a debug log. - TTYHandler and ui.Writer escape control characters in messages, attribute keys/values, and rendered identifiers/paths before colour codes are applied, so a crafted value cannot drive the terminal. Model: opus-4-8
This commit was merged in pull request #197.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package vaultik_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sneak.berlin/go/vaultik/internal/blobgen"
|
||||
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||
)
|
||||
|
||||
// TestFetchAndDecryptBlobBoundsPlaintext feeds a small, highly
|
||||
// compressible blob (256 KiB of zeros) whose decompressed size far exceeds
|
||||
// the plaintext bound passed to FetchAndDecryptBlob. Decompression must
|
||||
// stop with blobgen.ErrOutputTooLarge within the bound rather than
|
||||
// expanding the whole blob into the restore cache.
|
||||
func TestFetchAndDecryptBlobBoundsPlaintext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
identity, err := age.GenerateX25519Identity()
|
||||
require.NoError(t, err)
|
||||
|
||||
plaintext := make([]byte, 256*1024)
|
||||
encryptedData, correctHash := buildHashTestBlob(t, identity, plaintext)
|
||||
|
||||
mockStorage := NewMockStorer()
|
||||
blobPath := "blobs/" + correctHash[:2] + "/" +
|
||||
correctHash[2:4] + "/" + correctHash
|
||||
|
||||
mockStorage.mu.Lock()
|
||||
mockStorage.data[blobPath] = encryptedData
|
||||
mockStorage.mu.Unlock()
|
||||
|
||||
tv := vaultik.NewForTesting(mockStorage)
|
||||
|
||||
const maxPlaintext = 1024
|
||||
|
||||
rc, err := tv.FetchAndDecryptBlob(
|
||||
context.Background(), correctHash, maxPlaintext, identity)
|
||||
require.NoError(t, err)
|
||||
|
||||
n, copyErr := io.Copy(io.Discard, rc)
|
||||
_ = rc.Close()
|
||||
|
||||
require.ErrorIs(t, copyErr, blobgen.ErrOutputTooLarge)
|
||||
require.LessOrEqual(t, n, int64(maxPlaintext)+1,
|
||||
"decompression must stop within the recorded plaintext bound")
|
||||
}
|
||||
Reference in New Issue
Block a user