@neotales/is-elevated 0.0.0 → 0.1.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/README.md +30 -24
- package/esm/_dnt.polyfills.js +130 -0
- package/esm/ffi_bun.js +19 -13
- package/esm/ffi_deno.js +13 -8
- package/esm/ffi_koffi.js +11 -12
- package/esm/ffi_node.js +44 -46
- package/esm/ffi_txiki.js +58 -0
- package/esm/mod.js +101 -0
- package/esm/package.json +3 -0
- package/package.json +26 -42
- package/types/_dnt.polyfills.d.ts +100 -0
- package/types/ffi_bun.d.ts +6 -6
- package/types/ffi_deno.d.ts +1 -1
- package/types/ffi_koffi.d.ts +6 -6
- package/types/ffi_node.d.ts +6 -6
- package/types/ffi_txiki.d.ts +13 -0
- package/types/{index.d.ts → mod.d.ts} +7 -1
- package/esm/index.js +0 -71
- package/esm/index.test.js +0 -39
- package/esm/node.js +0 -20
- package/types/index.test.d.ts +0 -1
- package/types/node.d.ts +0 -11
package/README.md
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
`@neotales/is-elevated` detects whether the current process is running with elevated privileges.
|
|
6
6
|
On Unix-like systems this typically means root. On Windows it checks whether the current token is elevated.
|
|
7
7
|
|
|
8
|
-

