netzilo 4.3.14
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 +139 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +384 -0
- package/dist/loader.d.ts +21 -0
- package/dist/loader.js +157 -0
- package/dist/platformPkg.d.ts +6 -0
- package/dist/platformPkg.js +89 -0
- package/dist/probe.d.ts +29 -0
- package/dist/probe.js +241 -0
- package/dist/trust.d.ts +22 -0
- package/dist/trust.js +299 -0
- package/dist/wrapTool.d.ts +22 -0
- package/dist/wrapTool.js +90 -0
- package/package.json +56 -0
package/dist/loader.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.load = load;
|
|
37
|
+
exports._resetForTests = _resetForTests;
|
|
38
|
+
exports.tmpDir = tmpDir;
|
|
39
|
+
/**
|
|
40
|
+
* koffi loader for the embedded Netzilo native client.
|
|
41
|
+
*
|
|
42
|
+
* Resolves the platform-specific binary and binds the exported symbols with
|
|
43
|
+
* correct signatures.
|
|
44
|
+
*
|
|
45
|
+
* koffi (not the older/stale ffi-napi) is used: it ships prebuilt native
|
|
46
|
+
* bindings per platform, so installing this package never requires a C
|
|
47
|
+
* compiler or node-gyp on the consumer's machine.
|
|
48
|
+
*/
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const os = __importStar(require("os"));
|
|
52
|
+
const platformPkg_1 = require("./platformPkg");
|
|
53
|
+
let cached = null;
|
|
54
|
+
function libFilename() {
|
|
55
|
+
switch (process.platform) {
|
|
56
|
+
case "darwin":
|
|
57
|
+
return "netzilo.dylib";
|
|
58
|
+
case "win32":
|
|
59
|
+
return "netzilo.dll";
|
|
60
|
+
default:
|
|
61
|
+
return "netzilo.so";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function libPath() {
|
|
65
|
+
// Allow an override for development / custom builds.
|
|
66
|
+
const override = process.env.NETZILO_LIB_PATH;
|
|
67
|
+
if (override) {
|
|
68
|
+
return override;
|
|
69
|
+
}
|
|
70
|
+
// Production path: the optional per-platform package (e.g.
|
|
71
|
+
// netzilo-darwin-arm64), installed automatically by npm based on the
|
|
72
|
+
// consumer's platform/arch.
|
|
73
|
+
const platformDir = (0, platformPkg_1.resolvePlatformLibDir)();
|
|
74
|
+
if (platformDir) {
|
|
75
|
+
const candidate = path.join(platformDir, libFilename());
|
|
76
|
+
if (fs.existsSync(candidate)) {
|
|
77
|
+
return candidate;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Local monorepo dev fallback: a binary dropped directly into _lib/.
|
|
81
|
+
return path.join(__dirname, "..", "_lib", libFilename());
|
|
82
|
+
}
|
|
83
|
+
function noop() {
|
|
84
|
+
// Intentionally empty — used for exports absent in older bundled libraries.
|
|
85
|
+
}
|
|
86
|
+
function load() {
|
|
87
|
+
if (cached) {
|
|
88
|
+
return cached;
|
|
89
|
+
}
|
|
90
|
+
const resolvedPath = libPath();
|
|
91
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
92
|
+
throw new Error(`netzilo: shared library not found at ${resolvedPath}. ` +
|
|
93
|
+
"This package may not include a binary for your platform, or set " +
|
|
94
|
+
"NETZILO_LIB_PATH to a locally built lib.");
|
|
95
|
+
}
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
97
|
+
const koffi = require("koffi");
|
|
98
|
+
const lib = koffi.load(resolvedPath);
|
|
99
|
+
const start = lib.func("int Start(str configJSON)");
|
|
100
|
+
const stop = lib.func("int Stop()");
|
|
101
|
+
const isRunning = lib.func("int IsRunning()");
|
|
102
|
+
// Evaluate returns a heap-allocated char* that must be released with
|
|
103
|
+
// FreeString. A koffi "disposable type" wraps 'str' so koffi decodes the
|
|
104
|
+
// pointer to a JS string AND calls FreeString on the original pointer
|
|
105
|
+
// immediately after — the caller never touches the pointer, so there is no
|
|
106
|
+
// manual free step (and no way to double-free or use-after-free it).
|
|
107
|
+
const freeString = lib.func("void FreeString(void *s)");
|
|
108
|
+
// disposable() registers the named type globally and returns a type token
|
|
109
|
+
// (for the array-style declaration form) — the prototype-string form below
|
|
110
|
+
// references it by the registered NAME, not by that return value.
|
|
111
|
+
koffi.disposable("NetziloHeapStr", "str", freeString);
|
|
112
|
+
const evaluate = lib.func("NetziloHeapStr Evaluate(str reqJSON)");
|
|
113
|
+
// FlushEvents/FlushSnapshot/GetIntegrityLevel may be absent in older
|
|
114
|
+
// bundled libraries; bind defensively.
|
|
115
|
+
let flushEvents = noop;
|
|
116
|
+
let flushSnapshot = noop;
|
|
117
|
+
let getIntegrityLevel = () => 2; // medium — the documented default
|
|
118
|
+
try {
|
|
119
|
+
flushEvents = lib.func("void FlushEvents()");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// absent in this build — keep the no-op
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
flushSnapshot = lib.func("void FlushSnapshot()");
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// absent in this build — keep the no-op
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
getIntegrityLevel = lib.func("int GetIntegrityLevel()");
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// absent in this build — keep the medium-default fallback
|
|
135
|
+
}
|
|
136
|
+
cached = {
|
|
137
|
+
Start: (configJSON) => start(configJSON),
|
|
138
|
+
Stop: () => stop(),
|
|
139
|
+
IsRunning: () => isRunning(),
|
|
140
|
+
Evaluate: (reqJSON) => evaluate(reqJSON),
|
|
141
|
+
FlushEvents: () => {
|
|
142
|
+
flushEvents();
|
|
143
|
+
},
|
|
144
|
+
FlushSnapshot: () => {
|
|
145
|
+
flushSnapshot();
|
|
146
|
+
},
|
|
147
|
+
GetIntegrityLevel: () => getIntegrityLevel(),
|
|
148
|
+
};
|
|
149
|
+
return cached;
|
|
150
|
+
}
|
|
151
|
+
/** Test-only: forget the cached binding so a fresh NETZILO_LIB_PATH can be picked up. */
|
|
152
|
+
function _resetForTests() {
|
|
153
|
+
cached = null;
|
|
154
|
+
}
|
|
155
|
+
function tmpDir() {
|
|
156
|
+
return process.env.TMPDIR || os.tmpdir() || "/tmp";
|
|
157
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolvePlatformLibDir = resolvePlatformLibDir;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the optional per-platform binary package for the current
|
|
39
|
+
* platform/arch — e.g. `netzilo-darwin-arm64`, `netzilo-linux-x64`. Each
|
|
40
|
+
* ships exactly one platform's binaries (netzilo.dylib/.so/.dll, and probe.so
|
|
41
|
+
* on Linux) as an npm `optionalDependency` of the main `netzilo` package, so
|
|
42
|
+
* `npm install netzilo` only ever downloads the one matching the install
|
|
43
|
+
* target.
|
|
44
|
+
*/
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const SUPPORTED = {
|
|
47
|
+
darwin: ["arm64", "x64"],
|
|
48
|
+
linux: ["arm64", "x64"],
|
|
49
|
+
win32: ["x64"],
|
|
50
|
+
};
|
|
51
|
+
// Package name overrides for platform/arch pairs that don't follow the plain
|
|
52
|
+
// `netzilo-${platform}-${arch}` pattern. Currently just win32-x64: npm's
|
|
53
|
+
// automated spam detection flagged the exact string "netzilo-win32-x64" for
|
|
54
|
+
// this account (reproduced directly — publishing that exact name fails with
|
|
55
|
+
// a 403 regardless of content/version, while every other name, including
|
|
56
|
+
// ones containing "win32", publishes fine) and blocked publishing under it.
|
|
57
|
+
// Renamed to netzilo-windows-x64 as the unblock; a support ticket with npm
|
|
58
|
+
// covers reclaiming the original name later if ever needed.
|
|
59
|
+
const NAME_OVERRIDES = {
|
|
60
|
+
"win32-x64": "netzilo-windows-x64",
|
|
61
|
+
};
|
|
62
|
+
function platformPackageName() {
|
|
63
|
+
const platform = process.platform;
|
|
64
|
+
const arch = process.arch;
|
|
65
|
+
if (!SUPPORTED[platform] || !SUPPORTED[platform].includes(arch)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const key = `${platform}-${arch}`;
|
|
69
|
+
return NAME_OVERRIDES[key] || `netzilo-${key}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Directory of the installed platform package, or null if none is installed
|
|
73
|
+
* (unsupported platform/arch, or the optional dependency wasn't installed).
|
|
74
|
+
* Never throws.
|
|
75
|
+
*/
|
|
76
|
+
function resolvePlatformLibDir() {
|
|
77
|
+
const pkgName = platformPackageName();
|
|
78
|
+
if (!pkgName) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
83
|
+
const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
|
|
84
|
+
return path.dirname(pkgJsonPath);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/probe.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datagram socket path shared by the probe and the native client's receiver.
|
|
3
|
+
*
|
|
4
|
+
* MUST match the receiver's own default (derived from the OS temp dir and
|
|
5
|
+
* this process's pid). The native runtime does NOT observe NETZILO_PROBE_SOCK
|
|
6
|
+
* that Node sets at runtime, so it falls back to this default — both sides
|
|
7
|
+
* therefore have to agree on it independently. The pid makes it unique per
|
|
8
|
+
* process so two co-located embedded agents don't collide; the embedded
|
|
9
|
+
* native runtime is THIS process, so its pid equals ours and it computes the
|
|
10
|
+
* identical path.
|
|
11
|
+
*/
|
|
12
|
+
export declare function socketPath(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Publish NETZILO_PROBE_SOCK BEFORE the native client starts, so its receiver
|
|
15
|
+
* binds the exact path the probe will later connect to (no startup race).
|
|
16
|
+
* Linux-only; respects an explicit caller override; idempotent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function primeSocketEnv(_configDir: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Inject the probe into this process and all future subprocesses. Linux only;
|
|
21
|
+
* idempotent; never throws. Returns true if injected.
|
|
22
|
+
*/
|
|
23
|
+
export declare function install(configDir: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Restore LD_PRELOAD so subprocesses spawned after stop() don't load the
|
|
26
|
+
* probe. The in-process dlopen can't be undone, but the probe fails open once
|
|
27
|
+
* the receiver socket is gone. Idempotent; never throws.
|
|
28
|
+
*/
|
|
29
|
+
export declare function uninstall(): void;
|
package/dist/probe.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.socketPath = socketPath;
|
|
37
|
+
exports.primeSocketEnv = primeSocketEnv;
|
|
38
|
+
exports.install = install;
|
|
39
|
+
exports.uninstall = uninstall;
|
|
40
|
+
/**
|
|
41
|
+
* In-process security-probe injection for advanced governance (Linux only).
|
|
42
|
+
*
|
|
43
|
+
* When enableAdvancedGovernance is on, the agent's process activity — process
|
|
44
|
+
* creation and filesystem access, for the agent itself AND every subprocess it
|
|
45
|
+
* spawns (curl, git, python, shell pipelines, ...) — is monitored by a
|
|
46
|
+
* userspace probe injected via LD_PRELOAD. The probe emits the same
|
|
47
|
+
* SecurityEvent stream the macOS Endpoint Security extension produces, over
|
|
48
|
+
* an AF_UNIX datagram socket to the embedded native client's receiver.
|
|
49
|
+
*
|
|
50
|
+
* Two injection vectors, both required for full parity with the macOS
|
|
51
|
+
* extension:
|
|
52
|
+
* - dlopen the probe into THIS process so the agent runtime itself is hooked
|
|
53
|
+
* (LD_PRELOAD cannot retroactively hook an already-running process);
|
|
54
|
+
* - prepend the probe to LD_PRELOAD so every subprocess loads it on exec.
|
|
55
|
+
*
|
|
56
|
+
* Gated entirely behind advanced governance: with it off, nothing is injected
|
|
57
|
+
* and the probe socket is never primed, so no hooks exist. Linux-only — a
|
|
58
|
+
* no-op on macOS (the ES extension covers everything) and Windows. Idempotent;
|
|
59
|
+
* never throws — probe setup must not break the agent.
|
|
60
|
+
*/
|
|
61
|
+
const fs = __importStar(require("fs"));
|
|
62
|
+
const path = __importStar(require("path"));
|
|
63
|
+
const platformPkg_1 = require("./platformPkg");
|
|
64
|
+
const PROBE_SOCK_ENV = "NETZILO_PROBE_SOCK";
|
|
65
|
+
const PROBE_IGNORE_ENV = "NETZILO_PROBE_IGNORE";
|
|
66
|
+
let installed = false;
|
|
67
|
+
let priorLdPreload;
|
|
68
|
+
// The dlopened probe MUST stay loaded for the process lifetime: its inline
|
|
69
|
+
// hooks are patched into libc, so unloading it would leave those patches
|
|
70
|
+
// jumping into freed memory. Holding the koffi handle in this module keeps a
|
|
71
|
+
// reference alive for the process lifetime (never unloaded — the hooks can't
|
|
72
|
+
// be cleanly removed anyway).
|
|
73
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
+
let probeHandle = null;
|
|
75
|
+
function isLinux() {
|
|
76
|
+
return process.platform === "linux";
|
|
77
|
+
}
|
|
78
|
+
function probeLibPath() {
|
|
79
|
+
// NETZILO_PROBE_PATH overrides for development / custom builds.
|
|
80
|
+
const override = process.env.NETZILO_PROBE_PATH;
|
|
81
|
+
if (override) {
|
|
82
|
+
return override;
|
|
83
|
+
}
|
|
84
|
+
// Production path: ships alongside netzilo.so in the Linux platform
|
|
85
|
+
// package (netzilo-linux-x64 / netzilo-linux-arm64).
|
|
86
|
+
const platformDir = (0, platformPkg_1.resolvePlatformLibDir)();
|
|
87
|
+
if (platformDir) {
|
|
88
|
+
const candidate = path.join(platformDir, "probe.so");
|
|
89
|
+
if (fs.existsSync(candidate)) {
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Local monorepo dev fallback: a binary dropped directly into _lib/.
|
|
94
|
+
return path.join(__dirname, "..", "_lib", "probe.so");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Datagram socket path shared by the probe and the native client's receiver.
|
|
98
|
+
*
|
|
99
|
+
* MUST match the receiver's own default (derived from the OS temp dir and
|
|
100
|
+
* this process's pid). The native runtime does NOT observe NETZILO_PROBE_SOCK
|
|
101
|
+
* that Node sets at runtime, so it falls back to this default — both sides
|
|
102
|
+
* therefore have to agree on it independently. The pid makes it unique per
|
|
103
|
+
* process so two co-located embedded agents don't collide; the embedded
|
|
104
|
+
* native runtime is THIS process, so its pid equals ours and it computes the
|
|
105
|
+
* identical path.
|
|
106
|
+
*/
|
|
107
|
+
function socketPath() {
|
|
108
|
+
const base = process.env.TMPDIR || "/tmp";
|
|
109
|
+
return path.join(base, `netzilo-probe-${process.pid}.sock`);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Publish NETZILO_PROBE_SOCK BEFORE the native client starts, so its receiver
|
|
113
|
+
* binds the exact path the probe will later connect to (no startup race).
|
|
114
|
+
* Linux-only; respects an explicit caller override; idempotent.
|
|
115
|
+
*/
|
|
116
|
+
function primeSocketEnv(_configDir) {
|
|
117
|
+
if (!isLinux()) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!process.env[PROBE_SOCK_ENV]) {
|
|
121
|
+
process.env[PROBE_SOCK_ENV] = socketPath();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function setIgnorePrefixes(configDir) {
|
|
125
|
+
// Publish ':'-separated path prefixes the probe should NOT report — the
|
|
126
|
+
// client's own state dir and the bundled-library dir — so the embedded
|
|
127
|
+
// native client's internal I/O doesn't pollute the behavior graph.
|
|
128
|
+
// Preserves any caller-provided prefixes. Idempotent.
|
|
129
|
+
const prefixes = [];
|
|
130
|
+
if (configDir) {
|
|
131
|
+
prefixes.push(configDir);
|
|
132
|
+
}
|
|
133
|
+
const libDir = path.dirname(probeLibPath());
|
|
134
|
+
if (libDir) {
|
|
135
|
+
prefixes.push(libDir);
|
|
136
|
+
}
|
|
137
|
+
const existing = process.env[PROBE_IGNORE_ENV];
|
|
138
|
+
if (existing) {
|
|
139
|
+
prefixes.push(existing);
|
|
140
|
+
}
|
|
141
|
+
// De-dup while preserving order; the probe caps at a small fixed count.
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const ordered = [];
|
|
144
|
+
for (const p of prefixes.join(":").split(":")) {
|
|
145
|
+
if (p && !seen.has(p)) {
|
|
146
|
+
seen.add(p);
|
|
147
|
+
ordered.push(p);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
process.env[PROBE_IGNORE_ENV] = ordered.join(":");
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Inject the probe into this process and all future subprocesses. Linux only;
|
|
154
|
+
* idempotent; never throws. Returns true if injected.
|
|
155
|
+
*/
|
|
156
|
+
function install(configDir) {
|
|
157
|
+
if (installed) {
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (!isLinux()) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const probe = probeLibPath();
|
|
165
|
+
if (!fs.existsSync(probe)) {
|
|
166
|
+
// eslint-disable-next-line no-console
|
|
167
|
+
console.log(`netzilo.probe: probe.so not bundled at ${probe} — skipping`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
// The receiver should already have this from primeSocketEnv(); set it
|
|
171
|
+
// defensively so the probe can find the socket regardless of order.
|
|
172
|
+
if (!process.env[PROBE_SOCK_ENV]) {
|
|
173
|
+
process.env[PROBE_SOCK_ENV] = socketPath();
|
|
174
|
+
}
|
|
175
|
+
// Tell the probe to ignore the client's OWN file I/O so the embedded
|
|
176
|
+
// native client (netzilo.so, hosted in this same process) doesn't
|
|
177
|
+
// pollute the behavior graph with its config/db/CA/WASM reads+writes.
|
|
178
|
+
// Covers both the in-process self-hook and subprocesses (which inherit
|
|
179
|
+
// the env).
|
|
180
|
+
setIgnorePrefixes(configDir);
|
|
181
|
+
// 1) Subprocesses: prepend the probe to LD_PRELOAD, preserving any
|
|
182
|
+
// existing entries (e.g. a sanitizer or another interposer). Node
|
|
183
|
+
// subprocesses spawned via child_process inherit process.env by default.
|
|
184
|
+
priorLdPreload = process.env.LD_PRELOAD;
|
|
185
|
+
process.env.LD_PRELOAD = [probe, priorLdPreload].filter(Boolean).join(" ");
|
|
186
|
+
// 2) This process: dlopen so the agent runtime itself is hooked.
|
|
187
|
+
// LD_PRELOAD is read only at exec, so it cannot hook the already-running
|
|
188
|
+
// process; loading it now runs the probe's constructor in-process. The
|
|
189
|
+
// probe is reentrancy-guarded and fails open, so this is safe in a live
|
|
190
|
+
// Node process. Set NETZILO_PROBE_SELF=0 to cover only subprocesses
|
|
191
|
+
// (LD_PRELOAD) and skip hooking this process.
|
|
192
|
+
if (!["0", "false", "no"].includes(process.env.NETZILO_PROBE_SELF ?? "1")) {
|
|
193
|
+
try {
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
195
|
+
const koffi = require("koffi");
|
|
196
|
+
// Pin the handle (see module comment) so it is never garbage
|
|
197
|
+
// collected/unloaded out from under the installed hooks.
|
|
198
|
+
probeHandle = koffi.load(probe);
|
|
199
|
+
}
|
|
200
|
+
catch (exc) {
|
|
201
|
+
// eslint-disable-next-line no-console
|
|
202
|
+
console.warn("netzilo.probe: in-process dlopen failed (agent self-hook off):", exc);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
installed = true;
|
|
206
|
+
// eslint-disable-next-line no-console
|
|
207
|
+
console.log(`netzilo.probe: injected (LD_PRELOAD + in-process), sock=${process.env[PROBE_SOCK_ENV]}`);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
catch (exc) {
|
|
211
|
+
// eslint-disable-next-line no-console
|
|
212
|
+
console.warn("netzilo.probe: install failed:", exc);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Restore LD_PRELOAD so subprocesses spawned after stop() don't load the
|
|
218
|
+
* probe. The in-process dlopen can't be undone, but the probe fails open once
|
|
219
|
+
* the receiver socket is gone. Idempotent; never throws.
|
|
220
|
+
*/
|
|
221
|
+
function uninstall() {
|
|
222
|
+
if (!installed) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
if (priorLdPreload === undefined) {
|
|
227
|
+
delete process.env.LD_PRELOAD;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
process.env.LD_PRELOAD = priorLdPreload;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (exc) {
|
|
234
|
+
// eslint-disable-next-line no-console
|
|
235
|
+
console.warn("netzilo.probe: uninstall failed:", exc);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
priorLdPreload = undefined;
|
|
239
|
+
installed = false;
|
|
240
|
+
}
|
|
241
|
+
}
|
package/dist/trust.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make the current process trust the MITM CA. Idempotent; no-op if the CA
|
|
3
|
+
* isn't present (MITM disabled). Never throws — trust setup must not break
|
|
4
|
+
* the agent. Returns true if trust was applied.
|
|
5
|
+
*/
|
|
6
|
+
export declare function installTrust(configDir: string, waitSecs?: number): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* Route this process's (and subprocesses') traffic through the unified proxy
|
|
9
|
+
* so it can be governed. Sets standard proxy env vars (covers subprocesses
|
|
10
|
+
* and any proxy-env-aware library) and, when the optional `undici` peer
|
|
11
|
+
* dependency is installed, wires undici's EnvHttpProxyAgent as the global
|
|
12
|
+
* dispatcher — the mechanism Node's global fetch() actually honors.
|
|
13
|
+
*
|
|
14
|
+
* Idempotent; never throws. Returns true if applied.
|
|
15
|
+
*/
|
|
16
|
+
export declare function installRouting(unifiedPort: number, extraNoProxy?: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Restore the proxy env vars to their pre-install values. Called on stop() so
|
|
19
|
+
* a long-lived host process that keeps running after stop() does not keep
|
|
20
|
+
* routing traffic to the (now closed) egress port. Idempotent; never throws.
|
|
21
|
+
*/
|
|
22
|
+
export declare function uninstallRouting(): void;
|