Skip to content

Conversation

@ehsandeep
Copy link
Member

@ehsandeep ehsandeep commented Aug 17, 2025

Proposed changes

Related utils update - projectdiscovery/utils#666

Checklist

  • Pull request is created against the dev branch
  • All checks passed (lint, unit/integration/regression tests etc.) with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Summary by CodeRabbit

  • Chores

    • Added lint targets and a test-with-lint workflow to the build system.
  • Refactor

    • Standardized error handling across the project for more consistent, clearer diagnostics.
    • Converted some static errors into parameterized constructors, improving contextual messages.
    • Harmonized error message prefixes (e.g., http:, ssl:, headless:, signer:) for easier troubleshooting.
  • Tests

    • Enhanced DSL tests to run against explicit DNS resolvers for more reliable results.

- Replace deprecated errorutil with modern errkit
- Convert error declarations from var to func for better compatibility
- Fix all SA1019 deprecation warnings
- Maintain error chain support and stack traces
- Configure test to use Google DNS (8.8.8.8) for stability
- Fix nil pointer issue in DNS client initialization
- Keep production defaults unchanged
@auto-assign auto-assign bot requested a review from dwisiswant0 August 17, 2025 01:01
@ehsandeep ehsandeep changed the title Lint + test fixes after utilx dep update Lint + test fixes after utils dep update Aug 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 17, 2025

Walkthrough

Migrates repository-wide error handling from github.com/projectdiscovery/utils/errors → github.com/projectdiscovery/utils/errkit, converting many exported error variables into constructor functions and updating call sites; additionally adds lint, lint-strict, and test-with-lint targets to the Makefile.

Changes

