Legacy session name can write outside test_history
Description
pentestgpt-legacy accepts a user-provided session name when saving on exit. On current main (e8b1bb7), that value is passed directly to:
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
- Launch
pentestgpt-legacyfrom a writable directory. - Quit and choose to save the session.
- Enter
../outside.jsonas the session name. - Observe that
outside.jsonis created in the parent working directory instead of undertest_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_historydirectory - 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.jsonnested/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.
Source: GreyDGL/PentestGPT