Skip to content

Reporter

import "Engine-AntiGinx/App/Reporter"

Package Reporter provides multiple reporting implementations for test results. This file contains the CLI reporter which outputs formatted test results directly to the console (stdout) for local debugging and interactive usage.

The CLI reporter is used when no backend URL is configured or when running the scanner in standalone/development mode. It provides human-readable output with visual formatting including ASCII art banner and result separators.

Package Reporter provides the core interface for test result reporting implementations. This file defines the Reporter interface that must be implemented by all reporter types, enabling polymorphic handling of different reporting strategies (CLI, HTTP backend, etc.).

Package Reporter provides asynchronous test result reporting functionality using the producer-consumer pattern. It consumes TestResult objects from a channel and forwards them to an external backend service via HTTP POST requests with intelligent retry logic.

The reporter implements:

  • Non-blocking retry mechanism with exponential backoff
  • Graceful shutdown with no data loss
  • Error classification (retryable vs. fatal errors)
  • Concurrent processing of results and retries
  • Configurable retry limits and timeouts

Error codes:

  • 100: JSON marshaling error (not retryable)
  • 101: HTTP request creation error (not retryable)
  • 102: Network error (retryable)
  • 103: HTTP status error (retryable for 5xx, not retryable for 4xx)

Index

Variables

banner is the ASCII art logo displayed at the start of CLI reporting. It identifies the application as "ANTIGINX ENGINE" and is printed once when the reporter starts listening for results.

var banner string = `
    _    _   _ _____ ___ ____ ___ _   _ _  __
   / \  | \ | |_   _|_ _/ ___|_ _| \ | \ \/ /
  / _ \ |  \| | | |  | | |  _ | ||  \| |\  / 
 / ___ \| |\  | | |  | | |_| || || |\  |/  \ 
/_/   \_\_| \_| |_| |___\____|___|_| \_/_/\_\
 _____ _   _  ____ ___ _   _ _____ 
| ____| \ | |/ ___|_ _| \ | | ____|
|  _| |  \| | |  _ | ||  \| |  _|  
| |___| |\  | |_| || || |\  | |___ 
|_____|_| \_|\____|___|_| \_|_____|
`

var helpBanner = `
    _    _   _ _____ ___ ____ ___ _   _ _  __   _   _ _____ _     ____  
   / \  | \ | |_   _|_ _/ ___|_ _| \ | \ \/ /  | | | | ____| |   |  _ \ 
  / _ \ |  \| | | |  | | |  _ | ||  \| |\  /   | |_| |  _| | |   | |_) |
 / ___ \| |\  | | |  | | |_| || || |\  |/  \   |  _  | |___| |___|  __/ 
/_/   \_\_| \_| |_| |___\____|___|_| \_/_/\_\  |_| |_|_____|_____|_|  
`

separator is a visual delimiter printed between test results in the console output. It helps distinguish individual test results and improves readability of the output when multiple tests are executed.

var separator string = `---------------------------------------------`

func printTestResult

func printTestResult(result Tests.TestResult)

printTestResult formats and prints a single test result to stdout with structured formatting. This helper function provides consistent, human-readable output for all test results.

Output format:

  • Test name: [string] - The human-readable name of the test
  • Certainty: [0-100] - Confidence percentage in the result
  • Threat level: [0-5] - Security threat classification
  • 0 = None (no issues)
  • 1 = Info (informational)
  • 2 = Low (minor issues)
  • 3 = Medium (moderate concern)
  • 4 = High (serious vulnerability)
  • 5 = Critical (severe vulnerability)
  • Description: [string] - Detailed explanation of the finding
  • Separator line for visual distinction

The function is called internally by StartListening for each result received from the result channel.

Parameters:

  • result: The TestResult structure containing test execution data

Example output:

Test name: HTTPS Protocol Verification
Certainty: 100
Threat level: 4
Description: Connection uses insecure HTTP protocol - data is transmitted in plaintext
---------------------------------------------

type ConcreteResolver

type ConcreteResolver struct{}

func NewResolver

func NewResolver() *ConcreteResolver

NewResolver initializes and returns a new instance of the ConcreteResolver struct.

