Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isolate and modularize sanitization and rss feed #242

Merged
merged 3 commits into from
Nov 30, 2024
Merged

Isolate and modularize sanitization and rss feed #242

merged 3 commits into from
Nov 30, 2024

Conversation

synfinner
Copy link
Owner

No description provided.

@synfinner synfinner self-assigned this Nov 30, 2024
Copy link
Contributor

coderabbitai bot commented Nov 30, 2024

Walkthrough

The changes in this pull request involve significant modifications to the sanitization of input queries and the generation of RSS feeds within the application. The sanitize_query function has been removed from kevin.py and schema/api.py, with its logic refactored into a new utility module located in utils/sanitizer.py. Additionally, the RSS feed generation has been updated, replacing the previous manual construction with a new function, create_rss_feed, imported from utils/rss_feed.py. These changes enhance code organization while maintaining the existing control flow.

Changes

File Change Summary
kevin.py Removed sanitize_query function; replaced RSS feed generation logic with create_rss_feed.
schema/api.py Removed sanitize_query function; updated to call the new sanitize_query from utils/sanitizer.
utils/rss_feed.py Added create_rss_feed(recent_entries) function for generating RSS feeds.
utils/sanitizer.py Added sanitize_query(query) function for input sanitization.

Possibly related PRs

🐇 In the garden, queries bloom,
With sanitization, we dispel the gloom.
RSS feeds now crafted with care,
In the code, a cleaner air.
Hopping forward, we embrace the change,
For a safer path, we rearrange! 🌼

Warning

Rate limit exceeded

@synfinner has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 15 minutes and 37 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0de7456 and 406bceb.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0de7456 and 406bceb.

