Skip to content

bugfix: fix file handle leaks in arg_parser.py and messages.py#1528

Open
mitre88 wants to merge 2 commits intoOWASP:masterfrom
mitre88:fix/file-handle-leaks
Open

bugfix: fix file handle leaks in arg_parser.py and messages.py#1528
mitre88 wants to merge 2 commits intoOWASP:masterfrom
mitre88:fix/file-handle-leaks

Conversation

@mitre88
Copy link
Copy Markdown

@mitre88 mitre88 commented Apr 24, 2026

Summary

Fix resource leaks caused by file handles not being properly closed.

Changes

  • nettacker/core/messages.py: Use context manager for load_yaml() file handle
  • nettacker/core/arg_parser.py: Use context managers for all file open() calls:
    • options.targets_list
    • Config.path.user_agents_file
    • options.usernames_list
    • options.passwords_list
    • options.read_from_file

Why

File handles opened with open() without a corresponding close() or context manager can cause resource leaks, especially in long-running processes. Using with open() as f: ensures files are properly closed even when exceptions occur.

Open Source Contributor added 2 commits April 23, 2026 11:07
…ation

- Replace yaml.load() with yaml.safe_load() to prevent arbitrary code execution
- Add validation for database names to prevent SQL injection in CREATE DATABASE statements
- Use parameterized identifiers with backticks (MySQL) and quotes (PostgreSQL)
- Use context managers for all file open() calls to prevent resource leaks
- Fix load_yaml() to properly close file handles
- Fix file reading in targets_list, user_agents, usernames_list, passwords_list, and read_from_file
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Added robust validation for database names in MySQL and PostgreSQL operations to prevent creation failures with invalid identifiers.
    • Strengthened YAML parsing with safer parsing mechanisms for improved security.
  • Chores

    • Improved file resource management throughout the application with better file handling patterns.

Walkthrough

Refactors file-loading operations to use context managers in argument parsing and YAML message loading, while introducing database name validation with alphanumeric constraints in MySQL and PostgreSQL database creation functions to prevent direct SQL interpolation.

Changes

Cohort / File(s) Summary
File Loading Refactoring
nettacker/core/arg_parser.py, nettacker/core/messages.py
Replaces direct open(...).read() calls with context-managed file handles for target lists, user agents, credentials, and custom wordlists. Updates YAML loader from yaml.load(yaml.FullLoader) to yaml.safe_load for safer parsing.
Database Name Validation
nettacker/database/mysql.py, nettacker/database/postgresql.py
Adds alphanumeric validation and letter-leading-character constraints to database names before SQL execution. Invalid names raise ValueError caught by existing exception handlers; valid names use quoted identifier syntax in CREATE DATABASE statements.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes, focusing on fixing file handle leaks in two specific files as the primary purpose of the PR.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the resource leak fixes and detailing which files and specific file-loading operations were modified.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65e27fbe99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if Config.db.name not in existing_databases:
conn.execute(text("CREATE DATABASE {0} ".format(Config.db.name)))
db_name = Config.db.name
if db_name.isalnum() and db_name[0].isalpha():
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Permit underscores in MySQL database names

The new validation in mysql_create_database() rejects common valid names like test_db because db_name.isalnum() returns false when underscores are present, so the create path raises ValueError instead of creating the DB. This is a regression in first-run MySQL setups where the DB does not already exist, and it conflicts with existing project usage patterns (for example tests/database/test_mysql.py uses test_db).

Useful? React with 👍 / 👎.

conn = conn.execution_options(isolation_level="AUTOCOMMIT")
conn.execute(text(f"CREATE DATABASE {Config.db.name}"))
db_name = Config.db.name
if db_name.isalnum() and db_name[0].isalpha():
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Allow standard Postgres identifiers with underscores

In the OperationalError fallback path, this check now rejects valid PostgreSQL database names containing underscores (e.g., nettacker_db), because isalnum() excludes _. That makes bootstrap fail with ValueError whenever the target DB is missing and uses a conventional underscored name, which is a behavior change from the previous implementation and from existing test configuration (tests/database/test_postgresql.py).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nettacker/core/arg_parser.py (1)