Returns:

  • *ConcreteResolver: A pointer to the newly created resolver instance

func (*ConcreteResolver) Resolve

func (r *ConcreteResolver) Resolve(ch chan strategy.ResultWrapper, taskId string, target string, clientTimeOut int, retryDelay int, strategies []strategy.TestStrategy) Reporter

Resolve determines and initializes the appropriate Reporter implementation based on the provided strategies and environment configuration.

The resolution logic follows this priority order: 1. If strategies prefer a HelpReporter, it returns a new HelpReporter. 2. If the "BACK_URL" environment variable is set, it returns an initialized BackendReporter. 3. Otherwise, it defaults to returning an InitializeCliReporter.

Parameters:

  • ch: The channel used for transmitting strategy result wrappers
  • taskId: The unique identifier for the current task
  • target: The target endpoint or system being tested
  • clientTimeOut: The timeout duration for the client in seconds (or ms, depending on impl)
  • retryDelay: The delay duration between retries
  • strategies: A slice of test strategies to be validated and used for reporting decisions

Returns:

  • Reporter: An interface satisfying the Reporter contract (Help, Backend, or CLI)

func (*ConcreteResolver) checkStrategies

func (r *ConcreteResolver) checkStrategies(strategies []strategy.TestStrategy) strategy.ReporterType

checkStrategies validates that all provided strategies share the same preferred reporter type.

This helper method iterates through the provided strategies to ensure consistency. It will panic if the strategy list is empty or if there is a conflict in the preferred reporter type between different strategies.

Parameters:

  • strategies: The list of strategies to validate

Returns:

  • strategy.ReporterType: The unified reporter type preferred by the strategies

type Reporter

Reporter is the interface that defines the contract for all test result reporting implementations. It provides a unified abstraction for consuming test results from a channel and processing them according to the specific reporter's strategy.

The interface enables the application to work with different reporting backends without coupling the test execution logic to specific output mechanisms. This allows for:

  • Flexible output destinations (console, HTTP backend, file, etc.)
  • Easy addition of new reporter types
  • Testability through mock implementations
  • Runtime selection of reporting strategy

Current implementations:

  • cliReporter: Outputs formatted results to stdout (console)
  • backendReporter: Sends results to external HTTP backend with retry logic

Expected behavior:

  • StartListening() should spawn a goroutine for asynchronous processing
  • The reporter should consume all results from its input channel
  • Processing should continue until the input channel is closed
  • The returned channel should signal completion and provide error/failure count
  • Implementations should handle graceful shutdown without data loss

Example usage:

// Create reporter based on configuration
var reporter Reporter
if backendURL != "" {
    reporter = InitializeBackendReporter(resultChan, backendURL)
} else {
    reporter = InitializeCliReporter(resultChan)
}

// Start processing
doneChan := reporter.StartListening()

// Send results to reporter...
for _, result := range testResults {
    resultChan <- result
}
close(resultChan)

// Wait for completion
failureCount := <-doneChan
if failureCount > 0 {
    log.Printf("Warning: %d results failed to process", failureCount)
}
type Reporter interface {
    // StartListening initiates asynchronous processing of test results from the reporter's
    // input channel. This method must be non-blocking and should spawn a goroutine for
    // background processing.
    //
    // The method returns a receive-only channel that signals when all processing is complete.
    // The integer value sent on this channel represents the count of failed operations:
    //   - 0: All results processed successfully (or no failures possible, as in CLI reporter)
    //   - >0: Number of results that failed to process (e.g., failed HTTP uploads)
    //
    // Implementations must:
    //   - Process all results until the input channel is closed
    //   - Handle errors gracefully with appropriate retry logic if applicable
    //   - Ensure no data loss during shutdown
    //   - Send exactly one value to the returned channel before closing it
    //
    // Returns:
    //   - <-chan int: Completion signal channel with failure count
    StartListening() <-chan int
}

type Resolver

Resolver defines the contract for determining and creating the appropriate Reporter instance based on the execution context and configuration.

Implementations of this interface are responsible for analyzing the provided strategies and environment settings to decide which reporting mechanism (e.g., CLI, Backend, Help) should be used.

type Resolver interface {

    // Resolve instantiates and returns a concrete Reporter implementation.
    //
    // It acts as a factory method that uses the provided strategies to determine
    // the correct reporting type and initializes it with the necessary context.
    //
    // Parameters:
    //  - ch: The channel used for transmitting result wrappers asynchronously
    //  - taskId: The unique identifier for the current task
    //  - target: The target endpoint or system string
    //  - clientTimeOut: The timeout duration for the client
    //  - retryDelay: The delay duration between retries
    //  - strategies: A list of TestStrategy objects to derive the preferred reporter type
    //
    // Returns:
    //  - Reporter: An initialized reporter interface ready to handle results
    Resolve(ch chan strategy.ResultWrapper, taskId string,
        target string, clientTimeOut int, retryDelay int, strategies []strategy.TestStrategy) Reporter
}

type backendReporter

backendReporter handles the consumption of test results and forwards them to an external backend service via HTTP. It implements a robust producer-consumer pattern with a non-blocking retry mechanism for handling transient failures.

The reporter processes results asynchronously in a separate goroutine and applies intelligent retry logic based on error types. Network errors and server errors (5xx) are retried, while client errors (4xx) and marshaling errors are not.

Architecture:

  • Main processing loop: Consumes from resultChannel
  • Retry queue: Buffered channel for failed submissions
  • Retry goroutines: Sleep-based backoff for rate limiting

Fields:

  • resultChannel: Input channel for test results from the Runner
  • backendURL: Target HTTP endpoint for result submission
  • testId: ID of test from RabbitMQ
  • maxRetries: Maximum retry attempts for failed submissions (default: 2)
  • httpClient: HTTP client with configured timeout (default: 5 seconds)
type backendReporter struct {
    resultChannel chan strategy.ResultWrapper
    backendURL    string
    testId        string
    target        string
    maxRetries    int
    retryDelay    int
    httpClient    *http.Client
}

func InitializeBackendReporter

func InitializeBackendReporter(channel chan strategy.ResultWrapper, backendURL string, testId string, target string, clientTimeOut int, retryDelay int) *backendReporter

InitializeBackendReporter creates and configures a new instance of backendReporter with sensible defaults for production use. The reporter is ready to start processing results immediately after initialization.

Default configuration:

  • HTTP timeout: 5 seconds
  • Max retries: 2 attempts
  • Retry delay: 2 seconds (hardcoded in tryToSendOrEnqueue)

The reporter must be started by calling StartListening() to begin processing results.

Parameters:

  • channel: The input channel where test results are published by the Runner
  • backendURL: The target HTTP endpoint for result submission (e.g., "http://api.example.com/results")

Returns:

  • *backendReporter: Configured reporter instance ready to start listening

Example:

resultChan := make(chan Tests.TestResult, 10)
reporter := InitializeBackendReporter(resultChan, "http://api.example.com/results")
doneChan := reporter.StartListening()
// ... send results to resultChan ...
close(resultChan)
failedCount := <-doneChan
fmt.Printf("Processing complete. Failed uploads: %d\n", failedCount)

func (*backendReporter) StartListening

func (b *backendReporter) StartListening() <-chan int

StartListening initiates the asynchronous background processing loop that consumes test results and forwards them to the backend service. This method spawns a goroutine that handles both new results and retry attempts concurrently.

Processing architecture:

The method uses a select statement to handle two input sources:

  1. resultChannel: New test results from the Runner
  2. retryChan: Failed results waiting for retry after backoff delay

Graceful shutdown sequence:

  1. Producer closes resultChannel signaling no more results
  2. Reporter processes all remaining results in the channel
  3. Reporter waits for all sleeping retry goroutines (via retryWg)
  4. Reporter processes any new retries added by sleeping goroutines
  5. Reporter sends final failure count and exits

This ensures zero data loss during shutdown - all results are either successfully submitted or counted as failures.

Retry mechanism:

  • Buffered retry channel (capacity: 10) prevents blocking
  • WaitGroup tracks sleeping retry goroutines
  • 2-second delay between retry attempts
  • Retryable errors are re-queued up to maxRetries limit

