{"record":{"id":"2f4a265112fbbfb3","repo":"restic/restic","slug":"invalid-filename-specified","errorCode":null,"errorMessage":"invalid filename specified","messagePattern":"invalid filename specified","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/fs/fs_reader.go","lineNumber":49,"sourceCode":"\tfi             *ExtendedFileInfo\n\trc             io.ReadCloser\n\tallowEmptyFile bool\n\n\tchildren []string\n}\n\n// statically ensure that Local implements FS.\nvar _ FS = &reader{}\n\n// NewReader returns a new FS which provides a directory with a single file. When\n// this file is opened for reading, the reader is passed through. The file can\n// be opened once, all subsequent open calls return syscall.EIO. For Lstat(),\n// the provided FileInfo is returned.\nfunc NewReader(name string, r io.ReadCloser, opts ReaderOptions) (FS, error) {\n\titems := make(map[string]readerItem)\n\tname = readerCleanPath(name)\n\tif name == \"/\" {\n\t\treturn nil, fmt.Errorf(\"invalid filename specified\")\n\t}\n\n\tisFile := true\n\tfor {\n\t\tif isFile {\n\t\t\tfi := &ExtendedFileInfo{\n\t\t\t\tName:    path.Base(name),\n\t\t\t\tMode:    opts.Mode,\n\t\t\t\tModTime: opts.ModTime,\n\t\t\t\tSize:    opts.Size,\n\t\t\t}\n\t\t\titems[name] = readerItem{\n\t\t\t\topen:           &sync.Once{},\n\t\t\t\tfi:             fi,\n\t\t\t\trc:             r,\n\t\t\t\tallowEmptyFile: opts.AllowEmptyFile,\n\t\t\t}\n\t\t\tisFile = false","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/restic/restic/blob/a80be1478a4c537f8396e0db2b05120aa78f11e0/internal/fs/fs_reader.go#L31-L67","documentation":"NewReader builds a virtual FS that exposes exactly one file at the given name. The name is cleaned first; if it cleans to \"/\" there is no filename component to place the reader at, so the constructor rejects it with \"invalid filename specified\".","triggerScenarios":"Calling NewReader(\"/\", r, opts) or with a name that readerCleanPath reduces to the root (empty string, \"/\", \"//\").","commonSituations":"Defaulting a filename variable to \"/\" or \"\" when the caller meant something like \"stdin\"; path-joining bugs that strip the last component.","solutions":["Pass a concrete filename such as \"stdin\" (restic's own convention is /stdin)","Guard upstream code that computes the name so it never reduces to the root"],"exampleFix":"// before\nfs, err := fs.NewReader(filepath.Clean(userPath), r, opts) // userPath == \"/\"\n\n// after\nname := path.Clean(userPath)\nif name == \"/\" || name == \"\" {\n\tname = \"stdin\"\n}\nfs, err := fs.NewReader(name, r, opts)","handlingStrategy":"validation","validationCode":"name = path.Clean(name)\nif name == \"/\" || name == \"\" {\n\treturn errors.New(\"reader filename must not be the filesystem root\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default reader filenames to a constant like \"stdin\" instead of \"\"","Unit-test the name derivation whenever it comes from user input","Remember NewReader exposes exactly one file; root-level virtual dirs are not supported"],"tags":["restic","fs","stdin","validation"],"backgroundTag":null,"analyzedSha":"a80be1478a4c537f8396e0db2b05120aa78f11e0","analyzedAt":"2026-08-15T15:30:29.928Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}