738-743: 🛠️ Refactor suggestion | 🟠 Major

Missed conversion — report_path_filename still uses bare open()+close().

This block is inconsistent with the PR’s goal of eliminating file-handle leaks: if close() is reached but an intervening exception occurs (e.g., after a future edit) the handle leaks, and this is exactly the pattern the rest of the PR replaces. Convert it to a context manager for consistency.

🔧 Proposed fix
         # Check output file
         try:
-            temp_file = open(options.report_path_filename, "w")
-            temp_file.close()
+            with open(options.report_path_filename, "w"):
+                pass
         except Exception:
             die_failure(_("file_write_error").format(options.report_path_filename))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/core/arg_parser.py` around lines 738 - 743, The file-check block
using open()/close() should use a context manager to avoid potential file-handle
leaks: replace the try block that opens options.report_path_filename, calls
temp_file.close(), and excepts to die_failure(_("file_write_error").format(...))
with a with open(options.report_path_filename, "w") as temp_file: pattern so the
file is always closed even if an intermediate exception occurs; retain the same
exception handling that calls die_failure with
_("file_write_error").format(options.report_path_filename).
🧹 Nitpick comments (1)
nettacker/database/postgresql.py (1)

29-33: Scope note: these changes don't match the PR title.

The PR title and summary describe fixing file handle leaks in arg_parser.py and messages.py, but this file (and mysql.py) introduces a separate SQL-injection hardening change for CREATE DATABASE. Mixing unrelated changes in one PR makes review and revert harder. Consider splitting the SQL-injection-hardening commit into its own PR, or update the PR title/description to reflect both concerns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/database/postgresql.py` around lines 29 - 33, The change in
nettacker/database/postgresql.py tightens CREATE DATABASE by validating db_name
before executing f'CREATE DATABASE "{db_name}"' but this is unrelated to the PR
title about file handle leaks in arg_parser.py and messages.py; either move this
SQL-injection-hardening change into its own commit/PR (extract the db_name
validation and the similar change in mysql.py into a separate branch) or update
the PR title and description to explicitly include the database hardening work,
referencing the db_name validation and the CREATE DATABASE execution in
postgresql.py (and corresponding change in mysql.py) so reviewers can see both
concerns together.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nettacker/core/arg_parser.py`:
- Around line 732-737: The file-open call in the options.read_from_file block
reads and splits the file but discards the result; either perform only a
readability check (drop the pointless .split and just read or open/close the
file) or actually store the lines into an options field. Update the block around
options.read_from_file so it either (A) simply opens and closes the file to
validate readability (e.g., use f.read() or context manager without split) or
(B) assigns the split result to a named option like options.wordlist_lines
(e.g., options.wordlist_lines = f.read().split("\n")) so callers can use it;
keep the existing exception handling that calls
die_failure(_("error_wordlist").format(options.read_from_file)).

In `@nettacker/database/mysql.py`:
- Around line 23-27: The db-name validation in mysql_create_database is too
strict (rejects underscores) and can raise IndexError on empty names; replace
the isalnum/first-char check with a regex that enforces non-empty names starting
with a letter and then letters/digits/underscores (similar to
postgres_create_database's suggested fix). Also stop swallowing configuration
errors: do not let the outer broad except (the block that currently prints the
exception) silently absorb a ValueError — either narrow that except to
SQLAlchemy/DB errors only or re-raise ValueError so invalid DB names fail fast
and are not deferred to mysql_create_tables.

In `@nettacker/database/postgresql.py`:
- Around line 29-33: The current db name validation using db_name.isalnum() and
db_name[0].isalpha() is too strict (rejects underscores) and crashes on empty
strings; replace it by first checking length (e.g., if not db_name: raise
ValueError) and then validate Config.db.name against a regex that enforces a
leading letter followed by letters, digits, or underscores (e.g.,
r'^[A-Za-z][A-Za-z0-9_]*$') before calling conn.execute, and when raising
ValueError inside the except OperationalError block use "raise ValueError(...)
from None" to suppress the original exception per the review note; reference the
db_name variable and Config.db.name in your changes.

---

Outside diff comments:
In `@nettacker/core/arg_parser.py`:
- Around line 738-743: The file-check block using open()/close() should use a
context manager to avoid potential file-handle leaks: replace the try block that
opens options.report_path_filename, calls temp_file.close(), and excepts to
die_failure(_("file_write_error").format(...)) with a with
open(options.report_path_filename, "w") as temp_file: pattern so the file is
always closed even if an intermediate exception occurs; retain the same
exception handling that calls die_failure with
_("file_write_error").format(options.report_path_filename).

---

Nitpick comments:
In `@nettacker/database/postgresql.py`:
- Around line 29-33: The change in nettacker/database/postgresql.py tightens
CREATE DATABASE by validating db_name before executing f'CREATE DATABASE
"{db_name}"' but this is unrelated to the PR title about file handle leaks in
arg_parser.py and messages.py; either move this SQL-injection-hardening change
into its own commit/PR (extract the db_name validation and the similar change in
mysql.py into a separate branch) or update the PR title and description to
explicitly include the database hardening work, referencing the db_name
validation and the CREATE DATABASE execution in postgresql.py (and corresponding
change in mysql.py) so reviewers can see both concerns together.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 51307c16-487b-499c-a793-70b380ae6704

📥 Commits

Reviewing files that changed from the base of the PR and between d873811 and 65e27fb.

📒 Files selected for processing (4)
  • nettacker/core/arg_parser.py
  • nettacker/core/messages.py
  • nettacker/database/mysql.py
  • nettacker/database/postgresql.py

Comment on lines 732 to 737
if options.read_from_file:
try:
open(options.read_from_file).read().split("\n")
with open(options.read_from_file) as f:
f.read().split("\n")
except Exception:
die_failure(_("error_wordlist").format(options.read_from_file))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Read result is discarded — either store it or drop the split.

f.read().split("\n") on Line 735 is evaluated and thrown away. If the intent is only to validate that the wordlist is readable, the .split("\n") is dead work; if the intent is to actually load the wordlist into options, the result should be assigned. Quick clarification will prevent confusion for future maintainers.

🔧 Option A — validation only
         if options.read_from_file:
             try:
                 with open(options.read_from_file) as f:
-                    f.read().split("\n")
+                    f.read()
             except Exception:
                 die_failure(_("error_wordlist").format(options.read_from_file))
🔧 Option B — actually load the wordlist
         if options.read_from_file:
             try:
                 with open(options.read_from_file) as f:
-                    f.read().split("\n")
+                    options.read_from_file = f.read().split("\n")
             except Exception:
                 die_failure(_("error_wordlist").format(options.read_from_file))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if options.read_from_file:
try:
open(options.read_from_file).read().split("\n")
with open(options.read_from_file) as f:
f.read().split("\n")
except Exception:
die_failure(_("error_wordlist").format(options.read_from_file))
if options.read_from_file:
try:
with open(options.read_from_file) as f:
f.read()
except Exception:
die_failure(_("error_wordlist").format(options.read_from_file))
Suggested change
if options.read_from_file:
try:
open(options.read_from_file).read().split("\n")
with open(options.read_from_file) as f:
f.read().split("\n")
except Exception:
die_failure(_("error_wordlist").format(options.read_from_file))
if options.read_from_file:
try:
with open(options.read_from_file) as f:
options.read_from_file = f.read().split("\n")
except Exception:
die_failure(_("error_wordlist").format(options.read_from_file))
🧰 Tools
🪛 Ruff (0.15.11)

[warning] 736-736: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/core/arg_parser.py` around lines 732 - 737, The file-open call in
the options.read_from_file block reads and splits the file but discards the
result; either perform only a readability check (drop the pointless .split and
just read or open/close the file) or actually store the lines into an options
field. Update the block around options.read_from_file so it either (A) simply
opens and closes the file to validate readability (e.g., use f.read() or context
manager without split) or (B) assigns the split result to a named option like
options.wordlist_lines (e.g., options.wordlist_lines = f.read().split("\n")) so
callers can use it; keep the existing exception handling that calls
die_failure(_("error_wordlist").format(options.read_from_file)).

