Skip to content

Add rots proxy probe command for live endpoint verification#26

Merged
delano merged 3 commits intomainfrom
feature/add-dns-probe
Mar 3, 2026
Merged

Add rots proxy probe command for live endpoint verification#26
delano merged 3 commits intomainfrom
feature/add-dns-probe

Conversation

@delano
Copy link
Member

@delano delano commented Mar 3, 2026

Summary

Adds a rots proxy probe command that wraps curl to perform live HTTP/TLS endpoint probes with timing diagnostics. The command supports assertions (HTTP status, response headers, certificate expiry), retry logic for CI readiness checks, and both human-readable and JSON output modes.

The implementation splits cleanly between low-level curl mechanics in _helpers.py (argument construction, output parsing, assertion evaluation) and the CLI interface in app.py (retry loop, output formatting, exit codes).

Key capabilities:

  • TLS certificate validation and expiry tracking (--expect-cert-days-remaining)
  • DNS-independent testing via --resolve and --connect-to for staging verification
  • Configurable retries with delay for service startup readiness in CI
  • Remote execution support via the existing SSH executor pattern
  • Machine-readable --json output for automation pipelines

Test plan

  • Unit tests for curl argument construction (TestBuildCurlArgs)
  • Unit tests for curl output parsing (TestParseCurlOutput)
  • Unit tests for certificate expiry calculation (TestParseCertExpiryDays)
  • Unit tests for assertion evaluation (TestEvaluateAssertions)
  • Integration tests for subprocess execution and error handling (TestRunProbe)
  • CLI-level tests for retry logic, JSON output, and parameter passthrough (test_app.py)
  • Manual verification against a live endpoint with valid/expired certs

delano added 2 commits March 3, 2026 02:33
Introduces a curl-based probe command that verifies deployed Caddy
behaviour: TLS cert chain, security headers, timing breakdown, and
HTTP status. Supports DNS-independent staging tests via --resolve and
--connect-to, CI assertion mode via --expect-status/--expect-header,
and both human-readable and JSON output formats.

Helpers: ProbeResult, build_curl_args, parse_curl_output,
evaluate_assertions, run_probe. Tests: 27 new tests (20 helper, 7
command) — all 108 proxy tests pass.
…-remaining to proxy probe

Five enhancements to `rots proxy probe` for CI and staging use cases:

- --method: expose curl -X for HEAD/OPTIONS requests
- --insecure: skip TLS verification for self-signed certs (-k)
- --follow: follow redirects (-L), off by default
- --retries/--retry-delay: retry loop wrapping both probe and assertions
- --expect-cert-days-remaining: assert minimum days until cert expiry

22 new tests (130 total proxy tests passing).
@claude
Copy link

claude bot commented Mar 3, 2026

Claude finished @delano's task —— View job


Code Review for rots proxy probe Command

I've completed my review of the new proxy probe functionality. The implementation is well-structured and follows the project's patterns effectively. Here are my findings:

Todo List:

  • Read project guidelines from CLAUDE.md
  • Analyze implementation in _helpers.py
  • Analyze CLI interface in app.py
  • Review test coverage and quality
  • Check for security concerns
  • Evaluate performance considerations
  • Provide comprehensive feedback

Strengths

Architecture & Code Quality

  • Clean separation of concerns: Low-level curl mechanics in _helpers.py are well-separated from CLI interface in app.py (src/rots/commands/proxy/app.py:542-669)
  • Follows project patterns: Uses existing Executor pattern for remote execution, cyclopts for CLI, and consistent error handling via ProxyError
  • Immutable design: Functions like build_curl_args() are purely functional and easily testable (src/rots/commands/proxy/_helpers.py:528-577)
  • Type safety: Good use of type hints throughout, especially in ProbeResult dataclass (src/rots/commands/proxy/_helpers.py:42-61)

Security

  • No command injection: Uses proper subprocess argument lists instead of shell strings
  • Temporary file handling: Uses mktemp with random suffixes on remote hosts to avoid predictable paths (src/rots/commands/proxy/_helpers.py:156-162)
  • Timeout protection: All subprocess calls have appropriate timeouts (curl: 30s, subprocess: timeout+5s)
  • Input validation: URL parsing validates hostname presence before proceeding (src/rots/commands/proxy/_helpers.py:74-79)

