{"record":{"id":"a955492360d7af41","repo":"projectdiscovery/nuclei","slug":"goimpacket-refusing-to-dial-s-s-without-an-exec","errorCode":null,"errorMessage":"goimpacket: refusing to dial %s/%s without an executionId-bound dialer; wrap the call site with a *gptransport.Dialer built via NewExecDialer","messagePattern":"goimpacket: refusing to dial (.+?)/(.+?) without an executionId-bound dialer; wrap the call site with a \\*gptransport\\.Dialer built via NewExecDialer","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/js/libs/gptransport/dialer.go","lineNumber":23,"sourceCode":"// the global tripwire: any goimpacket dial without an execution-bound Dialer\n// fails closed instead of leaking across scans.\npackage gptransport\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\n\tgptr \"github.com/Mzack9999/goimpacket/pkg/transport\"\n\n\t\"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate\"\n)\n\nfunc init() {\n\tgptr.SetDial(func(ctx context.Context, network, address string) (net.Conn, error) {\n\t\texecID := ExecutionIDFromCtx(ctx)\n\t\tif execID == \"\" {\n\t\t\treturn 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)\n\t\t}\n\t\treturn DialWithExec(ctx, execID, network, address)\n\t})\n}\n\n// NewExecDialer returns a *gptr.Dialer whose DialFn is bound to the\n// given executionId. Every connection made through the returned dialer is\n// validated against the execution's network policy and routed through the\n// matching fastdialer.\nfunc NewExecDialer(execID string) *gptr.Dialer {\n\tif execID == \"\" {\n\t\treturn &gptr.Dialer{}\n\t}\n\treturn &gptr.Dialer{\n\t\tDialFn: func(ctx context.Context, network, address string) (net.Conn, error) {\n\t\t\treturn DialWithExec(ctx, execID, network, address)\n\t\t},\n\t}","sourceCodeStart":5,"sourceCodeEnd":41,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/js/libs/gptransport/dialer.go#L5-L41","documentation":"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.","triggerScenarios":"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 ''.","commonSituations":"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.","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"],"exampleFix":"// before: uses goimpacket's global dial -> refused\nclient := smbclient.New(target)\n\n// after: every dial is bound to the execution and its network policy\nexecID := gptransport.ExecutionIDFromCtx(ctx)\nclient := smbclient.New(target, gptransport.NewExecDialer(execID))","handlingStrategy":"validation","validationCode":"execID := gptransport.ExecutionIDFromCtx(ctx)\nif execID == \"\" {\n    return errors.New(\"ctx carries no executionId; bind the call site with gptransport.NewExecDialer\")\n}\ndialer := gptransport.NewExecDialer(execID) // hand this dialer to the goimpacket client","typeGuard":"func isExecutionBound(ctx context.Context) bool {\n    return gptransport.ExecutionIDFromCtx(ctx) != \"\"\n}","tryCatchPattern":"conn, err := dial(ctx, network, addr)\nif err != nil {\n    if strings.Contains(err.Error(), \"executionId-bound dialer\") {\n        // call site not wrapped: rebuild the client with gptransport.NewExecDialer(execID); do not bypass\n    }\n    return err\n}","preventionTips":["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"],"tags":["network","go","smb","security","dialer"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}