Skip to content

fix: support nested starter code paths and block path traversal#435

Open
anshul23102 wants to merge 1 commit into
komalharshita:mainfrom
anshul23102:fix/nested-starter-code-resolution
Open

fix: support nested starter code paths and block path traversal#435
anshul23102 wants to merge 1 commit into
komalharshita:mainfrom
anshul23102:fix/nested-starter-code-resolution

Conversation

@anshul23102
Copy link
Copy Markdown
Contributor

Summary [required]

Projects whose starter_code value contains a subdirectory (e.g. starter_code/survey_form/index.html) were returning 404 for both the View Code and Download endpoints. The root cause was a two-part bug:

  1. resolve_starter_file in utils/file_server.py called os.path.basename on the raw path, stripping the subdirectory completely. survey_form/index.html became just index.html, which does not exist at the root of the starter_code directory.

  2. Even after fixing (1), the download route in routes/main_routes.py passed get_starter_code_dir() (the root starter_code folder) to send_from_directory together with only the basename, so Flask still could not locate nested files.

This PR fixes both layers. resolve_starter_file now strips any leading starter_code/ prefix, resolves the relative subpath inside STARTER_CODE_DIR, and uses os.path.normpath plus a prefix check to block path traversal attempts. The download route derives the serving directory from os.path.dirname(full_path) so Flask serves the file from its actual location regardless of nesting depth.

Related Issue [required]

Closes #407

Type of Change [required]

  • Bug fix — resolves a broken behaviour
  • Test — adds or updates tests

What Was Changed [required]

File Change made
utils/file_server.py Rewrote resolve_starter_file to support nested paths with a path traversal guard
routes/main_routes.py Fixed download_code to serve from the file's actual directory, not the starter_code root
tests/test_basic.py Added three tests: nested /code, nested /download, and path traversal rejection

How to Test This PR [required]

  1. Clone this branch: git checkout fix/nested-starter-code-resolution
  2. Install dependencies: pip install -r requirements.txt
  3. Run the app: python app.py
  4. Navigate to http://127.0.0.1:5000/project/9 and click View Code. The survey form HTML should render.
  5. Click Download File. The index.html file should download successfully.
  6. Run the tests: python tests/test_basic.py

Expected test output:

33 passed, 0 failed out of 33 tests

Test Results [required]

  PASS  test_projects_json_loads
  PASS  test_each_project_has_required_fields
  PASS  test_find_project_by_id_found
  PASS  test_find_project_by_id_missing
  PASS  test_parse_skills_basic
  PASS  test_parse_skills_empty_string
  PASS  test_parse_skills_single_entry
  PASS  test_score_single_project_full_match
  PASS  test_score_single_project_no_match
  PASS  test_get_recommendations_returns_results
  PASS  test_get_recommendations_max_three
  PASS  test_get_recommendations_no_match_returns_empty
  PASS  test_get_recommendations_result_format
  PASS  test_validate_all_valid
  PASS  test_validate_missing_skills
  PASS  test_validate_missing_level
  PASS  test_validate_missing_interest
  PASS  test_validate_missing_time
  PASS  test_validate_all_missing
  PASS  test_home_route
  PASS  test_recommend_api_valid
  PASS  test_recommend_api_missing_field
  PASS  test_recommend_api_empty_body
  PASS  test_project_detail_found
  PASS  test_project_detail_not_found
  PASS  test_internal_server_error_page
  PASS  test_view_code_found
  PASS  test_download_code_found
  PASS  test_view_code_nested_path
  PASS  test_download_code_nested_path
  PASS  test_resolve_starter_file_path_traversal
  PASS  test_health_check
  PASS  test_scoring_weights_has_all_keys

33 passed, 0 failed out of 33 tests

Self-Review Checklist [required]

  • I have read CONTRIBUTING.md and followed all guidelines
  • My branch name follows the convention: feat/, fix/, docs/, data/, style/, test/
  • I have run python tests/test_basic.py and all tests pass
  • I have not introduced any print() or console.log() debug statements
  • Every new function I wrote has a docstring
  • I have not modified files outside the scope of the linked issue

Notes for Reviewer

The path traversal guard works by normalising the joined path with os.path.normpath and confirming it starts with STARTER_CODE_DIR + os.sep. Without the trailing separator the check could be bypassed by a directory name that shares the same prefix (e.g. starter_code_evil/), so the separator is included intentionally.

The download route change is minimal: the resolved full_path from resolve_starter_file is already validated, so deriving file_dir from it adds no new trust surface.

…lharshita#407)

resolve_starter_file only extracted the basename, so any project whose
starter_code value contained a subdirectory (e.g. survey_form/index.html)
always resolved to the wrong flat path and returned 404.

Strip the leading "starter_code/" prefix, join the remainder with
STARTER_CODE_DIR, normalise with os.path.normpath, and verify the result
stays inside STARTER_CODE_DIR before accepting it. This allows any depth
of nesting while preventing path traversal attacks.

Also fix the download route: send_from_directory was called with the root
starter_code directory but only the basename, so nested files were never
served. Changed to derive the directory from the resolved full_path.

Three new tests cover: /code for a nested project, /download for a nested
project, and rejection of a path traversal payload.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

@anshul23102 is attempting to deploy a commit to the komalsony234-1530's projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Broken file resolution for nested starter code assets in file_server.py

1 participant