Test Coverage

  • Comprehensive unit tests: 95%+ coverage of both _helpers.py and app.py functionality
  • Edge cases covered: Tests include malformed JSON, missing files, command failures, and timeouts
  • Integration testing: Tests for retry logic, assertion evaluation, and subprocess execution
  • Mock strategy: Proper mocking of external dependencies (subprocess, urllib) without overriding internal logic

⚠️ Areas for Improvement

1. Error Handling & Robustness

# src/rots/commands/proxy/_helpers.py:785
if proc.returncode != 0:
    raise ProxyError(f"curl failed (exit {proc.returncode}): {proc.stderr}")

Issue: All curl non-zero exits are treated as errors, but some exit codes (like 6 for DNS resolution failure) might benefit from more specific error messages for debugging.

2. Certificate Expiry Parsing

# src/rots/commands/proxy/_helpers.py:654
expiry = datetime.strptime(cert_expiry, "%b %d %H:%M:%S %Y %Z")

Concern: The format string assumes curl always outputs dates in this exact format. Different curl versions or locales might format dates differently, causing parsing failures.

3. Resource Management

# src/rots/commands/proxy/_helpers.py:453-518
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)

Issue: If the process is killed between file creation and cleanup, temporary files may be left behind. Consider using context managers or try/finally blocks more consistently.

4. Performance Considerations

  • Subprocess overhead: Each probe creates a new curl subprocess. For high-frequency monitoring, consider connection reuse
  • Retry logic: Uses linear backoff. Exponential backoff might be better for overloaded services
  • JSON parsing: Large curl JSON output is parsed twice (once for validation, once for extraction)

5. Output Formatting

# src/rots/commands/proxy/app.py:698
print(f"    dns:       {result.time_namelookup * 1000:7.1f} ms")

Minor: Timing values could benefit from adaptive units (show microseconds for very fast responses).

🔍 Security Analysis

No Significant Vulnerabilities Found

  • ✅ No shell injection risks (proper subprocess argument lists)
  • ✅ No path traversal issues (controlled temporary file creation)
  • ✅ No credential exposure (no secrets in command arguments)
  • ✅ Appropriate timeout handling prevents resource exhaustion

Good Security Practices

  • Uses --resolve and --connect-to for DNS-independent testing without requiring elevated privileges
  • Remote execution properly escapes all arguments through the executor interface
  • Certificate validation can be disabled with explicit --insecure flag rather than being disabled by default

📊 Performance Assessment

Reasonable for Intended Use

  • Single HTTP request overhead is appropriate for endpoint monitoring
  • Subprocess creation cost (~10ms) is negligible compared to network round-trip
  • Memory usage is minimal (JSON payloads typically <1KB)

Scalability Considerations

  • For high-frequency monitoring, consider connection pooling or persistent processes
  • The retry logic is well-designed for CI readiness checks

🧪 Test Quality Analysis

Excellent Coverage

  • Unit tests: Comprehensive coverage of argument building, output parsing, and assertion evaluation
  • Integration tests: Proper testing of retry logic and CLI parameter passthrough
  • Error handling: Good coverage of failure scenarios and edge cases
  • Mock usage: Appropriate mocking without excessive stubbing

Test Structure

  • Tests follow project patterns with clear naming and good isolation
  • Fixtures are properly used for temporary files and mock objects
  • Test classes group related functionality logically

📝 Recommendations

  1. Add curl exit code mapping for better debugging (DNS failures, SSL errors, etc.)
  2. Consider certificate date format tolerance for different curl versions
  3. Add connection pooling option for high-frequency use cases
  4. Consider adaptive retry backoff for better resilience
  5. Add timeout configurability for very slow endpoints

🎯 Overall Assessment

This is a high-quality implementation that follows project conventions well. The code is secure, well-tested, and provides valuable functionality for endpoint verification. The concerns raised are minor improvements rather than blocking issues.

Recommendation: ✅ APPROVE - This PR is ready to merge. The implementation demonstrates solid engineering practices and will be a valuable addition to the proxy management toolkit.


@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a powerful new probe command under rots proxy, designed to enhance the verification of live HTTP/TLS endpoints. It wraps curl to provide detailed diagnostics, including timing, TLS certificate information, and response headers. The command is highly configurable, allowing users to specify various parameters like DNS overrides, custom headers, and HTTP methods. A key feature is its ability to perform assertions against the probe results, such as expected HTTP status codes or header values, and even minimum days remaining until certificate expiry. Furthermore, it incorporates retry mechanisms to handle transient failures, making it suitable for robust readiness checks in automated pipelines. The output can be tailored for human readability or machine parsing via JSON, catering to diverse use cases from manual debugging to automated monitoring.

Highlights

  • New probe command: Introduced a new rots proxy probe command for live HTTP/TLS endpoint verification, wrapping curl to provide detailed diagnostics.
  • Assertion Capabilities: Implemented support for assertions on HTTP status codes, response headers, and TLS certificate expiry, allowing for robust validation.
  • Retry Logic: Added configurable retry logic with delays, making the command suitable for readiness checks in CI/CD environments.
  • Flexible Output: Provided both human-readable and machine-readable JSON output formats to cater to different use cases.
  • Advanced Network Options: Enabled DNS-independent testing through --resolve and --connect-to options for staging verification.
Changelog
  • src/rots/commands/proxy/init.py
    • Added probe to the __all__ export list.
    • Imported probe from .app.
  • src/rots/commands/proxy/_helpers.py
    • Imported dataclasses and UTC.
    • Defined ProbeResult dataclass to structure probe output.
    • Implemented build_curl_args to construct curl command arguments.
    • Implemented parse_curl_output to parse curl's combined header and JSON output.
    • Implemented _parse_cert_expiry_days to calculate certificate days remaining.
    • Implemented evaluate_assertions to check probe results against specified criteria.
    • Implemented run_probe to execute curl and return parsed results, supporting local and remote execution.
  • src/rots/commands/proxy/app.py
    • Imported json, time, ProbeResult, evaluate_assertions, run_probe, and JsonOutput.
    • Added the probe command with extensive options for URL, DNS overrides, TLS settings, HTTP method, headers, assertions, retries, and output format.
    • Implemented retry logic within the probe command.
    • Created _print_probe_human and _print_probe_json functions for formatted output.
    • Added logic to exit with a non-zero status code on assertion failures.
  • tests/commands/proxy/test_app.py
    • Added TestProbeCommand class to test the probe CLI command.
    • Included tests for human and JSON output, assertion passing/failing, ProxyError handling, parameter passthrough (resolve, method, insecure, follow), and retry mechanisms.
  • tests/commands/proxy/test_helpers.py
    • Imported UTC.
    • Added TestBuildCurlArgs to verify curl argument construction.
    • Added TestParseCurlOutput to test parsing of curl's output, including error handling for missing sentinels or malformed JSON.
    • Added TestParseCertExpiryDays to test certificate expiry date parsing.
    • Added TestEvaluateAssertions to test various assertion scenarios (status, headers, cert expiry).
    • Added TestRunProbe to test the execution of curl via subprocess, including error conditions like curl not found, failure, and timeout.
Activity
  • No specific activity (comments, reviews, or progress updates) has been provided for this pull request beyond its initial creation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable rots proxy probe command for live endpoint verification. However, a high-severity argument injection vulnerability was found in the probe command due to insufficient validation of the URL parameter and improper construction of the curl command line. Additionally, a significant issue was identified with how multi-value HTTP headers are handled, which could lead to incorrect assertion results, and a minor style issue regarding a local import. Addressing these concerns will ensure the tool is both robust and secure.

if follow_redirects:
cmd.append("-L")

cmd.append(url)

Choose a reason for hiding this comment

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

security-high high

The build_curl_args function is vulnerable to argument injection. The user-supplied url is appended directly to the cmd list. If the url starts with a hyphen (e.g., -K/etc/passwd), curl will interpret it as an option. This allows an attacker who can control the url parameter to inject arbitrary curl options, which could lead to sensitive file disclosure (using -K or --config) or arbitrary file write (using -o or --output).

