Design a Recursive File-Search API
AmazonSoftware Engineer · HR ScreenLast updated
Implement a secure, recursive file-search API that validates search boundaries, handles symlink cycles, catches permission errors gracefully, and returns deterministically sorted matching paths.
Problem Statement
You are tasked with building a robust file-search backend service for a multi-tenant cloud environment. The service must allow users to search for files within a given directory while strictly preventing unauthorized access to the underlying host filesystem.
Implement the class FileSearchService with the following interface:
class FileSearchService:
def __init__(self, allowed_root: str):
"""
Initializes the service with a restricted root directory.
No search operation should ever access files outside this root.
"""
pass
def search(
self,
start_path: str,
query: str,
max_depth: int = -1,
follow_symlinks: bool = False
) -> list[str]:
"""
Recursively searches for files matching `query` within `start_path`.
Returns relative paths from `start_path`.
"""
pass
Requirements and Validation
- Path Boundary & Security:
start_pathcan be relative or absolute, but its canonical path must resolve withinallowed_root. If it escapesallowed_root(e.g., via..or symlinks), raise aPermissionError("Access denied: path escapes allowed root").- If
start_pathis empty orNone, raiseValueError("Path cannot be empty"). - If
start_pathdoes not exist, raiseFileNotFoundError("Directory does not exist"). - If
start_pathexists but is not a directory, raiseNotADirectoryError("Target is not a directory").
- Search Logic & Matching:
- A file matches if
query.lower()is a substring of the file's name (case-insensitive). max_depthdefines how deep recursion can go.max_depth = 0searches onlystart_path. Ifmax_depth = -1, search depth is unlimited.
- A file matches if
- Symlink and Permission Handling:
- If
follow_symlinksisFalse, symbolic links to directories must not be traversed. - If
follow_symlinksisTrue, circular symlinks must be detected and skipped without entering infinite loops. Any symlink resolving outsideallowed_rootmust be skipped. - If an operating system permission error occurs while reading a subdirectory or file, silently skip that entity and continue searching remaining items.
- If
- Determinism:
- The returned list of matching file paths must be relative to
start_path, using forward slashes (/), and sorted lexicographically in ascending order.
- The returned list of matching file paths must be relative to
Example
Allowed root: /var/workspace
Directory structure:
/var/workspace/project/
├── notes.txt
├── src/
│ ├── app.py
│ └── app_test.py
└── secret_link -> /etc/passwd
service = FileSearchService("/var/workspace")
results = service.search("/var/workspace/project", "app", max_depth=2)
# Returns: ["src/app.py", "src/app_test.py"]
Constraints
- Directory tree depth <= 100 levels.
- Total files in directory tree <= 100,000.
- Query length <= 255 characters.
Hints
Hint 1
Use canonical absolute path resolution (such as `os.path.realpath`) to verify that the target directory and any symlinks stay strictly within the canonical `allowed_root`.
Hint 2
Track visited canonical paths or unique device/inode tuples in a set during traversal to prevent infinite cycles when following symlinks.
Hint 3
Wrap directory listing calls in a try-except block catching OS permission errors to ensure the search continues when encountering unreadable items.
Follow-up questions
- How would you adapt this service to stream results via an iterator or chunked API when returning millions of matching files?
- How would you parallelize directory traversal across multiple CPU cores without introducing race conditions or redundant visits?
- How would you extend this design to safely support user-defined glob or regex patterns without exposing the service to Regular Expression Denial of Service (ReDoS)?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.