Migrate ordinary I/O from confined file helpers

This commit is contained in:
2026-09-01 17:51:37 +00:00
parent 0d57422b61
commit ba858cc997
34 changed files with 96 additions and 547 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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")