fix: contain generated files and assets and harden SQL rendering
This commit is contained in:
+38
-30
@@ -18,9 +18,10 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/safefile"
|
||||
)
|
||||
|
||||
// ManifestEntry describes a single file to load from an assets.yaml manifest.
|
||||
@@ -37,6 +38,9 @@ type ManifestEntry struct {
|
||||
// Item combines a manifest entry with its ordering metadata and the resolved
|
||||
// directory where the manifest and asset file reside.
|
||||
type Item struct {
|
||||
// AssetRoot is the trusted scan root, retained for validation at execution time.
|
||||
AssetRoot string
|
||||
MaxFileBytes int64
|
||||
// Priority and Sequence come from the parent directory's naming pattern.
|
||||
Priority int
|
||||
Sequence uint
|
||||
@@ -57,7 +61,12 @@ var dirPattern = regexp.MustCompile(`^(\d+)[_-](\d+)[_-](.+)$`)
|
||||
// absent or contains invalid YAML.
|
||||
func LoadManifest(dir string) ([]ManifestEntry, error) {
|
||||
manifestPath := filepath.Join(dir, "assets.yaml")
|
||||
data, err := os.ReadFile(manifestPath)
|
||||
root, err := openAssetRoot(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer root.Close()
|
||||
data, err := safefile.Read(root, "assets.yaml", DefaultMaxAssetBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", manifestPath, err)
|
||||
}
|
||||
@@ -92,7 +101,7 @@ func ScanDir(baseDir string) ([]Item, error) {
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if d.Name() != "assets.yaml" {
|
||||
if d.Type()&os.ModeSymlink != 0 || d.Name() != "assets.yaml" {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -119,10 +128,12 @@ func ScanDir(baseDir string) ([]Item, error) {
|
||||
}
|
||||
|
||||
items = append(items, Item{
|
||||
Priority: priority,
|
||||
Sequence: sequence,
|
||||
DirName: dirName,
|
||||
Dir: manifestDir,
|
||||
AssetRoot: absBase,
|
||||
MaxFileBytes: DefaultMaxAssetBytes,
|
||||
Priority: priority,
|
||||
Sequence: sequence,
|
||||
DirName: dirName,
|
||||
Dir: manifestDir,
|
||||
Entry: ManifestEntry{
|
||||
File: resolved, // absolute path, safe to read
|
||||
Call: entry.Call,
|
||||
@@ -170,31 +181,28 @@ func parseDirName(dir string) (priority int, sequence uint, name string) {
|
||||
// Returns the absolute path, a skip flag (true when the entry should be silently
|
||||
// dropped), and any hard error.
|
||||
func resolveAssetPath(absBase, manifestDir, file string) (absPath string, skip bool, err error) {
|
||||
// Clean and join before any symlink resolution so we can detect traversal.
|
||||
joined := filepath.Join(manifestDir, filepath.Clean(file))
|
||||
|
||||
// Ensure the cleaned path is still inside absBase.
|
||||
rel, err := filepath.Rel(absBase, joined)
|
||||
if err != nil || strings.HasPrefix(rel, "..") {
|
||||
// Path escapes the base directory; skip silently.
|
||||
if _, err := safefile.Relative(file); err != nil {
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
// Walk each component to detect symlinks.
|
||||
parts := strings.Split(rel, string(filepath.Separator))
|
||||
current := absBase
|
||||
for _, part := range parts {
|
||||
current = filepath.Join(current, part)
|
||||
info, statErr := os.Lstat(current)
|
||||
if statErr != nil {
|
||||
// File doesn't exist; skip.
|
||||
return "", true, nil
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
// Symlink in path; skip silently.
|
||||
return "", true, nil
|
||||
}
|
||||
joined := filepath.Join(manifestDir, file)
|
||||
rel, err := filepath.Rel(absBase, joined)
|
||||
if err != nil {
|
||||
return "", true, nil
|
||||
}
|
||||
root, err := openAssetRoot(absBase)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer root.Close()
|
||||
if err := safefile.Check(root, rel); err != nil {
|
||||
return "", true, nil
|
||||
}
|
||||
info, err := root.Stat(rel)
|
||||
if err != nil || !info.Mode().IsRegular() {
|
||||
return "", true, nil
|
||||
}
|
||||
if info.Size() > DefaultMaxAssetBytes {
|
||||
return "", false, fmt.Errorf("asset %q exceeds %d byte limit", rel, DefaultMaxAssetBytes)
|
||||
}
|
||||
|
||||
return joined, false, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user