Migrate ordinary I/O from confined file helpers
This commit is contained in:
@@ -166,11 +166,7 @@ func (invalidAnalyzeOutputRunner) RunArtifact(_ context.Context, req scriptorium
|
||||
if err := os.MkdirAll(filepath.Dir(req.OutputPath), 0o755); err != nil {
|
||||
return scriptorium.ArtifactResult{}, err
|
||||
}
|
||||
target := req.OutputPath + ".target"
|
||||
if err := os.WriteFile(target, []byte("unsafe\n"), 0o644); err != nil {
|
||||
return scriptorium.ArtifactResult{}, err
|
||||
}
|
||||
if err := os.Symlink(target, req.OutputPath); err != nil {
|
||||
if err := os.Mkdir(req.OutputPath, 0o755); err != nil {
|
||||
return scriptorium.ArtifactResult{}, err
|
||||
}
|
||||
return scriptorium.ArtifactResult{OutputPath: req.OutputPath, CommandMode: scriptorium.CommandModeRun}, nil
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -13,7 +14,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
|
||||
)
|
||||
|
||||
@@ -263,7 +263,7 @@ func hashAnalyzeInputFile(paths artifacts.SessionPaths, path string) (string, in
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("input path is outside session root: %w", err)
|
||||
}
|
||||
file, err := fileops.OpenConfinedRegularFile(root, relative)
|
||||
file, err := os.Open(target)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
@@ -272,6 +272,10 @@ func hashAnalyzeInputFile(paths artifacts.SessionPaths, path string) (string, in
|
||||
_ = file.Close()
|
||||
return "", 0, fmt.Errorf("inspect input: %w", err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
_ = file.Close()
|
||||
return "", 0, fmt.Errorf("input is not a regular file")
|
||||
}
|
||||
digest := sha256.New()
|
||||
size, readErr := io.Copy(digest, io.LimitReader(file, artifacts.MaxResolvedArtifactBytes+1))
|
||||
closeErr := file.Close()
|
||||
|
||||
@@ -182,21 +182,6 @@ func TestResolveAnalyzeInputIdentitiesRejectsUnsafePathsAndFileTypes(t *testing.
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink", sourceID: "narratio.transcript.final_trimmed",
|
||||
setup: func(t *testing.T, env *Env, m *manifest.Manifest) {
|
||||
t.Helper()
|
||||
path := filepath.Join(sessionPathsForEnv(env, m.SessionID).TranscriptsDir, "final.trimmed.json")
|
||||
outside := filepath.Join(t.TempDir(), "outside.json")
|
||||
writeAnalyzeFile(t, outside, `{"segments":[]}`)
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(outside, path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "manifest path outside session", sourceID: artifactpolicy.SourceInputPlayers,
|
||||
setup: func(t *testing.T, _ *Env, m *manifest.Manifest) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
const MaxExternalResultFileBytes int64 = 64 * 1024 * 1024
|
||||
|
||||
func readExternalResult(path, category string) ([]byte, error) {
|
||||
data, err := fileops.ReadRegularFile(path, MaxExternalResultFileBytes)
|
||||
data, err := fileops.ReadFileBounded(path, MaxExternalResultFileBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s exceeds or cannot be read within %d-byte limit: %w", category, MaxExternalResultFileBytes, err)
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ func checksumRegularFile(path string, requireJSON bool) (string, error) {
|
||||
if info.Size() == 0 {
|
||||
return "", fmt.Errorf("path %q must be non-empty", path)
|
||||
}
|
||||
data, err := fileops.ReadRegularFile(path, artifacts.MaxExtractionPayloadBytes)
|
||||
data, err := fileops.ReadFileBounded(path, artifacts.MaxExtractionPayloadBytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("notarius lane result exceeds or cannot be read within %d-byte limit: %w", artifacts.MaxExtractionPayloadBytes, err)
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func materializeExtractReferenceSnapshots(
|
||||
}
|
||||
destinationOwners[destination] = identity.SourceID
|
||||
|
||||
file, err := fileops.OpenConfinedRegularFile(paths.Root, identity.Path)
|
||||
file, err := os.Open(filepath.Join(paths.Root, filepath.FromSlash(identity.Path)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open reference %q source %q for snapshot: %w", identity.Selector, identity.SourceID, err)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func verifyExtractReferenceSnapshots(snapshotRoot string, references resolvedExt
|
||||
continue
|
||||
}
|
||||
filename := filepath.Base(filepath.FromSlash(identity.Path))
|
||||
file, err := fileops.OpenConfinedRegularFile(snapshotRoot, filename)
|
||||
file, err := os.Open(filepath.Join(snapshotRoot, filename))
|
||||
if err != nil {
|
||||
return fmt.Errorf("reopen reference %q source %q snapshot: %w", identity.Selector, identity.SourceID, err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactpolicy"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/pathsafe"
|
||||
)
|
||||
@@ -106,7 +105,7 @@ func (publishStage) Run(ctx context.Context, env *Env, m *manifest.Manifest) (*S
|
||||
return nil, fmt.Errorf("publish: run id is required")
|
||||
}
|
||||
|
||||
runManifestFile, err := fileops.OpenConfinedRegularFile(runRoot, filepath.Base(runManifestPath))
|
||||
runManifestFile, err := openPublishOutputSource(filepath.Join(runRoot, filepath.Base(runManifestPath)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("publish: open run manifest: %w", err)
|
||||
}
|
||||
@@ -420,7 +419,14 @@ func openPublishOutputSource(localPath string) (*os.File, error) {
|
||||
if path == "" || path == "." {
|
||||
return nil, fmt.Errorf("local path is required")
|
||||
}
|
||||
return fileops.OpenConfinedRegularFile(filepath.Dir(path), filepath.Base(path))
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("source is not a regular file")
|
||||
}
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func publishOutputRemoteKey(runPrefix, destination string) string {
|
||||
@@ -913,7 +919,7 @@ func collectPublishPreviousFiles(ctx context.Context, previousDir string) ([]pub
|
||||
return nil, nil, fmt.Errorf("previous directory is not a regular directory")
|
||||
}
|
||||
|
||||
manifestFile, err := fileops.OpenConfinedRegularFile(previousDir, "manifest.json")
|
||||
manifestFile, err := openPublishOutputSource(filepath.Join(previousDir, "manifest.json"))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil, nil
|
||||
@@ -1005,7 +1011,7 @@ func openPublishRunFiles(runRoot string, files []publishUploadFile, manifestFile
|
||||
file := manifestFile
|
||||
if candidate.SourceRelativePath != "manifest.json" {
|
||||
var err error
|
||||
file, err = fileops.OpenConfinedRegularFile(runRoot, candidate.SourceRelativePath)
|
||||
file, err = openPublishOutputSource(filepath.Join(runRoot, filepath.FromSlash(candidate.SourceRelativePath)))
|
||||
if err != nil {
|
||||
closePublishSources(sources)
|
||||
return nil, err
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifactmodel"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
|
||||
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
||||
)
|
||||
|
||||
@@ -239,7 +238,7 @@ func TestPublishRejectsDeclaredLeafSymlinkBeforeUploading(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := (publishStage{}).Run(context.Background(), env, m)
|
||||
if err == nil || !strings.Contains(err.Error(), `source "logs/audita.stderr.log" is not a regular file`) {
|
||||
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
|
||||
t.Fatalf("Run() error = %v, want rejected symlink", err)
|
||||
}
|
||||
assertNoPublishUploads(t, env.ObjectStore.(*storage.FakeBackend), "outside secret")
|
||||
@@ -261,13 +260,13 @@ func TestPublishRejectsSymlinkedRunManifestBeforeUploading(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := (publishStage{}).Run(context.Background(), env, m)
|
||||
if err == nil || !strings.Contains(err.Error(), `source "manifest.json" is not a regular file`) {
|
||||
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
|
||||
t.Fatalf("Run() error = %v, want rejected run manifest symlink", err)
|
||||
}
|
||||
assertNoPublishUploads(t, env.ObjectStore.(*storage.FakeBackend), "outside")
|
||||
}
|
||||
|
||||
func TestPublishRejectsSymlinkedSourceAncestorBeforeUploading(t *testing.T) {
|
||||
func TestPublishUsesOrdinarySourceAncestorPathSemantics(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("symlink creation requires privileges on Windows")
|
||||
}
|
||||
@@ -282,11 +281,9 @@ func TestPublishRejectsSymlinkedSourceAncestorBeforeUploading(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err := (publishStage{}).Run(context.Background(), env, m)
|
||||
if err == nil || !strings.Contains(err.Error(), "source ancestor") {
|
||||
t.Fatalf("Run() error = %v, want rejected symlinked ancestor", err)
|
||||
if _, err := (publishStage{}).Run(context.Background(), env, m); err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
assertNoPublishUploads(t, env.ObjectStore.(*storage.FakeBackend), "outside secret")
|
||||
}
|
||||
|
||||
func TestPublishRejectsDeclaredSourceReplacement(t *testing.T) {
|
||||
@@ -304,7 +301,7 @@ func TestPublishRejectsDeclaredSourceReplacement(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeStageTestFile(t, target, "replacement\n")
|
||||
manifestFile, err := fileops.OpenConfinedRegularFile(runRoot, "manifest.json")
|
||||
manifestFile, err := os.Open(filepath.Join(runRoot, "manifest.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -327,7 +324,7 @@ func TestPublishRejectsDeclaredDirectoryBeforeUploading(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := (publishStage{}).Run(context.Background(), env, m)
|
||||
if err == nil || !strings.Contains(err.Error(), `source "logs/audita.stderr.log" is not a regular file`) {
|
||||
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
|
||||
t.Fatalf("Run() error = %v, want rejected directory", err)
|
||||
}
|
||||
assertNoPublishUploads(t, env.ObjectStore.(*storage.FakeBackend), "")
|
||||
@@ -377,7 +374,7 @@ func TestPublishRejectsDeclaredPreviousSymlinkBeforeUploading(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err := (publishStage{}).Run(context.Background(), env, m)
|
||||
if err == nil || !strings.Contains(err.Error(), `source "artifacts/session_recap.md" is not a regular file`) {
|
||||
if err == nil || !strings.Contains(err.Error(), "not a regular file") {
|
||||
t.Fatalf("Run() error = %v, want rejected previous symlink", err)
|
||||
}
|
||||
assertNoPublishUploads(t, env.ObjectStore.(*storage.FakeBackend), "outside previous secret")
|
||||
|
||||
Reference in New Issue
Block a user