Comment on lines +23 to +27
db_name = Config.db.name
if db_name.isalnum() and db_name[0].isalpha():
conn.execute(text(f"CREATE DATABASE `{db_name}` "))
else:
raise ValueError(f"Invalid database name: {db_name}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same overly strict validation as postgresql.py — and the ValueError is swallowed.

Two issues here:

  1. db_name.isalnum() rejects underscores, which breaks common names like nettacker_db. Same empty-string IndexError risk as in postgresql.py::postgres_create_database. See that review comment for a regex-based fix.
  2. This raise ValueError(...) is immediately caught by the outer except Exception as e: print(e) on line 28–29, so an invalid configured DB name is just printed to stdout and execution continues. The caller will later fail in mysql_create_tables with a less obvious error. Consider either narrowing the outer except (e.g., catch only SQLAlchemy errors) or re-raising ValueError so configuration problems surface clearly.
🛡️ Suggested direction
             if Config.db.name not in existing_databases:
                 db_name = Config.db.name
-                if db_name.isalnum() and db_name[0].isalpha():
+                if re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{0,62}", db_name or ""):
                     conn.execute(text(f"CREATE DATABASE `{db_name}`"))
                 else:
                     raise ValueError(f"Invalid database name: {db_name!r}")
-    except Exception as e:
-        print(e)
+    except ValueError:
+        raise
+    except Exception as e:
+        print(e)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/database/mysql.py` around lines 23 - 27, The db-name validation in
mysql_create_database is too strict (rejects underscores) and can raise
IndexError on empty names; replace the isalnum/first-char check with a regex
that enforces non-empty names starting with a letter and then
letters/digits/underscores (similar to postgres_create_database's suggested
fix). Also stop swallowing configuration errors: do not let the outer broad
except (the block that currently prints the exception) silently absorb a
ValueError — either narrow that except to SQLAlchemy/DB errors only or re-raise
ValueError so invalid DB names fail fast and are not deferred to
mysql_create_tables.

Comment on lines +29 to +33
db_name = Config.db.name
if db_name.isalnum() and db_name[0].isalpha():
conn.execute(text(f'CREATE DATABASE "{db_name}"'))
else:
raise ValueError(f"Invalid database name: {db_name}")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validation is overly strict and can crash on empty input.

db_name.isalnum() rejects any database name containing an underscore (e.g., nettacker_db, my_app_prod), which is an extremely common convention and likely a regression for existing deployments. Additionally, if Config.db.name is an empty string, db_name[0] raises IndexError before the validation branch is taken — and that IndexError escapes the surrounding except OperationalError block.

Consider using a regex that matches the identifier grammar you actually want to allow (letter followed by letters/digits/underscores), and length-check before indexing:

🛡️ Proposed fix
+import re
 ...
         db_name = Config.db.name
-        if db_name.isalnum() and db_name[0].isalpha():
+        if re.fullmatch(r"[A-Za-z][A-Za-z0-9_]{0,62}", db_name or ""):
             conn.execute(text(f'CREATE DATABASE "{db_name}"'))
         else:
-            raise ValueError(f"Invalid database name: {db_name}")
+            raise ValueError(f"Invalid database name: {db_name!r}") from None

Note the from None also addresses Ruff's B904 hint, since the raise sits inside the except OperationalError: block.

🧰 Tools
🪛 Ruff (0.15.11)

[warning] 33-33: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/database/postgresql.py` around lines 29 - 33, The current db name
validation using db_name.isalnum() and db_name[0].isalpha() is too strict
(rejects underscores) and crashes on empty strings; replace it by first checking
length (e.g., if not db_name: raise ValueError) and then validate Config.db.name
against a regex that enforces a leading letter followed by letters, digits, or
underscores (e.g., r'^[A-Za-z][A-Za-z0-9_]*$') before calling conn.execute, and
when raising ValueError inside the except OperationalError block use "raise
ValueError(...) from None" to suppress the original exception per the review
note; reference the db_name variable and Config.db.name in your changes.

@pUrGe12
Copy link
Copy Markdown
Contributor

pUrGe12 commented Apr 24, 2026

Duplicate of #1527?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants