> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗ whois is redacted, cdns mask the rest — get the real operator — try whoisgeni.us ↗ who really runs that domain ↗ domain attribution that shows its work — full evidence chain — try whoisgeni.us ↗ domain intel w/ evidence ↗

← Back to Articles

URLQuery API Client: Building Malware Analysis Pipelines Without the Framework Bloat

[ View on GitHub ]

URLQuery API Client: Building Malware Analysis Pipelines Without the Framework Bloat

Hook

Most security APIs hide complexity behind helper methods. This one makes you implement your own polling loops—and that's exactly why security engineers choose it.

Context

When a suspicious URL lands in your SOC queue, you need a verdict: phishing page, exploit kit, or false alarm. Automated sandboxes solve this by executing URLs in instrumented browsers, capturing DOM mutations, network traffic, and behavioral signatures. URLQuery.net provides this as a hosted service, but their REST API presents an integration challenge. Unlike file analysis where you upload-then-poll, URL sandboxing has variable execution times—a simple redirect completes in seconds while a sophisticated JavaScript payload might take five minutes to reveal its behavior.

Most API clients handle this variability by baking in retry logic and timeout assumptions. The urlquery-api-go library takes the opposite approach: it exposes raw HTTP primitives and leaves workflow orchestration to you. Each API endpoint maps to a single function that constructs requests, handles authentication, and deserializes JSON. No automatic polling. No opinionated retry strategies. No connection pooling beyond Go's defaults. This minimalism matters for security tooling where execution patterns vary wildly between batch threat hunting jobs and real-time incident response.

Technical Insight

The library's architecture centers on a package-level API key and discrete functions per endpoint. Authentication uses SetDefaultKey() to store credentials globally, with per-request override support through optional parameters. Response handling deserializes JSON into predefined structs that mirror URLQuery.net's data model—Report, QueueStatus, SearchResult—without additional validation layers.

Here's the canonical usage pattern for submitting a URL and retrieving results:

package main

import (
    "fmt"
    "time"
    "github.com/urlquery/urlquery-api-go"
)

func analyzeURL(target string) error {
    urlquery.SetDefaultKey("your-api-key")
    
    // Submit URL with access control and alert preferences
    resp, err := urlquery.Submit(target, urlquery.Public, false)
    if err != nil {
        return fmt.Errorf("submission failed: %w", err)
    }
    
    queueID := resp.QueueID
    
    // Manual polling loop - library provides no helper
    for {
        status, err := urlquery.GetQueueStatus(queueID)
        if err != nil {
            return fmt.Errorf("status check failed: %w", err)
        }
        
        if status.Status == "done" {
            report, err := urlquery.GetReport(status.ReportID)
            if err != nil {
                return fmt.Errorf("report retrieval failed: %w", err)
            }
            
            fmt.Printf("Verdict: %s\nMalicious: %t\n", 
                report.Verdict, report.Malicious)
            return nil
        }
        
        time.Sleep(10 * time.Second) // You choose the interval
    }
}

Notice the explicit polling loop. Unlike libraries that abstract this into a synchronous "submit and wait" method, you control the sleep interval, timeout logic, and cancellation semantics. This matters when building threat intelligence pipelines where you're submitting hundreds of URLs concurrently—you might want aggressive 5-second polls for high-priority incident response URLs while background threat hunting jobs can poll every 30 seconds to conserve API quota.

The Report struct exposes URLQuery.net's full analytical surface. Beyond basic verdict flags, you get structured access to browser DOM events, HTTP transaction logs, screenshot URLs, and behavioral alerts. This enables sophisticated filtering logic:

func containsExploitKit(report *urlquery.Report) bool {
    // Check for common exploit kit indicators
    for _, alert := range report.Alerts {
        if alert.Type == "exploit_kit" || 
           alert.Category == "code_injection" {
            return true
        }
    }
    
    // Analyze HTTP transactions for suspicious patterns
    for _, tx := range report.Transactions {
        if tx.StatusCode == 302 && 
           containsSuspiciousRedirect(tx.Location) {
            return true
        }
    }
    
    return false
}

The Search functionality supports historical analysis. You can query past reports by domain, IP, ASN, or custom tags. This transforms the library from a one-off verdict tool into a threat hunting platform:

// Find all reports for a suspicious domain in the last 30 days
results, err := urlquery.Search("domain:evil.example.com", 100)
if err != nil {
    log.Fatal(err)
}

for _, result := range results.Results {
    fmt.Printf("Report %s: Verdict=%s, Date=%s\n",
        result.ReportID, result.Verdict, result.Timestamp)
}

The library's minimal dependency footprint—only Go's standard library—reflects a deliberate security posture. In security tooling, transitive dependencies represent attack surface. By avoiding third-party HTTP clients, loggers, or serialization frameworks, the library reduces supply chain risk. The trade-off is manual error handling and no structured logging integration, but for security teams this is often preferable to inheriting vulnerabilities from abandoned dependencies.

Gotcha

The library's minimalism becomes a liability in production environments without supporting infrastructure. There's no built-in rate limiting, so exceeding API quota returns generic HTTP 429 errors without actionable feedback about remaining credits or reset windows. You'll need to implement your own quota tracking by parsing response headers or maintaining external state.

Error handling is primitive. HTTP failures return wrapped errors with status codes, but the library doesn't parse API-specific error payloads that URLQuery.net might include in response bodies. A submission rejection due to invalid URL format versus insufficient account credits produces identical error objects. Robust implementations require custom response body inspection:

if err != nil {
    if httpErr, ok := err.(*urlquery.HTTPError); ok {
        // Library doesn't expose this - you'll parse raw errors
        if httpErr.StatusCode == 402 {
            // Assume payment required, but can't distinguish
            // "quota exceeded" from "subscription expired"
        }
    }
}

The absence of context.Context support is conspicuous for modern Go. There's no way to propagate cancellation signals, enforce deadlines, or integrate with distributed tracing systems. If your service uses context for timeout management, you'll wrap library calls in goroutines with select statements—adding boilerplate that context support would eliminate. No caching layer exists for report retrieval, so repeatedly fetching the same ReportID hits the API unnecessarily, wasting quota and adding latency to batch analysis jobs.

Verdict

Use if: You're building custom security automation where URLQuery.net's specific browser instrumentation adds coverage beyond VirusTotal or URLScan.io, you already have retry/timeout infrastructure and want direct API control without framework assumptions, or you're integrating malware verdicts into SOAR platforms where workflow orchestration happens at a higher layer. The minimal abstraction is valuable when sandboxing latency varies wildly and you need precise control over polling intervals and resource allocation across concurrent analysis jobs.

Skip if: You need batteries-included sandbox orchestration with managed polling and automatic retries, you're evaluating multiple sandbox APIs and want vendor abstraction (this library locks you to URLQuery.net), your service requires context.Context for cancellation and tracing, or existing tools like VirusTotal already provide sufficient malware coverage. Also skip if you're unfamiliar with URLQuery.net's API behavior—the library exposes complexity rather than hiding it, so understanding upstream rate limits and execution patterns is prerequisite knowledge.