Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ A clear and concise description of what you expected to happen.
## Environment

- OS: [e.g. Ubuntu 22.04]
- Python version: [e.g. 3.9]
- RedOPS version: [e.g. 1.0.0]
- Python version: [e.g. 3.12]
- RedOPS version: [e.g. 1.5.0]
- Command used: [e.g. python -m redops.main run …]

## Additional Context
Expand Down
2 changes: 1 addition & 1 deletion .github/TOPICS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,5 @@ Topics have been configured in `pyproject.toml` with proper classifiers:
- Intended Audience: Information Technology, System Administrators
- Topic: Security, System Networking Monitoring
- License: MIT
- Programming Language: Python 3.8-3.12
- Programming Language: Python 3.10-3.12
- Environment: Console
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ We are committed to providing a welcoming and inclusive environment for all cont

### Prerequisites

- Python 3.8 or higher
- Python 3.10 or higher
- Git
- Basic understanding of OSINT, security assessment, or related fields
- Familiarity with modular Python development
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# RedOPS Framework

[![CI Pipeline](https://github.com/AreteDriver/RedOPS/actions/workflows/ci.yml/badge.svg)](https://github.com/AreteDriver/RedOPS/actions/workflows/ci.yml)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**Professional Cybersecurity Intelligence & Attack Surface Management Platform**
Expand Down Expand Up @@ -189,7 +189,7 @@ RedOPS is designed with strict ethical and operational boundaries:
## Installation

### Requirements
- Python 3.8 or higher
- Python 3.10 or higher
- pip package manager
- (Optional) Virtual environment recommended

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This guide will help you get up and running with RedOPS.

## Prerequisites

- Python 3.8 or higher
- Python 3.10 or higher
- pip package manager

## Installation
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
RedOPS Documentation
====================

.. image:: https://img.shields.io/badge/python-3.8+-blue.svg
.. image:: https://img.shields.io/badge/python-3.10+-blue.svg
:target: https://www.python.org/downloads/

.. image:: https://img.shields.io/badge/license-MIT-green.svg
Expand Down
7 changes: 3 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

setup(
name="redops",
version="1.0.0",
version="1.5.0",
description="Advanced modular AI-assisted recon, forensics, and exposure-analysis framework",
long_description=long_description,
long_description_content_type="text/markdown",
author="RedOps Contributors",
url="https://github.com/AreteDriver/RedOPS",
packages=find_packages(where="src"),
package_dir={"": "src"},
python_requires=">=3.8",
python_requires=">=3.10",
install_requires=[
"pydantic>=2.0.0",
],
Expand All @@ -39,9 +39,8 @@
"Intended Audience :: Information Technology",
"Topic :: Security",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
)
21 changes: 15 additions & 6 deletions src/redops/modules/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Webhooks (generic)
"""

import logging
import os
import smtplib
from email.mime.text import MIMEText
Expand All @@ -16,6 +17,8 @@
from dataclasses import dataclass
from datetime import datetime, timezone

logger = logging.getLogger(__name__)

try:
import requests

Expand Down Expand Up @@ -262,7 +265,8 @@ def _send_slack(self, message: dict[str, Any], critical: int, high: int) -> bool
timeout=10,
)
return response.status_code == 200
except Exception:
except Exception as e:
logger.warning("Failed to send Slack notification: %s", e)
Comment on lines +268 to +269

Choose a reason for hiding this comment

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

medium

To fully leverage the StructuredLogger (if adopted as suggested in the previous comment), the exception details should be passed as a keyword argument to the logging method. This allows the structured logger to include the error details in a more parseable format.

If the standard logging.getLogger is retained, the current format string approach is acceptable, but using keyword arguments is preferred for structured logging.

Suggested change
except Exception as e:
logger.warning("Failed to send Slack notification: %s", e)
except Exception as e:
logger.warning("Failed to send Slack notification", error=str(e))

return False

def _send_slack_alert(self, title: str, message: str, color: str) -> bool:
Expand All @@ -289,7 +293,8 @@ def _send_slack_alert(self, title: str, message: str, color: str) -> bool:
timeout=10,
)
return response.status_code == 200
except Exception:
except Exception as e:
logger.warning("Failed to send Slack alert: %s", e)
Comment on lines +296 to +297

Choose a reason for hiding this comment

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

medium

To fully leverage the StructuredLogger (if adopted as suggested in the previous comment), the exception details should be passed as a keyword argument to the logging method. This allows the structured logger to include the error details in a more parseable format.

If the standard logging.getLogger is retained, the current format string approach is acceptable, but using keyword arguments is preferred for structured logging.

Suggested change
except Exception as e:
logger.warning("Failed to send Slack alert: %s", e)
except Exception as e:
logger.warning("Failed to send Slack alert", error=str(e))

return False

def _send_discord(self, message: dict[str, Any], critical: int, high: int) -> bool:
Expand Down Expand Up @@ -333,7 +338,8 @@ def _send_discord(self, message: dict[str, Any], critical: int, high: int) -> bo
timeout=10,
)
return response.status_code in (200, 204)
except Exception:
except Exception as e:
logger.warning("Failed to send Discord notification: %s", e)
Comment on lines +341 to +342

Choose a reason for hiding this comment

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

medium

To fully leverage the StructuredLogger (if adopted as suggested in the previous comment), the exception details should be passed as a keyword argument to the logging method. This allows the structured logger to include the error details in a more parseable format.

If the standard logging.getLogger is retained, the current format string approach is acceptable, but using keyword arguments is preferred for structured logging.

Suggested change
except Exception as e:
logger.warning("Failed to send Discord notification: %s", e)
except Exception as e:
logger.warning("Failed to send Discord notification", error=str(e))

return False

def _send_discord_alert(self, title: str, message: str, color: str) -> bool:
Expand Down Expand Up @@ -363,7 +369,8 @@ def _send_discord_alert(self, title: str, message: str, color: str) -> bool:
timeout=10,
)
return response.status_code in (200, 204)
except Exception:
except Exception as e:
logger.warning("Failed to send Discord alert: %s", e)
Comment on lines +372 to +373

Choose a reason for hiding this comment

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

medium

To fully leverage the StructuredLogger (if adopted as suggested in the previous comment), the exception details should be passed as a keyword argument to the logging method. This allows the structured logger to include the error details in a more parseable format.

If the standard logging.getLogger is retained, the current format string approach is acceptable, but using keyword arguments is preferred for structured logging.

Suggested change
except Exception as e:
logger.warning("Failed to send Discord alert: %s", e)
except Exception as e:
logger.warning("Failed to send Discord alert", error=str(e))

return False

def _send_email(self, subject: str, body: str) -> bool:
Expand All @@ -386,7 +393,8 @@ def _send_email(self, subject: str, body: str) -> bool:
server.send_message(msg)

return True
except Exception:
except Exception as e:
logger.warning("Failed to send email notification: %s", e)
Comment on lines +396 to +397

Choose a reason for hiding this comment

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

medium

To fully leverage the StructuredLogger (if adopted as suggested in the previous comment), the exception details should be passed as a keyword argument to the logging method. This allows the structured logger to include the error details in a more parseable format.

If the standard logging.getLogger is retained, the current format string approach is acceptable, but using keyword arguments is preferred for structured logging.

Suggested change
except Exception as e:
logger.warning("Failed to send email notification: %s", e)
except Exception as e:
logger.warning("Failed to send email notification", error=str(e))

return False

def _send_webhook(self, url: str, data: dict[str, Any]) -> bool:
Expand All @@ -397,7 +405,8 @@ def _send_webhook(self, url: str, data: dict[str, Any]) -> bool:
try:
response = requests.post(url, json=data, timeout=10)
return response.status_code in (200, 201, 202, 204)
except Exception:
except Exception as e:
logger.warning("Failed to send webhook to %s: %s", url, e)

Choose a reason for hiding this comment

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

security-medium medium

The newly added logging statement in the _send_webhook function logs the full webhook URL in case of an error. Webhook URLs often contain secrets, and logging them exposes these secrets in application logs. An attacker with access to the logs could steal these credentials.

Remediation:
Avoid logging the full URL. If the URL must be logged for debugging, parse it and log only the host or parts that do not contain secrets. For example, you could log urlparse(url).netloc.

Suggested change
logger.warning("Failed to send webhook to %s: %s", url, e)
logger.warning("Failed to send webhook to %s: %s", url.split('?')[0].split('/')[-1], e)

return False


Expand Down
Loading