{"record":{"id":"6eed981b67ec42ce","repo":"projectdiscovery/nuclei","slug":"smb-session-not-connected","errorCode":null,"errorMessage":"smb session not connected","messagePattern":"smb session not connected","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/js/libs/smbsession/session.go","lineNumber":152,"sourceCode":"\nfunc (s *Session) ops() shareBackend {\n\tif s == nil {\n\t\treturn nil\n\t}\n\tif s.backend != nil {\n\t\treturn s.backend\n\t}\n\tif s.client == nil {\n\t\treturn nil\n\t}\n\treturn s.client\n}\n\n// ListShares enumerates share names.\nfunc (s *Session) ListShares() ([]string, error) {\n\tops := s.ops()\n\tif ops == nil {\n\t\treturn nil, fmt.Errorf(\"smb session not connected\")\n\t}\n\treturn ops.ListShares()\n}\n\n// ListDir lists one directory on share (share-relative path).\nfunc (s *Session) ListDir(share, dir string) ([]Entry, error) {\n\tops := s.ops()\n\tif ops == nil {\n\t\treturn nil, fmt.Errorf(\"smb session not connected\")\n\t}\n\treturn listDir(ops, share, dir)\n}\n\n// ReadFile reads a file from share, capped at maxBytes (default DefaultMaxReadBytes).\nfunc (s *Session) ReadFile(share, filePath string, maxBytes int64) (string, error) {\n\tops := s.ops()\n\tif ops == nil {\n\t\treturn \"\", fmt.Errorf(\"smb session not connected\")","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/js/libs/smbsession/session.go#L134-L170","documentation":"Session.ListShares calls s.ops(), which returns nil when the Session holds no client: Dial was never completed, failed, Close() already ran, or the Session was built zero-valued / via FromClient(nil). The guard converts that broken state into a clear error instead of a nil-pointer panic inside goimpacket.","triggerScenarios":"Ignoring Dial's error and calling methods on the nil session; calling ListShares after s.Close(); constructing smbsession.Session{} directly; smbsession.FromClient(nil) wrapping a nil goimpacket client.","commonSituations":"Fire-and-forget template code that skips error checks; reusing a session object across scan iterations after teardown; interop code taking a client from dcerpc that may be nil.","solutions":["Always check the error returned by smbsession.Dial before using the session","Do not call methods after Close() — create a new session with Dial","Guard with a connected-check: s != nil && s.Native() != nil","When wrapping an external client with FromClient, verify the client is non-nil first"],"exampleFix":"// before\ns, _ := smbsession.Dial(ctx, execID, host, 445, creds)\nshares, _ := s.ListShares() // smb session not connected\n\n// after\ns, err := smbsession.Dial(ctx, execID, host, 445, creds)\nif err != nil {\n    return err\n}\ndefer s.Close()\nshares, err := s.ListShares()","handlingStrategy":"type-guard","validationCode":"if s == nil || s.Native() == nil {\n    return errors.New(\"session not connected; dial first\")\n}\nshares, err := s.ListShares()","typeGuard":"func sessionConnected(s *smbsession.Session) bool {\n    return s != nil && s.Native() != nil\n}","tryCatchPattern":"shares, err := s.ListShares()\nif err != nil {\n    if strings.Contains(err.Error(), \"not connected\") {\n        // stale/nil session: re-dial once, then give up\n        if s, err2 := smbsession.Dial(ctx, execID, host, 445, creds); err2 == nil {\n            shares, err = s.ListShares()\n        }\n    }\n}","preventionTips":["Always check Dial's error and abort on failure","Close a session exactly once and never use it afterwards","Scope the Session variable to the code region that owns its lifetime"],"tags":["state","smb","null-safety","usage"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}