projectdiscovery/nuclei · error
goimpacket: refusing to dial %s/%s without an executionId-bo
Error message
goimpacket: refusing to dial %s/%s without an executionId-bound dialer; wrap the call site with a *gptransport.Dialer built via NewExecDialer
What it means
A fail-closed tripwire installed by gptransport's init(): it replaces goimpacket's global dial function so that any SMB/DCOM-style connection attempted without a per-execution binding is refused instead of silently escaping nuclei's network policy. It fires when goimpacket falls back to the global dialer because the call site did not use a *gptr.Dialer built by gptransport.NewExecDialer(execID), or the context carries no 'executionId' key.
Source
Thrown at pkg/js/libs/gptransport/dialer.go:23
// the global tripwire: any goimpacket dial without an execution-bound Dialer
// fails closed instead of leaking across scans.
package gptransport
import (
"context"
"fmt"
"net"
gptr "github.com/Mzack9999/goimpacket/pkg/transport"
"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)
func init() {
gptr.SetDial(func(ctx context.Context, network, address string) (net.Conn, error) {
execID := ExecutionIDFromCtx(ctx)
if execID == "" {
return nil, fmt.Errorf("goimpacket: refusing to dial %s/%s without an executionId-bound dialer; wrap the call site with a *gptransport.Dialer built via NewExecDialer", network, address)
}
return DialWithExec(ctx, execID, network, address)
})
}
// NewExecDialer returns a *gptr.Dialer whose DialFn is bound to the
// given executionId. Every connection made through the returned dialer is
// validated against the execution's network policy and routed through the
// matching fastdialer.
func NewExecDialer(execID string) *gptr.Dialer {
if execID == "" {
return &gptr.Dialer{}
}
return &gptr.Dialer{
DialFn: func(ctx context.Context, network, address string) (net.Conn, error) {
return DialWithExec(ctx, execID, network, address)
},
}View on GitHub (pinned to 265b3a3dec)
Solutions
- Wrap the goimpacket call site: dialer := gptransport.NewExecDialer(execID) and pass that *gptr.Dialer into the goimpacket client/transport so every dial is execution-bound
- Propagate the execution context: ctx = context.WithValue(ctx, "executionId", execID) so ExecutionIDFromCtx can recover the id
- Verify protocolstate dialers are registered for that executionId before dialing (otherwise you hit 'no fastdialer registered' next)
- In tests, construct clients with an explicit exec-bound Dialer instead of relying on the global dial function
Example fix
// before: uses goimpacket's global dial -> refused client := smbclient.New(target) // after: every dial is bound to the execution and its network policy execID := gptransport.ExecutionIDFromCtx(ctx) client := smbclient.New(target, gptransport.NewExecDialer(execID))
Defensive patterns
Strategy: validation
Validate before calling
execID := gptransport.ExecutionIDFromCtx(ctx)
if execID == "" {
return errors.New("ctx carries no executionId; bind the call site with gptransport.NewExecDialer")
}
dialer := gptransport.NewExecDialer(execID) // hand this dialer to the goimpacket client Type guard
func isExecutionBound(ctx context.Context) bool {
return gptransport.ExecutionIDFromCtx(ctx) != ""
} Try / catch
conn, err := dial(ctx, network, addr)
if err != nil {
if strings.Contains(err.Error(), "executionId-bound dialer") {
// call site not wrapped: rebuild the client with gptransport.NewExecDialer(execID); do not bypass
}
return err
} Prevention
- Never let goimpacket use its global dial function inside nuclei; construct every client with gptransport.NewExecDialer(execID)
- Propagate the scan's execution context into every goroutine that dials
- Treat this error as a security tripwire: fix the call site, never route around it
When it happens
Trigger: Calling goimpacket transport dial APIs directly (not via a Dialer from NewExecDialer) in a binary that imports pkg/js/libs/gptransport (e.g. through smbsession/dcerpc libs); dialing with context.Background() or any ctx where ExecutionIDFromCtx returns ''.
Common situations: Embedding nuclei via lib/ with goimpacket clients created before per-execution dialers existed; new JS protocol libs (smb/dcerpc/winrm) that forget to wrap their connection with NewExecDialer; unit tests that dial with a bare context; goroutines that drop the scan context.
Related errors
- goimpacket: no fastdialer registered for executionId %q
- authentication failed
- prompt not found (read cap reached)
- smb connect: %w
- open pipe %q: %w
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/a955492360d7af41.
Report an issue: GitHub.