Returns:

  • \<-chan int: Read-only channel that receives the total count of failed uploads once all processing is complete (including retries)

Example:

reporter := InitializeBackendReporter(resultChan, "http://api.example.com/results")
doneChan := reporter.StartListening()

// Send results...
for _, result := range testResults {
    resultChan <- result
}
close(resultChan)

// Wait for completion
failedCount := <-doneChan
if failedCount > 0 {
    log.Printf("Warning: %d results failed to upload", failedCount)
}

func (*backendReporter) handleRetryLogic

func (b *backendReporter) handleRetryLogic(response *http.Response) *Errors.Error

func (*backendReporter) prepareReqWithErrHandling

func (b *backendReporter) prepareReqWithErrHandling(result Tests.TestResultWrapper) (*http.Request, *Errors.Error)

func (*backendReporter) sendLastWithFlag

func (b *backendReporter) sendLastWithFlag(result Tests.TestResultWrapper, failedUploads *int)

sendLastWithFlag sends a final request to the backend to signal that the engine has completed its work.

This method sends an empty TestResult with EndFlag=true to the backend, indicating completion of all test processing. It handles both retryable and non-retryable errors: if the error is retryable, it waits 2 seconds and retries once more; otherwise, it increments the failedUploads counter.

Parameters:

  • result: The TestResultWrapper to send. Typically, this should be an empty result with EndFlag set to true.
  • failedUploads: Pointer to an integer counter tracking the number of failed uploads. This will be incremented if the final request fails.

func (*backendReporter) sendToBackend

func (b *backendReporter) sendToBackend(result Tests.TestResultWrapper) error

sendToBackend performs the actual HTTP POST request to the configured backend endpoint with comprehensive error handling and classification. This method executes the complete HTTP request lifecycle from marshaling to response validation.

Request process:

  1. Marshal the TestResult to JSON
  2. Create HTTP POST request with JSON payload
  3. Set Content-Type header to application/json
  4. Execute request with configured timeout (default: 5 seconds)
  5. Validate response status code

Error classification:

  • Code 100 (JSON Marshal): Not retryable - indicates invalid test result structure
  • Code 101 (Request Creation): Not retryable - indicates programming error
  • Code 102 (Network Error): Retryable - transient network issues, DNS failures, timeouts
  • Code 103 (HTTP Status): Conditional retry based on status code:
  • 200-299: Success, no error
  • 400, 401, 403: Not retryable (client errors, auth issues)
  • 404, 405, etc.: Not retryable (client errors)
  • 500-599: Retryable (server errors, temporary outages)

The method returns structured Errors.Error objects that include the IsRetryable flag, allowing the retry logic to make intelligent decisions about whether to re-attempt the submission.

Parameters:

  • result: The TestResult to submit to the backend

Returns:

  • error: nil on success (HTTP 2xx), *Errors.Error with retry information on failure

Example error handling:

err := reporter.sendToBackend(testResult)
if err != nil {
    var customErr *Errors.Error
    if errors.As(err, &customErr) && customErr.IsRetryable {
        // Retry logic
    } else {
        // Permanent failure
    }
}

func (*backendReporter) tryToSendOrEnqueue

func (b *backendReporter) tryToSendOrEnqueue(result strategy.ResultWrapper, attNumber int, retryChan chan retryResult, retryWg *sync.WaitGroup, failedUploads *int)

tryToSendOrEnqueue attempts to send a test result to the backend and manages the retry workflow based on the outcome. This method implements the core retry logic with exponential backoff and retry limit enforcement.

Workflow:

  1. Attempt to send the result via sendToBackend
  2. On success: return immediately
  3. On failure: check if error is retryable
  4. If retryable and under retry limit: spawn backoff goroutine
  5. If not retryable or limit exceeded: increment failure counter

Retry behavior:

  • Network errors (code 102): Retried
  • Server errors 5xx (code 103): Retried
  • Client errors 4xx (code 103): Not retried
  • Marshaling errors (code 100): Not retried
  • Request creation errors (code 101): Not retried

The retry goroutine sleeps for 2 seconds before re-queuing the result, preventing rapid retry storms and giving the backend time to recover from transient issues.

