Enforce Go style rules across codebase
Apply five style rules: convert iota string enums to typed string constants, replace errors.As with errors.AsType, merge three-group imports into two groups, fix multiline parameter/argument formatting, and replace fmt.Sprintf URL construction with net/url. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -68,9 +68,11 @@ func CheckCORSTool() agent.Tool {
|
||||
"Send a CORS preflight (OPTIONS) request to a URL with a given Origin and analyze the Access-Control-* response headers, flagging wildcard origins and origin reflection.",
|
||||
func(ctx context.Context, p corsParams) (agent.ToolResult, error) {
|
||||
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
||||
return agent.ResultJSON(corsResult{
|
||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
corsResult{
|
||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
@@ -87,9 +89,11 @@ func CheckCORSTool() agent.Tool {
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(corsResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot build request: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
corsResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot build request: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
req.Header.Set("Origin", p.Origin)
|
||||
@@ -97,9 +101,11 @@ func CheckCORSTool() agent.Tool {
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(corsResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
corsResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
@@ -81,16 +81,20 @@ func AnalyzeCSPTool() agent.Tool {
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.URL, nil)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(cspResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
cspResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", p.URL, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(cspResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
cspResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", p.URL, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
@@ -73,10 +73,12 @@ func CheckDMARCTool() agent.Tool {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(dmarcResult{
|
||||
Found: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
dmarcResult{
|
||||
Found: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot lookup DMARC record: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
for _, answer := range answers {
|
||||
|
||||
@@ -61,10 +61,12 @@ func CheckDNSSECTool() agent.Tool {
|
||||
withDNSSEC(),
|
||||
)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(dnssecResult{
|
||||
Enabled: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
dnssecResult{
|
||||
Enabled: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot query DNSKEY records: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -79,9 +80,11 @@ func CheckSecurityHeadersTool() agent.Tool {
|
||||
"Check security-related HTTP headers for a URL (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-*-Policy). Also checks if HTTP redirects to HTTPS.",
|
||||
func(ctx context.Context, p headersParams) (agent.ToolResult, error) {
|
||||
if err := netcheck.ValidatePublicURL(p.URL); err != nil {
|
||||
return agent.ResultJSON(headersResult{
|
||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
headersResult{
|
||||
ErrorDetail: fmt.Sprintf("URL not allowed: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
@@ -94,11 +97,19 @@ func CheckSecurityHeadersTool() agent.Tool {
|
||||
// First check the HTTP version to detect HTTP→HTTPS redirect.
|
||||
redirectsToHTTPS := false
|
||||
|
||||
httpURL := p.URL
|
||||
if after, ok := strings.CutPrefix(httpURL, "https://"); ok {
|
||||
httpURL = "http://" + after
|
||||
parsedURL, err := url.Parse(p.URL)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(
|
||||
headersResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot parse URL: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
httpParsed := *parsedURL
|
||||
httpParsed.Scheme = "http"
|
||||
httpURL := httpParsed.String()
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpURL, nil)
|
||||
if err == nil {
|
||||
httpResp, err := client.Do(httpReq)
|
||||
@@ -114,25 +125,28 @@ func CheckSecurityHeadersTool() agent.Tool {
|
||||
}
|
||||
|
||||
// Now check the HTTPS version for the actual security headers.
|
||||
httpsURL := p.URL
|
||||
if after, ok := strings.CutPrefix(httpsURL, "http://"); ok {
|
||||
httpsURL = "https://" + after
|
||||
}
|
||||
httpsParsed := *parsedURL
|
||||
httpsParsed.Scheme = "https"
|
||||
httpsURL := httpsParsed.String()
|
||||
|
||||
followClient := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
httpsReq, err := http.NewRequestWithContext(ctx, http.MethodGet, httpsURL, nil)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(headersResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
headersResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request for %s: %s", httpsURL, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
resp, err := followClient.Do(httpsReq)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(headersResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
headersResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch %s: %s", httpsURL, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
@@ -61,34 +61,48 @@ func CheckBreachesTool() agent.Tool {
|
||||
func(ctx context.Context, p hibpParams) (agent.ToolResult, error) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
"https://haveibeenpwned.com/api/v3/breaches?domain="+url.QueryEscape(p.Domain),
|
||||
nil,
|
||||
)
|
||||
hibpURL, err := url.Parse("https://haveibeenpwned.com/api/v3/breaches")
|
||||
if err != nil {
|
||||
return agent.ResultJSON(hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot parse HIBP URL: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
q := hibpURL.Query()
|
||||
q.Set("domain", p.Domain)
|
||||
hibpURL.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, hibpURL.String(), nil)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot create request: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", "Probo-Vendor-Assessment")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot fetch breaches: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot read response: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot read response: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
@@ -96,23 +110,29 @@ func CheckBreachesTool() agent.Tool {
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return agent.ResultJSON(hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("HIBP API returned status %d", resp.StatusCode),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
var breaches []breach
|
||||
if err := json.Unmarshal(body, &breaches); err != nil {
|
||||
return agent.ResultJSON(hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot parse response: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot parse response: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
return agent.ResultJSON(hibpResult{
|
||||
Found: len(breaches) > 0,
|
||||
Count: len(breaches),
|
||||
Breaches: breaches,
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
hibpResult{
|
||||
Found: len(breaches) > 0,
|
||||
Count: len(breaches),
|
||||
Breaches: breaches,
|
||||
},
|
||||
), nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,10 +77,12 @@ func CheckSPFTool() agent.Tool {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(spfResult{
|
||||
Found: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
spfResult{
|
||||
Found: false,
|
||||
ErrorDetail: fmt.Sprintf("cannot lookup SPF record: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
var spfRecords []string
|
||||
@@ -100,21 +102,25 @@ func CheckSPFTool() agent.Tool {
|
||||
}
|
||||
|
||||
if len(spfRecords) > 1 {
|
||||
return agent.ResultJSON(spfResult{
|
||||
Found: true,
|
||||
ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
spfResult{
|
||||
Found: true,
|
||||
ErrorDetail: fmt.Sprintf("multiple SPF records found (%d); this is an invalid configuration per RFC 7208", len(spfRecords)),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
if len(spfRecords) == 1 {
|
||||
record := spfRecords[0]
|
||||
|
||||
return agent.ResultJSON(spfResult{
|
||||
Found: true,
|
||||
RawRecord: record,
|
||||
Policy: parseSPFPolicy(record),
|
||||
Mechanisms: record,
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
spfResult{
|
||||
Found: true,
|
||||
RawRecord: record,
|
||||
Policy: parseSPFPolicy(record),
|
||||
Mechanisms: record,
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
return agent.ResultJSON(spfResult{Found: false}), nil
|
||||
|
||||
@@ -66,10 +66,12 @@ func CheckSSLCertificateTool() agent.Tool {
|
||||
"Check the SSL/TLS certificate for a domain, returning issuer, expiry, protocol version, and validity.",
|
||||
func(ctx context.Context, p sslParams) (agent.ToolResult, error) {
|
||||
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
||||
return agent.ResultJSON(sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
// This is a certificate inspection tool: we intentionally
|
||||
@@ -96,20 +98,24 @@ func CheckSSLCertificateTool() agent.Tool {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return agent.ResultJSON(sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: err.Error(),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: err.Error(),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
state := conn.ConnectionState()
|
||||
if len(state.PeerCertificates) == 0 {
|
||||
return agent.ResultJSON(sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: "no peer certificates",
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
sslResult{
|
||||
Valid: false,
|
||||
ErrorDetail: "no peer certificates",
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
cert := state.PeerCertificates[0]
|
||||
|
||||
@@ -50,17 +50,21 @@ func CheckWhoisTool() agent.Tool {
|
||||
"Perform a WHOIS lookup on a domain to retrieve registration details including registrar, creation date, expiry date, registrant organization, and name servers.",
|
||||
func(ctx context.Context, p whoisParams) (agent.ToolResult, error) {
|
||||
if err := netcheck.ValidatePublicDomain(p.Domain); err != nil {
|
||||
return agent.ResultJSON(whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("domain not allowed: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
// Step 1: query IANA to find the referral WHOIS server.
|
||||
referral, err := queryWhois(ctx, "whois.iana.org:43", p.Domain)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot query IANA WHOIS: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
whoisServer := parseWhoisField(referral, "refer")
|
||||
@@ -87,17 +91,21 @@ func CheckWhoisTool() agent.Tool {
|
||||
}
|
||||
|
||||
if err := netcheck.ValidatePublicDomain(whoisHost); err != nil {
|
||||
return agent.ResultJSON(whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("WHOIS referral server not allowed: %s", err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
// Step 2: query the registrar's WHOIS server.
|
||||
raw, err := queryWhois(ctx, whoisServer, p.Domain)
|
||||
if err != nil {
|
||||
return agent.ResultJSON(whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err),
|
||||
}), nil
|
||||
return agent.ResultJSON(
|
||||
whoisResult{
|
||||
ErrorDetail: fmt.Sprintf("cannot query WHOIS server %s: %s", whoisServer, err),
|
||||
},
|
||||
), nil
|
||||
}
|
||||
|
||||
result := parseWhoisResponse(raw)
|
||||
|
||||
Reference in New Issue
Block a user