Skip to content

Path containment check uses startswith without a separator — sibling project directories are readable #29

Description

@dolceo

read_file_content resolves the path and then compares string prefixes:

    real_abs = os.path.realpath(abs_path)
    real_repo = os.path.realpath(repo_dir)
    if not real_abs.startswith(real_repo):
        return None

backend/app/services/code_project_service.py:282

get_file_abs_path has the identical check at backend/app/services/code_project_service.py:306.

real_repo for project cproj_aaaa is .../data/code_projects/cproj_aaaa/repo. The string .../data/code_projects/cproj_aaaa/repo_backup/secrets.env also passes startswith. Note that the module already contains a correct implementation a few lines above — _safe_join_repo at backend/app/services/code_project_service.py:147 compares against real_repo + os.sep — it is simply not used on the read paths.

The router-level guard does not help either: it rejects only the literal substring ".." (backend/app/modules/code/code_projects_api.py:270), which a symlink inside the repo defeats without ever containing ...

Trigger: the code generator writes a symlink, or an operator creates any sibling directory sharing the repo-dir prefix; then GET /api/v1/code/projects/cproj_aaaa/file?path=<...>.
Observed: file contents outside the project root are returned as 200.
Expected: _safe_join_repo reused for both readers, plus an os.path.islink rejection.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions