Skip to content

Conversation

@ocervell
Copy link
Contributor

@ocervell ocervell commented Dec 4, 2025

Summary by CodeRabbit

  • New Features

    • Introduced Scapy-based ARP scanning task for network discovery.
    • IP output now includes metadata for enriched result information.
    • Added PythonRunner base class for Python-based task development.
  • Bug Fixes

    • Improved robustness with defensive attribute handling across core modules.
  • Tests

    • Added comprehensive unit tests for PythonRunner functionality.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI and others added 8 commits November 22, 2025 14:31
Co-authored-by: ocervell <9629314+ocervell@users.noreply.github.com>
Co-authored-by: ocervell <9629314+ocervell@users.noreply.github.com>
Co-authored-by: ocervell <9629314+ocervell@users.noreply.github.com>
@ocervell ocervell changed the title feat: add scapy integration (basic) feat: add scapy integration (basic ARP scan) Dec 4, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR introduces a new PythonRunner base class for Python-based tasks, adds defensive attribute access across multiple files to prevent AttributeErrors, enhances the Ip output type with an extra_data field, and includes an example ARP scanning task implementation with corresponding unit tests.

Changes

Cohort / File(s) Summary
Defensive attribute access
secator/cli.py, secator/loader.py, secator/template.py
Use getattr() with fallback to prevent AttributeErrors when accessing optional task attributes (cmd, default_cmd, install_cmd, meta_opts).
Initialization safety
secator/cli_helper.py, secator/utils.py
Initialize input_types variable before conditional assignment in register_runner; broaden path handling condition in expand_input to treat empty input_types as enabling path resolution.
Ip output type enhancement
secator/output_types/ip.py
Add extra_data: dict field to Ip dataclass with default empty dict; enhance __repr__ to render extra data via format_object.
PythonRunner base class
secator/runners/python.py
Introduce new PythonRunner class inheriting from Runner with dynamic config building via TemplateLoader, input validation hooks (_validate_input_nonempty, _validate_chunked_input), abstract yielder() method, and async execution via delay() classmethod with Celery integration.
PythonRunner export
secator/runners/__init__.py
Add PythonRunner to public exports.
Scapy ARP scanning task
secator/tasks/scapy.py
Introduce scapy task using PythonRunner to perform ARP scans on CIDR ranges with RTT computation and error handling for insufficient permissions.
PythonRunner unit tests
tests/unit/test_python_runner.py
Add comprehensive test suite covering task instantiation, output type validation, multi-type result filtering, options handling, context access, input validation failures, and abstract method enforcement.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Key areas for attention:
    • secator/runners/python.py: Review the __init__ config-building logic via TemplateLoader, validator initialization conditions, and the delay() classmethod's Celery integration with profile-based queue selection.
    • secator/tasks/scapy.py: Verify ARP packet construction, RTT calculation, error handling for permission issues, and correct use of extra_data field in Ip output.
    • secator/utils.py: Confirm the broadened expand_input condition (treating empty input_types as enabling path handling) does not introduce unintended side effects in callers.
    • Defensive getattr calls: Ensure fallback defaults (None, {}) align with downstream usage expectations in all modified files.

Possibly related PRs

  • feat: memory optimizations #681: Modifies secator/cli_helper.py's register_runner function with profiling flags and runner execution changes, overlapping with this PR's initialization of input_types in the same function.

Poem

🐰 A PythonRunner bounds through the fields,
ARP packets scatter seeds that RTT yields,
Validators guard each input with care,
Defensive getattrs shield code everywhere,
Extra data blooms—a tale of Scapy's share! 🌾

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding Scapy integration with basic ARP scanning capability.

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

🧹 Nitpick comments (4)
tests/unit/test_python_runner.py (1)

1-162: LGTM! Comprehensive test suite for PythonRunner.

The test suite thoroughly covers PythonRunner functionality:

  • Basic task execution and output types
  • Custom options passing
  • Context access (name, inputs)
  • Input validation (empty/multiple inputs)
  • NotImplementedError when yielder not overridden

All test logic is sound and assertions are appropriate.

Note on static analysis hints: The Ruff warnings about ClassVar annotations are false positives in this test context. Test class attributes (like input_types, output_types, opts) are intentionally defined as mutable class attributes for each test case and don't need ClassVar annotations since they're not shared across instances in a problematic way.

secator/tasks/scapy.py (1)

9-10: Consider adding ClassVar annotations for type safety.

The mutable class attributes input_types and output_types should be annotated with typing.ClassVar to indicate they are class-level attributes rather than instance attributes. This improves type safety and clarity.

Apply this diff:

+from typing import ClassVar
+
 @task()
 class scapy(PythonRunner):
 	"""Use Scapy to send an ARP request to a CIDR range and return the alive IPs."""
-	input_types = [CIDR_RANGE, IP]
-	output_types = [Ip]
+	input_types: ClassVar = [CIDR_RANGE, IP]
+	output_types: ClassVar = [Ip]
secator/runners/python.py (2)

37-38: Consider adding ClassVar annotations for type safety.

The mutable class attributes tags and opts should be annotated with typing.ClassVar for better type safety and to clearly indicate they are class-level attributes.

Apply this diff:

 """Python runner for executing custom Python code."""
 import logging
+from typing import ClassVar

 from secator.config import CONFIG
 from secator.runners import Runner
 from secator.template import TemplateLoader
 
 ...
 
 class PythonRunner(Runner):
 	...
 	default_exporters = CONFIG.tasks.exporters
-	tags = []
-	opts = {}
+	tags: ClassVar = []
+	opts: ClassVar = {}
 	profile = 'io'

41-42: Unused parameter in needs_chunking method.

The sync parameter is not used in the method body. If this parameter is required for interface compatibility with the base class or future use, consider prefixing it with an underscore (_sync) to indicate it's intentionally unused.

Apply this diff:

-	def needs_chunking(self, sync):
+	def needs_chunking(self, _sync):
 		return False
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36805d2 and d1647aa.

📒 Files selected for processing (10)
  • secator/cli.py (1 hunks)
  • secator/cli_helper.py (1 hunks)
  • secator/loader.py (1 hunks)
  • secator/output_types/ip.py (3 hunks)
  • secator/runners/__init__.py (1 hunks)
  • secator/runners/python.py (1 hunks)
  • secator/tasks/scapy.py (1 hunks)
  • secator/template.py (1 hunks)
  • secator/utils.py (1 hunks)
  • tests/unit/test_python_runner.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
secator/runners/__init__.py (1)
secator/runners/python.py (1)
  • PythonRunner (12-123)
secator/tasks/scapy.py (3)
secator/runners/python.py (1)
  • PythonRunner (12-123)
secator/output_types/ip.py (1)
  • Ip (16-45)
secator/output_types/error.py (1)
  • Error (8-42)
secator/output_types/ip.py (1)
secator/utils.py (2)
  • rich_to_ansi (295-312)
  • format_object (793-800)
tests/unit/test_python_runner.py (6)
secator/runners/python.py (2)
  • PythonRunner (12-123)
  • yielder (104-113)
secator/output_types/info.py (1)
  • Info (8-24)
secator/output_types/url.py (1)
  • Url (15-110)
secator/output_types/vulnerability.py (1)
  • Vulnerability (12-101)
secator/output_types/tag.py (1)
  • Tag (9-69)
secator/runners/_base.py (1)
  • errors (284-287)
secator/runners/python.py (3)
secator/runners/_base.py (1)
  • Runner (56-1231)
secator/template.py (1)
  • TemplateLoader (11-55)
secator/config.py (1)
  • get (226-249)
🪛 Flake8 (7.3.0)
secator/tasks/scapy.py

[error] 28-28: local variable 'e' is assigned to but never used

(F841)

🪛 Ruff (0.14.7)
secator/tasks/scapy.py

9-9: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


10-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


28-28: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

tests/unit/test_python_runner.py

16-16: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


35-35: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


65-65: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


66-68: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


87-87: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


100-100: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


120-120: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


137-137: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

secator/runners/python.py

37-37: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


38-38: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


41-41: Unused method argument: sync

(ARG002)


44-44: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)


91-91: Unused static method argument: self

(ARG004)


98-98: Unused static method argument: self

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: integration (3.11, ubuntu-latest)
🔇 Additional comments (11)
secator/cli_helper.py (1)

130-130: LGTM! Defensive initialization prevents undefined variable.

The explicit initialization of input_types = [] ensures it's defined before conditional branches at lines 149, 161, and 174 may assign it, preventing potential UnboundLocalError at line 178.

secator/runners/__init__.py (1)

7-7: LGTM! New PythonRunner properly exposed.

The addition of PythonRunner to __all__ and its import from secator.runners.python correctly expose the new public surface for Python-based tasks.

Also applies to: 14-14

secator/loader.py (1)

49-50: LGTM! Defensive attribute access prevents AttributeError.

Using getattr(cls, 'cmd', None) and getattr(cls, 'install_cmd', None) safely handles tasks (e.g., PythonRunner-based) that may not define these attributes, returning None instead of raising AttributeError.

secator/output_types/ip.py (2)

7-7: LGTM! New field enables richer IP metadata.

The addition of extra_data: dict allows carrying protocol-specific metadata (e.g., ARP protocol info, RTT) alongside the IP entry, enhancing the Ip output type's expressiveness.

Also applies to: 21-21


43-44: LGTM! Extra data properly rendered in representation.

The __repr__ enhancement correctly displays extra_data when present using format_object, maintaining consistency with the codebase's display patterns.

secator/template.py (1)

184-184: LGTM! Defensive attribute access for meta_opts.

Using getattr(cls, 'meta_opts', {}).copy() safely handles tasks that don't define meta_opts, returning an empty dict instead of raising AttributeError. This aligns with the defensive access pattern used throughout this PR.

secator/cli.py (1)

1627-1630: LGTM! Defensive handling of tasks without cmd attribute.

The code now safely skips tasks that don't define a cmd attribute (e.g., PythonRunner-based tasks) by using getattr(cls, 'cmd', None) and checking for None. This prevents AttributeError during the update process.

secator/utils.py (1)

93-94: Path-handling logic change is correct and intentional.

The expanded condition if not input_types or 'path' in input_types: aligns with the Runner base class default of input_types = []. When a runner (task/scan/workflow) declares no specific input types, it should accept any input form—including paths passed directly. The change prevents unintended file reading for runners without declared input requirements. All examined task implementations explicitly declare input_types, so existing tasks are unaffected. The behavior is safe and correct.

secator/tasks/scapy.py (1)

1-5: LGTM!

The initial imports are correctly organized and include all necessary components for defining a PythonRunner-based task.

secator/runners/python.py (2)

1-10: LGTM!

Module setup follows standard conventions with appropriate docstring, imports, and logger initialization.


104-123: LGTM!

The abstract yielder method and delay classmethod for Celery integration are well-implemented. The dynamic profile handling in delay is a nice touch for flexible queue assignment.

Comment on lines +17 to +18
from scapy.all import ARP, Ether, srp
import ipaddress
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 | 🟠 Major

Move imports to the top of the file.

Per PEP 8, all imports should be placed at the top of the file, before any code definitions. This improves code readability and makes dependencies explicit upfront.

Apply this diff:

 from secator.runners import PythonRunner
 from secator.definitions import CIDR_RANGE, IP
 from secator.output_types import Ip, Error
 from secator.decorators import task
+from scapy.all import ARP, Ether, srp
+import ipaddress

 @task()
 class scapy(PythonRunner):
 	"""Use Scapy to send an ARP request to a CIDR range and return the alive IPs."""
 	input_types = [CIDR_RANGE, IP]
 	output_types = [Ip]
 	
 	def yielder(self):
 		for cidr_range in self.inputs:
 			for finding in arp_scan(cidr_range):
 				yield finding

-from scapy.all import ARP, Ether, srp
-import ipaddress
-
 def arp_scan(network_range):
📝 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
from scapy.all import ARP, Ether, srp
import ipaddress
from secator.runners import PythonRunner
from secator.definitions import CIDR_RANGE, IP
from secator.output_types import Ip, Error
from secator.decorators import task
from scapy.all import ARP, Ether, srp
import ipaddress
@task()
class scapy(PythonRunner):
"""Use Scapy to send an ARP request to a CIDR range and return the alive IPs."""
input_types = [CIDR_RANGE, IP]
output_types = [Ip]
def yielder(self):
for cidr_range in self.inputs:
for finding in arp_scan(cidr_range):
yield finding
def arp_scan(network_range):
🤖 Prompt for AI Agents
In secator/tasks/scapy.py around lines 17 to 18, the imports "from scapy.all
import ARP, Ether, srp" and "import ipaddress" must be moved to the top of the
file per PEP8; relocate these import statements to the module-level import
section (above any function/class definitions), group standard library and
third-party imports appropriately, and ensure there are no duplicate imports or
circular dependencies after the move.

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