@neotales/is-elevated 0.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ # MIT License
2
+
3
+ Copyright 2024-2026 Mr. Neobeard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
6
+ documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
7
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
8
+ persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
11
+ Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
14
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
15
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @neotales/is-elevated
2
+
3
+ ## Overview
4
+
5
+ `@neotales/is-elevated` detects whether the current process is running with elevated privileges.
6
+ On Unix-like systems this typically means root. On Windows it checks whether the current token is elevated.
7
+
8
+ ![logo](https://raw.githubusercontent.com/neotales/js-std/refs/heads/dev/eng/assets/logo.png)
9
+
10
+ [![JSR](https://jsr.io/badges/@neotales/is-elevated)](https://jsr.io/@neotales/is-elevated)
11
+ [![npm version](https://badge.fury.io/js/@neostd%2Fis-elevated.svg)](https://badge.fury.io/js/@neostd%2Fis-elevated)
12
+ [![GitHub version](https://badge.fury.io/gh/neostd%2Fjs.svg)](https://badge.fury.io/gh/neostd%2Fjs)
13
+
14
+ ## Documentation
15
+
16
+ Documentation is available on [jsr.io](https://jsr.io/@neotales/is-elevated/doc).
17
+
18
+ A list of other modules can be found at [github.com/neotales/js-os](https://github.com/neotales/js-os).
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Deno
24
+ deno add jsr:@neotales/is-elevated
25
+
26
+ # npm from jsr
27
+ npx jsr add @neotales/is-elevated
28
+
29
+ # from npmjs.org
30
+ npm install @neotales/is-elevated
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```typescript
36
+ import { isElevated } from "@neotales/is-elevated";
37
+
38
+ if (!isElevated()) {
39
+ throw new Error("Run this as admin/root");
40
+ }
41
+ ```
42
+
43
+ ## Exports
44
+
45
+ | Export | Subpath | Description |
46
+ | ------------ | ----------------------- | ------------------------------------------------ |
47
+ | `isElevated` | `@neotales/is-elevated` | Detects whether the current process is elevated. |
48
+
49
+ ```typescript
50
+ import { isElevated } from "@neotales/is-elevated";
51
+
52
+ const elevated = isElevated();
53
+
54
+ if (elevated) {
55
+ console.log("ready for privileged work");
56
+ }
57
+ ```
58
+
59
+ ## Elevation Detection
60
+
61
+ On Unix-like systems, elevation means an effective user ID of `0`. Node and Bun check `process.geteuid()` when available, then fall back to `process.getuid()`. The result is cached so repeated checks do not need to query the runtime again.
62
+
63
+ On Windows, the package opens the current process token and calls `GetTokenInformation` with `TokenElevation`. Node uses native `node:ffi` when available and otherwise the optional `koffi` dependency; Bun and Deno use their native FFI implementations. The FFI implementations are loaded only on Windows.
64
+
65
+ This intentionally does not use `Shell32.IsUserAnAdmin`. That API checks administrator-group membership rather than the current process token, so it can disagree under User Account Control when an administrator account is running with a filtered, non-elevated token. `TokenElevation` reports the process state directly.
66
+
67
+ ## Runtime Notes
68
+
69
+ Node.js uses either `node:ffi` or the optional `koffi` peer dependency when available. Bun and Deno use their native FFI support.
70
+ Tests for OS-specific behavior are skipped when the current runtime does not support the required platform semantics.
71
+
72
+ ## License
73
+
74
+ [MIT License](./LICENSE.md)
75
+
76
+ ## Runtime Support
77
+
78
+ This npm package supports Node, Bun, and Deno. Deno users can import it with `npm:@neotales/is-elevated`; use the JSR package for the Deno-only implementation.
package/esm/ffi_bun.js ADDED
@@ -0,0 +1,59 @@
1
+ /// <reference types="@types/bun" />
2
+ import { dlopen, FFIType, ptr } from "bun:ffi";
3
+ let elevated;
4
+ /**
5
+ * Reports whether the current process is running with elevated privileges.
6
+ *
7
+ * @param cache - Whether to reuse the result from the first evaluation.
8
+ * @returns Whether the process has elevated privileges.
9
+ * @example
10
+ * ```ts
11
+ * import { evalIsProcessElevated } from "./ffi_bun.js";
12
+ *
13
+ * const elevated = evalIsProcessElevated();
14
+ * ```
15
+ */
16
+ export function evalIsProcessElevated(cache = true) {
17
+ if (cache && elevated !== undefined) {
18
+ return elevated;
19
+ }
20
+ elevated = globalThis.process?.getuid?.() === 0;
21
+ if (!globalThis.Bun) {
22
+ return elevated;
23
+ }
24
+ if (globalThis.process?.platform !== "win32") {
25
+ return elevated;
26
+ }
27
+ const advapi32 = dlopen("Advapi32.dll", {
28
+ OpenProcessToken: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr], returns: FFIType.bool },
29
+ GetTokenInformation: {
30
+ args: [FFIType.u64, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.ptr],
31
+ returns: FFIType.bool,
32
+ },
33
+ });
34
+ const kernel32 = dlopen("Kernel32.dll", {
35
+ GetCurrentProcess: { args: [], returns: FFIType.ptr },
36
+ CloseHandle: { args: [FFIType.ptr], returns: FFIType.bool },
37
+ GetLastError: { args: [], returns: FFIType.i32 },
38
+ });
39
+ const TOKEN_QUERY = 0x0008;
40
+ const TOKEN_ELEVATION = 20;
41
+ const processHandle = kernel32.symbols.GetCurrentProcess();
42
+ const tokenHandle = new BigUint64Array(1);
43
+ const tokenHandlePtr = ptr(tokenHandle);
44
+ const success = advapi32.symbols.OpenProcessToken(processHandle, TOKEN_QUERY, tokenHandlePtr);
45
+ if (!success)
46
+ throw new Error("Failed to open process token");
47
+ try {
48
+ const tokenInfo = new Uint8Array(4);
49
+ const returnLength = new Uint32Array(1);
50
+ const result = advapi32.symbols.GetTokenInformation(tokenHandle[0], TOKEN_ELEVATION, ptr(tokenInfo), 4, ptr(returnLength));
51
+ if (!result)
52
+ throw new Error(`Failed to get token information ${kernel32.symbols.GetLastError()}`);
53
+ elevated = tokenInfo[0] !== 0;
54
+ return elevated;
55
+ }
56
+ finally {
57
+ kernel32.symbols.CloseHandle(tokenHandlePtr);
58
+ }
59
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Implements native Windows elevation detection for Deno.
3
+ *
4
+ * @module @neotales/is-elevated/ffi_deno
5
+ */
6
+ let elevated;
7
+ const deno = globalThis.Deno;
8
+ /**
9
+ * Reports whether the current process is running with elevated privileges.
10
+ *
11
+ * @param cache - Whether to reuse the result from the first evaluation.
12
+ * @returns Whether the process has elevated privileges.
13
+ * @example
14
+ * ```ts
15
+ * import { evalIsProcessElevated } from "./ffi_deno.js";
16
+ *
17
+ * const elevated = evalIsProcessElevated();
18
+ * ```
19
+ */
20
+ export function evalIsProcessElevated(cache = true) {
21
+ if (!cache || elevated === undefined) {
22
+ elevated = deno.uid() === 0;
23
+ }
24
+ if (deno.build.os !== "windows") {
25
+ return elevated;
26
+ }
27
+ const advapi32 = deno.dlopen("Advapi32.dll", {
28
+ OpenProcessToken: { parameters: ["pointer", "u32", "pointer"], result: "bool" },
29
+ GetTokenInformation: {
30
+ parameters: ["u64", "u32", "pointer", "u32", "pointer"],
31
+ result: "bool",
32
+ },
33
+ });
34
+ const kernel32 = deno.dlopen("Kernel32.dll", {
35
+ GetCurrentProcess: { parameters: [], result: "pointer" },
36
+ CloseHandle: { parameters: ["pointer"], result: "bool" },
37
+ GetLastError: { parameters: [], result: "i32" },
38
+ });
39
+ try {
40
+ const TOKEN_QUERY = 0x0008;
41
+ const TOKEN_ELEVATION = 20;
42
+ const processHandle = kernel32.symbols.GetCurrentProcess();
43
+ const tokenHandle = new BigUint64Array(1);
44
+ const tokenHandlePtr = deno.UnsafePointer.of(tokenHandle);
45
+ const success = advapi32.symbols.OpenProcessToken(processHandle, TOKEN_QUERY, tokenHandlePtr);
46
+ if (!success) {
47
+ throw new Error("Failed to open process token");
48
+ }
49
+ try {
50
+ const tokenInfo = new Uint8Array(4);
51
+ const returnLength = new Uint32Array(1);
52
+ const result = advapi32.symbols.GetTokenInformation(tokenHandle[0], TOKEN_ELEVATION, deno.UnsafePointer.of(tokenInfo), 4, deno.UnsafePointer.of(returnLength));
53
+ if (!result) {
54
+ throw new Error("Failed to get token information " + kernel32.symbols.GetLastError());
55
+ }
56
+ elevated = tokenInfo[0] !== 0;
57
+ return elevated;
58
+ }
59
+ finally {
60
+ kernel32.symbols.CloseHandle(tokenHandlePtr);
61
+ }
62
+ }
63
+ finally {
64
+ advapi32.close();
65
+ kernel32.close();
66
+ }
67
+ }
@@ -0,0 +1,51 @@
1
+ import process from "node:process";
2
+ const { createRequire } = process.getBuiltinModule("node:module");
3
+ const require = createRequire(import.meta.url);
4
+ const koffi = require("koffi");
5
+ const advapi32 = koffi.load("Advapi32.dll");
6
+ const kernel32 = koffi.load("Kernel32.dll");
7
+ const OpenProcessToken = advapi32.func("int __stdcall OpenProcessToken(void *ProcessHandle, uint32 DesiredAccess, _Out_ void **TokenHandle)");
8
+ const GetTokenInformation = advapi32.func("int __stdcall GetTokenInformation(void *TokenHandle, uint32 TokenInformationClass, void *TokenInformation, uint32 TokenInformationLength, _Out_ uint32 *ReturnLength)");
9
+ const GetCurrentProcess = kernel32.func("void * __stdcall GetCurrentProcess()");
10
+ const CloseHandle = kernel32.func("int __stdcall CloseHandle(void *hObject)");
11
+ const GetLastError = kernel32.func("uint32 __stdcall GetLastError()");
12
+ let elevated;
13
+ /**
14
+ * Reports whether the current process is running with elevated privileges.
15
+ *
16
+ * @param cache - Whether to reuse the result from the first evaluation.
17
+ * @returns Whether the process has elevated privileges.
18
+ * @example
19
+ * ```ts
20
+ * import { evalIsProcessElevated } from "./ffi_koffi.js";
21
+ *
22
+ * const elevated = evalIsProcessElevated();
23
+ * ```
24
+ */
25
+ export function evalIsProcessElevated(cache = true) {
26
+ if (cache && elevated !== undefined) {
27
+ return elevated;
28
+ }
29
+ const TOKEN_QUERY = 0x0008;
30
+ const TOKEN_ELEVATION = 20;
31
+ const processHandle = GetCurrentProcess();
32
+ const tokenOut = [null];
33
+ const opened = OpenProcessToken(processHandle, TOKEN_QUERY, tokenOut);
34
+ if (!opened) {
35
+ throw new Error(`Failed to open process token (${GetLastError()})`);
36
+ }
37
+ try {
38
+ const tokenInfo = new Uint8Array(4);
39
+ const returnLength = [0];
40
+ const ok = GetTokenInformation(tokenOut[0], TOKEN_ELEVATION, tokenInfo, 4, returnLength);
41
+ if (!ok) {
42
+ throw new Error(`Failed to get token information (${GetLastError()})`);
43
+ }
44
+ elevated = tokenInfo[0] !== 0;
45
+ return elevated;
46
+ }
47
+ finally {
48
+ if (tokenOut[0])
49
+ CloseHandle(tokenOut[0]);
50
+ }
51
+ }
@@ -0,0 +1,56 @@
1
+ import process from "node:process";
2
+ const { createRequire } = process.getBuiltinModule("node:module");
3
+ const require = createRequire(import.meta.url);
4
+ const ffi = require("node:ffi");
5
+ const advapi32 = ffi.dlopen("Advapi32.dll", {
6
+ OpenProcessToken: { arguments: ["pointer", "u32", "pointer"], return: "u8" },
7
+ GetTokenInformation: {
8
+ arguments: ["pointer", "u32", "pointer", "u32", "pointer"],
9
+ return: "u8",
10
+ },
11
+ });
12
+ const kernel32 = ffi.dlopen("Kernel32.dll", {
13
+ GetCurrentProcess: { arguments: [], return: "pointer" },
14
+ CloseHandle: { arguments: ["pointer"], return: "u8" },
15
+ GetLastError: { arguments: [], return: "u32" },
16
+ });
17
+ let elevated;
18
+ /**
19
+ * Reports whether the current process is running with elevated privileges.
20
+ *
21
+ * @param cache - Whether to reuse the result from the first evaluation.
22
+ * @returns Whether the process has elevated privileges.
23
+ * @example
24
+ * ```ts
25
+ * import { evalIsProcessElevated } from "./ffi_node.js";
26
+ *
27
+ * const elevated = evalIsProcessElevated();
28
+ * ```
29
+ */
30
+ export function evalIsProcessElevated(cache = true) {
31
+ if (cache && elevated !== undefined) {
32
+ return elevated;
33
+ }
34
+ const TOKEN_QUERY = 0x0008;
35
+ const TOKEN_ELEVATION = 20;
36
+ const processHandle = kernel32.functions.GetCurrentProcess();
37
+ const tokenHandleBuf = new Uint8Array(8);
38
+ const opened = advapi32.functions.OpenProcessToken(processHandle, TOKEN_QUERY, tokenHandleBuf);
39
+ if (!opened) {
40
+ throw new Error(`Failed to open process token (${kernel32.functions.GetLastError()})`);
41
+ }
42
+ const tokenHandle = new DataView(tokenHandleBuf.buffer).getBigUint64(0, true);
43
+ try {
44
+ const tokenInfo = new Uint8Array(4);
45
+ const returnLength = new Uint8Array(4);
46
+ const ok = advapi32.functions.GetTokenInformation(tokenHandle, TOKEN_ELEVATION, tokenInfo, 4, returnLength);
47
+ if (!ok) {
48
+ throw new Error(`Failed to get token information (${kernel32.functions.GetLastError()})`);
49
+ }
50
+ elevated = tokenInfo[0] !== 0;
51
+ return elevated;
52
+ }
53
+ finally {
54
+ kernel32.functions.CloseHandle(tokenHandle);
55
+ }
56
+ }
package/esm/index.js ADDED
@@ -0,0 +1,56 @@
1
+ import process from "node:process";
2
+ let elevated;
3
+ function evalUnixElevation(cache = true) {
4
+ if (cache && elevated !== undefined) {
5
+ return elevated;
6
+ }
7
+ const uid = process.geteuid?.() ?? process.getuid?.();
8
+ elevated = uid === 0;
9
+ return elevated;
10
+ }
11
+ let impl = evalUnixElevation;
12
+ const runtime = globalThis;
13
+ if (process.platform === "win32") {
14
+ const { createRequire } = process.getBuiltinModule("node:module");
15
+ const require = createRequire(import.meta.url);
16
+ if (runtime.Deno) {
17
+ impl = require("./ffi_deno.js").evalIsProcessElevated;
18
+ }
19
+ else if (runtime.Bun) {
20
+ impl = require("./ffi_bun.js").evalIsProcessElevated;
21
+ }
22
+ else {
23
+ try {
24
+ if (process.getBuiltinModule("node:ffi")) {
25
+ impl = require("./ffi_node.js").evalIsProcessElevated;
26
+ }
27
+ else {
28
+ impl = require("./ffi_koffi.js").evalIsProcessElevated;
29
+ }
30
+ }
31
+ catch (error) {
32
+ try {
33
+ impl = require("./ffi_koffi.js").evalIsProcessElevated;
34
+ }
35
+ catch {
36
+ if (process.env.DEBUG === "true")
37
+ console.debug(error);
38
+ }
39
+ }
40
+ }
41
+ }
42
+ /**
43
+ * Reports whether the current process is running with elevated privileges.
44
+ *
45
+ * @param cache - Whether to reuse the result from the first evaluation.
46
+ * @returns Whether the process has elevated privileges.
47
+ * @example
48
+ * import { isElevated } from "@neotales/is-elevated";
49
+ *
50
+ * if (isElevated()) {
51
+ * console.log("The process is elevated.");
52
+ * }
53
+ */
54
+ export function isElevated(cache = true) {
55
+ return impl(cache);
56
+ }
package/esm/node.js ADDED
@@ -0,0 +1,20 @@
1
+ import process from "node:process";
2
+ let elevated;
3
+ /**
4
+ * Reports whether the current process has an effective user ID of zero.
5
+ *
6
+ * @param cache - Whether to reuse the result from the first evaluation.
7
+ * @returns Whether the process has an effective user ID of zero.
8
+ * @example
9
+ * import { evalIsProcessElevated } from "./node.js";
10
+ *
11
+ * const elevated = evalIsProcessElevated();
12
+ */
13
+ export function evalIsProcessElevated(cache = true) {
14
+ if (cache && elevated !== undefined) {
15
+ return elevated;
16
+ }
17
+ const uid = process.geteuid?.() ?? process.getuid?.();
18
+ elevated = uid === 0;
19
+ return elevated;
20
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@neotales/is-elevated",
3
+ "version": "0.0.0-alpha.0",
4
+ "description": "Detect whether the current process is elevated.",
5
+ "keywords": [
6
+ "admin",
7
+ "elevated",
8
+ "root",
9
+ "std",
10
+ "sudo",
11
+ "windows"
12
+ ],
13
+ "homepage": "https://github.com/neotales/js-os",
14
+ "bugs": {
15
+ "url": "https://github.com/neotales/js-os/issues"
16
+ },
17
+ "license": "MIT",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/neotales/js-os.git",
21
+ "directory": "npm/is-elevated"
22
+ },
23
+ "files": [
24
+ "esm",
25
+ "types"
26
+ ],
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./types/index.d.ts",
31
+ "import": "./esm/index.js"
32
+ }
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "@types/bun": "^1.3.14",
39
+ "@types/node": "^25.9.3",
40
+ "typescript": "^6.0.3"
41
+ },
42
+ "optionalDependencies": {
43
+ "koffi": "^2.9.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=22"
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.json",
50
+ "build:test": "tsc -p tsconfig.test.json",
51
+ "test": "pnpm run build:test && node --test .test/tests/*.test.js",
52
+ "test:ffi": "pnpm run build:test && node --experimental-ffi --test .test/tests/*.test.js",
53
+ "test:bun": "pnpm run build:test && bun test ./.test/tests",
54
+ "test:deno": "pnpm run build:test && deno test -A .test/tests"
55
+ }
56
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reports whether the current process is running with elevated privileges.
3
+ *
4
+ * @param cache - Whether to reuse the result from the first evaluation.
5
+ * @returns Whether the process has elevated privileges.
6
+ * @example
7
+ * ```ts
8
+ * import { evalIsProcessElevated } from "./ffi_bun.js";
9
+ *
10
+ * const elevated = evalIsProcessElevated();
11
+ * ```
12
+ */
13
+ export declare function evalIsProcessElevated(cache?: boolean): boolean;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Implements native Windows elevation detection for Deno.
3
+ *
4
+ * @module @neotales/is-elevated/ffi_deno
5
+ */
6
+ /**
7
+ * Reports whether the current process is running with elevated privileges.
8
+ *
9
+ * @param cache - Whether to reuse the result from the first evaluation.
10
+ * @returns Whether the process has elevated privileges.
11
+ * @example
12
+ * ```ts
13
+ * import { evalIsProcessElevated } from "./ffi_deno.js";
14
+ *
15
+ * const elevated = evalIsProcessElevated();
16
+ * ```
17
+ */
18
+ export declare function evalIsProcessElevated(cache?: boolean): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reports whether the current process is running with elevated privileges.
3
+ *
4
+ * @param cache - Whether to reuse the result from the first evaluation.
5
+ * @returns Whether the process has elevated privileges.
6
+ * @example
7
+ * ```ts
8
+ * import { evalIsProcessElevated } from "./ffi_koffi.js";
9
+ *
10
+ * const elevated = evalIsProcessElevated();
11
+ * ```
12
+ */
13
+ export declare function evalIsProcessElevated(cache?: boolean): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reports whether the current process is running with elevated privileges.
3
+ *
4
+ * @param cache - Whether to reuse the result from the first evaluation.
5
+ * @returns Whether the process has elevated privileges.
6
+ * @example
7
+ * ```ts
8
+ * import { evalIsProcessElevated } from "./ffi_node.js";
9
+ *
10
+ * const elevated = evalIsProcessElevated();
11
+ * ```
12
+ */
13
+ export declare function evalIsProcessElevated(cache?: boolean): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Reports whether the current process is running with elevated privileges.
3
+ *
4
+ * @param cache - Whether to reuse the result from the first evaluation.
5
+ * @returns Whether the process has elevated privileges.
6
+ * @example
7
+ * import { isElevated } from "@neotales/is-elevated";
8
+ *
9
+ * if (isElevated()) {
10
+ * console.log("The process is elevated.");
11
+ * }
12
+ */
13
+ export declare function isElevated(cache?: boolean): boolean;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Reports whether the current process has an effective user ID of zero.
3
+ *
4
+ * @param cache - Whether to reuse the result from the first evaluation.
5
+ * @returns Whether the process has an effective user ID of zero.
6
+ * @example
7
+ * import { evalIsProcessElevated } from "./node.js";
8
+ *
9
+ * const elevated = evalIsProcessElevated();
10
+ */
11
+ export declare function evalIsProcessElevated(cache?: boolean): boolean;