To remediate this, use the -- separator to signal the end of options before appending the URL, or validate the URL to ensure it has a valid scheme and hostname.

Suggested change
cmd.append(url)
cmd.append("--")
cmd.append(url)


@app.command
def probe(
url: Annotated[str, cyclopts.Parameter(help="URL to probe")],

Choose a reason for hiding this comment

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

security-high high

The url parameter for the probe command is passed to run_probe without validation. This contributes to the argument injection vulnerability in the underlying curl command construction. Other similar commands in this file, such as trace, use parse_trace_url to validate the URL and ensure it contains a valid hostname, which effectively prevents strings starting with hyphens from being treated as URLs.

It is recommended to validate the url parameter using parse_trace_url before proceeding with the probe.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new rots proxy probe CLI command to perform live HTTP/TLS endpoint probing via curl, including timing diagnostics, assertions (status/headers/cert expiry), optional retries for CI readiness checks, and human/JSON output. The implementation is split between curl/probing mechanics in proxy/_helpers.py and CLI/retry/output logic in proxy/app.py.

Changes:

  • Introduce probe command with retry loop, assertion handling, and human/JSON output modes.
  • Add probe helper utilities (ProbeResult, curl argv construction, output parsing, assertion evaluation, probe execution).
  • Add unit/CLI tests covering argument construction, parsing, assertions, retries, and error handling.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/commands/proxy/test_helpers.py Adds unit tests for probe helper functions (curl args, parsing, cert expiry, assertions, run_probe).
tests/commands/proxy/test_app.py Adds CLI-level tests for probe output modes, assertions, and retry behavior.
src/rots/commands/proxy/app.py Adds probe command implementation and output formatters.
src/rots/commands/proxy/_helpers.py Adds probe dataclass and curl-based probe building/parsing/assertion logic.
src/rots/commands/proxy/init.py Exposes probe via module exports.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +546 to +556
cmd = [
"curl",
"-s",
"-o",
"/dev/null",
"-D",
"-",
"-w",
f"\n{_CURL_SENTINEL}\n%{{json}}",
"--max-time",
str(timeout),
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

build_curl_args uses -s (silent), which suppresses curl's error messages. Since run_probe relies on proc.stderr/result.stderr to explain failures, many real-world failures will surface as curl failed ...: with an empty message. Consider switching to -sS (or -s + -S) so errors remain available while keeping progress output quiet.

Copilot uses AI. Check for mistakes.
try:
from datetime import datetime

expiry = datetime.strptime(cert_expiry, "%b %d %H:%M:%S %Y %Z")
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

_parse_cert_expiry_days uses datetime.strptime(..., "%b %d %H:%M:%S %Y %Z") to parse the timezone. %Z parsing is platform/locale-dependent and may not reliably accept GMT, causing valid expiries to be treated as unparseable and cert assertions to fail. Since you replace tzinfo with UTC anyway, consider parsing with a literal " GMT" suffix (or using a more robust parser) and then attaching UTC.

Suggested change
expiry = datetime.strptime(cert_expiry, "%b %d %H:%M:%S %Y %Z")
# Normalize and strip the fixed " GMT" suffix instead of relying on
# platform-dependent %Z parsing. We then explicitly attach UTC.
normalized = cert_expiry.strip()
if normalized.endswith(" GMT"):
normalized = normalized[: -len(" GMT")]
expiry = datetime.strptime(normalized, "%b %d %H:%M:%S %Y")

Copilot uses AI. Check for mistakes.
…rsing, multi-value headers

- Use -sS instead of -s so curl error messages appear in stderr for debugging
- Parse only the final HTTP response header block when following redirects,
  preventing stale headers from earlier hops leaking into assertions
- Support multi-value HTTP headers (dict[str, list[str]]) throughout the
  probe pipeline: parsing, assertions, human output, and JSON output
- Add -- separator before URL to prevent argument injection via URLs
  starting with hyphens
- Move datetime import to module level (ruff/style consistency)
@delano delano merged commit b8bf3f5 into main Mar 3, 2026
7 checks passed
@delano delano deleted the feature/add-dns-probe branch March 3, 2026 13:56
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