Legacy session name can write outside test_history

Author: rksharma-owgCreated Jul 23, 2026Updated Sep 17, 2026

Description

pentestgpt-legacy accepts a user-provided session name when saving on exit. On current main (e8b1bb7), that value is passed directly to:

python
open(os.path.join(save_root, save_name), "w")

A name such as ../outside.json or an absolute path escapes test_history/ and overwrites any user-writable file at that path with the session JSON.

Reproduction

  1. Launch pentestgpt-legacy from a writable directory.
  2. Quit and choose to save the session.
  3. Enter ../outside.json as the session name.
  4. Observe that outside.json is created in the parent working directory instead of under test_history/.

The same behavior follows directly from pentestgpt_legacy/utils/pentest_gpt.py lines 410-413 and is covered by a deterministic regression test without making any provider request.

Expected behavior

Session names should be plain filenames and every transcript should remain a direct child of test_history/. Invalid names should be rejected and reprompted.

Actual behavior

Relative traversal and absolute paths can select an arbitrary user-writable destination. Existing files are opened with "w" and truncated.

Impact

This is a local file-write boundary bug. It does not grant privileges beyond the account running PentestGPT, but an accidental or copied session name can overwrite unrelated files outside the documented session directory.

Root cause

The save path is joined but never normalized or checked against the intended root before opening.

Prepared fix

I prepared commit 467aeba (fix: contain legacy session files) on local branch codex/validate-session-filename:

  • resolve the candidate path and require its parent to equal the resolved test_history directory
  • reject empty, absolute, traversal, and nested path components
  • reprompt after an invalid name instead of ending the session with an exception
  • preserve valid filenames and the existing transcript JSON format
  • write with explicit UTF-8 encoding

Regression coverage includes:

  • ../outside.json
  • nested/session.json
  • an absolute destination
  • end-to-end invalid-name reprompt followed by a valid save, proving no outside file is created

Validation

  • make ci: passed
  • Root Pytest: 137 passed
  • Maintained-agent Pytest: 118 passed, 1 skipped
  • Ruff lint and format: passed
  • Mypy: passed
  • Root and nested uv lock --check: passed
  • Both package builds: passed
  • docker compose config --quiet: passed

I attempted to push the branch, but upstream denied write access (403). I did not create a fork.