📒 Files selected for processing (1)
  • utils/sanitizer.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • utils/sanitizer.py

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Pylintpython3 (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

kevin.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/rss_feed.py Dismissed Show dismissed Hide dismissed
utils/sanitizer.py Dismissed Show dismissed Hide dismissed
utils/sanitizer.py Dismissed Show dismissed Hide dismissed
utils/sanitizer.py Dismissed Show dismissed Hide dismissed
utils/sanitizer.py Dismissed Show dismissed Hide dismissed
utils/sanitizer.py Dismissed Show dismissed Hide dismissed
Copy link
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: 5

🧹 Outside diff range and nitpick comments (6)
utils/sanitizer.py (2)

59-60: Update comment to reflect allowed characters

The comment mentions "allowed characters (alphanumeric, spaces, hyphens)", but the regex also allows underscores. Update the comment to include underscores for clarity.

Apply this diff:

-    # allowed characters (alphanumeric, spaces, hyphens)
+    # Allowed characters (alphanumeric, spaces, hyphens, underscores)

69-70: Relying on regex for SQL injection detection is unreliable

Using regular expressions to detect SQL injection patterns can lead to false positives and negatives. Consider using parameterized queries or ORM features that automatically handle SQL injection prevention.

schema/api.py (4)

27-28: Correct the typo in the comment

In line 27, the word "fectching" should be corrected to "fetching".


Line range hint 194-213: Simplify redundant conditional logic in caching implementation

In the get method of AllKevVulnerabilitiesResource, there are redundant conditional checks for actor_query when determining whether to use caching. Both the outer and inner if actor_query conditions check the same thing, leading to unnecessary code duplication.

To improve code readability and maintainability, consider refactoring the conditional logic as follows:

def get(self):
    # ... [previous code] ...
    if actor_query:
        # No caching if actor is specified
        total_vulns = self.count_documents(query)
        vulnerabilities = self.fetch_vulnerabilities(query, sort_criteria, page, per_page)
    else:
        # Use caching when actor is not specified
        cache_key = f"all_listing_page_{page}_per_page_{per_page}_sort_{sort_param}_order_{order_param}_search_{search_query}_filter_{filter_ransomware}"

        @cache(timeout=120, key_prefix=cache_key)
        def cached_fetch():
            total_vulns = self.count_documents(query)
            vulnerabilities = self.fetch_vulnerabilities(query, sort_criteria, page, per_page)
            return total_vulns, vulnerabilities

        total_vulns, vulnerabilities = cached_fetch()

This eliminates the redundant inner if actor_query condition and ensures caching is properly applied when actor_query is not specified.

🧰 Tools
🪛 Ruff (0.8.0)

8-8: flask.Response imported but unused

Remove unused import

(F401)


8-8: flask.json imported but unused

Remove unused import

(F401)


Line range hint 67-99: Consider refactoring duplicate code in cveNVDResource and cveMitreResource

The get methods in cveNVDResource and cveMitreResource classes contain similar logic for sanitizing the CVE ID, querying the database, and returning serialized data. To adhere to the DRY (Don't Repeat Yourself) principle, consider abstracting the common functionality into a shared method or base class to reduce code duplication and improve maintainability.

For example, you can create a base class method:

class BaseResource(Resource):
    # ... existing methods ...

    def get_vulnerability_data(self, cve_id, serializer_function):
        sanitized_cve_id = sanitize_query(cve_id)
        if not sanitized_cve_id:
            return self.handle_error("Invalid CVE ID", 400)
        vulnerability = all_vulns_collection.find_one({"_id": sanitized_cve_id})
        if not vulnerability:
            return self.handle_error("Vulnerability not found")
        data = serializer_function(vulnerability)
        return self.make_json_response(data)

Then modify the get methods:

class cveNVDResource(BaseResource):
    @cache()
    def get(self, cve_id):
        return self.get_vulnerability_data(cve_id, nvd_serializer)

class cveMitreResource(BaseResource):
    @cache()
    def get(self, cve_id):
        return self.get_vulnerability_data(cve_id, mitre_serializer)
🧰 Tools
🪛 Ruff (0.8.0)

8-8: flask.Response imported but unused

Remove unused import

(F401)


8-8: flask.json imported but unused

Remove unused import

(F401)


Line range hint 332-346: Ensure proper validation of the 'days' parameter without using sanitize_query

In the get method of RecentVulnerabilitiesByDaysResource, the days parameter is sanitized using sanitize_query, which might not be appropriate for numerical input. Since days is expected to be a non-negative integer, consider validating it directly to prevent potential issues.

Adjust the code as follows to validate 'days' correctly:

def get(self):
    # Get the query parameters
    days = request.args.get("days")
    page = request.args.get("page", default=1, type=int)
    per_page = request.args.get("per_page", default=25, type=int)
    if per_page > 100:
        return self.handle_error("The 'per_page' parameter cannot exceed 100.", 400)
    per_page = max(1, per_page)
    if days is None:
        return self.handle_error("You must provide 'days' parameter", 400)
-   days = sanitize_query(days)
-   if not days.isdigit() or int(days) < 0:
+   try:
+       days = int(days)
+       if days < 0 or days > 14:
+           raise ValueError
+   except ValueError:
        return self.handle_error("Invalid value for days parameter. Please provide a non-negative integer no greater than 14.", 400)
-   if int(days) > 14:
-       return self.handle_error("Exceeded the maximum limit of 14 days", 400)

This ensures that 'days' is properly validated as an integer within the expected range without relying on sanitize_query.

🧰 Tools
🪛 Ruff (0.8.0)

8-8: flask.Response imported but unused

Remove unused import

(F401)


8-8: flask.json imported but unused

Remove unused import

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6be9959 and ffa13ee.

📒 Files selected for processing (4)
  • kevin.py (2 hunks)
  • schema/api.py (2 hunks)
  • utils/rss_feed.py (1 hunks)
  • utils/sanitizer.py (1 hunks)
🔇 Additional comments (3)
kevin.py (1)

17-18: Successful modularization of sanitize_query and create_rss_feed functions

The sanitize_query and create_rss_feed functions have been correctly modularized into separate utility modules and imported appropriately. This enhances code organization, reusability, and maintainability.

Also applies to: 141-142

schema/api.py (2)

5-5: Correct the import statement for sanitize_query

Ensure that the import path for sanitize_query is correct and that the utils.sanitizer module exists in the project structure.

Run the following script to verify the existence of the sanitize_query function in utils/sanitizer.py:

✅ Verification successful

Import statement for sanitize_query is correctly defined

The sanitize_query function is properly defined in utils/sanitizer.py and the import statement in schema/api.py correctly references this function. The function is well-documented and implements query sanitization logic as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if 'sanitize_query' is defined in 'utils/sanitizer.py'

# Test: Search for the definition of 'sanitize_query' in 'utils/sanitizer.py'
# Expect: The function definition should be found exactly once.
ast-grep --pattern $'def sanitize_query($_):\n    $$$' utils/sanitizer.py

Length of output: 2902


27-28: Clarify the collection name in the comment

The comment mentions "cveland" collection. Ensure that "cveland" is the correct collection name. If it should be "cve_land" or another name, please update the comment accordingly for clarity.

Run the following script to verify the collection name used in the codebase:

This will help identify all instances where collections are referenced and ensure consistent naming.

✅ Verification successful

Let me gather more specific information about the collection names used in the codebase.


The collection name "cveland" in the comment is correct

The comment accurately reflects the collection name. This is verified by multiple pieces of evidence in the codebase:

  • The database name is explicitly defined as ALL_VULNS_DB_NAME = "cveland" in utils/database.py
  • The term "cveland" is consistently used throughout the codebase, including in resource names and route definitions
  • The comment correctly describes that CVE-ID is used as the _id field in the cveland collection
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of collection names in the codebase.

# Test: Search for collection names. Expect consistent naming.
rg -A 2 $'collection\.\w+' --glob "*.py"

Length of output: 4999


Script:

#!/bin/bash
# Search for collection variable definitions and initializations
rg -A 2 "(collection|all_vulns_collection)\s*=\s*" --glob "*.py"

# Search for any mentions of "cveland" in the codebase
rg -i "cveland" --glob "*.py"

Length of output: 1442

Comment on lines +56 to +57
except Exception: # Catch any exceptions that may occur during URL decoding
return None # Return None if decoding fails
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid catching broad exceptions

Catching all exceptions with except Exception can mask unexpected errors and make debugging difficult. It's better to catch specific exceptions that may occur during URL decoding, such as UnicodeDecodeError.

Apply this diff:

         except Exception:  # Catch any exceptions that may occur during URL decoding
+        except UnicodeDecodeError:  # Catch decoding errors during URL decoding
             return None  # Return None if decoding fails

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 44 to 45
if query is None or len(query) > 50:
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential TypeError when checking the length of query

If query is not a string or does not support the len() function, calling len(query) may raise a TypeError. To prevent this, consider converting query to a string before performing the length check.

Apply this diff to fix the issue:

 def sanitize_query(query):
-    if query is None or len(query) > 50:
+    if query is None:
+        return None
+    query = str(query).strip()
+    if len(query) > 50:
         return None

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +2 to +3
from defusedxml import ElementTree
from xml.etree.ElementTree import Element, SubElement
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistent use of XML parsing libraries; potential security risk

You're importing ElementTree from defusedxml, which is secure against XML vulnerabilities, but importing Element and SubElement from xml.etree.ElementTree. This inconsistency could negate the security benefits of using defusedxml. Import Element and SubElement from defusedxml.ElementTree instead.

Apply this diff:

-import html
-from defusedxml import ElementTree
-from xml.etree.ElementTree import Element, SubElement
+import html
+from defusedxml.ElementTree import Element, SubElement, tostring

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +51 to +52
for poc in github_pocs:
description_html += f"<li>{poc}</li>"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing HTML escaping for poc variable

When adding GitHub POCs to the description, the poc variable is inserted into the HTML without escaping, which could lead to XSS vulnerabilities if poc contains malicious content. Use html.escape() to sanitize poc before including it in the HTML.

Apply this diff:

             for poc in github_pocs:
-                description_html += f"<li>{poc}</li>"
+                description_html += f"<li>{html.escape(poc)}</li>"

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +43 to +76
<li><strong>Known Ransomware Usage:</strong> {entry.get('knownRansomwareCampaignUse', 'No Known Ransomware Usage')}</li>
<li><strong>GitHub POCs:</strong>
<ul>
"""

# Handle lists for GitHub POCs
github_pocs = entry.get("githubPocs", [])
if isinstance(github_pocs, list) and github_pocs:
for poc in github_pocs:
description_html += f"<li>{poc}</li>"
else:
description_html += "<li>No GitHub POCs</li>"

description_html += "</ul></li></ul>"

open_threat_data = entry.get("openThreatData", [])
if isinstance(open_threat_data, list) and open_threat_data:
adversaries = []
affected_industries = []
for data in open_threat_data:
adversaries.extend(data.get("adversaries", []))
affected_industries.extend(data.get("affectedIndustries", []))

adversaries_str = ", ".join(set(adversaries)) if adversaries else "No Adversaries"
affected_industries_str = ", ".join(set(affected_industries)) if affected_industries else "No Affected Industries"
open_threat_data_html = f"""
<ul>
<li><strong>Adversaries:</strong> {adversaries_str}</li>
<li><strong>Affected Industries:</strong> {affected_industries_str}</li>
</ul>
"""
else:
open_threat_data_html = "<p>No Open Threat Data</p>"

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure all user-supplied data is properly escaped to prevent XSS

Several fields in the description HTML are inserted without proper HTML escaping, which could lead to XSS vulnerabilities if the data contains malicious content. Ensure that all user-supplied data included in the HTML is escaped using html.escape().

Apply these changes:

  • Escape entry.get('knownRansomwareCampaignUse', 'No Known Ransomware Usage'):

                <li><strong>Known Ransomware Usage:</strong> {entry.get('knownRansomwareCampaignUse', 'No Known Ransomware Usage')}</li>
    +           <li><strong>Known Ransomware Usage:</strong> {html.escape(entry.get('knownRansomwareCampaignUse', 'No Known Ransomware Usage'))}</li>
  • After joining adversaries and industries, escape the strings:

                adversaries_str = ", ".join(set(adversaries)) if adversaries else "No Adversaries"
                affected_industries_str = ", ".join(set(affected_industries)) if affected_industries else "No Affected Industries"
    +           adversaries_str = html.escape(adversaries_str)
    +           affected_industries_str = html.escape(affected_industries_str)

Committable suggestion skipped: line range outside the PR's diff.

utils/sanitizer.py Fixed Show fixed Hide fixed
utils/sanitizer.py Fixed Show fixed Hide fixed
utils/sanitizer.py Fixed Show fixed Hide fixed
@synfinner synfinner merged commit 4494406 into main Nov 30, 2024
9 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 3, 2024
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.

1 participant