Skip to content

fix(api): Display correct cert error message#1355

Open
jess-tech-lab wants to merge 3 commits into
OWASP:masterfrom
jess-tech-lab:fix-cert-error
Open

fix(api): Display correct cert error message#1355
jess-tech-lab wants to merge 3 commits into
OWASP:masterfrom
jess-tech-lab:fix-cert-error

Conversation

@jess-tech-lab
Copy link
Copy Markdown
Contributor

Proposed change

  • Enhanced SSL Error Trapping: Added a try-except block to the API engine to catch ssl.SSLError and ValueError during the Flask server initialization.
  • New Localization Keys: Added two new message keys to en.yaml to provide user-friendly feedback when certificate loading fails, replacing generic tracebacks with actionable error messages.
  • Implemented an explicit sys.exit(1) within start_api_server. If the child process terminates with a non-zero exit code (e.g., due to an SSL PEM lib error), the parent process now calls sys.exit(1) immediately. This prevents the misleading Cannot specify the target(s) error.

With invalid cert files,

poetry run nettacker --start-api --api-host 127.0.0.1 --api-port 8080 --api-debug-mode --api-cert /etc/hosts --api-cert-key /etc/hosts
invalid_cert_file_fx



With wrong cert paths,

poetry run nettacker --start-api --api-host 127.0.0.1 --api-port 8080 --api-debug-mode --api-cert /etc/xxx --api-cert-key /etc/xxx
invalid_cert_file_not_found

Closes #1354

Type of change

  • New core framework functionality
  • Bugfix (non-breaking change which fixes an issue)
  • Code refactoring without any functionality changes
  • New or existing module/payload change
  • Documentation/localization improvement
  • Test coverage improvement
  • Dependency upgrade
  • Other improvement (best practice, cleanup, optimization, etc)

Checklist

  • I've followed the contributing guidelines
  • I have digitally signed all my commits in this PR
  • I've run make pre-commit and confirm it didn't generate any warnings/changes
  • I've run make test, I confirm all tests passed locally
  • I've added/updated any relevant documentation in the docs/ folder
  • I've linked this PR with an open issue
  • I've tested and verified that my code works as intended and resolves the issue as described
  • I have attached screenshots demonstrating my code works as intended
  • I've checked all other open PRs to avoid submitting duplicate work
  • I confirm that the code and comments in this PR are not direct unreviewed outputs of AI
  • I confirm that I am the Sole Responsible Author for every line of code, comment, and design decision

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 6, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0abe0014-4a0f-4072-9b7a-a5d8dc1c96ac

📥 Commits

Reviewing files that changed from the base of the PR and between fd4d5e4 and 4bc479b.

📒 Files selected for processing (1)
  • nettacker/api/engine.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • nettacker/api/engine.py

Summary by CodeRabbit

  • Bug Fixes

    • More specific startup error handling for SSL and missing-file conditions; the service now exits immediately on critical subprocess failures to avoid inconsistent states and confusing output.
  • Localization

    • Added two English error messages to clarify missing-file and SSL configuration load failures for end users.

Walkthrough

Adds SSL and sys imports, introduces targeted exception handling for SSL/load/file-not-found errors during API subprocess startup, ensures the parent process exits with status 1 if the API child fails, and adds two English locale entries for those error messages.

Changes

API startup changes

Layer / File(s) Summary
Imports
nettacker/api/engine.py
Adds ssl and sys imports used by new exception and exit handling.
Subprocess SSL/file exception handling
nettacker/api/engine.py
Adds except ssl.SSLError and except FileNotFoundError branches in start_api_subprocess that call die_failure(...) with localized messages.
Parent exit on child failure
nettacker/api/engine.py
After waiting for the API child, parent checks p.exitcode and calls sys.exit(1) when the child exited non-zero.
Localization
nettacker/locale/en.yaml
Adds file_not_found and load_ssl_fail English message keys for the new error cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • OWASP/Nettacker#1343: Also modifies API startup behavior (changes Flask run to disable reloader); related to API lifecycle/run behavior.

Suggested labels

bug fixed

Suggested reviewers

  • arkid15r
  • securestep9
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(api): Display correct cert error message' directly corresponds to the main change of improving SSL certificate error messages displayed to users.
Description check ✅ Passed The description is well-detailed and directly related to the changeset, explaining the SSL error trapping, new localization keys, explicit process exit, and providing reproduction examples.
Linked Issues check ✅ Passed The PR fully implements the requirements from issue #1354: catches SSL errors with proper exception handling [engine.py], adds user-friendly error messages [en.yaml], and implements sys.exit(1) to prevent misleading 'Cannot specify targets' error.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the SSL certificate error handling issue: imports for ssl/sys, exception handling for SSLError/FileNotFoundError, child process exit code checking, and new localization strings for error messages.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
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.