Parameters:

  • result: The test result to submit
  • attNumber: Current attempt number (0-based)
  • retryChan: Channel for re-queuing failed results
  • retryWg: WaitGroup for tracking sleeping retry goroutines
  • failedUploads: Pointer to counter for permanent failures

type cliReporter

cliReporter is a console-based reporter implementation that outputs test results directly to standard output (stdout). It provides formatted, human-readable output for interactive use and local development.

Unlike the backendReporter, this implementation does not perform HTTP requests or implement retry logic. It simply formats and prints results synchronously as they arrive on the result channel.

The reporter is typically used in scenarios:

  • Local development and debugging
  • Standalone scanner execution without backend
  • Manual security testing and verification
  • CI/CD pipelines that need console output

Fields:

  • resultChannel: Receive-only channel for consuming test results
type cliReporter struct {
    resultChannel <-chan strategy.ResultWrapper
}

func InitializeCliReporter

func InitializeCliReporter(channel chan strategy.ResultWrapper) *cliReporter

InitializeCliReporter creates and returns a new instance of the CLI reporter configured to consume test results from the specified channel. The reporter is ready to start listening immediately after initialization.

This factory function provides a simple way to create a CLI reporter for scenarios where HTTP backend reporting is not required or desired. It's commonly used for:

  • Local development and testing
  • Debugging security tests
  • Standalone scanner execution
  • Quick security assessments

The reporter will format and print each result to stdout as it's received, providing immediate feedback during test execution.

Parameters:

  • channel: The input channel where test results are published by the Runner

Returns:

  • *cliReporter: Configured reporter instance ready to start listening

Example:

resultChan := make(chan Tests.TestResult, 10)
reporter := InitializeCliReporter(resultChan)
doneChan := reporter.StartListening()

// Send test results...
for _, result := range testResults {
    resultChan <- result
}
close(resultChan)

// Wait for completion
<-doneChan

func (*cliReporter) StartListening

func (c *cliReporter) StartListening() <-chan int

StartListening begins the asynchronous process of consuming and printing test results to the console. This method spawns a goroutine that displays the application banner and then continuously processes results until the input channel is closed.

Processing sequence:

  1. Print ASCII art banner to stdout
  2. Print "TEST RESULT" header
  3. Enter processing loop (range over resultChannel)
  4. For each result: call printTestResult to format and display
  5. When channel closes: send completion signal and exit

Output format for each test:

  • Test Name
  • Certainty percentage (0-100)
  • Threat Level (0-5: None, Info, Low, Medium, High, Critical)
  • Description
  • Visual separator line

The method provides immediate visual feedback as tests complete, making it ideal for interactive use and debugging. Unlike the backend reporter, this implementation is synchronous (no retries) and has no failure modes - all results are printed.

Returns:

  • \<-chan int: Read-only channel that receives 0 when processing is complete (always 0 for CLI reporter as there are no upload failures)

Example:

reporter := InitializeCliReporter(resultChan)
doneChan := reporter.StartListening()

// Results are printed as they arrive...
// Output:
// [ASCII Banner]
// TEST RESULT
// Test name: HTTPS Protocol Verification
// Certainty: 100
// Threat level: 0
// Description: Connection is secured with HTTPS protocol
// ---------------------------------------------

<-doneChan  // Blocks until all results processed

type helpReporter

type helpReporter struct {
    resultChan chan strategy.ResultWrapper
}

func NewHelpReporter

func NewHelpReporter(resultChan chan strategy.ResultWrapper) *helpReporter

func (*helpReporter) StartListening

func (hr *helpReporter) StartListening() <-chan int

func (*helpReporter) printHelpInstruction

func (hr *helpReporter) printHelpInstruction(helpInstruction strategy.HelpStrategyResult)

type retryResult

retryResult is an internal wrapper structure used to track the state of a failed submission in the retry queue. It encapsulates both the original test result and metadata about the retry attempt to enforce retry limits and prevent infinite retry loops.

Fields:

  • result: The original TestResult that failed to submit
  • attNum: Current attempt number (0-based, incremented with each retry)
type retryResult struct {
    result strategy.ResultWrapper
    attNum int
}

Generated by gomarkdoc