|
|
9
9
|
|
|
10
10
|
[](https://jsr.io/@neotales/is-elevated)
|
|
11
11
|
[](https://badge.fury.io/js/@neotales%2Fis-elevated)
|
|
12
|
-
[](https://badge.fury.io/gh/neotales%2Fjs-lib)
|
|
13
13
|
|
|
14
14
|
## Documentation
|
|
15
15
|
|
|
16
16
|
Documentation is available on [jsr.io](https://jsr.io/@neotales/is-elevated/doc).
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
[Module list](https://github.com/neotales/js-lib)..
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -25,6 +25,25 @@ npx jsr add @neotales/is-elevated
|
|
|
25
25
|
npm install @neotales/is-elevated
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### Runtime support
|
|
29
|
+
|
|
30
|
+
The module README table is the support claim. A browser can still load `is-elevated`. That is not support: `isElevatedAvailable()` is false there, and `isElevated()` throws.
|
|
31
|
+
|
|
32
|
+
| Runtime | Supported | Generated from the TypeScript |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| Deno | ✅ | source; requires --allow-sys and --allow-ffi |
|
|
35
|
+
| Node.js | ✅ | published npm ESM twin (node >= 22) |
|
|
36
|
+
| Bun | ✅ | source; Bun rewrites @neotales/ imports to relative paths |
|
|
37
|
+
| Ende | ✅ | source; requires --allow-sys |
|
|
38
|
+
| QuickJS-ng | ❌ | one ESM bundle |
|
|
39
|
+
| workerd | ✅ | one ESM bundle, imported from the worker |
|
|
40
|
+
| txiki.js | ✅ | one ESM bundle, run with `tjs run`; Windows backend is `tjs:ffi` |
|
|
41
|
+
| ClearScript | ❌ | TypeScript graph transpiled to .js, then loaded by the host |
|
|
42
|
+
| browsers | ❌ | one ESM bundle, no syntax rewrite; leaves ffi_*.ts out; node:module and bun:ffi are not loaded |
|
|
43
|
+
| JerryScript | ❌ | no bundle; FFI files import host modules the IIFE cannot include |
|
|
44
|
+
|
|
45
|
+
Load instructions: [runtime documentation](../../../docs/README.md).
|
|
46
|
+
|
|
28
47
|
## Usage
|
|
29
48
|
|
|
30
49
|
```typescript
|
|
@@ -37,9 +56,10 @@ if (!isElevated()) {
|
|
|
37
56
|
|
|
38
57
|
## Exports
|
|
39
58
|
|
|
40
|
-
| Export
|
|
41
|
-
|
|
|
42
|
-
| `isElevated`
|
|
59
|
+
| Export | Subpath | Description |
|
|
60
|
+
| --------------------- | ----------------------- | ------------------------------------------------------------ |
|
|
61
|
+
| `isElevated` | `@neotales/is-elevated` | Detects whether the current process is elevated. |
|
|
62
|
+
| `isElevatedAvailable` | `@neotales/is-elevated` | Reports whether elevation detection can run in this runtime. |
|
|
43
63
|
|
|
44
64
|
```typescript
|
|
45
65
|
import { isElevated } from "@neotales/is-elevated";
|
|
@@ -53,29 +73,15 @@ if (elevated) {
|
|
|
53
73
|
|
|
54
74
|
## Elevation Detection
|
|
55
75
|
|
|
56
|
-
On Unix-like systems, elevation means an effective user ID of `0`.
|
|
57
|
-
|
|
58
|
-
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.
|
|
59
|
-
|
|
60
|
-
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.
|
|
61
|
-
|
|
62
|
-
## Runtime Support
|
|
76
|
+
On Unix-like systems, elevation means an effective user ID of `0`. The module uses an effective ID when the runtime exposes one and otherwise falls back to `Deno.uid()`. This keeps the result tied to the identity under which the process is actually executing.
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
`npm:@neotales/is-elevated`. Call `isElevatedAvailable()` before relying on
|
|
66
|
-
Windows elevation detection when the runtime's FFI configuration may be unknown.
|
|
78
|
+
On Windows, the module opens the current process token and calls `GetTokenInformation` with `TokenElevation`. The returned `TokenIsElevated` value describes the current process token, which is the relevant answer for an operation that needs administrator privileges.
|
|
67
79
|
|
|
68
|
-
|
|
69
|
-
- **Bun**: the native `bun:ffi` backend is selected automatically.
|
|
70
|
-
- **Node.js**: see [Node.js FFI](#nodejs-ffi).
|
|
80
|
+
This intentionally does not use `Shell32.IsUserAnAdmin`. That API is a convenience membership check and can disagree with the current token under User Account Control: an administrator account may be running with a filtered, non-elevated token. Token elevation inspection reports the process state directly.
|
|
71
81
|
|
|
72
82
|
## Node.js FFI
|
|
73
83
|
|
|
74
|
-
On Windows, Node.
|
|
75
|
-
`--experimental-ffi` to enable `node:ffi`, or install koffi with
|
|
76
|
-
`npm install koffi` when optional dependencies were omitted or unavailable.
|
|
77
|
-
When neither is available, `isElevatedAvailable()` returns `false` and
|
|
78
|
-
`isElevated()` throws an error that links to this section.
|
|
84
|
+
On Windows, Node uses `node:ffi` when the module loads. It was added in 26.1.0, is still experimental, and requires `--experimental-ffi`. If it does not load, the same call goes through koffi. The generated npm package declares koffi `^2.9.0` as an optional dependency. When neither is available, `isElevatedAvailable()` returns `false` and `isElevated()` throws.
|
|
79
85
|
|
|
80
86
|
## License
|
|
81
87
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
|
|
3
|
+
* but instead of using npm to install additional dependencies,
|
|
4
|
+
* this approach manually consolidates cjs/mjs/d.ts into a single file.
|
|
5
|
+
*
|
|
6
|
+
* Note that this code might be imported multiple times
|
|
7
|
+
* (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
|
|
8
|
+
* or Node.js might dynamically clear the cache and then force a require).
|
|
9
|
+
* Therefore, it's important to avoid redundant writes to global objects.
|
|
10
|
+
* Additionally, consider that commonjs is used alongside esm,
|
|
11
|
+
* so the two ponyfill functions are stored independently in two separate global objects.
|
|
12
|
+
*/
|
|
13
|
+
//@ts-ignore
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
//@ts-ignore
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
|
+
//@ts-ignore
|
|
18
|
+
import { dirname } from "node:path";
|
|
19
|
+
const defineGlobalPonyfill = (symbolFor, fn) => {
|
|
20
|
+
if (!Reflect.has(globalThis, Symbol.for(symbolFor))) {
|
|
21
|
+
Object.defineProperty(globalThis, Symbol.for(symbolFor), {
|
|
22
|
+
configurable: true,
|
|
23
|
+
get() {
|
|
24
|
+
return fn;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export let import_meta_ponyfill_commonjs = (Reflect.get(globalThis, Symbol.for("import-meta-ponyfill-commonjs")) ??
|
|
30
|
+
(() => {
|
|
31
|
+
const moduleImportMetaWM = new WeakMap();
|
|
32
|
+
return (require, module) => {
|
|
33
|
+
let importMetaCache = moduleImportMetaWM.get(module);
|
|
34
|
+
if (importMetaCache == null) {
|
|
35
|
+
const importMeta = Object.assign(Object.create(null), {
|
|
36
|
+
url: pathToFileURL(module.filename).href,
|
|
37
|
+
main: require.main == module,
|
|
38
|
+
resolve: (specifier, parentURL = importMeta.url) => {
|
|
39
|
+
return pathToFileURL((importMeta.url === parentURL
|
|
40
|
+
? require
|
|
41
|
+
: createRequire(parentURL))
|
|
42
|
+
.resolve(specifier)).href;
|
|
43
|
+
},
|
|
44
|
+
filename: module.filename,
|
|
45
|
+
dirname: module.path,
|
|
46
|
+
});
|
|
47
|
+
moduleImportMetaWM.set(module, importMeta);
|
|
48
|
+
importMetaCache = importMeta;
|
|
49
|
+
}
|
|
50
|
+
return importMetaCache;
|
|
51
|
+
};
|
|
52
|
+
})());
|
|
53
|
+
defineGlobalPonyfill("import-meta-ponyfill-commonjs", import_meta_ponyfill_commonjs);
|
|
54
|
+
export let import_meta_ponyfill_esmodule = (Reflect.get(globalThis, Symbol.for("import-meta-ponyfill-esmodule")) ??
|
|
55
|
+
((importMeta) => {
|
|
56
|
+
const resolveFunStr = String(importMeta.resolve);
|
|
57
|
+
const shimWs = new WeakSet();
|
|
58
|
+
//@ts-ignore
|
|
59
|
+
const mainUrl = ("file:///" + process.argv[1].replace(/\\/g, "/"))
|
|
60
|
+
.replace(/\/{3,}/, "///");
|
|
61
|
+
const commonShim = (importMeta) => {
|
|
62
|
+
if (typeof importMeta.main !== "boolean") {
|
|
63
|
+
importMeta.main = importMeta.url === mainUrl;
|
|
64
|
+
}
|
|
65
|
+
if (typeof importMeta.filename !== "string") {
|
|
66
|
+
importMeta.filename = fileURLToPath(importMeta.url);
|
|
67
|
+
importMeta.dirname = dirname(importMeta.filename);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (
|
|
71
|
+
// v16.2.0+, v14.18.0+: Add support for WHATWG URL object to parentURL parameter.
|
|
72
|
+
resolveFunStr === "undefined" ||
|
|
73
|
+
// v20.0.0+, v18.19.0+"" This API now returns a string synchronously instead of a Promise.
|
|
74
|
+
resolveFunStr.startsWith("async")
|
|
75
|
+
// enable by --experimental-import-meta-resolve flag
|
|
76
|
+
) {
|
|
77
|
+
import_meta_ponyfill_esmodule = (importMeta) => {
|
|
78
|
+
if (!shimWs.has(importMeta)) {
|
|
79
|
+
shimWs.add(importMeta);
|
|
80
|
+
const importMetaUrlRequire = {
|
|
81
|
+
url: importMeta.url,
|
|
82
|
+
require: createRequire(importMeta.url),
|
|
83
|
+
};
|
|
84
|
+
// note: the parameter types must be explicitly annotated because
|
|
85
|
+
// `@types/node` also declares `ImportMeta.resolve`, which makes
|
|
86
|
+
// the contextual type an overload set that provides no inference
|
|
87
|
+
importMeta.resolve = function resolve(specifier, parentURL = importMeta.url) {
|
|
88
|
+
return pathToFileURL((importMetaUrlRequire.url === parentURL
|
|
89
|
+
? importMetaUrlRequire.require
|
|
90
|
+
: createRequire(parentURL)).resolve(specifier)).href;
|
|
91
|
+
};
|
|
92
|
+
commonShim(importMeta);
|
|
93
|
+
}
|
|
94
|
+
return importMeta;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
/// native support
|
|
99
|
+
import_meta_ponyfill_esmodule = (importMeta) => {
|
|
100
|
+
if (!shimWs.has(importMeta)) {
|
|
101
|
+
shimWs.add(importMeta);
|
|
102
|
+
commonShim(importMeta);
|
|
103
|
+
}
|
|
104
|
+
return importMeta;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return import_meta_ponyfill_esmodule(importMeta);
|
|
108
|
+
}));
|
|
109
|
+
defineGlobalPonyfill("import-meta-ponyfill-esmodule", import_meta_ponyfill_esmodule);
|
|
110
|
+
export let import_meta_ponyfill = ((...args) => {
|
|
111
|
+
const _MODULE = (() => {
|
|
112
|
+
if (typeof require === "function" && typeof module === "object") {
|
|
113
|
+
return "commonjs";
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// eval("typeof import.meta");
|
|
117
|
+
return "esmodule";
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
if (_MODULE === "commonjs") {
|
|
121
|
+
//@ts-ignore
|
|
122
|
+
import_meta_ponyfill = (r, m) => import_meta_ponyfill_commonjs(r, m);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
//@ts-ignore
|
|
126
|
+
import_meta_ponyfill = (im) => import_meta_ponyfill_esmodule(im);
|
|
127
|
+
}
|
|
128
|
+
//@ts-ignore
|
|
129
|
+
return import_meta_ponyfill(...args);
|
|
130
|
+
});
|
package/esm/ffi_bun.js
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Implements native Windows elevation detection through Bun FFI.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_bun
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
const specifier = "bun:ffi";
|
|
8
|
+
let ffi;
|
|
9
|
+
try {
|
|
10
|
+
ffi = await import(/* @vite-ignore */ specifier);
|
|
11
|
+
}
|
|
12
|
+
catch (cause) {
|
|
13
|
+
throw new Error("Unable to load bun:ffi. See https://github.com/neotales/js-lib/blob/dev/os/jsr/is-elevated/README.md#runtime-support", { cause });
|
|
14
|
+
}
|
|
3
15
|
let elevated;
|
|
4
16
|
/**
|
|
5
17
|
* Reports whether the current process is running with elevated privileges.
|
|
6
18
|
*
|
|
7
19
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
8
20
|
* @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
21
|
*/
|
|
16
22
|
export function evalIsProcessElevated(cache = true) {
|
|
17
|
-
if (cache && elevated !== undefined)
|
|
23
|
+
if (cache && elevated !== undefined)
|
|
18
24
|
return elevated;
|
|
19
|
-
}
|
|
25
|
+
const { FFIType, dlopen, ptr } = ffi;
|
|
20
26
|
const advapi32 = dlopen("Advapi32.dll", {
|
|
21
27
|
OpenProcessToken: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr], returns: FFIType.bool },
|
|
22
28
|
GetTokenInformation: {
|
|
@@ -33,15 +39,15 @@ export function evalIsProcessElevated(cache = true) {
|
|
|
33
39
|
const TOKEN_QUERY = 0x0008;
|
|
34
40
|
const TOKEN_ELEVATION = 20;
|
|
35
41
|
const tokenHandle = new BigUint64Array(1);
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
42
|
+
const opened = advapi32.symbols.OpenProcessToken(kernel32.symbols.GetCurrentProcess(), TOKEN_QUERY, ptr(tokenHandle));
|
|
43
|
+
if (!opened)
|
|
38
44
|
throw new Error("Failed to open process token");
|
|
39
45
|
try {
|
|
40
46
|
const tokenInfo = new Uint8Array(4);
|
|
41
47
|
const returnLength = new Uint32Array(1);
|
|
42
48
|
const result = advapi32.symbols.GetTokenInformation(tokenHandle[0], TOKEN_ELEVATION, ptr(tokenInfo), 4, ptr(returnLength));
|
|
43
49
|
if (!result)
|
|
44
|
-
throw new Error(`Failed to get token information ${kernel32.symbols.GetLastError()}`);
|
|
50
|
+
throw new Error(`Failed to get token information (${kernel32.symbols.GetLastError()})`);
|
|
45
51
|
elevated = tokenInfo[0] !== 0;
|
|
46
52
|
return elevated;
|
|
47
53
|
}
|
package/esm/ffi_deno.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @module @neotales/is-elevated/ffi_deno
|
|
5
5
|
*/
|
|
6
6
|
let elevated;
|
|
7
|
-
const deno = globalThis.Deno;
|
|
8
7
|
/**
|
|
9
8
|
* Reports whether the current process is running with elevated privileges.
|
|
10
9
|
*
|
|
@@ -12,22 +11,27 @@ const deno = globalThis.Deno;
|
|
|
12
11
|
* @returns Whether the process has elevated privileges.
|
|
13
12
|
* @example
|
|
14
13
|
* ```ts
|
|
15
|
-
* import { evalIsProcessElevated } from "./ffi_deno.
|
|
14
|
+
* import { evalIsProcessElevated } from "./ffi_deno.ts";
|
|
16
15
|
*
|
|
17
16
|
* const elevated = evalIsProcessElevated();
|
|
18
17
|
* ```
|
|
19
18
|
*/
|
|
20
19
|
export function evalIsProcessElevated(cache = true) {
|
|
20
|
+
if (Deno.build.os !== "windows") {
|
|
21
|
+
if (!cache || elevated === undefined)
|
|
22
|
+
elevated = Deno.uid() === 0;
|
|
23
|
+
return elevated;
|
|
24
|
+
}
|
|
21
25
|
if (cache && elevated !== undefined)
|
|
22
26
|
return elevated;
|
|
23
|
-
const advapi32 =
|
|
27
|
+
const advapi32 = Deno.dlopen("Advapi32.dll", {
|
|
24
28
|
OpenProcessToken: { parameters: ["pointer", "u32", "pointer"], result: "bool" },
|
|
25
29
|
GetTokenInformation: {
|
|
26
30
|
parameters: ["u64", "u32", "pointer", "u32", "pointer"],
|
|
27
31
|
result: "bool",
|
|
28
32
|
},
|
|
29
33
|
});
|
|
30
|
-
const kernel32 =
|
|
34
|
+
const kernel32 = Deno.dlopen("Kernel32.dll", {
|
|
31
35
|
GetCurrentProcess: { parameters: [], result: "pointer" },
|
|
32
36
|
CloseHandle: { parameters: ["pointer"], result: "bool" },
|
|
33
37
|
GetLastError: { parameters: [], result: "i32" },
|
|
@@ -37,21 +41,22 @@ export function evalIsProcessElevated(cache = true) {
|
|
|
37
41
|
const TOKEN_ELEVATION = 20;
|
|
38
42
|
const processHandle = kernel32.symbols.GetCurrentProcess();
|
|
39
43
|
const tokenHandle = new BigUint64Array(1);
|
|
40
|
-
const tokenHandlePtr =
|
|
44
|
+
const tokenHandlePtr = Deno.UnsafePointer.of(tokenHandle);
|
|
41
45
|
const success = advapi32.symbols.OpenProcessToken(processHandle, TOKEN_QUERY, tokenHandlePtr);
|
|
42
|
-
if (!success)
|
|
46
|
+
if (!success) {
|
|
43
47
|
throw new Error("Failed to open process token");
|
|
48
|
+
}
|
|
44
49
|
try {
|
|
45
50
|
const tokenInfo = new Uint8Array(4);
|
|
46
51
|
const returnLength = new Uint32Array(1);
|
|
47
|
-
const result = advapi32.symbols.GetTokenInformation(tokenHandle[0], TOKEN_ELEVATION,
|
|
52
|
+
const result = advapi32.symbols.GetTokenInformation(tokenHandle[0], TOKEN_ELEVATION, Deno.UnsafePointer.of(tokenInfo), 4, Deno.UnsafePointer.of(returnLength));
|
|
48
53
|
if (!result)
|
|
49
54
|
throw new Error("Failed to get token information " + kernel32.symbols.GetLastError());
|
|
50
55
|
elevated = tokenInfo[0] !== 0;
|
|
51
56
|
return elevated;
|
|
52
57
|
}
|
|
53
58
|
finally {
|
|
54
|
-
kernel32.symbols.CloseHandle(
|
|
59
|
+
kernel32.symbols.CloseHandle(Deno.UnsafePointer.create(tokenHandle[0]));
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
finally {
|
package/esm/ffi_koffi.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Windows elevation through koffi, used when node:ffi is not available.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_koffi
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
const require = createRequire(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url);
|
|
9
|
+
const specifier = "koffi";
|
|
10
|
+
const koffi = require(specifier);
|
|
5
11
|
const advapi32 = koffi.load("Advapi32.dll");
|
|
6
12
|
const kernel32 = koffi.load("Kernel32.dll");
|
|
7
13
|
const OpenProcessToken = advapi32.func("int __stdcall OpenProcessToken(void *ProcessHandle, uint32 DesiredAccess, _Out_ void **TokenHandle)");
|
|
@@ -15,17 +21,10 @@ let elevated;
|
|
|
15
21
|
*
|
|
16
22
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
17
23
|
* @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
24
|
*/
|
|
25
25
|
export function evalIsProcessElevated(cache = true) {
|
|
26
|
-
if (cache && elevated !== undefined)
|
|
26
|
+
if (cache && elevated !== undefined)
|
|
27
27
|
return elevated;
|
|
28
|
-
}
|
|
29
28
|
const TOKEN_QUERY = 0x0008;
|
|
30
29
|
const TOKEN_ELEVATION = 20;
|
|
31
30
|
const processHandle = GetCurrentProcess();
|
package/esm/ffi_node.js
CHANGED
|
@@ -1,63 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Implements native Windows elevation detection through Node.js FFI.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_node
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
const require = createRequire(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url);
|
|
9
|
+
const specifier = "node:ffi";
|
|
10
|
+
let ffi;
|
|
11
|
+
try {
|
|
12
|
+
ffi = require(specifier);
|
|
13
|
+
}
|
|
14
|
+
catch (cause) {
|
|
15
|
+
throw new Error(`Unable to load ${specifier}. See https://github.com/neotales/js-lib/blob/dev/os/jsr/is-elevated/README.md#nodejs-ffi`, { cause });
|
|
7
16
|
}
|
|
17
|
+
const advapi32 = ffi.dlopen("Advapi32.dll", {
|
|
18
|
+
OpenProcessToken: { arguments: ["pointer", "u32", "pointer"], return: "u8" },
|
|
19
|
+
GetTokenInformation: {
|
|
20
|
+
arguments: ["pointer", "u32", "pointer", "u32", "pointer"],
|
|
21
|
+
return: "u8",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const kernel32 = ffi.dlopen("Kernel32.dll", {
|
|
25
|
+
GetCurrentProcess: { arguments: [], return: "pointer" },
|
|
26
|
+
CloseHandle: { arguments: ["pointer"], return: "u8" },
|
|
27
|
+
GetLastError: { arguments: [], return: "u32" },
|
|
28
|
+
});
|
|
8
29
|
let elevated;
|
|
9
30
|
/**
|
|
10
31
|
* Reports whether the current process is running with elevated privileges.
|
|
11
32
|
*
|
|
12
33
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
13
34
|
* @returns Whether the process has elevated privileges.
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { evalIsProcessElevated } from "./ffi_node.js";
|
|
17
|
-
*
|
|
18
|
-
* const elevated = evalIsProcessElevated();
|
|
19
|
-
* ```
|
|
20
35
|
*/
|
|
21
36
|
export function evalIsProcessElevated(cache = true) {
|
|
22
|
-
if (cache && elevated !== undefined)
|
|
37
|
+
if (cache && elevated !== undefined)
|
|
23
38
|
return elevated;
|
|
39
|
+
const TOKEN_QUERY = 0x0008;
|
|
40
|
+
const TOKEN_ELEVATION = 20;
|
|
41
|
+
const processHandle = kernel32.functions.GetCurrentProcess();
|
|
42
|
+
const tokenHandleBuffer = new Uint8Array(8);
|
|
43
|
+
const opened = advapi32.functions.OpenProcessToken(processHandle, TOKEN_QUERY, tokenHandleBuffer);
|
|
44
|
+
if (!opened) {
|
|
45
|
+
throw new Error(`Failed to open process token (${kernel32.functions.GetLastError()})`);
|
|
24
46
|
}
|
|
25
|
-
const
|
|
26
|
-
OpenProcessToken: { arguments: ["pointer", "u32", "pointer"], return: "u8" },
|
|
27
|
-
GetTokenInformation: {
|
|
28
|
-
arguments: ["pointer", "u32", "pointer", "u32", "pointer"],
|
|
29
|
-
return: "u8",
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
const kernel32 = ffi.dlopen("Kernel32.dll", {
|
|
33
|
-
GetCurrentProcess: { arguments: [], return: "pointer" },
|
|
34
|
-
CloseHandle: { arguments: ["pointer"], return: "u8" },
|
|
35
|
-
GetLastError: { arguments: [], return: "u32" },
|
|
36
|
-
});
|
|
47
|
+
const tokenHandle = new DataView(tokenHandleBuffer.buffer).getBigUint64(0, true);
|
|
37
48
|
try {
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!success)
|
|
44
|
-
throw new Error(`Failed to open process token (${kernel32.functions.GetLastError()})`);
|
|
45
|
-
const tokenHandle = new DataView(tokenHandleBuf.buffer).getBigUint64(0, true);
|
|
46
|
-
try {
|
|
47
|
-
const tokenInfo = new Uint8Array(4);
|
|
48
|
-
const returnLength = new Uint8Array(4);
|
|
49
|
-
const result = advapi32.functions.GetTokenInformation(tokenHandle, TOKEN_ELEVATION, tokenInfo, 4, returnLength);
|
|
50
|
-
if (!result)
|
|
51
|
-
throw new Error(`Failed to get token information (${kernel32.functions.GetLastError()})`);
|
|
52
|
-
elevated = tokenInfo[0] !== 0;
|
|
53
|
-
return elevated;
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
kernel32.functions.CloseHandle(tokenHandle);
|
|
49
|
+
const tokenInfo = new Uint8Array(4);
|
|
50
|
+
const returnLength = new Uint8Array(4);
|
|
51
|
+
const result = advapi32.functions.GetTokenInformation(tokenHandle, TOKEN_ELEVATION, tokenInfo, 4, returnLength);
|
|
52
|
+
if (!result) {
|
|
53
|
+
throw new Error(`Failed to get token information (${kernel32.functions.GetLastError()})`);
|
|
57
54
|
}
|
|
55
|
+
elevated = tokenInfo[0] !== 0;
|
|
56
|
+
return elevated;
|
|
58
57
|
}
|
|
59
58
|
finally {
|
|
60
|
-
|
|
61
|
-
kernel32.lib.close();
|
|
59
|
+
kernel32.functions.CloseHandle(tokenHandle);
|
|
62
60
|
}
|
|
63
61
|
}
|
package/esm/ffi_txiki.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows elevation through txiki.js `tjs:ffi`.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_txiki
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
const specifier = "tjs:ffi";
|
|
8
|
+
let ffi;
|
|
9
|
+
try {
|
|
10
|
+
ffi = await import(/* @vite-ignore */ specifier);
|
|
11
|
+
}
|
|
12
|
+
catch (cause) {
|
|
13
|
+
throw new Error("Unable to load tjs:ffi. See https://github.com/neotales/js-lib/blob/dev/os/jsr/is-elevated/README.md#runtime-support", { cause });
|
|
14
|
+
}
|
|
15
|
+
let elevated;
|
|
16
|
+
/**
|
|
17
|
+
* Reports whether the current process is running with elevated privileges.
|
|
18
|
+
*
|
|
19
|
+
* @param cache - Whether to reuse the result from the first evaluation.
|
|
20
|
+
* @returns Whether the process has elevated privileges.
|
|
21
|
+
*/
|
|
22
|
+
export function evalIsProcessElevated(cache = true) {
|
|
23
|
+
if (cache && elevated !== undefined)
|
|
24
|
+
return elevated;
|
|
25
|
+
const advapi32 = ffi.dlopen("Advapi32.dll", {
|
|
26
|
+
OpenProcessToken: { args: ["ptr", "u32", "buffer"], returns: "bool_u32" },
|
|
27
|
+
GetTokenInformation: { args: ["ptr", "u32", "buffer", "u32", "buffer"], returns: "bool_u32" },
|
|
28
|
+
});
|
|
29
|
+
const kernel32 = ffi.dlopen("Kernel32.dll", {
|
|
30
|
+
GetCurrentProcess: { args: [], returns: "ptr" },
|
|
31
|
+
CloseHandle: { args: ["ptr"], returns: "bool_u32" },
|
|
32
|
+
GetLastError: { args: [], returns: "u32" },
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const TOKEN_QUERY = 0x0008;
|
|
36
|
+
const TOKEN_ELEVATION = 20;
|
|
37
|
+
const tokenHandle = new Uint8Array(8);
|
|
38
|
+
const opened = advapi32.symbols.OpenProcessToken(kernel32.symbols.GetCurrentProcess(), TOKEN_QUERY, tokenHandle);
|
|
39
|
+
if (!opened)
|
|
40
|
+
throw new Error(`Failed to open process token (${kernel32.symbols.GetLastError()})`);
|
|
41
|
+
try {
|
|
42
|
+
const tokenInfo = new Uint8Array(4);
|
|
43
|
+
const returnLength = new Uint8Array(4);
|
|
44
|
+
const result = advapi32.symbols.GetTokenInformation(tokenHandle, TOKEN_ELEVATION, tokenInfo, 4, returnLength);
|
|
45
|
+
if (!result)
|
|
46
|
+
throw new Error(`Failed to get token information (${kernel32.symbols.GetLastError()})`);
|
|
47
|
+
elevated = tokenInfo[0] !== 0;
|
|
48
|
+
return elevated;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
kernel32.symbols.CloseHandle(tokenHandle);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
advapi32.close();
|
|
56
|
+
kernel32.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects whether the current process is running with elevated privileges.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated
|
|
5
|
+
*/
|
|
6
|
+
import "./_dnt.polyfills.js";
|
|
7
|
+
let elevated;
|
|
8
|
+
function evalUnixElevation(cache = true) {
|
|
9
|
+
if (cache && elevated !== undefined)
|
|
10
|
+
return elevated;
|
|
11
|
+
let uid = null;
|
|
12
|
+
if ("process" in globalThis && process.geteuid)
|
|
13
|
+
uid = process.geteuid();
|
|
14
|
+
if (uid === null && "process" in globalThis && process.getuid)
|
|
15
|
+
uid = process.getuid();
|
|
16
|
+
// Ende has no process. Deno is a permanent alias there, so this is the Ende path too.
|
|
17
|
+
const globals = globalThis;
|
|
18
|
+
if (uid === null && globals.Deno?.euid)
|
|
19
|
+
uid = globals.Deno.euid();
|
|
20
|
+
if (uid === null && globals.Deno?.uid)
|
|
21
|
+
uid = globals.Deno.uid();
|
|
22
|
+
elevated = uid === 0;
|
|
23
|
+
return elevated;
|
|
24
|
+
}
|
|
25
|
+
function unsupportedNodeFfi() {
|
|
26
|
+
throw new Error("Unable to determine Windows process elevation because neither node:ffi nor koffi is available. Run Node.js >= 26 with --experimental-ffi, or install koffi. See https://github.com/neotales/js-lib/blob/dev/os/jsr/is-elevated/README.md#nodejs-ffi");
|
|
27
|
+
}
|
|
28
|
+
function unsupportedRuntimeFfi() {
|
|
29
|
+
throw new Error(`Unable to determine Windows process elevation because the runtime FFI backend is unavailable.${"Deno" in globalThis ? " Run Deno with --allow-ffi." : ""} See https://github.com/neotales/js-lib/blob/dev/os/jsr/is-elevated/README.md#runtime-support`);
|
|
30
|
+
}
|
|
31
|
+
function windows() {
|
|
32
|
+
if ("process" in globalThis && process.platform === "win32")
|
|
33
|
+
return true;
|
|
34
|
+
const nav = globalThis;
|
|
35
|
+
return nav.navigator?.userAgentData?.platform === "Windows";
|
|
36
|
+
}
|
|
37
|
+
let impl = evalUnixElevation;
|
|
38
|
+
let isSupported = "process" in globalThis || "Deno" in globalThis || "Bun" in globalThis || "tjs" in globalThis;
|
|
39
|
+
if (isSupported && windows()) {
|
|
40
|
+
let isNode = false;
|
|
41
|
+
try {
|
|
42
|
+
if ("Deno" in globalThis) {
|
|
43
|
+
impl = (await import("./ffi_deno.js")).evalIsProcessElevated;
|
|
44
|
+
}
|
|
45
|
+
else if ("Bun" in globalThis) {
|
|
46
|
+
impl = (await import("./ffi_bun.js")).evalIsProcessElevated;
|
|
47
|
+
}
|
|
48
|
+
else if ("tjs" in globalThis) {
|
|
49
|
+
impl = (await import("./ffi_txiki.js")).evalIsProcessElevated;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
isNode = true;
|
|
53
|
+
try {
|
|
54
|
+
impl = (await import("./ffi_node.js")).evalIsProcessElevated;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
impl = (await import("./ffi_koffi.js")).evalIsProcessElevated;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Loading a backend alone does not prove FFI permissions and native
|
|
61
|
+
// libraries are usable, so evaluate once before advertising support.
|
|
62
|
+
impl();
|
|
63
|
+
isSupported = true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const debug = "process" in globalThis && process.env.DEBUG === "true";
|
|
67
|
+
if (debug)
|
|
68
|
+
console.debug(error);
|
|
69
|
+
isSupported = false;
|
|
70
|
+
impl = isNode ? unsupportedNodeFfi : unsupportedRuntimeFfi;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Returns whether elevation detection is available in the current runtime.
|
|
75
|
+
*
|
|
76
|
+
* @returns `true` when {@linkcode isElevated} can evaluate the current process.
|
|
77
|
+
*/
|
|
78
|
+
export function isElevatedAvailable() {
|
|
79
|
+
return isSupported;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reports whether the current process is running with elevated privileges.
|
|
83
|
+
*
|
|
84
|
+
* @param cache - Whether to reuse the result from the first evaluation.
|
|
85
|
+
* @returns Whether the process has elevated privileges.
|
|
86
|
+
* @throws {Error} On Windows when the runtime has no usable FFI backend.
|
|
87
|
+
* @example
|
|
88
|
+
* import { isElevated } from "jsr:@neotales/is-elevated";
|
|
89
|
+
*
|
|
90
|
+
* if (isElevated()) {
|
|
91
|
+
* console.log("The process is elevated.");
|
|
92
|
+
* }
|
|
93
|
+
*/
|
|
94
|
+
export function isElevated(cache = true) {
|
|
95
|
+
if (!isSupported) {
|
|
96
|
+
if (impl !== evalUnixElevation)
|
|
97
|
+
impl();
|
|
98
|
+
unsupportedRuntimeFfi();
|
|
99
|
+
}
|
|
100
|
+
return impl(cache);
|
|
101
|
+
}
|
package/esm/package.json
ADDED
package/package.json
CHANGED
|
@@ -1,58 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neotales/is-elevated",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"description": "isElevated function determines if the current process is running elevated (admin/root) across windows, linux, and darwin platforms.",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
5
4
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"darwin",
|
|
14
|
-
"is-elevated"
|
|
5
|
+
"is-elevated",
|
|
6
|
+
"deno",
|
|
7
|
+
"node",
|
|
8
|
+
"bun",
|
|
9
|
+
"ende",
|
|
10
|
+
"workerd",
|
|
11
|
+
"txiki"
|
|
15
12
|
],
|
|
16
|
-
"homepage": "https://github.com/neotales/js-os",
|
|
17
|
-
"bugs": {
|
|
18
|
-
"url": "https://github.com/neotales/js-os/issues"
|
|
19
|
-
},
|
|
20
|
-
"license": "MIT",
|
|
21
13
|
"repository": {
|
|
22
14
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/neotales/js-
|
|
24
|
-
"directory": "npm/is-elevated"
|
|
15
|
+
"url": "git+https://github.com/neotales/js-lib.git",
|
|
16
|
+
"directory": "os/npm/is-elevated"
|
|
25
17
|
},
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
],
|
|
30
|
-
"type": "module",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"module": "./esm/mod.js",
|
|
20
|
+
"types": "./types/mod.d.ts",
|
|
31
21
|
"exports": {
|
|
32
22
|
".": {
|
|
33
|
-
"
|
|
34
|
-
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./types/mod.d.ts",
|
|
25
|
+
"default": "./esm/mod.js"
|
|
26
|
+
},
|
|
27
|
+
"default": {
|
|
28
|
+
"types": "./types/mod.d.ts",
|
|
29
|
+
"default": "./esm/mod.js"
|
|
30
|
+
}
|
|
35
31
|
}
|
|
36
32
|
},
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@types/bun": "^1.3.14",
|
|
42
|
-
"@types/node": "^25.9.3",
|
|
43
|
-
"typescript": "^6.0.3"
|
|
33
|
+
"type": "module",
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=22"
|
|
44
36
|
},
|
|
45
37
|
"optionalDependencies": {
|
|
46
38
|
"koffi": "^2.9.0"
|
|
47
39
|
},
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "tsc -p tsconfig.json",
|
|
53
|
-
"test": "node --test esm/*.test.js",
|
|
54
|
-
"test:ffi": "node --experimental-ffi --test esm/*.test.js",
|
|
55
|
-
"test:bun": "bun test ./esm",
|
|
56
|
-
"test:deno": "deno test -A esm/*.test.js"
|
|
57
|
-
}
|
|
40
|
+
"_generatedBy": "dnt@0.44.0",
|
|
41
|
+
"scripts": {}
|
|
58
42
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
|
|
3
|
+
* but instead of using npm to install additional dependencies,
|
|
4
|
+
* this approach manually consolidates cjs/mjs/d.ts into a single file.
|
|
5
|
+
*
|
|
6
|
+
* Note that this code might be imported multiple times
|
|
7
|
+
* (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
|
|
8
|
+
* or Node.js might dynamically clear the cache and then force a require).
|
|
9
|
+
* Therefore, it's important to avoid redundant writes to global objects.
|
|
10
|
+
* Additionally, consider that commonjs is used alongside esm,
|
|
11
|
+
* so the two ponyfill functions are stored independently in two separate global objects.
|
|
12
|
+
*/
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import { type URL } from "node:url";
|
|
15
|
+
declare global {
|
|
16
|
+
interface ImportMeta {
|
|
17
|
+
/** A string representation of the fully qualified module URL. When the
|
|
18
|
+
* module is loaded locally, the value will be a file URL (e.g.
|
|
19
|
+
* `file:///path/module.ts`).
|
|
20
|
+
*
|
|
21
|
+
* You can also parse the string as a URL to determine more information about
|
|
22
|
+
* how the current module was loaded. For example to determine if a module was
|
|
23
|
+
* local or not:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const url = new URL(import.meta.url);
|
|
27
|
+
* if (url.protocol === "file:") {
|
|
28
|
+
* console.log("this module was loaded locally");
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
url: string;
|
|
33
|
+
/**
|
|
34
|
+
* A function that returns resolved specifier as if it would be imported
|
|
35
|
+
* using `import(specifier)`.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* console.log(import.meta.resolve("./foo.js"));
|
|
39
|
+
* // file:///dev/foo.js
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param specifier The module specifier to resolve relative to `parent`.
|
|
43
|
+
* @param parent The absolute parent module URL to resolve from.
|
|
44
|
+
* @returns The absolute (`file:`) URL string for the resolved module.
|
|
45
|
+
*/
|
|
46
|
+
resolve(specifier: string, parent?: string | URL | undefined): string;
|
|
47
|
+
/** A flag that indicates if the current module is the main module that was
|
|
48
|
+
* called when starting the program under Deno.
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* if (import.meta.main) {
|
|
52
|
+
* // this was loaded as the main module, maybe do some bootstrapping
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
main: boolean;
|
|
57
|
+
/** The absolute path of the current module.
|
|
58
|
+
*
|
|
59
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
60
|
+
*
|
|
61
|
+
* Example:
|
|
62
|
+
* ```
|
|
63
|
+
* // Unix
|
|
64
|
+
* console.log(import.meta.filename); // /home/alice/my_module.ts
|
|
65
|
+
*
|
|
66
|
+
* // Windows
|
|
67
|
+
* console.log(import.meta.filename); // C:\alice\my_module.ts
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
filename: string;
|
|
71
|
+
/** The absolute path of the directory containing the current module.
|
|
72
|
+
*
|
|
73
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
74
|
+
*
|
|
75
|
+
* * Example:
|
|
76
|
+
* ```
|
|
77
|
+
* // Unix
|
|
78
|
+
* console.log(import.meta.dirname); // /home/alice
|
|
79
|
+
*
|
|
80
|
+
* // Windows
|
|
81
|
+
* console.log(import.meta.dirname); // C:\alice
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
dirname: string;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
type NodeRequest = ReturnType<typeof createRequire>;
|
|
88
|
+
type NodeModule = NonNullable<NodeRequest["main"]>;
|
|
89
|
+
interface ImportMetaPonyfillCommonjs {
|
|
90
|
+
(require: NodeRequest, module: NodeModule): ImportMeta;
|
|
91
|
+
}
|
|
92
|
+
interface ImportMetaPonyfillEsmodule {
|
|
93
|
+
(importMeta: ImportMeta): ImportMeta;
|
|
94
|
+
}
|
|
95
|
+
interface ImportMetaPonyfill extends ImportMetaPonyfillCommonjs, ImportMetaPonyfillEsmodule {
|
|
96
|
+
}
|
|
97
|
+
export declare let import_meta_ponyfill_commonjs: ImportMetaPonyfillCommonjs;
|
|
98
|
+
export declare let import_meta_ponyfill_esmodule: ImportMetaPonyfillEsmodule;
|
|
99
|
+
export declare let import_meta_ponyfill: ImportMetaPonyfill;
|
|
100
|
+
export {};
|
package/types/ffi_bun.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements native Windows elevation detection through Bun FFI.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_bun
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
1
7
|
/**
|
|
2
8
|
* Reports whether the current process is running with elevated privileges.
|
|
3
9
|
*
|
|
4
10
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
5
11
|
* @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
12
|
*/
|
|
13
13
|
export declare function evalIsProcessElevated(cache?: boolean): boolean;
|
package/types/ffi_deno.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @returns Whether the process has elevated privileges.
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
13
|
-
* import { evalIsProcessElevated } from "./ffi_deno.
|
|
13
|
+
* import { evalIsProcessElevated } from "./ffi_deno.ts";
|
|
14
14
|
*
|
|
15
15
|
* const elevated = evalIsProcessElevated();
|
|
16
16
|
* ```
|
package/types/ffi_koffi.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows elevation through koffi, used when node:ffi is not available.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_koffi
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
1
7
|
/**
|
|
2
8
|
* Reports whether the current process is running with elevated privileges.
|
|
3
9
|
*
|
|
4
10
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
5
11
|
* @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
12
|
*/
|
|
13
13
|
export declare function evalIsProcessElevated(cache?: boolean): boolean;
|
package/types/ffi_node.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements native Windows elevation detection through Node.js FFI.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_node
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
1
7
|
/**
|
|
2
8
|
* Reports whether the current process is running with elevated privileges.
|
|
3
9
|
*
|
|
4
10
|
* @param cache - Whether to reuse the result from the first evaluation.
|
|
5
11
|
* @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
12
|
*/
|
|
13
13
|
export declare function evalIsProcessElevated(cache?: boolean): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows elevation through txiki.js `tjs:ffi`.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated/ffi_txiki
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Reports whether the current process is running with elevated privileges.
|
|
9
|
+
*
|
|
10
|
+
* @param cache - Whether to reuse the result from the first evaluation.
|
|
11
|
+
* @returns Whether the process has elevated privileges.
|
|
12
|
+
*/
|
|
13
|
+
export declare function evalIsProcessElevated(cache?: boolean): boolean;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detects whether the current process is running with elevated privileges.
|
|
3
|
+
*
|
|
4
|
+
* @module @neotales/is-elevated
|
|
5
|
+
*/
|
|
6
|
+
import "./_dnt.polyfills.js";
|
|
1
7
|
/**
|
|
2
8
|
* Returns whether elevation detection is available in the current runtime.
|
|
3
9
|
*
|
|
@@ -11,7 +17,7 @@ export declare function isElevatedAvailable(): boolean;
|
|
|
11
17
|
* @returns Whether the process has elevated privileges.
|
|
12
18
|
* @throws {Error} On Windows when the runtime has no usable FFI backend.
|
|
13
19
|
* @example
|
|
14
|
-
* import { isElevated } from "
|
|
20
|
+
* import { isElevated } from "jsr:@neotales/is-elevated";
|
|
15
21
|
*
|
|
16
22
|
* if (isElevated()) {
|
|
17
23
|
* console.log("The process is elevated.");
|
package/esm/index.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
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
|
-
const runtime = globalThis;
|
|
12
|
-
const isWindows = process.platform === "win32";
|
|
13
|
-
function unsupportedNodeFfi() {
|
|
14
|
-
throw new Error("Unable to determine Windows process elevation because neither node:ffi nor koffi is available. Run Node.js >= 26 with --experimental-ffi, or install koffi with npm install koffi. See https://github.com/neotales/js-os/blob/dev/npm/is-elevated/README.md#nodejs-ffi");
|
|
15
|
-
}
|
|
16
|
-
function unsupportedRuntimeFfi() {
|
|
17
|
-
throw new Error("Unable to determine Windows process elevation because the runtime FFI backend is unavailable. Run Deno with --allow-ffi. See https://github.com/neotales/js-os/blob/dev/npm/is-elevated/README.md#runtime-support");
|
|
18
|
-
}
|
|
19
|
-
let impl = evalUnixElevation;
|
|
20
|
-
let isSupported = !isWindows;
|
|
21
|
-
if (isWindows) {
|
|
22
|
-
try {
|
|
23
|
-
if (runtime.Deno) {
|
|
24
|
-
impl = (await import("./ffi_deno.js")).evalIsProcessElevated;
|
|
25
|
-
}
|
|
26
|
-
else if (runtime.Bun) {
|
|
27
|
-
impl = (await import("./ffi_bun.js")).evalIsProcessElevated;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
try {
|
|
31
|
-
impl = (await import("./ffi_node.js")).evalIsProcessElevated;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
impl = (await import("./ffi_koffi.js")).evalIsProcessElevated;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// A backend import can succeed while FFI permissions or DLL loading fail.
|
|
38
|
-
// Probe it once before exposing a supported evaluator.
|
|
39
|
-
impl();
|
|
40
|
-
isSupported = true;
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
if (process.env.DEBUG === "true")
|
|
44
|
-
console.debug(error);
|
|
45
|
-
impl = runtime.Deno || runtime.Bun ? unsupportedRuntimeFfi : unsupportedNodeFfi;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Returns whether elevation detection is available in the current runtime.
|
|
50
|
-
*
|
|
51
|
-
* @returns `true` when {@linkcode isElevated} can evaluate the current process.
|
|
52
|
-
*/
|
|
53
|
-
export function isElevatedAvailable() {
|
|
54
|
-
return isSupported;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Reports whether the current process is running with elevated privileges.
|
|
58
|
-
*
|
|
59
|
-
* @param cache - Whether to reuse the result from the first evaluation.
|
|
60
|
-
* @returns Whether the process has elevated privileges.
|
|
61
|
-
* @throws {Error} On Windows when the runtime has no usable FFI backend.
|
|
62
|
-
* @example
|
|
63
|
-
* import { isElevated } from "@neotales/is-elevated";
|
|
64
|
-
*
|
|
65
|
-
* if (isElevated()) {
|
|
66
|
-
* console.log("The process is elevated.");
|
|
67
|
-
* }
|
|
68
|
-
*/
|
|
69
|
-
export function isElevated(cache = true) {
|
|
70
|
-
return impl(cache);
|
|
71
|
-
}
|
package/esm/index.test.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { strictEqual, throws } from "node:assert/strict";
|
|
2
|
-
import process from "node:process";
|
|
3
|
-
import { test } from "node:test";
|
|
4
|
-
import { isElevated, isElevatedAvailable } from "./index.js";
|
|
5
|
-
import { evalIsProcessElevated as evalNodeIsProcessElevated } from "./node.js";
|
|
6
|
-
test("isElevated returns a boolean", () => {
|
|
7
|
-
if (process.platform === "win32" && !isElevatedAvailable())
|
|
8
|
-
return;
|
|
9
|
-
strictEqual(typeof isElevated(), "boolean");
|
|
10
|
-
strictEqual(typeof isElevated(false), "boolean");
|
|
11
|
-
});
|
|
12
|
-
test("isElevatedAvailable returns a boolean", () => {
|
|
13
|
-
strictEqual(typeof isElevatedAvailable(), "boolean");
|
|
14
|
-
});
|
|
15
|
-
test("isElevated caches by default", () => {
|
|
16
|
-
if (process.platform === "win32" && !isElevatedAvailable())
|
|
17
|
-
return;
|
|
18
|
-
strictEqual(isElevated(), isElevated());
|
|
19
|
-
});
|
|
20
|
-
test("node evaluator returns a boolean", () => {
|
|
21
|
-
strictEqual(typeof evalNodeIsProcessElevated(), "boolean");
|
|
22
|
-
strictEqual(typeof evalNodeIsProcessElevated(false), "boolean");
|
|
23
|
-
});
|
|
24
|
-
test("node evaluator matches effective uid semantics on unix-like runtimes", { skip: process.platform === "win32" || (!process.geteuid && !process.getuid) }, () => {
|
|
25
|
-
strictEqual(evalNodeIsProcessElevated(false), (process.geteuid?.() ?? process.getuid?.()) === 0);
|
|
26
|
-
});
|
|
27
|
-
test("isElevated matches effective uid semantics on unix-like runtimes", { skip: process.platform === "win32" || (!process.geteuid && !process.getuid) }, () => {
|
|
28
|
-
strictEqual(isElevated(false), (process.geteuid?.() ?? process.getuid?.()) === 0);
|
|
29
|
-
});
|
|
30
|
-
test("node evaluator falls back to false when uid is unavailable", { skip: process.platform !== "win32" }, () => {
|
|
31
|
-
strictEqual(evalNodeIsProcessElevated(false), false);
|
|
32
|
-
});
|
|
33
|
-
test("root helper returns a boolean on Windows-specific runtimes", { skip: process.platform !== "win32" }, () => {
|
|
34
|
-
if (!isElevatedAvailable()) {
|
|
35
|
-
throws(() => isElevated(), /node:ffi nor koffi.*#nodejs-ffi/);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
strictEqual(typeof isElevated(false), "boolean");
|
|
39
|
-
});
|
package/esm/node.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
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/types/index.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/types/node.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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;
|