Caution

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

⚠️ Outside diff range comments (1)
nettacker/api/engine.py (1)

616-626: ⚠️ Potential issue | 🟠 Major

Exit API mode after the child stops, not only on failures.

The API subprocess runs a long-running Flask server (app.run()). If the subprocess exits with code 0, the current code returns to nettacker/core/arg_parser.py and resumes target validation, creating an unexpected fallthrough path in API mode.

Suggested fix
-    while len(multiprocessing.active_children()) != 0:
+    while p.is_alive():
         try:
             time.sleep(0.3)
         except KeyboardInterrupt:
-            for process in multiprocessing.active_children():
-                process.terminate()
+            p.terminate()
             break
-
-    if p.exitcode != 0:
-        # The child stopped. Exit the parent so it doesn't print "Cannot specify targets"
-        sys.exit(1)
+    p.join()
+    raise SystemExit(p.exitcode or 0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nettacker/api/engine.py` around lines 616 - 626, The parent should exit when
the API subprocess stops regardless of its exit code; replace the conditional
block that only exits on non-zero exit codes with an unconditional exit using
the child's exit code. Locate the variable p in nettacker/api/engine.py (the
subprocess running the Flask server) and after the loop that waits for
multiprocessing.active_children(), call sys.exit(p.exitcode) (or
sys.exit(p.exitcode or 0) to guard against None) so the parent process doesn't
continue into arg_parser.py when the API child has terminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@nettacker/api/engine.py`:
- Around line 616-626: The parent should exit when the API subprocess stops
regardless of its exit code; replace the conditional block that only exits on
non-zero exit codes with an unconditional exit using the child's exit code.
Locate the variable p in nettacker/api/engine.py (the subprocess running the
Flask server) and after the loop that waits for
multiprocessing.active_children(), call sys.exit(p.exitcode) (or
sys.exit(p.exitcode or 0) to guard against None) so the parent process doesn't
continue into arg_parser.py when the API child has terminated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 935030db-85cb-4ddd-ab2d-bf57d6720f48

📥 Commits

Reviewing files that changed from the base of the PR and between e1e1ac7 and 55073e9.

📒 Files selected for processing (2)
  • nettacker/api/engine.py
  • nettacker/locale/en.yaml

@jess-tech-lab
Copy link
Copy Markdown
Contributor Author

Hi @securestep9 and @arkid15r, please review when you have a chance. Thanks.

@jess-tech-lab
Copy link
Copy Markdown
Contributor Author

@arkid15r and @securestep9 - can you review? Thanks.

@securestep9
Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

✅ Actions performed

Full review triggered.

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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nettacker/api/engine.py`:
- Around line 624-626: After the KeyboardInterrupt handler that terminates child
processes and breaks out of the loop, ensure you wait for the child process to
fully terminate before inspecting p.exitcode (use p.join() or p.wait(timeout) on
the process object referenced as p), then check that p.exitcode is not None and
!= 0; in other words, replace the direct check "if p.exitcode != 0" with a
sequence that joins/waits for p, then evaluates "if p.exitcode is not None and
p.exitcode != 0" (referencing the process variable p and the KeyboardInterrupt
handling block).
- Around line 595-596: The except clause currently catches (ssl.SSLError,
ValueError) which is too broad; update the handler in nettacker/api/engine.py to
only catch ssl.SSLError for the SSL certificate loading path (leave
die_failure(_("load_ssl_fail")) in place), removing ValueError from the tuple,
so unrelated Flask/ssl_context ValueErrors aren't misreported; other errors
(e.g., FileNotFoundError handled earlier and the generic Exception handler in
this function) will continue to handle remaining cases.
🪄 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: cb700971-60a3-4d33-99d0-95c6d07cedf2

📥 Commits

Reviewing files that changed from the base of the PR and between 6f4f347 and d03f673.

📒 Files selected for processing (2)
  • nettacker/api/engine.py
  • nettacker/locale/en.yaml

Comment thread nettacker/api/engine.py Outdated
Comment thread nettacker/api/engine.py
Signed-off-by: jess-tech-lab <221731682+jess-tech-lab@users.noreply.github.com>
Signed-off-by: jess-tech-lab <221731682+jess-tech-lab@users.noreply.github.com>
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: 1

♻️ Duplicate comments (1)
nettacker/api/engine.py (1)

624-626: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Race condition: p.exitcode may be None after KeyboardInterrupt.

This issue was raised in a previous review and remains unresolved. After the KeyboardInterrupt handler calls process.terminate() and breaks from the loop, the child process p hasn't fully terminated yet (.terminate() is asynchronous). At that point, p.exitcode will still be None, causing the condition p.exitcode != 0 to evaluate to True (since None != 0), which incorrectly triggers sys.exit(1) even for a clean Ctrl+C interruption.

🔒 Recommended fix

Wait for the process to fully terminate before checking its exit code:

         except KeyboardInterrupt:
             for process in multiprocessing.active_children():
                 process.terminate()
             break
 
+    p.join(timeout=5)  # Wait for process to fully terminate
-    if p.exitcode != 0:
+    if p.exitcode is not None and p.exitcode > 0:
         # The child stopped. Exit the parent so it doesn't print "Cannot specify targets"
         sys.exit(1)

Changes:

  • Added p.join(timeout=5) to wait for the process to terminate (with a timeout for safety)
  • Changed condition to p.exitcode is not None and p.exitcode > 0 to only exit with error when the child actually exited with a positive error code (not None, not signal-based termination, not clean exit)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nettacker/api/engine.py` around lines 624 - 626, The current check uses
p.exitcode != 0 which treats None (still-running after process.terminate()) as
an error; modify the logic to first wait for the child to fully terminate (call
p.join(timeout=5) after process.terminate()/break) and then only call
sys.exit(1) when p.exitcode is not None and greater than 0; reference the
existing variables/methods p, process.terminate(), p.join(), p.exitcode and
sys.exit to locate and implement the change.
🧹 Nitpick comments (1)
nettacker/api/engine.py (1)

595-598: ⚡ Quick win

Consider adding PermissionError handling for completeness.

The current implementation handles missing files (FileNotFoundError) and invalid certificate content (ssl.SSLError), but doesn't specifically handle permission-denied errors when certificate files exist but can't be read. While such errors will fall through to the generic Exception handler, adding explicit handling would provide a more user-friendly error message.

💡 Proposed enhancement
     except ssl.SSLError:
         die_failure(_("load_ssl_fail"))
     except FileNotFoundError:
         die_failure(_("file_not_found"))
+    except PermissionError:
+        die_failure(_("permission_denied"))
     except Exception as e:
         die_failure(str(e))

Note: This would require adding a permission_denied key to the locale file (e.g., "Permission denied. Please check file permissions for your --api-cert and --api-cert-key files.").

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nettacker/api/engine.py` around lines 595 - 598, Add explicit handling for
PermissionError next to the existing except blocks that catch ssl.SSLError and
FileNotFoundError (the same block that calls die_failure and uses _("...")),
e.g., catch PermissionError and call die_failure(_("permission_denied")) so
unreadable cert/key files produce a clear message; also add a
"permission_denied" entry to the localization files with a short message like
"Permission denied. Please check file permissions for your --api-cert and
--api-cert-key files." Ensure this change is placed alongside the existing
except handlers that currently reference die_failure and _("...") so behavior
remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nettacker/api/engine.py`:
- Line 595: The except clause uses unnecessary parentheses: replace "except
(ssl.SSLError):" with "except ssl.SSLError:" to satisfy the formatter and avoid
the pipeline failure; locate the exception handler referencing ssl.SSLError in
nettacker/api/engine.py (the except block catching SSL exceptions) and remove
the surrounding parentheses so the handler reads "except ssl.SSLError:".

---

Duplicate comments:
In `@nettacker/api/engine.py`:
- Around line 624-626: The current check uses p.exitcode != 0 which treats None
(still-running after process.terminate()) as an error; modify the logic to first
wait for the child to fully terminate (call p.join(timeout=5) after
process.terminate()/break) and then only call sys.exit(1) when p.exitcode is not
None and greater than 0; reference the existing variables/methods p,
process.terminate(), p.join(), p.exitcode and sys.exit to locate and implement
the change.

---

Nitpick comments:
In `@nettacker/api/engine.py`:
- Around line 595-598: Add explicit handling for PermissionError next to the
existing except blocks that catch ssl.SSLError and FileNotFoundError (the same
block that calls die_failure and uses _("...")), e.g., catch PermissionError and
call die_failure(_("permission_denied")) so unreadable cert/key files produce a
clear message; also add a "permission_denied" entry to the localization files
with a short message like "Permission denied. Please check file permissions for
your --api-cert and --api-cert-key files." Ensure this change is placed
alongside the existing except handlers that currently reference die_failure and
_("...") so behavior remains consistent.
🪄 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: 88c34b12-f1dc-4f4b-ad09-ecabd07250bb

📥 Commits

Reviewing files that changed from the base of the PR and between d03f673 and fd4d5e4.

📒 Files selected for processing (2)
  • nettacker/api/engine.py
  • nettacker/locale/en.yaml
✅ Files skipped from review due to trivial changes (1)
  • nettacker/locale/en.yaml

Comment thread nettacker/api/engine.py Outdated
@jess-tech-lab
Copy link
Copy Markdown
Contributor Author

@securestep9 - I've addressed the Gemini review suggestions. Would you please take another look? Thanks!

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.

API service masks SSL certificate errors with the misleading "Cannot specify targets" message

2 participants