diff --git a/go.mod b/go.mod
index 2752c9f..b991e19 100644
--- a/go.mod
+++ b/go.mod
@@ -6,8 +6,19 @@ require (
 	github.com/davecgh/go-spew v1.1.1
 	github.com/djherbis/times v1.6.0
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
-	golang.org/x/sync v0.9.0
-	golang.org/x/sys v0.26.0
+	github.com/shirou/gopsutil/v4 v4.25.5
+	golang.org/x/sync v0.15.0
+	golang.org/x/sys v0.33.0
 	honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
-	r00t2.io/goutils v1.7.1
+	r00t2.io/goutils v1.8.1
+)
+
+require (
+	github.com/ebitengine/purego v0.8.4 // indirect
+	github.com/go-ole/go-ole v1.3.0 // indirect
+	github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
+	github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
+	github.com/tklauser/go-sysconf v0.3.15 // indirect
+	github.com/tklauser/numcpus v0.10.0 // indirect
+	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 )
diff --git a/go.sum b/go.sum
index 7e771f3..9a41992 100644
--- a/go.sum
+++ b/go.sum
@@ -3,17 +3,45 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
 github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
+github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
+github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
+github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
+github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
+github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
+github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
+github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
+github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
+github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
 honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
-r00t2.io/goutils v1.7.1 h1:Yzl9rxX1sR9WT0FcjK60qqOgBoFBOGHYKZVtReVLoQc=
-r00t2.io/goutils v1.7.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
+r00t2.io/goutils v1.8.1 h1:TQcUycPKsYn0QI4uCqb56utmvu/vVSxlblBg98iXStg=
+r00t2.io/goutils v1.8.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
 r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=
diff --git a/ispriv/consts_nix.go b/ispriv/consts_nix.go
new file mode 100644
index 0000000..703fa69
--- /dev/null
+++ b/ispriv/consts_nix.go
@@ -0,0 +1,14 @@
+//go:build unix
+
+package ispriv
+
+const (
+	sudoEnvPfx   string = "SUDO_"
+	sudoUidEnv   string = sudoEnvPfx + "UID"
+	sudoGidEnv   string = sudoEnvPfx + "GID"
+	sudoUnameEnv string = sudoEnvPfx + "USER"
+)
+
+const (
+	curLoginUidFile string = "/proc/self/loginuid"
+)
diff --git a/ispriv/doc_nix.go b/ispriv/doc_nix.go
new file mode 100644
index 0000000..020ab56
--- /dev/null
+++ b/ispriv/doc_nix.go
@@ -0,0 +1,7 @@
+//go:build unix
+
+/*
+ispriv provides functions and a method to determine if a process is being run SUID/SGID, under sudo, etc.
+*/
+
+package ispriv
diff --git a/ispriv/doc_windows.go b/ispriv/doc_windows.go
new file mode 100644
index 0000000..2353c74
--- /dev/null
+++ b/ispriv/doc_windows.go
@@ -0,0 +1,7 @@
+//go:build windows
+
+/*
+ispriv provides functions on Windows to determine the currentl privilege status.
+*/
+
+package ispriv
diff --git a/ispriv/funcs_nix.go b/ispriv/funcs_nix.go
new file mode 100644
index 0000000..e470374
--- /dev/null
+++ b/ispriv/funcs_nix.go
@@ -0,0 +1,68 @@
+//go:build unix
+
+package ispriv
+
+import (
+	`os`
+
+	`github.com/shirou/gopsutil/v4/process`
+)
+
+/*
+	GetProcIDs returns a ProcIDs from a given PID. An error will be raised if the process ID doesn't exist.
+	A negative value indicates "self" (see also GetProcIDsSelf).
+
+	Note that if you are not EUID == 0 (root) or you/the sudo target user does not own the process,
+	the returning ProcIDs is HIGHLY LIKELY to be very inaccurate.
+*/
+func GetProcIDs(pid int32) (p *ProcIDs, err error) {
+
+	var proc ProcIDs
+	var ids []uint32
+
+	if pid < 0 {
+		pid = int32(os.Getpid())
+	}
+
+	if proc.proc, err = process.NewProcess(pid); err != nil {
+		return
+	}
+	if ids, err = proc.proc.Gids(); err != nil {
+		return
+	}
+	p.gids = &IdInfo{
+		real:       uint(ids[0]),
+		effective:  uint(ids[1]),
+		savedSet:   uint(ids[2]),
+		filesystem: nil,
+	}
+	if len(ids) == 4 {
+		p.gids.filesystem = new(uint)
+		*p.gids.filesystem = uint(ids[3])
+	}
+	if ids, err = proc.proc.Uids(); err != nil {
+		return
+	}
+	p.uids = &IdInfo{
+		real:       uint(ids[0]),
+		effective:  uint(ids[1]),
+		savedSet:   uint(ids[2]),
+		filesystem: nil,
+	}
+	if len(ids) == 4 {
+		p.uids.filesystem = new(uint)
+		*p.uids.filesystem = uint(ids[3])
+	}
+
+	p = &proc
+
+	return
+}
+
+// GetProcIDsSelf returns a ProcIDs from the current process' PID.
+func GetProcIDsSelf() (p *ProcIDs, err error) {
+
+	p, err = GetProcIDs(int32(os.Getpid()))
+
+	return
+}
diff --git a/ispriv/funcs_procids_nix.go b/ispriv/funcs_procids_nix.go
new file mode 100644
index 0000000..cbe5cf0
--- /dev/null
+++ b/ispriv/funcs_procids_nix.go
@@ -0,0 +1,426 @@
+//go:build unix
+
+package ispriv
+
+import (
+	`errors`
+	`os`
+	`os/user`
+	`strconv`
+	`strings`
+
+	`github.com/shirou/gopsutil/v4/process`
+	`golang.org/x/sys/unix`
+	`r00t2.io/sysutils/envs`
+	`r00t2.io/sysutils/paths`
+)
+
+// GetEffective returns the EUID/EGID.
+func (p *ProcIDs) GetEffective() (euid, egid uint) {
+
+	euid = p.uids.effective
+	egid = p.gids.effective
+
+	return
+}
+
+// GetFS returns the FSUID/FSGID. Not all platforms have this, in which case they'll be nil.
+func (p *ProcIDs) GetFS() (fsuid, fsgid *uint) {
+
+	if p.uids.filesystem != nil {
+		fsuid = new(uint)
+		*fsuid = *p.uids.filesystem
+	}
+	if p.gids.filesystem != nil {
+		fsgid = new(uint)
+		*fsgid = *p.gids.filesystem
+	}
+
+	return
+}
+
+/*
+	GetGids returms a set of a ProcIDs GIDs.
+	fs will be nil if unsupported on the platform.
+	If invoked with SGID, "savedSet" will be the SGID GID.
+*/
+func (p *ProcIDs) GetGids() (real, effective, savedSet uint, fs *uint) {
+
+	real = p.gids.real
+	effective = p.gids.effective
+	savedSet = p.gids.savedSet
+	if p.gids.filesystem != nil {
+		fs = new(uint)
+		*fs = *p.gids.filesystem
+	}
+
+	return
+}
+
+// GetReal returns the (R)UID/(R)GID.
+func (p *ProcIDs) GetReal() (ruid, rgid uint) {
+
+	ruid = p.uids.real
+	rgid = p.gids.real
+
+	return
+}
+
+// GetSaved returns the SUID/SGID.
+func (p *ProcIDs) GetSaved() (suid, sgid uint) {
+
+	suid = p.uids.savedSet
+	sgid = p.gids.savedSet
+
+	return
+}
+
+/*
+	GetUids returms a set of a ProcIDs UIDs.
+	fs will be nil if unsupported on the platform.
+	If invoked with SUID, "savedSet" will be the SUID UID.
+*/
+func (p *ProcIDs) GetUids() (real, effective, savedSet uint, fs *uint) {
+
+	real = p.uids.real
+	effective = p.uids.effective
+	savedSet = p.uids.savedSet
+	if p.uids.filesystem != nil {
+		fs = new(uint)
+		*fs = *p.uids.filesystem
+	}
+
+	return
+}
+
+/*
+	IsSGID returns true if the process is Set GID/SGID.
+
+	Note that it will return false if invoked by a group with the same GID as an SGID that's set.
+*/
+func (p *ProcIDs) IsSGID() (isSgid bool) {
+
+	isSgid = p.gids.real != p.gids.savedSet
+
+	return
+}
+
+/*
+	IsSUID returns true if the process is Set UID/SUID.
+
+	Note that it will return false if invoked by a user with the same UID as an SUID that's set.
+*/
+func (p *ProcIDs) IsSUID() (isSuid bool) {
+
+	isSuid = p.uids.real != p.uids.savedSet
+
+	return
+}
+
+/*
+	IsSudo does a very fast (and potentially inaccurate) evaluation of whether the process is running under sudo.
+
+	DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations!
+	Use IsSudoWithConfidence instead for those cases.
+	IsSudo only does the most basic of checking, which can be easily and completely overridden by a non-privileged user.
+*/
+func (p *ProcIDs) IsSudo() (isSudo bool) {
+
+	// This is how every other Joe Blow does this. It's an extremely dumb way to do it. The caller has been warned.
+	for k, _ := range envs.GetEnvMap() {
+		if strings.HasPrefix(k, sudoEnvPfx) {
+			isSudo = true
+			return
+		}
+	}
+
+	return
+}
+
+/*
+	IsSudoDetailed returns true for a very fast evaluation of whether the process is running under sudo,
+	and information about that context.
+	(If isSudo is false, originalUid/originalGid will both be -1 and originalUser will be nil.)
+
+	DO NOT use this function for security-sensitive uses, fully accurate results, or critical implementations!
+	Use IsSudoWithConfidenceDetailed instead for those cases.
+	IsSudoDetailed only does the most basic of checking, which can be easily and completely overridden by a non-privileged user.
+*/
+func (p *ProcIDs) IsSudoDetailed() (isSudo bool, originalUid, originalGid int, originalUser *user.User, err error) {
+
+	if originalUid, originalGid, originalUser, err = p.getSudoInfoEnv(); err != nil {
+		return
+	}
+
+	if originalUid >= 0 || originalGid >= 0 || originalUser != nil {
+		isSudo = true
+	}
+
+	return
+}
+
+/*
+	IsSudoWithConfidence is like IsSudo, but is *much* more throrough.
+
+	It not only returns isSudo, which is true if *any* indicators pass,
+	but also:
+
+	* a confidence value (which indicates *how many* indicators *passed*)
+	* a maxConfidence value (which indicates how many indicators were *tested*)
+	* a score value (which is a float indicating overall confidence on a fixed and weighted scale; higher is more confident, 1.0 indicates 100% confidence)
+*/
+func (p *ProcIDs) IsSudoWithConfidence() (isSudo bool, confidence, maxConfidence uint, score float64, err error) {
+
+	// confidence/maxConfidence are not used directly; they're unweighted counters.
+	var scoreConf uint
+	var scoreMaxConf uint
+
+	score = float64(scoreConf) / float64(scoreMaxConf)
+
+	return
+}
+
+/*
+	IsSudoWithConfidenceDetailed is like IsSudoDetailed, but is *much* more throrough.
+
+	It not only returns the same results as IsSudoDetailed, but includes the same scoring values/system as IsSudoWithConfidence.
+*/
+func (p *ProcIDs) IsSudoWithConfidenceDetailed() (isSudo bool, confidence, maxConfidence uint, score float64, originalUid, originalGid int, originalUser *user.User, err error) {
+
+	var b []byte
+	var ok bool
+	var permErr bool
+	var envUid int
+	var envGid int
+	var scoreConf uint
+	var scoreMaxConf uint
+	var curUser *user.User
+	var envUser *user.User
+	var curUid uint64
+	var fstat unix.Stat_t
+	var fsUid int
+	var procFiles []process.OpenFilesStat
+	var loginUidFile string = curLoginUidFile
+
+	if curUser, err = user.Current(); err != nil {
+		return
+	}
+	if curUid, err = strconv.ParseUint(curUser.Uid, 10, 32); err != nil {
+		return
+	}
+
+	if procFiles, err = p.proc.OpenFiles(); err != nil {
+		return
+	}
+
+	// Env vars; only score 1x/each.
+	maxConfidence += 3
+	scoreMaxConf += 3
+	if envUid, envGid, envUser, err = p.getSudoInfoEnv(); err != nil {
+		return
+	}
+	originalUid, originalGid, originalUser = envUid, envGid, envUser
+	if envUid >= 0 {
+		confidence++
+		scoreConf++
+	}
+	if envGid >= 0 {
+		confidence++
+		scoreConf++
+	}
+	if envUser != nil {
+		confidence++
+		scoreConf++
+	}
+
+	/*
+		TTY/PTY ownership. We (can) only check this if we're running in an interactive session.
+
+		Typically this is done via (golang.org/x/term).IsTerminal(),
+		That pulls in a bunch of stuff I don't need, though, so I'll just replicate (...).IsTerminal() here;
+		it's just a wrapped single function call.
+	*/
+	// procFiles[0] is always STDIN. Whether it's a pipe, or TTY/PTY, or file, etc.
+	// (likewise, procFiles[1] is always STDOUT, procFiles[2] is always STDERR); however...
+	if _, err = unix.IoctlGetTermios(int(procFiles[0].Fd), unix.TCGETS); err == nil {
+		// Interactive
+		maxConfidence++
+		// This is only worth 2. It's pretty hard to fake unless origin user is root,
+		// but it's ALSO usually set to the target user.
+		scoreMaxConf += 2
+		fstat = unix.Stat_t{}
+		if err = unix.Fstat(int(procFiles[0].Fd), &fstat); err != nil {
+			return
+		}
+		if uint64(fstat.Uid) != curUid {
+			// This is a... *potential* indicator, if a lateral sudo was done (user1 => user2),
+			// or root used sudo to *drop* privs to a regular user.
+			// We mark it as a pass for confidence since it IS a terminal, and it's permission-related.
+			confidence++
+			scoreConf += 2
+			originalUid = int(fstat.Uid)
+		}
+	} else {
+		// err is OK; just means non-interactive. No counter or score/max score increase; basically a NO-OP.
+		err = nil
+	}
+
+	// /proc/self/loginuid
+	// This is a REALLY good indicator. Probably the strongest next to reverse-walking the proc tree. It depends on PAM and auditd support, I think,
+	// BUT if it's present it's *really* really strong.
+	if ok, err = paths.RealPathExists(&loginUidFile); err != nil {
+		return
+	}
+	if ok {
+		maxConfidence++
+		scoreMaxConf += 5
+		if b, err = os.ReadFile(loginUidFile); err != nil {
+			return
+		}
+		if fsUid, err = strconv.Atoi(strings.TrimSpace(string(b))); err != nil {
+			return
+		}
+		if uint64(fsUid) != curUid {
+			confidence++
+			scoreConf += 5
+			originalUid = fsUid
+		}
+	}
+
+	// proc tree reverse walking.
+	// This is, by far, the most reliable method.
+	// There are some valid conditions in which this would fail due to permissions
+	// (e.g. lateral sudo: user1 => user2), but if it's a permission error it's *probably*
+	// a lateral move anyways.
+	if isSudo, permErr, originalUid, originalGid, originalUser, err = p.revProcWalk(); err != nil {
+		return
+	}
+	maxConfidence++
+	scoreMaxConf += 10
+	if permErr {
+		confidence++
+		scoreConf += 5
+	} else if isSudo {
+		confidence++
+		scoreConf += 10
+	}
+
+	score = float64(scoreConf) / float64(scoreMaxConf)
+
+	return
+}
+
+/*
+	getSudoInfoEnv returns env var driven sudo information.
+
+	These are in no way guaranteed to be accurate as the user can remove or override them.
+*/
+func (p *ProcIDs) getSudoInfoEnv() (uid, gid int, u *user.User, err error) {
+
+	var ok bool
+	var val string
+	var envMap map[string]string = envs.GetEnvMap()
+
+	uid = -1
+	gid = -1
+
+	if val, ok = envMap[sudoUnameEnv]; ok {
+		if u, err = user.Lookup(val); err != nil {
+			return
+		}
+	}
+	if val, ok = envMap[sudoUidEnv]; ok {
+		if uid, err = strconv.Atoi(val); err != nil {
+			return
+		}
+	}
+	if val, ok = envMap[sudoGidEnv]; ok {
+		if gid, err = strconv.Atoi(val); err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+/*
+	revProcWalk walks up the process tree ("proctree") until it either:
+
+		* finds a process invoked with sudo (true)
+		* hits PID == 1 (false)
+		* hits a permission error (true-ish)
+*/
+func (p *ProcIDs) revProcWalk() (sudoFound, isPermErr bool, origUid, origGid int, origUser *user.User, err error) {
+
+	var cmd []string
+	var parent *ProcIDs
+	var parentPid int32
+	var parentUname string
+	var parentUids []uint32
+	var parentGids []uint32
+
+	origUid = -1
+	origGid = -1
+
+	parent = p
+	for {
+		if parent == nil || parent.proc.Pid == 1 {
+			break
+		}
+		if cmd, err = parent.proc.CmdlineSlice(); err != nil {
+			if errors.Is(err, os.ErrPermission) {
+				isPermErr = true
+				err = nil
+			}
+			return
+		}
+		if cmd[0] == "sudo" {
+			sudoFound = true
+			if parentUname, err = parent.proc.Username(); err != nil {
+				if errors.Is(err, os.ErrPermission) {
+					isPermErr = true
+					err = nil
+				}
+				return
+			}
+			if parentUids, err = parent.proc.Uids(); err != nil {
+				if errors.Is(err, os.ErrPermission) {
+					isPermErr = true
+					err = nil
+				}
+				return
+			}
+			if parentGids, err = parent.proc.Gids(); err != nil {
+				if errors.Is(err, os.ErrPermission) {
+					isPermErr = true
+					err = nil
+				}
+				return
+			}
+			if origUser, err = user.Lookup(parentUname); err != nil {
+				return
+			}
+			origUid = int(parentUids[0])
+			origGid = int(parentGids[0])
+		}
+		if sudoFound {
+			break
+		}
+		if parentPid, err = parent.proc.Ppid(); err != nil {
+			if errors.Is(err, os.ErrPermission) {
+				isPermErr = true
+				err = nil
+			}
+			return
+		}
+		if parent, err = GetProcIDs(parentPid); err != nil {
+			if errors.Is(err, os.ErrPermission) {
+				isPermErr = true
+				err = nil
+			}
+			return
+		}
+	}
+
+	return
+}
diff --git a/ispriv/funcs_windows.go b/ispriv/funcs_windows.go
new file mode 100644
index 0000000..7eb44c4
--- /dev/null
+++ b/ispriv/funcs_windows.go
@@ -0,0 +1,60 @@
+//go:build windows
+
+package ispriv
+
+import (
+	`golang.org/x/sys/windows`
+)
+
+// IsAdmin returns true if currently running with Administrator privileges.
+func IsAdmin() (admin bool, err error) {
+
+	var sid *windows.SID
+	var tok windows.Token
+
+	if err = windows.AllocateAndInitializeSid(
+		&windows.SECURITY_NT_AUTHORITY,      // identAuth
+		2,                                   // subAuth
+		windows.SECURITY_BUILTIN_DOMAIN_RID, // subAuth0
+		windows.DOMAIN_ALIAS_RID_ADMINS,     // subAuth1
+		0, 0, 0, 0, 0, 0,                    // subAuth2-10
+		&sid, // sid
+	); err != nil {
+		return
+	}
+	defer windows.FreeSid(sid)
+
+	tok = windows.Token(0)
+	if admin, err = tok.IsMember(sid); err != nil {
+		return
+	}
+
+	return
+}
+
+// IsElevated returns true if running in an elevated ("Run as Administrator") context.
+func IsElevated() (elevated bool) {
+
+	var tok windows.Token = windows.Token(0)
+
+	elevated = tok.IsElevated()
+
+	return
+}
+
+/*
+	IsPrivileged indicates that the current security context is running both
+	with Administrator priviliges AND is elevated.
+*/
+func IsPrivileged() (privileged bool, err error) {
+
+	if privileged, err = IsAdmin(); err != nil {
+		return
+	}
+
+	if privileged {
+		privileged = IsElevated()
+	}
+
+	return
+}
diff --git a/ispriv/types_nix.go b/ispriv/types_nix.go
new file mode 100644
index 0000000..6518a67
--- /dev/null
+++ b/ispriv/types_nix.go
@@ -0,0 +1,19 @@
+//go:build unix
+
+package ispriv
+
+import (
+	`github.com/shirou/gopsutil/v4/process`
+)
+
+type ProcIDs struct {
+	proc *process.Process
+	uids *IdInfo
+	gids *IdInfo
+}
+type IdInfo struct {
+	real       uint
+	effective  uint
+	savedSet   uint
+	filesystem *uint
+}