fix(node): harden bundle stream error handling
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
727b5fdb8d
commit
61fb52e8f2
1 changed files with 25 additions and 15 deletions
|
|
@ -237,15 +237,15 @@ func createTarball(files map[string][]byte) ([]byte, error) {
|
||||||
Size: int64(len(content)),
|
Size: int64(len(content)),
|
||||||
}
|
}
|
||||||
if err := tw.WriteHeader(hdr); err != nil {
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
return nil, err
|
return nil, coreerr.E("createTarball", "failed to write tar header", err)
|
||||||
}
|
}
|
||||||
if _, err := tw.Write(content); err != nil {
|
if _, err := tw.Write(content); err != nil {
|
||||||
return nil, err
|
return nil, coreerr.E("createTarball", "failed to write tar content", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tw.Close(); err != nil {
|
if err := tw.Close(); err != nil {
|
||||||
return nil, err
|
return nil, coreerr.E("createTarball", "failed to close tar writer", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
|
|
@ -261,11 +261,11 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
|
||||||
absDestDir = filepath.Clean(absDestDir)
|
absDestDir = filepath.Clean(absDestDir)
|
||||||
|
|
||||||
if err := coreio.Local.EnsureDir(absDestDir); err != nil {
|
if err := coreio.Local.EnsureDir(absDestDir); err != nil {
|
||||||
return "", err
|
return "", coreerr.E("extractTarball", "failed to ensure destination directory", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tr := tar.NewReader(bytes.NewReader(tarData))
|
tr := tar.NewReader(bytes.NewReader(tarData))
|
||||||
var firstExecutable string
|
var firstExecutablePath string
|
||||||
|
|
||||||
for {
|
for {
|
||||||
hdr, err := tr.Next()
|
hdr, err := tr.Next()
|
||||||
|
|
@ -273,7 +273,7 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", coreerr.E("extractTarball", "failed to read tar entry", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Security: Sanitize the tar entry name to prevent path traversal (Zip Slip)
|
// Security: Sanitize the tar entry name to prevent path traversal (Zip Slip)
|
||||||
|
|
@ -301,12 +301,12 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
|
||||||
switch hdr.Typeflag {
|
switch hdr.Typeflag {
|
||||||
case tar.TypeDir:
|
case tar.TypeDir:
|
||||||
if err := coreio.Local.EnsureDir(fullPath); err != nil {
|
if err := coreio.Local.EnsureDir(fullPath); err != nil {
|
||||||
return "", err
|
return "", coreerr.E("extractTarball", "failed to create directory "+cleanName, err)
|
||||||
}
|
}
|
||||||
case tar.TypeReg:
|
case tar.TypeReg:
|
||||||
// Ensure parent directory exists
|
// Ensure parent directory exists
|
||||||
if err := coreio.Local.EnsureDir(filepath.Dir(fullPath)); err != nil {
|
if err := coreio.Local.EnsureDir(filepath.Dir(fullPath)); err != nil {
|
||||||
return "", err
|
return "", coreerr.E("extractTarball", "failed to create parent directory for "+cleanName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// os.OpenFile is used deliberately here instead of coreio.Local.Create/Write
|
// os.OpenFile is used deliberately here instead of coreio.Local.Create/Write
|
||||||
|
|
@ -321,18 +321,24 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
|
||||||
const maxFileSize int64 = 100 * 1024 * 1024
|
const maxFileSize int64 = 100 * 1024 * 1024
|
||||||
limitedReader := io.LimitReader(tr, maxFileSize+1)
|
limitedReader := io.LimitReader(tr, maxFileSize+1)
|
||||||
written, err := io.Copy(f, limitedReader)
|
written, err := io.Copy(f, limitedReader)
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = f.Close()
|
||||||
return "", coreerr.E("extractTarball", "failed to write file "+hdr.Name, err)
|
return "", coreerr.E("extractTarball", "failed to write file "+hdr.Name, err)
|
||||||
}
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return "", coreerr.E("extractTarball", "failed to close extracted file "+hdr.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
if written > maxFileSize {
|
if written > maxFileSize {
|
||||||
coreio.Local.Delete(fullPath)
|
if err := coreio.Local.Delete(fullPath); err != nil {
|
||||||
|
return "", coreerr.E("extractTarball", "failed to clean up oversized file "+hdr.Name, err)
|
||||||
|
}
|
||||||
return "", coreerr.E("extractTarball", "file "+hdr.Name+" exceeds maximum size", nil)
|
return "", coreerr.E("extractTarball", "file "+hdr.Name+" exceeds maximum size", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track first executable
|
// Track first executable
|
||||||
if hdr.Mode&0111 != 0 && firstExecutable == "" {
|
if hdr.Mode&0111 != 0 && firstExecutablePath == "" {
|
||||||
firstExecutable = fullPath
|
firstExecutablePath = fullPath
|
||||||
}
|
}
|
||||||
// Explicitly ignore symlinks and hard links to prevent symlink attacks
|
// Explicitly ignore symlinks and hard links to prevent symlink attacks
|
||||||
case tar.TypeSymlink, tar.TypeLink:
|
case tar.TypeSymlink, tar.TypeLink:
|
||||||
|
|
@ -341,13 +347,17 @@ func extractTarball(tarData []byte, destDir string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return firstExecutable, nil
|
return firstExecutablePath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamBundle writes a bundle to a writer (for large transfers).
|
// StreamBundle writes a bundle to a writer (for large transfers).
|
||||||
func StreamBundle(bundle *Bundle, w io.Writer) error {
|
func StreamBundle(bundle *Bundle, w io.Writer) error {
|
||||||
encoder := json.NewEncoder(w)
|
encoder := json.NewEncoder(w)
|
||||||
return encoder.Encode(bundle)
|
if err := encoder.Encode(bundle); err != nil {
|
||||||
|
return coreerr.E("StreamBundle", "failed to encode bundle", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadBundle reads a bundle from a reader.
|
// ReadBundle reads a bundle from a reader.
|
||||||
|
|
@ -355,7 +365,7 @@ func ReadBundle(r io.Reader) (*Bundle, error) {
|
||||||
var bundle Bundle
|
var bundle Bundle
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.NewDecoder(r)
|
||||||
if err := decoder.Decode(&bundle); err != nil {
|
if err := decoder.Decode(&bundle); err != nil {
|
||||||
return nil, err
|
return nil, coreerr.E("ReadBundle", "failed to decode bundle", err)
|
||||||
}
|
}
|
||||||
return &bundle, nil
|
return &bundle, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue