Skip to content

improved detection - #1

Open
pentoshi007 wants to merge 1 commit into
mainfrom
feature
Open

improved detection#1
pentoshi007 wants to merge 1 commit into
mainfrom
feature

Conversation

@pentoshi007

@pentoshi007 pentoshi007 commented Feb 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Added scan profiles (LABS, SAFE) and confidence modes (HIGH, BALANCED, RECALL) for customized scanning behavior
    • Introduced variant capability matrix displaying implementation status (Implemented, Planned, Experimental)
    • Expanded HTTP/2 and WebSocket smuggling variant support
  • Documentation

    • Updated product branding and variant documentation with new capability-focused presentation
    • Added comprehensive variant maturity matrix across protocols
  • Improvements

    • Enhanced reporting now displays untested variants and skipped checks for transparency
    • Improved variant selection with clearer status indicators

Copilot AI review requested due to automatic review settings February 6, 2026 08:05
@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces HTTP/2 smuggling detection support and a variant capability matrix system. Changes include new scan profiles and confidence modes, transport-specific executors, enhanced reporting of untested variants and skipped checks, and comprehensive documentation updates reflecting variant maturity status.

Changes

Cohort / File(s) Summary
Documentation Updates
README.md, docs/TECHNICAL_REFERENCE.md, docs/VARIANTS.md
Updated branding, introduced "Variant Capability Matrix" with Status columns, replaced narrative descriptions with status-driven maturity model (Implemented/Planned/Experimental) across all variant types; reframed detection approach around capabilities rather than individual variant details.
Core Configuration & Types
http_smuggler/core/config.py, http_smuggler/core/__init__.py
Introduced new public enums ScanProfile (LABS, SAFE) and ConfidenceMode (HIGH, BALANCED, RECALL). Extended ScanConfig with profile, confidence_mode, confirm_attempts, and auto_listeners fields; added post-initialization hook for SAFE profile defaults. Extended NetworkConfig and PayloadConfig with request headers/cookies and not_tested_variants tracking.
Variant Registry (New)
http_smuggler/core/variant_registry.py
New centralized module defining VariantCapability dataclass, VARIANT_CAPABILITIES mapping, and helper functions (get_capability, list_capabilities, classify_requested_variants, build_variant_map) to serve as single source of truth for variant metadata, status, and transport information.
Detection with HTTP/2 Support
http_smuggler/detection/timing.py, http_smuggler/detection/differential.py
Added HTTP/2-aware detection paths: TimingDetector.measure_baseline now accepts transport parameter for HTTP/2 baseline measurement; DifferentialDetector.detect routes HTTP/2 payloads through new _detect_http2 method using HTTP2RawClient. Both detectors now accept confidence_mode parameter controlling threshold values.
Transport Executors (New)
http_smuggler/network/executor.py, http_smuggler/network/__init__.py
New executor framework with TransportExecutor abstract base, Http1Executor and Http2Executor implementations, plus inject_http1_context utility. Provides uniform interface for sending payloads over HTTP/1.1 or HTTP/2 with support for header injection, cookie management, and pause-based payload splitting.
Payload Generator Extensions
http_smuggler/payloads/generator.py
Extended Payload dataclass with transport field and HTTP/2-specific fields (http2_headers, http2_body), WebSocket fields, plus post_init for backward compatibility. Updated HTTP/2 variant generators (H2.CL, H2.TE, H2.CRLF) to populate HTTP/2 metadata on payload instances.
Engine Orchestration
http_smuggler/core/engine.py
Major refactoring: reorganized detection pipeline with _run_timing_checks and _run_differential_checks helpers; added baseline caching, variant applicability checks (_is_variant_applicable), and enhanced skipping/not_tested tracking. ScanResult now exposes not_tested and skipped fields. Improved exploit runner integration with auto_listeners composition.
Exploitation & Reporting
http_smuggler/exploits/exploit_runner.py, http_smuggler/analysis/reporter.py
Added status field to ExploitResult/ExploitationResult (confirmed/likely/requires_victim/not_confirmed). Enhanced Markdown and Text reports with "Capability Notes" sections showing not_tested variants and skipped checks tables. Updated markdown vulnerability output to display exploitation status.
Models & Data Structures
http_smuggler/core/models.py
Extended ExploitationResult with status field; extended ScanResult with not_tested and skipped list fields to track untested variants and skipped endpoint/variant combinations.
CLI & Main Entry Point
http_smuggler/main.py
Added CLI options for --profile, --confidence-mode, --confirm-attempts; updated _parse_variants to return (enabled_variants, not_tested_variants) tuple using variant registry utilities; updated list_variants to display capability matrix with Transport, Status, Detectors, Exploit columns; enhanced _print_summary to report not_tested and skipped statistics.
Test Coverage
tests/test_variant_registry.py, tests/test_cli_variants.py, tests/test_detection.py, tests/test_reporting.py
Added comprehensive tests for variant registry (default variants, classification, aliases, capabilities), CLI variant parsing with planned/unknown variant handling, detection configuration defaults, and reporting format validation for not_tested and skipped sections.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Engine
    participant VariantRegistry as Variant<br/>Registry
    participant Detector as Timing/<br/>Differential
    participant Executor
    participant Network as Network<br/>Client
    
    Client->>Engine: scan(target, variants)
    Engine->>VariantRegistry: classify_requested_variants(requested)
    VariantRegistry-->>Engine: (enabled, not_tested)
    Engine->>Engine: _is_variant_applicable(variant)
    
    alt Variant Applicable
        Engine->>Engine: _get_or_create_baseline(endpoint)
        Engine->>Detector: measure_baseline(transport)
        Detector->>Executor: get_executor(transport)
        Executor->>Network: send_simple_request(baseline)
        Network-->>Executor: response_time
        Executor-->>Detector: BaselineResult
        Detector-->>Engine: baseline
        
        Engine->>Detector: detect(payload, baseline)
        Detector->>Executor: send_payload(payload)
        Executor->>Network: send HTTP/1 or HTTP/2
        Network-->>Executor: response
        Executor-->>Detector: RawResponse
        Detector->>Detector: analyze_responses()
        Detector-->>Engine: DetectionResult
        
        Engine->>Engine: _confirm_timing_payload()
        Engine-->>Client: Vulnerability(status)
    else Not Applicable
        Engine->>Engine: _record_skip(endpoint, variant)
        Engine-->>Engine: add to not_tested/skipped
    end
Loading
sequenceDiagram
    participant Engine
    participant Registry as Variant<br/>Registry
    participant Detector as HTTP/2<br/>Differential
    participant Http2Client as HTTP/2<br/>Client
    
    Engine->>Registry: get_capability(H2_CL)
    Registry-->>Engine: VariantCapability(status, transport)
    
    alt transport == "http2"
        Engine->>Detector: detect(payload.transport="http2")
        Detector->>Detector: _detect_http2()
        Detector->>Http2Client: baseline_request()
        Http2Client-->>Detector: h2_response
        Detector->>Http2Client: smuggle_request()
        Http2Client-->>Detector: h2_response
        Detector->>Http2Client: victim_request()
        Http2Client-->>Detector: h2_response
        Detector->>Detector: _h2_to_raw(responses)
        Detector->>Detector: _analyze_responses()
        Detector-->>Engine: DetectionResult(http2)
    else transport == "http1"
        Engine->>Detector: detect(payload.transport="http1")
        Detector-->>Engine: DetectionResult(http1)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops through HTTP/2 tunnels with glee,
Variants now matrixing wild and free,
Capabilities tracked, status so clear,
Payloads transport-aware, far and near,
The smuggler's foundation, rebuilt with care! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'improved detection' is overly vague and generic, failing to communicate the specific changes made in this substantial pull request. Provide a more specific title that reflects the main changes, such as 'Add variant capability matrix and transport-aware detection' or 'Introduce scan profiles and confidence modes for detection'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 89.13% 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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (4)
docs/TECHNICAL_REFERENCE.md (2)

320-401: ⚠️ Potential issue | 🟡 Minor

Architecture tree is stale — missing new modules.

The tree doesn't list core/variant_registry.py or network/executor.py, both introduced in this PR. Consider updating to avoid confusion for contributors.


409-436: ⚠️ Potential issue | 🟡 Minor

Implementation plan task statuses out of date.

Tasks 13-16 (H2.CL, H2.TE, H2.CRLF, WS version smuggling) and surrounding items are shown as 🔲 Pending but are being delivered in this PR. Updating these statuses would keep the roadmap accurate.

http_smuggler/payloads/http2/crlf_injection.py (1)

348-361: ⚠️ Potential issue | 🟡 Minor

"\r\n" and "\x0d\x0a" are identical in Python.

In Python, "\r\n" is "\x0d\x0a" — they produce the same bytes. This means the list has a duplicate entry rather than a distinct variant. If the intent was to test a literal backslash-escaped string (e.g., for header-value injection), use a raw string or double-escape.

Proposed fix
         return [
             "\r\n",       # Standard CRLF
             "\n",         # LF only
             "\r",         # CR only
             "\r\n ",      # CRLF + space (folding)
             "\r\n\t",     # CRLF + tab (folding)
-            "\x0d\x0a",   # Explicit bytes
+            "\x00\r\n",   # Null byte + CRLF
         ]
http_smuggler/detection/timing.py (1)

411-423: ⚠️ Potential issue | 🟠 Major

detect_batch measures a single HTTP/1 baseline and reuses it for all payloads, including HTTP/2.

measure_baseline is called without a transport argument (defaults to "http1"), so the resulting timing baseline reflects HTTP/1.1 round-trip characteristics. This baseline is then passed to detect() for every payload — including HTTP/2 ones that may have materially different latency profiles. The mismatch can produce false positives (if HTTP/2 is naturally slower) or false negatives (if HTTP/2 is faster).

Consider grouping payloads by transport and measuring a baseline per group, or omitting the shared baseline so detect() measures an appropriate one per payload.

Sketch: per-transport baseline
-        # Measure baseline once
-        baseline = await self.measure_baseline(host, port, use_ssl)
+        # Measure baseline per transport
+        baselines: dict[str, BaselineResult] = {}
 
         results = []
 
         for payload in timing_payloads:
+            transport = getattr(payload, "transport", "http1")
+            if transport not in baselines:
+                baselines[transport] = await self.measure_baseline(
+                    host, port, use_ssl, transport=transport,
+                )
             result = await self.detect(
                 payload,
                 host,
                 port,
                 use_ssl,
-                baseline,
+                baselines[transport],
             )
🤖 Fix all issues with AI agents
In `@docs/VARIANTS.md`:
- Around line 35-37: The variant table is inconsistent: `H2.Tunnel` appears in
VARIANTS.md but is missing from the HTTP/2 variant tables in
TECHNICAL_REFERENCE.md and README.md; either add `H2.Tunnel` (with the same
columns as `H2.0`/`h2c`: human name, protocol `http2`, status `planned`) to
those other tables, or in VARIANTS.md add a short clarifying note explaining why
`H2.Tunnel` is intentionally excluded from TECHNICAL_REFERENCE and README (e.g.,
experimental/registry-only) so all docs are aligned; update any cross-references
or TOC entries that list HTTP/2 variants to include the change.

In `@http_smuggler/analysis/reporter.py`:
- Line 254: Remove the unnecessary f-string prefixes on static strings added to
the lines list (e.g., the calls to lines.append(f"✅ **Exploitation
Successful**") and the similar lines.append(f"...") around line 259) — change
them to plain string literals (lines.append("✅ **Exploitation Successful**")) in
reporter.py so there is no unused f-string interpolation; update any other
lines.append calls in the same function that use f"" without {…} to simple "" as
well.

In `@http_smuggler/core/engine.py`:
- Around line 574-579: Replace the assert guard with an explicit runtime check:
after the loop that sets best, change "assert best is not None" to an if block
that handles the None case (e.g., raise a clear RuntimeError or return a
sensible default) so the code is robust when running under -O or after future
refactors; ensure you still compute required = 1 if attempts == 1 else (attempts
// 2) + 1 and set best.vulnerable = positives >= required only when best is not
None, referencing the variables best, attempts, positives and required in the
updated logic.

In `@http_smuggler/detection/timing.py`:
- Around line 150-175: The baseline HTTP/2 branch instantiates Http2Executor and
calls send_simple_request without checking use_ssl, which can attempt a TLS
handshake on non-SSL targets; update the baseline loop to mirror send_payload's
behavior by only using Http2Executor/send_simple_request when use_ssl is True
(or delegate to the shared get_executor factory used in detect()), and otherwise
fall back to the AsyncRawHttpClient path; locate the baseline code in timing.py
(the for loop where Http2Executor is created and response.elapsed is read) and
change it to obtain the executor via get_executor(...) or guard the
Http2Executor branch with if use_ssl: so non-SSL targets don’t attempt TLS
handshakes. Ensure response handling and the existing exception handling remain
consistent.

In `@http_smuggler/main.py`:
- Around line 321-323: The current line unconditionally enables exploiting in
aggressive mode and overrides an explicit --no-exploit; change the logic so an
explicit exploit flag wins: set ExploitConfig(enabled = exploit if exploit is
not None else (scan_mode == ScanMode.AGGRESSIVE)), referencing ExploitConfig,
the enabled field, the exploit variable and scan_mode/ScanMode.AGGRESSIVE;
alternatively, if you prefer Option B, update the CLI/help to state that
aggressive mode forces exploitation and auto-set the exploit flag when parsing
the mode instead of silently overriding user input.
- Around line 465-466: Replace the unnecessary defensive getattr calls with
direct attribute access: change uses of getattr(result, "not_tested", []) and
getattr(result, "skipped", []) to result.not_tested and result.skipped
respectively (the table.add_row calls that add "Not Tested Variants" and
"Skipped Checks"). These fields are defined on the ScanResult dataclass
(not_tested and skipped), so access them directly for clarity and type safety.

In `@README.md`:
- Around line 204-206: The README table uses the non-canonical casing
"WS.VERSION"; update that table cell to the canonical "WS.Version" to match
VARIANTS.md and TECHNICAL_REFERENCE.md, and search for any other occurrences of
"WS.VERSION" and replace them with "WS.Version" (ensure references like the
table row containing "WS.VERSION | Fake 101 Server (port 9999) | WebSocket SSRF"
are updated so the variant name matches the registry).
🧹 Nitpick comments (24)
http_smuggler/network/__init__.py (1)

17-23: New executor exports look correct and consistent.

Imports and __all__ entries match. One minor organizational nit: inject_http1_context and Http1Executor are grouped under the # HTTP/2 comment block (Line 42), which is slightly misleading since they are HTTP/1-related. Consider adding a # Transport executors comment section to separate them from the HTTP/2 protocol primitives above.

Also applies to: 48-52

http_smuggler/core/models.py (1)

79-79: Consider documenting the status field contract more formally.

The allowed values (confirmed|likely|requires_victim|not_confirmed) are only documented in a comment. If more statuses are added later, a string literal type or enum would prevent silent misuse. Low priority — the exploit runner centralizes these as class constants already.

http_smuggler/network/executor.py (2)

145-160: Missing headers fallback returns a bare RawResponse without diagnostic context.

Line 150-151: when headers is falsy, the returned RawResponse has no indication of why the response is empty (i.e., "payload has no HTTP/2 headers to send"). This could make debugging difficult if a payload is misconfigured. Consider adding a log message or setting connection_closed=True to distinguish from a real empty response.


219-226: Factory only handles "http2""websocket" / "browser" transports fall through to Http1Executor.

This is fine for the current scope since WebSocket and Browser transports are EXPERIMENTAL/PLANNED, but callers passing transport="websocket" will silently get an HTTP/1 executor. If you'd like fail-fast behavior for unsupported transports, a warning log or a dedicated check could be added later.

http_smuggler/core/variant_registry.py (3)

44-193: Registry covers all 16 SmugglingVariant members — well organized.

The capability metadata is thorough. One observation: PLANNED variants (e.g., CL_CL, CL_0) have exploit_support=True and populated detectors tuples. If these describe intended capabilities (when implemented), that's fine, but it could mislead callers who check exploit_support without also checking status. Worth a docstring clarification on VariantCapability.exploit_support.


236-245: Redundant explicit aliases for "pause" and "csd".

Lines 243-244 manually add "pause" and "csd" aliases, but the loop at Lines 240-241 already adds lowercase versions of every variant value ("Pause".lower()"pause", "CSD".lower()"csd"). The explicit lines are harmless and arguably serve as documentation of intent, so this is just a nit.


204-206: No immediate issue, but consider adding validation to prevent registry mismatches if SmugglingVariant is extended in the future.

All 16 SmugglingVariant members are currently registered in VARIANT_CAPABILITIES, so get_capability is safe today. However, there's no test or startup validation to catch the case if a new enum member is added to models.py without being added to the registry—it would silently raise KeyError at runtime.

Consider either:

  • Test-based approach (preferred): Add a test that validates all SmugglingVariant members have entries in the registry.
  • Error messaging: Improve the error message as suggested in the original comment.

A test is preferable because it catches the issue at test time rather than runtime.

http_smuggler/exploits/exploit_runner.py (1)

148-154: Accessing private _active_servers dict bypasses AutoListenerManager encapsulation.

Lines 152–154 and 346 directly access self._listener_manager._active_servers to retrieve the server object and call get_loot(). This couples the exploit runner to the internal storage mechanism. Instead, add a public method to AutoListenerManager such as get_server(listener_type: str) -> Optional[CallbackServer] to safely expose server access, or add a delegating method get_loot(listener_type: str = "loot") that calls the underlying server's get_loot().

http_smuggler/core/config.py (2)

4-4: Unused import Any.

Any is imported but never referenced in this file.

🧹 Proposed fix
-from typing import Optional, List, Set, Dict, Any
+from typing import Optional, List, Set, Dict

163-167: enabled_variants default now driven by registry; not_tested_variants uses untyped dicts.

The default factory change to get_enabled_variants_for_scan cleanly decouples variant selection from the config. However, not_tested_variants is typed as List[Dict[str, str]] — the entries have an implicit schema (variant, reason, status keys) with no enforcement. Consider a small dataclass or TypedDict to prevent key mismatches.

tests/test_reporting.py (1)

11-11: datetime.utcnow() is deprecated since Python 3.12.

Use datetime.now(datetime.UTC) instead to avoid DeprecationWarning on Python 3.12+.

🧹 Proposed fix
-from datetime import datetime
+from datetime import datetime, timezone
-    now = datetime.utcnow()
+    now = datetime.now(timezone.utc)
tests/test_cli_variants.py (1)

10-17: Tests are tightly coupled to registry data — consider adding a brief comment documenting the assumption.

The test assumes CL.CL and H2.0 are both PLANNED. If their status changes in the registry (e.g., promoted to IMPLEMENTED), these tests will silently fail. A short comment noting the expected registry state would help future maintainers.

http_smuggler/payloads/http2/h2_cl.py (2)

75-93: Duplicated header/body data in both top-level fields and metadata.

http2_headers and http2_body are now first-class Payload fields, but the same data is also stored under metadata["h2_headers"] and metadata["body"] (with inconsistent key names). This duplication creates a risk of drift if one is updated without the other. Consider removing the redundant metadata entries and having consumers read from the top-level fields instead.


300-301: if body: is falsy for b"" — use if body is not None: instead.

The method signature accepts Optional[bytes], so b"" (empty body) is a valid input distinct from None. The current check would skip appending an empty body, which is correct for this file's callers but incorrect as a general contract.

🧹 Proposed fix
-        if body:
+        if body is not None:
http_smuggler/payloads/http2/h2_te.py (2)

75-91: Redundant metadata entries alongside first-class fields.

http2_headers and http2_body are now first-class Payload fields (lines 81-82), but the same data is duplicated in metadata (lines 88-90). The differential detector already prefers the first-class fields with metadata as a fallback. Consider removing the redundant h2_headers/body keys from metadata across all payloads in this file to avoid stale-data risks during future refactors.


43-52: Extract shared helpers to a common HTTP/2 base class.

_extract_host_path and _serialize_h2_request are duplicated verbatim across H2TEPayloadGenerator, H2CLPayloadGenerator, and H2CRLFPayloadGenerator. Consider extracting them into a shared H2PayloadGeneratorBase class or mixin to eliminate duplication and improve maintainability.

http_smuggler/detection/differential.py (2)

293-296: HTTP/2 path uses a fixed 0.5s delay; HTTP/1 path uses adaptive delay up to 5s.

The HTTP/1 path (lines 143-146) computes an adaptive delay based on baseline response time: max(0.5, min(baseline_time * 3, 5.0)). The HTTP/2 path uses a hardcoded 0.5 seconds. For slower backends or realistic downgrade scenarios, 0.5s may be insufficient for the smuggled request to settle, leading to false negatives.

Consider applying similar adaptive logic here, using the baseline HTTP/2 response time.


335-351: _h2_to_raw omits response headers from raw_data.

The synthetic raw_data (line 344) is HTTP/2 {status}\r\n\r\n{body} — it doesn't include the response headers. If any downstream code ever parses raw_data (e.g., RawResponse.from_raw()), the headers would be lost. Currently the headers field is set directly, so this is safe, but it's a subtle inconsistency to be aware of.

http_smuggler/core/engine.py (1)

180-191: Config mutation during init: enabled_variants is silently modified.

self.config.payload.enabled_variants.discard(variant) (line 191) mutates the config object's enabled_variants set. This violates the expectation that passing a config to the engine constructor won't modify it. Although the current call site doesn't re-inspect the config after engine creation, this is fragile API design. Consider storing effective variants in an instance variable instead.

Proposed fix
-    enabled = set(self.config.payload.enabled_variants)
+    enabled = set(self.config.payload.enabled_variants)  # local working copy
     ...
-            self.config.payload.enabled_variants.discard(variant)
+            enabled.discard(variant)
+
+    # Store the effective set for use in scan without mutating config
+    self._effective_variants = enabled

Then reference self._effective_variants instead of self.config.payload.enabled_variants in the scanning pipeline (lines 298 and 355).

http_smuggler/detection/timing.py (2)

150-150: Unused loop variable i — rename to _.

Per static analysis (Ruff B007), i is unused.

Fix
-        for i in range(self.baseline_requests):
+        for _ in range(self.baseline_requests):

432-449: Convenience function doesn't expose new confidence_mode or network_config parameters.

timing_detect always constructs TimingDetector(safety_config) with defaults for the newly added parameters. Callers of this public API have no way to configure confidence mode or network settings (custom headers/cookies). Consider widening the signature if this function is part of the intended public surface.

http_smuggler/main.py (3)

490-495: Return type annotation is unparameterized tuple.

The return type tuple loses the structural information for callers. A precise annotation would aid IDE support and static analysis.

Fix
+from typing import Optional, List, Dict, Set, Tuple
+
 def _parse_variants(
     variants_str: Optional[str],
     http2_only: bool,
     classic_only: bool,
-) -> tuple:
+) -> Tuple[Set[SmugglingVariant], List[Dict[str, str]]]:

512-513: Default path skips classify_requested_variants, returning a hardcoded empty not_tested list.

When no --variants flag is given, the default set from get_enabled_variants_for_scan() is returned directly with [] for not_tested. This is correct only if DEFAULT_SCAN_VARIANTS is guaranteed to exclude planned variants. If a planned variant is ever added to that default set, it won't be filtered out here.

Consider routing through classify_requested_variants for consistency with the other code paths.

Fix
     if not variants_str:
-        return get_enabled_variants_for_scan(), []
+        return classify_requested_variants(get_enabled_variants_for_scan())

303-316: --timeout only sets timing_detection_timeout; network-level timeouts remain at defaults.

The NetworkConfig at Line 303 is created without propagating the user's --timeout value, so connect_timeout (10s) and read_timeout (30s) remain hardcoded defaults. A user passing --timeout 30 may expect all operations to respect it, but only the timing detection timeout (Line 314) is affected.

Consider forwarding the timeout to NetworkConfig as well, or clarifying the --timeout help text.

Comment thread docs/VARIANTS.md
Comment on lines +35 to +37
| `H2.0` | HTTP/2 request tunneling | `http2` | `planned` |
| `h2c` | h2c cleartext upgrade smuggling | `http2` | `planned` |
| `H2.Tunnel` | HTTP/2 tunnel abuse | `http2` | `planned` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor inconsistency: H2.Tunnel only in this doc.

H2.Tunnel (line 37) appears here but is absent from the TECHNICAL_REFERENCE.md and README.md HTTP/2 variant tables. Consider aligning variant lists across all docs, or noting in the registry why it's excluded elsewhere.

🤖 Prompt for AI Agents
In `@docs/VARIANTS.md` around lines 35 - 37, The variant table is inconsistent:
`H2.Tunnel` appears in VARIANTS.md but is missing from the HTTP/2 variant tables
in TECHNICAL_REFERENCE.md and README.md; either add `H2.Tunnel` (with the same
columns as `H2.0`/`h2c`: human name, protocol `http2`, status `planned`) to
those other tables, or in VARIANTS.md add a short clarifying note explaining why
`H2.Tunnel` is intentionally excluded from TECHNICAL_REFERENCE and README (e.g.,
experimental/registry-only) so all docs are aligned; update any cross-references
or TOC entries that list HTTP/2 variants to include the change.

status = vuln.exploitation.status.replace("_", " ").title()
lines.append(f"- Status: **{status}**")
if vuln.exploitation.successful:
lines.append(f"✅ **Exploitation Successful**")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove extraneous f prefix from strings without placeholders.

Lines 254 and 259 use f-strings but contain no {…} interpolation. This is flagged by Ruff (F541).

Proposed fix
-                lines.append(f"✅ **Exploitation Successful**")
+                lines.append("✅ **Exploitation Successful**")
-                lines.append(f"❌ **Exploitation Not Confirmed**")
+                lines.append("❌ **Exploitation Not Confirmed**")

Also applies to: 259-259

🧰 Tools
🪛 Ruff (0.14.14)

[error] 254-254: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In `@http_smuggler/analysis/reporter.py` at line 254, Remove the unnecessary
f-string prefixes on static strings added to the lines list (e.g., the calls to
lines.append(f"✅ **Exploitation Successful**") and the similar
lines.append(f"...") around line 259) — change them to plain string literals
(lines.append("✅ **Exploitation Successful**")) in reporter.py so there is no
unused f-string interpolation; update any other lines.append calls in the same
function that use f"" without {…} to simple "" as well.

Comment on lines +574 to +579
await asyncio.sleep(0.1)

assert best is not None
required = 1 if attempts == 1 else (attempts // 2) + 1
best.vulnerable = positives >= required
return best

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace assert with an explicit guard for production robustness.

assert statements are stripped when Python runs with the -O flag. While the loop guarantees best is always assigned (since attempts >= 1), a future refactor could break this invariant. Use an explicit if to be resilient.

Proposed fix
-    assert best is not None
-    required = 1 if attempts == 1 else (attempts // 2) + 1
-    best.vulnerable = positives >= required
-    return best
+    if best is None:
+        raise RuntimeError("No timing detection results produced")
+    required = 1 if attempts == 1 else (attempts // 2) + 1
+    best.vulnerable = positives >= required
+    return best
📝 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
await asyncio.sleep(0.1)
assert best is not None
required = 1 if attempts == 1 else (attempts // 2) + 1
best.vulnerable = positives >= required
return best
await asyncio.sleep(0.1)
if best is None:
raise RuntimeError("No timing detection results produced")
required = 1 if attempts == 1 else (attempts // 2) + 1
best.vulnerable = positives >= required
return best
🤖 Prompt for AI Agents
In `@http_smuggler/core/engine.py` around lines 574 - 579, Replace the assert
guard with an explicit runtime check: after the loop that sets best, change
"assert best is not None" to an if block that handles the None case (e.g., raise
a clear RuntimeError or return a sensible default) so the code is robust when
running under -O or after future refactors; ensure you still compute required =
1 if attempts == 1 else (attempts // 2) + 1 and set best.vulnerable = positives
>= required only when best is not None, referencing the variables best,
attempts, positives and required in the updated logic.

Comment on lines 150 to +175
for i in range(self.baseline_requests):
try:
async with AsyncRawHttpClient(self.network) as client:
await client.connect(host, port, use_ssl)

start = time.monotonic()
response = await client.send_and_receive(
baseline_request,
if transport == "http2":
h2_executor = Http2Executor(self.network)
response = await h2_executor.send_simple_request(
host=host,
port=port,
path=path,
receive_timeout=self.safety.timing_detection_timeout,
method="GET",
headers=self.network.request_headers,
)
elapsed = time.monotonic() - start

if response.status_code and 200 <= response.status_code < 500:
times.append(elapsed)
elapsed = response.response_time
else:
async with AsyncRawHttpClient(self.network) as client:
await client.connect(host, port, use_ssl)

start = time.monotonic()
response = await client.send_and_receive(
baseline_request,
receive_timeout=self.safety.timing_detection_timeout,
)
elapsed = time.monotonic() - start

if response.status_code and 200 <= response.status_code < 500:
times.append(elapsed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

HTTP/2 baseline path lacks the use_ssl guard present in Http2Executor.send_payload.

Http2Executor.send_payload (executor.py Line 141) returns a safe empty response when use_ssl is False, but send_simple_request (used here for baseline) has no such guard. On a non-SSL target with an HTTP/2 payload, the baseline loop will attempt a TLS handshake and likely raise an unhandled exception that is silently swallowed by the bare except.

Also, consider using the get_executor factory (as detect() already does at Line 237) instead of instantiating Http2Executor directly — this keeps a single point of construction.

Proposed fix
             if transport == "http2":
-                    h2_executor = Http2Executor(self.network)
-                    response = await h2_executor.send_simple_request(
+                    if not use_ssl:
+                        break  # HTTP/2 requires TLS; skip baseline
+                    executor = get_executor("http2", self.network)
+                    response = await executor.send_simple_request(
                         host=host,
                         port=port,
                         path=path,
🧰 Tools
🪛 Ruff (0.14.14)

[warning] 150-150: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

🤖 Prompt for AI Agents
In `@http_smuggler/detection/timing.py` around lines 150 - 175, The baseline
HTTP/2 branch instantiates Http2Executor and calls send_simple_request without
checking use_ssl, which can attempt a TLS handshake on non-SSL targets; update
the baseline loop to mirror send_payload's behavior by only using
Http2Executor/send_simple_request when use_ssl is True (or delegate to the
shared get_executor factory used in detect()), and otherwise fall back to the
AsyncRawHttpClient path; locate the baseline code in timing.py (the for loop
where Http2Executor is created and response.elapsed is read) and change it to
obtain the executor via get_executor(...) or guard the Http2Executor branch with
if use_ssl: so non-SSL targets don’t attempt TLS handshakes. Ensure response
handling and the existing exception handling remain consistent.

Comment thread http_smuggler/main.py
Comment on lines 321 to 323
exploit=ExploitConfig(
enabled=exploit or mode == "aggressive",
enabled=exploit or scan_mode == ScanMode.AGGRESSIVE,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

--no-exploit is silently overridden in aggressive mode.

enabled=exploit or scan_mode == ScanMode.AGGRESSIVE means explicit --no-exploit --mode aggressive still enables exploitation, which contradicts the user's intent. If aggressive mode should always exploit, the --exploit flag's help text should say so; otherwise, respect the explicit opt-out.

Option A — respect explicit opt-out
         exploit=ExploitConfig(
-            enabled=exploit or scan_mode == ScanMode.AGGRESSIVE,
+            enabled=exploit,
         ),
Option B — document behavior and auto-set --exploit when aggressive is chosen
+    if scan_mode == ScanMode.AGGRESSIVE:
+        exploit = True
+
     config = ScanConfig(
         ...
         exploit=ExploitConfig(
-            enabled=exploit or scan_mode == ScanMode.AGGRESSIVE,
+            enabled=exploit,
         ),
📝 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
exploit=ExploitConfig(
enabled=exploit or mode == "aggressive",
enabled=exploit or scan_mode == ScanMode.AGGRESSIVE,
),
exploit=ExploitConfig(
enabled=exploit,
),
🤖 Prompt for AI Agents
In `@http_smuggler/main.py` around lines 321 - 323, The current line
unconditionally enables exploiting in aggressive mode and overrides an explicit
--no-exploit; change the logic so an explicit exploit flag wins: set
ExploitConfig(enabled = exploit if exploit is not None else (scan_mode ==
ScanMode.AGGRESSIVE)), referencing ExploitConfig, the enabled field, the exploit
variable and scan_mode/ScanMode.AGGRESSIVE; alternatively, if you prefer Option
B, update the CLI/help to state that aggressive mode forces exploitation and
auto-set the exploit flag when parsing the mode instead of silently overriding
user input.

Comment thread http_smuggler/main.py
Comment on lines +465 to +466
table.add_row("Not Tested Variants", str(len(getattr(result, "not_tested", []))))
table.add_row("Skipped Checks", str(len(getattr(result, "skipped", []))))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ScanResult class definition
rg -n "class ScanResult" --type=py -A 25

Repository: pentoshi007/http-smuggler

Length of output: 2044


🏁 Script executed:

#!/bin/bash
# Also search for dataclass or pydantic decorators on ScanResult
rg -n "ScanResult" --type=py | grep -E "(class|@|field)" | head -20

Repository: pentoshi007/http-smuggler

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Look for the context around lines 465-466 in main.py
cat -n http_smuggler/main.py | sed -n '450,475p'

Repository: pentoshi007/http-smuggler

Length of output: 1280


🏁 Script executed:

#!/bin/bash
# Check for `@dataclass` decorator on ScanResult
cat -n http_smuggler/core/models.py | sed -n '160,180p'

Repository: pentoshi007/http-smuggler

Length of output: 861


Replace defensive getattr() calls with direct attribute access.

ScanResult is a dataclass with both not_tested and skipped fields defined with field(default_factory=list) (lines 175-176 in http_smuggler/core/models.py). These fields are guaranteed to exist and never be None. The getattr fallbacks at lines 465-466 are unnecessary defensive code; use result.not_tested and result.skipped directly for clarity and type safety.

🤖 Prompt for AI Agents
In `@http_smuggler/main.py` around lines 465 - 466, Replace the unnecessary
defensive getattr calls with direct attribute access: change uses of
getattr(result, "not_tested", []) and getattr(result, "skipped", []) to
result.not_tested and result.skipped respectively (the table.add_row calls that
add "Not Tested Variants" and "Skipped Checks"). These fields are defined on the
ScanResult dataclass (not_tested and skipped), so access them directly for
clarity and type safety.

Comment thread README.md
Comment on lines +204 to 206
| CL.TE, TE.CL, TE.TE, H2.CL, H2.TE, H2.CRLF | Capture Server (port 8888) | Session hijacking |
| WS.VERSION | Fake 101 Server (port 9999) | WebSocket SSRF |
| Client-Side Desync | Loot Server (port 8080) | Cookie exfiltration |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent casing: WS.VERSION vs WS.Version.

Line 205 uses WS.VERSION (all-caps), while VARIANTS.md (line 43) and TECHNICAL_REFERENCE.md (line 49) use WS.Version. Standardize to match the canonical name used in the variant registry.

🤖 Prompt for AI Agents
In `@README.md` around lines 204 - 206, The README table uses the non-canonical
casing "WS.VERSION"; update that table cell to the canonical "WS.Version" to
match VARIANTS.md and TECHNICAL_REFERENCE.md, and search for any other
occurrences of "WS.VERSION" and replace them with "WS.Version" (ensure
references like the table row containing "WS.VERSION | Fake 101 Server (port
9999) | WebSocket SSRF" are updated so the variant name matches the registry).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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