Cohort / File(s) Summary
Build & Lint
Makefile
Adds lint, lint-strict, and test-with-lint PHONY targets; ensures/install golangci-lint, runs lint before tests; updates .PHONY list.
Integration tests
cmd/integration-test/*
cmd/integration-test/http.go, cmd/integration-test/loader.go, cmd/integration-test/profile-loader.go, cmd/integration-test/template-dir.go
Swap imports from utils/errors → utils/errkit; replace errorutil wrappers with errkit.New/Append/Build patterns; preserve control flow.
CLI mains
cmd/nuclei/main.go, cmd/tmc/main.go
Migrate error construction to errkit.New/Append/Build; adjust message formatting (fmt) where required.
Internal components
internal/pdcp/writer.go, internal/runner/*
internal/runner/lazy.go, internal/runner/proxy.go
errkit migration for error wraps; GetAuthTmplStore execOpts param expanded to *protocols.ExecutorOptions.
Core libs & SDK
lib/*
lib/config.go, lib/multi.go, lib/sdk.go
Replace error variables with errkit usage; introduce ErrOptionsNotSupported(option string) error; wrap loader errors with errkit.Append.
Auth providers
pkg/authprovider/*
pkg/authprovider/authx/*.go, pkg/authprovider/file.go
errkit migration: unmarshal/validation errors use errkit.New/Build/Append; added fmt imports where needed.
Catalog & loaders
pkg/catalog/*
pkg/catalog/config/nucleiconfig.go, pkg/catalog/loader/*.go, pkg/catalog/loader/ai_loader.go
errkit migration across config I/O and loaders, formatted messages via fmt; no API signature changes.
External template providers
pkg/external/customtemplates/*
errkit migration; provider client instantiation failures now return errkit.Append with formatted context.
Fuzzing API change
pkg/fuzz/execute.go
Replaces exported error var ErrRuleNotApplicable with constructor function ErrRuleNotApplicable(reason interface{}) error; updates call sites.
Input & OpenAPI
pkg/input/*
formats/openapi/generator.go, provider/interface.go
errkit migration; ErrNotImplemented changed from var → ErrNotImplemented(provider, method string) error function.
JS modules & host-policy
pkg/js/*
errkit migration; various exported error vars reworked; host-denied errors converted to function-style protocolstate.ErrHostDenied(host) and call sites adjusted.
Operators DSL (tests)
pkg/operators/common/dsl/*
Use a named options object for dnsclientpool init/get; tests updated to inject resolvers (googleDNS) and call a new test helper.
Protocols (broad)
pkg/protocols/*
Large errkit migration: many error vars → constructor functions (e.g., ErrEvalExpression, ErrUnresolvedVars), standardized/prefixed messages, Wrap→Append conversions.
Interactsh & protocolstate
pkg/protocols/common/*
errkit migration; use errors.Is for cache misses; ErrURLDenied/ErrHostDenied changed from vars → functions.
Reporting
pkg/reporting/*
errkit migration for client/exporter init errors and config creation; now chains underlying errors with errkit.Append.
Installer & templates
pkg/installer/*, pkg/templates/*
Extensive errkit migration; many exported error vars replaced by functions (e.g., ErrCouldNotLoadTemplate, ErrMandatoryFieldMissingFmt, ErrInvalidField, ErrLoadedWithWarnings); tests updated to expect new messages.
Template executor flow
pkg/tmplexec/flow/*
ErrInvalidRequestID changed from var → ErrInvalidRequestID(templateID, requestID) error; runtime/protocol errors wrapped via errkit.Append.
Types & helpers
pkg/types/types.go, assorted files
errkit migration for file/path errors; added fmt imports; error messages formatted and chained via errkit.Append/Build.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant Make as Makefile
  participant Lint as golangci-lint
  participant Test as go test

  Dev->>Make: make test-with-lint
  Make->>Lint: ensure/install golangci-lint
  Make->>Lint: golangci-lint run
  Lint-->>Make: lint status
  alt lint success
    Make->>Test: make test
    Test-->>Make: test results
  else lint failed
    Make-->>Dev: exit with lint errors
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • dwisiswant0
  • tarunKoyalwar

Poem

A rabbit hops with lint in paw,
Errkit stitched where old errors saw.
Vars became functions, messages gleam,
Tests and makefiles join the dream.
I twitch my nose and hop — clean code team! 🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b999546 and 7f1f46b.

📒 Files selected for processing (1)
  • Makefile (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Makefile
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tests (ubuntu-latest)
  • GitHub Check: Tests (macOS-latest)
  • GitHub Check: Tests (windows-latest)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch maint

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🔭 Outside diff range comments (11)
pkg/input/formats/openapi/generator.go (1)

400-423: Bug: incorrect AND/OR handling for security requirements and leaking state across iterations

Two issues here:

  • found is not reset per requirement, so a match in an earlier requirement incorrectly marks later ones as found.
  • For requirements with multiple schemes (AND semantics per OAS3), the loop continues after the first match and never enforces that all schemes are present.

This can lead to silently accepting incomplete/invalid security requirements and generating incorrect params. Fix by evaluating each requirement atomically (collecting all required schemes), marking success only when all schemes in that requirement are present, and breaking on the first satisfied requirement (OR semantics across requirements).

Apply this diff to correct the logic:

-	found := false
-	// this api is protected for each security scheme pull its corresponding scheme
-schemaLabel:
-	for _, security := range *requirement {
-		for name := range security {
-			if scheme, ok := schema.Components.SecuritySchemes[name]; ok {
-				found = true
-				param, err := GenerateParameterFromSecurityScheme(scheme)
-				if err != nil {
-					return nil, err
-				}
-				globalParams = append(globalParams, &openapi3.ParameterRef{Value: param})
-				continue schemaLabel
-			}
-		}
-		if (!found) && len(security) > 1 {
-			// if this is case then both security schemes are required
-			return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", security)).Build()
-		}
-	}
-	if !found {
-		return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", requirement)).Build()
-	}
+	anySatisfied := false
+	// Evaluate each requirement (OR across requirements). Within a requirement,
+	// all listed schemes are required (AND).
+	for _, security := range *requirement {
+		allPresent := true
+		localParams := openapi3.NewParameters()
+		for name := range security {
+			scheme, ok := schema.Components.SecuritySchemes[name]
+			if !ok {
+				allPresent = false
+				continue
+			}
+			param, err := GenerateParameterFromSecurityScheme(scheme)
+			if err != nil {
+				return nil, err
+			}
+			localParams = append(localParams, &openapi3.ParameterRef{Value: param})
+		}
+		if allPresent {
+			// Prefer the first fully-satisfied requirement and ignore alternatives.
+			globalParams = append(globalParams, localParams...)
+			anySatisfied = true
+			break
+		}
+		// If a multi-scheme requirement is partially present, it is invalid.
+		if len(security) > 1 && !allPresent {
+			return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", security)).Build()
+		}
+	}
+	if !anySatisfied {
+		return nil, errkit.New(fmt.Sprintf("openapi: security requirement (%+v) not found in openapi file", requirement)).Build()
+	}
pkg/protocols/network/network.go (1)

174-181: Fix TLS flag leakage across multiple addresses (once true, always true bug)

shouldUseTLS is shared across iterations. If any address has tls://, all subsequent addresses inherit TLS=true even if they don’t have the prefix. Compute TLS per-address to avoid incorrect behavior.

Apply this diff:

-	var shouldUseTLS bool
 	request.options = options
-	for _, address := range request.Address {
-		// check if the connection should be encrypted
-		if strings.HasPrefix(address, "tls://") {
-			shouldUseTLS = true
-			address = strings.TrimPrefix(address, "tls://")
-		}
-		request.addresses = append(request.addresses, addressKV{address: address, tls: shouldUseTLS})
-	}
+	for _, addr := range request.Address {
+		// compute TLS per address to avoid leakage across entries
+		tls := strings.HasPrefix(addr, "tls://")
+		if tls {
+			addr = strings.TrimPrefix(addr, "tls://")
+		}
+		request.addresses = append(request.addresses, addressKV{address: addr, tls: tls})
+	}
pkg/installer/util.go (1)

49-56: Close response body and enrich error context (prevents resource leak)

resp.Body is never closed, which can leak connections/descriptors. Also, including status details helps debugging.

Apply:

 func getNewAdditionsFileFromGitHub(version string) ([]string, error) {
   resp, err := retryableHttpClient.Get(fmt.Sprintf("https://raw.githubusercontent.com/projectdiscovery/nuclei-templates/%s/.new-additions", version))
   if err != nil {
     return nil, err
   }
+  defer resp.Body.Close()
   if resp.StatusCode != http.StatusOK {
-    return nil, errkit.New("version not found").Build()
+    return nil, errkit.New(fmt.Sprintf("version not found (status: %s) for version %s", resp.Status, version)).Build()
   }
internal/pdcp/writer.go (1)

187-195: Bug: lines are dropped when buffer flushes at chunk boundary

When buff.Len()+len(line) > MaxChunkSize, you flush but never append the current line to the new buffer, effectively losing that line. Also handle the case where a single line exceeds MaxChunkSize.

Apply this diff to preserve the current line after flush and handle oversized lines:

-           if buff.Len()+len(line) > MaxChunkSize {
-               // flush existing buffer
-               if err := u.uploadChunk(buff); err != nil {
-                   u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err)
-               }
-           } else {
-               buff.WriteString(line)
-           }
+           if buff.Len()+len(line) > MaxChunkSize {
+               // flush existing buffer
+               if err := u.uploadChunk(buff); err != nil {
+                   u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err)
+               }
+               // write the current line after flush; if line itself is huge, upload directly
+               if len(line) > MaxChunkSize {
+                   tmp := bytes.NewBufferString(line)
+                   if err := u.uploadChunk(tmp); err != nil {
+                       u.Logger.Error().Msgf("Failed to upload scan results on cloud: %v", err)
+                   }
+               } else {
+                   buff.WriteString(line)
+               }
+           } else {
+               buff.WriteString(line)
+           }
pkg/protocols/code/code.go (1)

426-433: Bug: incorrect handling of regex submatches for python env vars

FindAllStringSubmatch returns an array where match[0] is the full match and match[1] is the first capture group. The code uses the whole match in both the pattern and the map lookup, and formats a slice with %s. This leads to incorrect replacements and potential panics.

Apply this diff:

-	for _, match := range matches {
-		if len(match) == 0 {
-			continue
-		}
-		source = strings.ReplaceAll(source, fmt.Sprintf("os.getenv('%s')", match), fmt.Sprintf("'%s'", vars[match[0]]))
-	}
+	for _, match := range matches {
+		// match[0] is full match, match[1] is the captured env var name
+		if len(match) < 2 {
+			continue
+		}
+		name := match[1]
+		source = strings.ReplaceAll(
+			source,
+			fmt.Sprintf("os.getenv('%s')", name),
+			fmt.Sprintf("'%v'", vars[name]),
+		)
+	}
pkg/js/libs/ssh/ssh.go (1)

160-165: Guard Close against nil connection to avoid panic

Close dereferences c.connection without a nil check. If Close is called before a successful Connect, this will panic.

Apply this diff:

 func (c *SSHClient) Close() (bool, error) {
-  if err := c.connection.Close(); err != nil {
+  if c.connection == nil {
+    return true, nil
+  }
+  if err := c.connection.Close(); err != nil {
     return false, err
   }
   return true, nil
 }
cmd/tmc/main.go (4)

241-247: Close HTTP response body to prevent leaks (enhanceTemplate)

resp.Body is never closed. Add a defer immediately after a successful Post to avoid descriptor leaks.

Apply this diff:

 func enhanceTemplate(data string) (string, bool, error) {
   resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/enhance", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
   if err != nil {
     return data, false, err
   }
+  defer resp.Body.Close()
   if resp.StatusCode != 200 {
     return data, false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
   }

275-281: Close HTTP response body to prevent leaks (formatTemplate)

Same leak here; close the body after Post succeeds.

Apply this diff:

 func formatTemplate(data string) (string, bool, error) {
   resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/format", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
   if err != nil {
     return data, false, err
   }
+  defer resp.Body.Close()
   if resp.StatusCode != 200 {
     return data, false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
   }

309-315: Close HTTP response body to prevent leaks (lintTemplate)

Add a defer to close the response body.

Apply this diff:

 func lintTemplate(data string) (bool, error) {
   resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/lint", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
   if err != nil {
     return false, err
   }
+  defer resp.Body.Close()
   if resp.StatusCode != 200 {
     return false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
   }

331-337: Close HTTP response body to prevent leaks (validateTemplate)

Add a defer to close resp.Body here as well.

Apply this diff:

 func validateTemplate(data string) (bool, error) {
   resp, err := retryablehttp.DefaultClient().Post(fmt.Sprintf("%s/validate", tmBaseUrl), "application/x-yaml", strings.NewReader(data))
   if err != nil {
     return false, err
   }
+  defer resp.Body.Close()
   if resp.StatusCode != 200 {
     return false, errkit.New(fmt.Sprintf("unexpected status code: %v", resp.Status)).Build()
   }
cmd/integration-test/http.go (1)

631-647: Bug: returning the wrong error variable (err vs errx) hides validation failures

If parameter validation fails, the function returns the unrelated ‘err’ (which is nil on success), causing silent test pass. Return errx instead.

     if errx != nil {
-        return err
+        return errx
     }

Comment on lines 181 to 186
executionId := ctx.Value("executionId").(string)
if !protocolstate.IsHostAllowed(executionId, host) {
// host is not valid according to network policy
return false, protocolstate.ErrHostDenied.Msgf(host)
return false, protocolstate.ErrHostDenied(host)
}
// create a new client
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Return nil (not false/"") for interface{} result on errors

RunLuaScript returns (interface{}, error). On error paths you currently return false or "", which produces non-nil interface values and can confuse callers. Return nil for the value when an error is returned.

Apply this diff:

 func RunLuaScript(ctx context.Context, host string, port int, password string, script string) (interface{}, error) {
   executionId := ctx.Value("executionId").(string)
   if !protocolstate.IsHostAllowed(executionId, host) {
     // host is not valid according to network policy
-    return false, protocolstate.ErrHostDenied(host)
+    return nil, protocolstate.ErrHostDenied(host)
   }
   // create a new client
   client := redis.NewClient(&redis.Options{
@@
   // Ping the Redis server
-  _, err := client.Ping(context.TODO()).Result()
+  _, err := client.Ping(ctx).Result()
   if err != nil {
-    return "", err
+    return nil, err
   }
@@
-  infoCmd := client.Eval(context.Background(), script, []string{})
+  infoCmd := client.Eval(ctx, script, []string{})
 
   if infoCmd.Err() != nil {
-    return "", infoCmd.Err()
+    return nil, infoCmd.Err()
   }
 
   return infoCmd.Val(), nil
 }

Also applies to: 196-207

🤖 Prompt for AI Agents
In pkg/js/libs/redis/redis.go around lines 181-186 (and also apply the same
change at lines 196-207), the function RunLuaScript currently returns concrete
zero values like false or "" alongside an error which produces non-nil
interface{} results; change those error return paths to return nil for the
interface{} result and the corresponding error instead (i.e., replace returns
like 'return false, err' or 'return "", err' with 'return nil, err') so callers
receive a nil value when an error occurs.

Comment on lines +64 to 70
options := &types.Options{}
err := dnsclientpool.Init(options)
if err != nil {
return nil, err
}
dnsClient, err := dnsclientpool.Get(nil, &dnsclientpool.Configuration{})
dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid re-initializing the DNS pool and mismatching options; use a shared, injectable options instance

Creating a fresh types.Options{} and calling dnsclientpool.Init inside the DSL resolve handler can conflict with upstream/test initialization and cause the pool used by Get to diverge from the one the test initialized (different options pointer/values). This may ignore test-provided resolvers or create multiple pools/configs depending on the underlying implementation.

Recommend using a shared options instance in this package that callers/tests can set, then always pass that same instance to both Init and Get. This keeps behavior deterministic across tests and production.

Proposed change within this range:

-        options := &types.Options{}
-        err := dnsclientpool.Init(options)
+        // Initialize DNS client with shared options to support resolver injection from tests.
+        err := dnsclientpool.Init(dnsOptions)
         if err != nil {
             return nil, err
         }
-        dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})
+        dnsClient, err := dnsclientpool.Get(dnsOptions, &dnsclientpool.Configuration{})
         if err != nil {
             return nil, err
         }

And add these supporting pieces outside this range in the same file:

// At package scope (near other vars)
var dnsOptions = &types.Options{}

// Exported setter to allow tests or callers to inject custom resolvers/options.
func SetDNSResolvers(resolvers []string) {
    dnsOptions.InternalResolversList = resolvers
}

Optionally, if you want to avoid repeated Init calls on every resolve invocation, you can guard with a sync.Once at package scope; but this is not strictly necessary if dnsclientpool.Init is idempotent.

To verify whether this is a correctness risk in your codebase (idempotency and pool selection logic), run:

Expected:

  • If Init is guarded with sync.Once or otherwise idempotent, subsequent calls should be no-ops.
  • Confirm whether Get resolves the pool by pointer identity or by options’ content; using a shared instance avoids both pitfalls.

---

🏁 Script executed:

```shell
#!/bin/bash
set -euo pipefail

# Inspect Init/Get implementations and any once/idempotency guards
rg -nP -C3 'package\s+dnsclientpool\b|func\s+Init\s*\(|func\s+Get\s*\(|sync\.Once|once\.Do|initialized' --type=go

Length of output: 43187


🏁 Script executed:

#!/bin/bash
# Display the DNS client pool implementation to inspect Init and Get logic
sed -n '1,120p' pkg/protocols/dns/dnsclientpool/clientpool.go

Length of output: 2931


🏁 Script executed:

#!/bin/bash
# Show DSL resolve handler context including DNS initialization
sed -n '1,200p' pkg/operators/common/dsl/dsl.go

Length of output: 3633


🏁 Script executed:

#!/bin/bash
# Search for types.Options struct definition
rg -nP 'type Options\s+struct' -C5 pkg/types

Length of output: 793


Use a shared, injectable types.Options instance for DNS to ensure deterministic resolver configuration across tests and production

The current DSL resolve function always creates a new &types.Options{} on each call, causing the first invocation to initialize the DNS pool with the default resolvers and ignoring any test-injected settings on subsequent calls (because dnsclientpool.Init is guarded by a global normalClient and is a no-op after the first init). To fix this, introduce a package-scoped dnsOptions and an exported setter, then pass that shared instance to both Init and Get.

Locations to update:

  • pkg/operators/common/dsl/dsl.go (around lines 64–70)
  • pkg/operators/common/dsl/dsl.go (add package scope and setter near top of file)

Proposed changes:

--- a/pkg/operators/common/dsl/dsl.go
+++ b/pkg/operators/common/dsl/dsl.go
@@
// package-scope shared options for dnsclientpool
+var dnsOptions = &types.Options{}

// SetDNSResolvers allows tests or callers to inject custom resolvers before the first Init.
+func SetDNSResolvers(resolvers []string) {
+    dnsOptions.InternalResolversList = resolvers
+}

 func init() {
   _ = dsl.AddFunction(dsl.NewWithMultipleSignatures("resolve", []string{
@@
-       options := &types.Options{}
-       err := dnsclientpool.Init(options)
+       // Use shared dnsOptions to support resolver injection
+       err := dnsclientpool.Init(dnsOptions)
        if err != nil {
            return nil, err
        }
-       dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})
+       dnsClient, err := dnsclientpool.Get(dnsOptions, &dnsclientpool.Configuration{})
        if err != nil {
            return nil, err
        }

Note: dnsclientpool.Init is already idempotent (it returns early if normalClient is non-nil), so no additional sync.Once guard is required unless you prefer to avoid repeated method calls.

📝 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
options := &types.Options{}
err := dnsclientpool.Init(options)
if err != nil {
return nil, err
}
dnsClient, err := dnsclientpool.Get(nil, &dnsclientpool.Configuration{})
dnsClient, err := dnsclientpool.Get(options, &dnsclientpool.Configuration{})
if err != nil {
// Use shared dnsOptions to support resolver injection
err := dnsclientpool.Init(dnsOptions)
if err != nil {
return nil, err
}
dnsClient, err := dnsclientpool.Get(dnsOptions, &dnsclientpool.Configuration{})
if err != nil {
🤖 Prompt for AI Agents
In pkg/operators/common/dsl/dsl.go around lines 64–70, the code currently
creates a new &types.Options{} per call which prevents test-injected DNS
settings from taking effect; add a package-scoped variable (e.g. var dnsOptions
*types.Options) and an exported setter function (e.g. SetDNSOptions(o
*types.Options)) near the top of the file, then change the resolve function to
use that shared instance: use options := dnsOptions; if options == nil { options
= &types.Options{} } and pass that same options to dnsclientpool.Init and
dnsclientpool.Get so Init/Get use the injected, deterministic resolver
configuration across tests and production.

Comment on lines +21 to +29
// ErrURLDenied returns an error when a URL is denied by network policy
func ErrURLDenied(url, rule string) error {
return errkit.New(fmt.Sprintf("headless: url %v dropped by rule: %v", url, rule)).Build()
}

// ErrHostDenied returns an error when a host is denied by network policy
func ErrHostDenied(host string) error {
return errkit.New(fmt.Sprintf("host %v dropped by network policy", host)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Solid move to function-based error constructors; consider quoting for clarity and verify external usages

Switching from sentinel variables with Msgf to functions returning errors built via errkit is clean. As a tiny readability improvement, quote dynamic values.

Apply:

-func ErrURLDenied(url, rule string) error {
-    return errkit.New(fmt.Sprintf("headless: url %v dropped by rule: %v", url, rule)).Build()
-}
+func ErrURLDenied(url, rule string) error {
+    return errkit.New(fmt.Sprintf("headless: url %q dropped by rule: %q", url, rule)).Build()
+}

-func ErrHostDenied(host string) error {
-    return errkit.New(fmt.Sprintf("host %v dropped by network policy", host)).Build()
-}
+func ErrHostDenied(host string) error {
+    return errkit.New(fmt.Sprintf("host %q dropped by network policy", host)).Build()
+}

Also, since the exported API changed from variables to functions, please ensure no remaining usages rely on the old .Msgf helpers or on errors.Is/As with old sentinels. Run:


🏁 Script executed:

#!/bin/bash
# Find legacy formatting usage
rg -nP 'Err(URL|Host)Denied\.Msgf\(' -C2

# Find potential identity checks against old sentinels
rg -nP 'errors\.(Is|As)\(.*Err(URL|Host)Denied' -C3

Length of output: 370


Quote dynamic values and update docs example

To improve clarity and align examples with the new function-based constructors:

• In pkg/protocols/common/protocolstate/headless.go, switch from %v to %q:

 func ErrURLDenied(url, rule string) error {
-   return errkit.New(fmt.Sprintf("headless: url %v dropped by rule: %v", url, rule)).Build()
+   return errkit.New(fmt.Sprintf("headless: url %q dropped by rule: %q", url, rule)).Build()
 }

 func ErrHostDenied(host string) error {
-   return errkit.New(fmt.Sprintf("host %v dropped by network policy", host)).Build()
+   return errkit.New(fmt.Sprintf("host %q dropped by network policy", host)).Build()
 }

• In pkg/js/CONTRIBUTE.md, update the example to call the new constructor rather than .Msgf:

- return false, protocolstate.ErrHostDenied.Msgf(host)
+ return false, protocolstate.ErrHostDenied(host)

No remaining errors.Is/errors.As checks against the old sentinels were found. Ensure any other documentation or examples are updated similarly.

📝 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
// ErrURLDenied returns an error when a URL is denied by network policy
func ErrURLDenied(url, rule string) error {
return errkit.New(fmt.Sprintf("headless: url %v dropped by rule: %v", url, rule)).Build()
}
// ErrHostDenied returns an error when a host is denied by network policy
func ErrHostDenied(host string) error {
return errkit.New(fmt.Sprintf("host %v dropped by network policy", host)).Build()
}
// ErrURLDenied returns an error when a URL is denied by network policy
func ErrURLDenied(url, rule string) error {
return errkit.New(fmt.Sprintf("headless: url %q dropped by rule: %q", url, rule)).Build()
}
// ErrHostDenied returns an error when a host is denied by network policy
func ErrHostDenied(host string) error {
return errkit.New(fmt.Sprintf("host %q dropped by network policy", host)).Build()
}
🤖 Prompt for AI Agents
In pkg/protocols/common/protocolstate/headless.go around lines 21 to 29, the
formatted error messages use %v for dynamic values; change the formatting to use
%q so the URL and host are quoted in the error strings (e.g., "headless: url %q
dropped by rule: %q" and "host %q dropped by network policy"). Also update the
docs example in pkg/js/CONTRIBUTE.md to call the new function-based error
constructors (ErrURLDenied/ErrHostDenied) instead of using the old .Msgf pattern
so examples reflect the current API.

Comment on lines +228 to 229
return nil, errkit.New(fmt.Sprintf("http: failed to parse url %v while creating http request", reqData)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve cause: use Append instead of embedding err string

Use Append to carry the underlying parse error.

-		return nil, errkit.New(fmt.Sprintf("http: failed to parse url %v while creating http request", reqData)).Build()
+		return nil, errkit.Append(errkit.New(fmt.Sprintf("http: failed to parse url %v while creating http request", reqData)), err)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In pkg/protocols/http/build_request.go around lines 228-229, the error
construction currently embeds the parse error string into the message; replace
that with errkit.Append to preserve the original error as the cause. Change the
New(fmt.Sprintf(... with err string)) pattern to create the contextual message
with errkit.New("http: failed to parse url while creating http request") and
then call .Append(err) before .Build(), keeping the same descriptive text and
returning the built error.

Comment on lines 309 to 311
if err != nil {
return nil, errorutil.NewWithErr(err).Msgf("failed to parse %v in self contained request", data).WithTag("self-contained")
return nil, errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request: %s", data, err)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve cause: Append underlying parse error for self-contained URL

Don’t stringify the cause; keep it chained.

-		return nil, errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request: %s", data, err)).Build()
+		return nil, errkit.Append(errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request", data)), err)
📝 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
if err != nil {
return nil, errorutil.NewWithErr(err).Msgf("failed to parse %v in self contained request", data).WithTag("self-contained")
return nil, errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request: %s", data, err)).Build()
}
if err != nil {
return nil, errkit.Append(
errkit.New(fmt.Sprintf("self-contained: failed to parse %v in self contained request", data)),
err,
)
}
🤖 Prompt for AI Agents
In pkg/protocols/http/build_request.go around lines 309-311, the code
stringifies the underlying parse error into the message; instead, create the
errkit error and attach the original err as its cause (e.g.,
errkit.New("self-contained: failed to parse %v in self contained request",
data).WithCause(err).Build()) so the original error is preserved in the error
chain rather than being formatted into the message.

Comment on lines 223 to 226
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
if err != nil {
return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err)
return errkit.New(fmt.Sprintf("%s: could not execute pre-condition: %s", request.TemplateID, err)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrong error message: init code execution labeled as pre-condition

This block executes Init, not PreCondition. The error text is misleading.

Apply this diff to correct the message and preserve the cause via Append:

-        result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
-        if err != nil {
-            return errkit.New(fmt.Sprintf("%s: could not execute pre-condition: %s", request.TemplateID, err)).Build()
-        }
+        result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
+        if err != nil {
+            return errkit.Append(errkit.New(fmt.Sprintf("%s: could not execute init code", request.TemplateID)), err)
+        }
📝 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
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
if err != nil {
return errorutil.NewWithTag(request.TemplateID, "could not execute pre-condition: %s", err)
return errkit.New(fmt.Sprintf("%s: could not execute pre-condition: %s", request.TemplateID, err)).Build()
}
result, err := request.options.JsCompiler.ExecuteWithOptions(initCompiled, args, opts)
if err != nil {
return errkit.Append(
errkit.New(fmt.Sprintf("%s: could not execute init code", request.TemplateID)),
err,
)
}
🤖 Prompt for AI Agents
In pkg/protocols/javascript/js.go around lines 223-226, the error message
incorrectly refers to "pre-condition" even though this block executes Init;
change the message to indicate Init (e.g., "could not execute init") and
preserve the original error as the cause by appending it to the errkit before
building the final error (use errkit's Append with the original err and then
Build), so the returned errkit contains the corrected message and the original
error as the cause.

Comment on lines +203 to 204
return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", request.TemplateID, portInt)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Use options.TemplateID and avoid .Build() for consistency (confirm errkit API)

  • request.TemplateID is likely empty at this point; options.TemplateID is set. Prefer options.TemplateID for better diagnostics.
  • The mixed usage of errkit.New(...).Build() here vs plain errkit.New(...) elsewhere is inconsistent. If New already returns an error, .Build() is likely unnecessary.

Run this script to confirm idiomatic errkit usage in the repo and whether .Build() is expected after New:

If .Build() is not a common pattern after New, apply:

- return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", request.TemplateID, portInt)).Build()
+ return errkit.New(fmt.Sprintf("%s: port %d is not in valid range", options.TemplateID, portInt))

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Occurrences of errkit.New(...).Build(...):"
rg -nP -C2 'errkit\.New\([^)]*\)\.Build\('

echo
echo "General occurrences of errkit.New(...):"
rg -nP -C2 'errkit\.New\('

echo
echo "Append usage patterns:"
rg -nP -C2 'errkit\.Append\('

Length of output: 134970


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Occurrences of request.TemplateID in pkg/protocols/network/network.go:"
rg -nnC2 'request\.TemplateID' pkg/protocols/network/network.go || true

echo
echo "Occurrences of options.TemplateID in pkg/protocols/network/network.go:"
rg -nnC2 'options\.TemplateID' pkg/protocols/network/network.go || true

Length of output: 1321


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Context around port parsing/validation in network.go (lines 190–210):"
sed -n '190,210p' pkg/protocols/network/network.go || true

echo
echo "Occurrences of options.TemplateID in network.go:"
rg -n -C3 'options\.TemplateID' pkg/protocols/network/network.go || true

Length of output: 1381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "All errkit.New usages in network.go around port validation:"
rg -n 'errkit\.New' -C2 pkg/protocols/network/network.go || true

Length of output: 613


Use options.TemplateID in port‐range error

The template ID for diagnostics should come from options.TemplateID (which is always set) rather than request.TemplateID. Also keep .Build() here to match the file’s existing pattern for simple errkit.New(...).Build() errors.

Locations to update:

  • pkg/protocols/network/network.go:203

Diff:

-               return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", request.TemplateID, portInt)).Build()
+               return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", options.TemplateID, portInt)).Build()
📝 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
return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", request.TemplateID, portInt)).Build()
}
return errkit.New(fmt.Sprintf("%s: port %v is not in valid range", options.TemplateID, portInt)).Build()
}
🤖 Prompt for AI Agents
In pkg/protocols/network/network.go around lines 203-204, the error message uses
request.TemplateID but should use options.TemplateID (which is always set);
change the fmt.Sprintf to include options.TemplateID instead of
request.TemplateID and retain the .Build() call so the line remains
errkit.New(fmt.Sprintf("%s: port %v is not in valid range", options.TemplateID,
portInt)).Build().

Comment on lines 170 to 173
tlsxService, err := tlsx.New(tlsxOptions)
if err != nil {
return errorutil.NewWithTag(request.TemplateID, "could not create tlsx service")
return errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)).Build()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Don’t drop the underlying error from tlsx.New

Switch to Append to keep the original cause; Build here hides crucial diagnostics.

-	if err != nil {
-		return errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)).Build()
-	}
+	if err != nil {
+		return errkit.Append(errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)), err)
+	}
📝 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
tlsxService, err := tlsx.New(tlsxOptions)
if err != nil {
return errorutil.NewWithTag(request.TemplateID, "could not create tlsx service")
return errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)).Build()
}
tlsxService, err := tlsx.New(tlsxOptions)
if err != nil {
return errkit.Append(errkit.New(fmt.Sprintf("%s: could not create tlsx service", request.TemplateID)), err)
}
🤖 Prompt for AI Agents
In pkg/protocols/ssl/ssl.go around lines 170 to 173, the error returned when
tlsx.New fails is built without preserving the original error; change the err
construction to include the underlying err (for example use
errkit.New(fmt.Sprintf("%s: could not create tlsx service",
request.TemplateID)).Append(err).Build() or the equivalent wrap/append method
provided by errkit) so the original cause is retained in the returned error.

Comment on lines +483 to 484
return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse %s", template.Path)), err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: error message uses template.Path before it’s set (loses file context)

At this point template.Path is empty. The resulting error becomes “failed to parse ” with no path, hurting debuggability. Use options.TemplatePath (copied from srcOptions) instead.

Apply this diff:

-        return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse %s", template.Path)), err)
+        return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse %s", options.TemplatePath)), err)
📝 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
return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse %s", template.Path)), err)
}
return nil, errkit.Append(errkit.New(fmt.Sprintf("failed to parse %s", options.TemplatePath)), err)
}
🤖 Prompt for AI Agents
In pkg/templates/compile.go around lines 483-484, the error message currently
formats using template.Path which is still empty at this point; replace the use
of template.Path with options.TemplatePath (the value copied from srcOptions) so
the error reads the actual template path — update the fmt.Sprintf call to
include options.TemplatePath and ensure options is the variable in scope that
holds the copied srcOptions.TemplatePath.

Comment on lines 111 to 114
stats.Increment(SyntaxWarningStats)
checkOpenFileError(validationWarning)
return ret, ErrCouldNotLoadTemplate.Msgf(templatePath, validationWarning)
return ret, ErrCouldNotLoadTemplate(templatePath, validationWarning.Error())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Use the dedicated warning helper instead of “Could not load …”

Here ret can be true (template matched) and we’re emitting a warning. Using “Could not load” is misleading. Prefer the dedicated ErrLoadedWithWarnings for clarity.

Apply this diff:

-            return ret, ErrCouldNotLoadTemplate(templatePath, validationWarning.Error())
+            return ret, ErrLoadedWithWarnings(templatePath, validationWarning.Error())

Optionally confirm current usage of the warning helper and avoid regressions:


🏁 Script executed:

#!/bin/bash
# Inspect current call sites for the warning helper
rg -n -C2 -g '!**/vendor/**' '\bErrLoadedWithWarnings\s*\('

# Also inspect how LoadTemplate's warning path is handled elsewhere
rg -n -C3 -g '!**/vendor/**' 'LoadTemplate\('

Length of output: 7802


Use the dedicated warning helper for syntax warnings

Here, ret may be true (template loaded) despite a syntax warning, so “Could not load…” is misleading. Swap in the ErrLoadedWithWarnings helper for clarity.

• File: pkg/templates/parser.go
• Lines: 111–114

-           stats.Increment(SyntaxWarningStats)
-           checkOpenFileError(validationWarning)
-           return ret, ErrCouldNotLoadTemplate(templatePath, validationWarning.Error())
+           stats.Increment(SyntaxWarningStats)
+           checkOpenFileError(validationWarning)
+           return ret, ErrLoadedWithWarnings(templatePath, validationWarning.Error())
📝 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
stats.Increment(SyntaxWarningStats)
checkOpenFileError(validationWarning)
return ret, ErrCouldNotLoadTemplate.Msgf(templatePath, validationWarning)
return ret, ErrCouldNotLoadTemplate(templatePath, validationWarning.Error())
}
stats.Increment(SyntaxWarningStats)
checkOpenFileError(validationWarning)
return ret, ErrLoadedWithWarnings(templatePath, validationWarning.Error())
}
🤖 Prompt for AI Agents
In pkg/templates/parser.go around lines 111 to 114, the code logs a syntax
warning but then returns ErrCouldNotLoadTemplate which is misleading when the
template actually loaded; replace the error helper with the dedicated one for
loaded-with-warnings by returning
ErrLoadedWithWarnings(validationWarning.Error()) (keep the stats.Increment and
checkOpenFileError calls and still return ret) so the return correctly reflects
a successful load with warnings.

@ehsandeep
Copy link
Member Author

failing test - #6398

@ehsandeep ehsandeep merged commit b4644af into dev Aug 19, 2025
28 of 36 checks passed
@ehsandeep ehsandeep deleted the maint branch August 19, 2025 23:58
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.

3 participants