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/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# netzilo (Node.js / TypeScript)
|
|
2
|
+
|
|
3
|
+
Netzilo AI Detection & Response (AIDR) for Node.js / TypeScript
|
|
4
|
+
|
|
5
|
+
Provides full governance for custom AI agents written in Node.js / TypeScript
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
The Netzilo client is a native shared library loaded **in-process** via a
|
|
10
|
+
native FFI binding. Every LLM prompt, model response, and tool call your
|
|
11
|
+
agent makes can be evaluated — allowed, blocked, or redacted — against
|
|
12
|
+
policy pulled live from your Netzilo management server. No daemon, no
|
|
13
|
+
sidecar — nothing to stand up.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install netzilo
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Platform binaries are resolved automatically for macOS, Linux, and Windows —
|
|
22
|
+
no compiler or extra setup needed.
|
|
23
|
+
|
|
24
|
+
## Core API
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import * as netzilo from "netzilo";
|
|
28
|
+
|
|
29
|
+
await netzilo.start({
|
|
30
|
+
server: "https://srv.netzilo.com", // management server
|
|
31
|
+
pat: "nzl_...", // or setupKey: "..."
|
|
32
|
+
agentName: "my-agent", // non-blocking
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
netzilo.isRunning(); // -> boolean: true once policy has synced (ready to govern)
|
|
36
|
+
netzilo.mcpGatewayPort(); // -> number: scanner gateway port in use (auto-picked)
|
|
37
|
+
const { allowed, reason } = netzilo.isAllowed("Bash", { command: "rm -rf /" });
|
|
38
|
+
netzilo.reportResult("Bash", "<stdout>"); // post-tool observability
|
|
39
|
+
netzilo.flush(); // force-deliver buffered events to management
|
|
40
|
+
netzilo.snapshot(); // force AIDR behavior-graph snapshot + delivery (else hourly/at-stop)
|
|
41
|
+
netzilo.stop(); // graceful stop (snapshots graph + flushes events)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`isRunning()` returns `true` only after the initial policy sync — poll it
|
|
45
|
+
before kicking off work. Evaluation runs **inside the process** —
|
|
46
|
+
`evaluate()` calls the embedded engine directly, with no HTTP roundtrip and
|
|
47
|
+
no local port to manage.
|
|
48
|
+
|
|
49
|
+
## Advanced governance (`enableAdvancedGovernance: true`) — Linux only
|
|
50
|
+
|
|
51
|
+
`wrapTool()` and manual `isAllowed()`/`checkPrompt()`/`checkResponse()` calls
|
|
52
|
+
(below) govern at the tool/LLM-call boundary you wire them into, and work on
|
|
53
|
+
macOS, Linux, and Windows. Advanced governance adds deep inspection of the
|
|
54
|
+
agent's **own** outbound traffic — covering every prompt, response, and tool
|
|
55
|
+
call (including from subprocesses and libraries you have no hook for), with
|
|
56
|
+
full content analysis and semantic classification. It is currently only
|
|
57
|
+
supported when your agent process runs on Linux; on macOS/Windows, leave
|
|
58
|
+
`enableAdvancedGovernance` unset (default `false`) and rely on `wrapTool()` /
|
|
59
|
+
manual gating:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await netzilo.start({ server: "...", pat: "...", agentName: "...", enableAdvancedGovernance: true });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
It is fully automatic and requires **no root** and no changes to the host —
|
|
66
|
+
the SDK prepares the process so the agent's traffic is inspected and
|
|
67
|
+
continues to work normally. Inspected prompts/responses are analyzed and
|
|
68
|
+
classified, emitting `semantic.event` records to your dashboard alongside the
|
|
69
|
+
usual tool/LLM events. Default off; `wrapTool()` and manual gating work
|
|
70
|
+
without it.
|
|
71
|
+
|
|
72
|
+
For full HTTPS interception coverage of Node's built-in `fetch()`, also
|
|
73
|
+
install `undici` (`npm install undici`) — normal npm hoisting makes it
|
|
74
|
+
resolvable to netzilo automatically. Without it, HTTPS traffic still routes
|
|
75
|
+
through standard proxy environment variables (covering subprocesses and
|
|
76
|
+
proxy-aware libraries), but `fetch()` itself may bypass interception.
|
|
77
|
+
Libraries that build their own `http.Agent` without consulting proxy env
|
|
78
|
+
vars (e.g. `axios`/`got` without an explicit proxy agent) aren't
|
|
79
|
+
automatically covered either — prefer `undici`'s `fetch()`, or wire your
|
|
80
|
+
HTTP client's proxy option to `process.env.HTTPS_PROXY` /
|
|
81
|
+
`process.env.ALL_PROXY` directly.
|
|
82
|
+
|
|
83
|
+
## Framework integration
|
|
84
|
+
|
|
85
|
+
There's no separate package or submodule to install — everything ships in
|
|
86
|
+
`netzilo` itself.
|
|
87
|
+
|
|
88
|
+
**One-liner, fire-and-forget** — for agents whose own LLM/egress/subprocess
|
|
89
|
+
traffic should be inspected in-process, regardless of which framework issues
|
|
90
|
+
it:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { govern } from "netzilo";
|
|
94
|
+
|
|
95
|
+
await govern(); // config from NETZILO_* env vars; non-blocking
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Gating a specific tool call** — every Node agent framework's "tool" is
|
|
99
|
+
ultimately an async function the model calls with arguments. `wrapTool()`
|
|
100
|
+
gates at that common boundary instead of binding to one framework's specific
|
|
101
|
+
hook API (which drifts release to release):
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { wrapTool } from "netzilo/wrapTool";
|
|
105
|
+
|
|
106
|
+
const getOrderStatus = wrapTool(
|
|
107
|
+
"get_order_status",
|
|
108
|
+
async (args: { orderId: string }) => fetchOrder(args.orderId)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Register `getOrderStatus` with LangChain's tool(), the Vercel AI SDK's
|
|
112
|
+
// tool(), or any framework's tool registry exactly as you would the
|
|
113
|
+
// original function.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A blocked call throws `NetziloBlockedError`. A tool result that trips a
|
|
117
|
+
redaction rule (e.g. an AWS key or PII pattern) is returned to the caller
|
|
118
|
+
already sanitized.
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
`start()` / `govern()` accept an options object. Common keys:
|
|
123
|
+
|
|
124
|
+
| Key | Description |
|
|
125
|
+
|-----|-------------|
|
|
126
|
+
| `server` | Your Netzilo management server URL. |
|
|
127
|
+
| `pat` / `setupKey` | Credential used to enroll the agent. |
|
|
128
|
+
| `agentName` | Identifier this agent reports as (used for event attribution). |
|
|
129
|
+
| `mcpGatewayPort` | Optional; defaults to an auto-picked free port. `mcpGatewayPort: 0` disables governance. |
|
|
130
|
+
| `enableAdvancedGovernance` | Optional (default off). Deep inspection of the agent's own outbound traffic with semantic classification. |
|
|
131
|
+
| `logLevel` / `logFile` | Logging verbosity and destination. |
|
|
132
|
+
|
|
133
|
+
## Notes
|
|
134
|
+
|
|
135
|
+
The native binary is resolved automatically for your platform via an optional
|
|
136
|
+
dependency selected at install time; `npm install netzilo` never downloads a
|
|
137
|
+
binary for a platform you're not on.
|
|
138
|
+
|
|
139
|
+
Netzilo is a commercial product. See <https://www.netzilo.com>.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export declare const DEFAULT_CONFIG_PATH: string;
|
|
2
|
+
export interface StartOptions {
|
|
3
|
+
/** Management server URL (required on first login). Legacy alias: managementUrl. */
|
|
4
|
+
server?: string;
|
|
5
|
+
managementUrl?: string;
|
|
6
|
+
/** Non-interactive login — one of pat/setupKey required on first login. */
|
|
7
|
+
pat?: string;
|
|
8
|
+
setupKey?: string;
|
|
9
|
+
/** Peer/device name + event attribution (default: hostname). */
|
|
10
|
+
agentName?: string;
|
|
11
|
+
/** Config file path (default: ~/.netzilo-sdk/config.json). */
|
|
12
|
+
configPath?: string;
|
|
13
|
+
/** Scanner gateway port (default: an auto-picked free port). Set 0 to disable governance. */
|
|
14
|
+
mcpGatewayPort?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Deep inspection of the agent's OWN outbound traffic (beyond framework
|
|
17
|
+
* hooks): full prompt/response and tool-call analysis with semantic
|
|
18
|
+
* classification. Default off; framework adapters govern without it.
|
|
19
|
+
*/
|
|
20
|
+
enableAdvancedGovernance?: boolean;
|
|
21
|
+
socks5Port?: number;
|
|
22
|
+
webServPort?: number;
|
|
23
|
+
logLevel?: string;
|
|
24
|
+
logFile?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Start the embedded client. Non-blocking; the client runs in a background
|
|
28
|
+
* goroutine inside the shared library.
|
|
29
|
+
*
|
|
30
|
+
* Returns 0 on launch, 1 if already running, 2 on bad config.
|
|
31
|
+
*/
|
|
32
|
+
export declare function start(opts?: StartOptions): Promise<number>;
|
|
33
|
+
/**
|
|
34
|
+
* Port the in-process scanner (MCP) gateway is listening on. Auto-picked at
|
|
35
|
+
* start() unless one was passed explicitly. Returns 0 if not started or
|
|
36
|
+
* governance is disabled (mcpGatewayPort: 0).
|
|
37
|
+
*/
|
|
38
|
+
export declare function mcpGatewayPort(): number;
|
|
39
|
+
/** Egress port used by advanced governance. Returns 0 if not started. */
|
|
40
|
+
export declare function socks5Port(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Gracefully stop the client (context cancel). Safe to call repeatedly.
|
|
43
|
+
*
|
|
44
|
+
* Stop() flushes any buffered governance events to management first, so a
|
|
45
|
+
* short-lived embedded run delivers its events before teardown.
|
|
46
|
+
*/
|
|
47
|
+
export declare function stop(): number;
|
|
48
|
+
/**
|
|
49
|
+
* Force an immediate push of buffered governance events to management.
|
|
50
|
+
*
|
|
51
|
+
* Events are normally delivered in the background (near-real-time in
|
|
52
|
+
* embedded mode); call this to guarantee delivery on demand. No-op on older
|
|
53
|
+
* bundled libraries without the export.
|
|
54
|
+
*/
|
|
55
|
+
export declare function flush(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Force an immediate AIDR session-graph snapshot + delivery to management.
|
|
58
|
+
*
|
|
59
|
+
* The behavior graph (agents -> tools -> prompts -> redactions) is otherwise
|
|
60
|
+
* only uploaded hourly and at shutdown. No-op on older bundled libraries.
|
|
61
|
+
*/
|
|
62
|
+
export declare function snapshot(): void;
|
|
63
|
+
/** Whether the embedded client is currently running. */
|
|
64
|
+
export declare function isRunning(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Current agent integrity level: 0=breached, 1=low, 2=medium (default), 3=high.
|
|
67
|
+
*
|
|
68
|
+
* Reflects whatever posture rules (set_integrity_level) or host integrity
|
|
69
|
+
* checks have set in-process.
|
|
70
|
+
*/
|
|
71
|
+
export declare function integrityLevel(): number;
|
|
72
|
+
export type Action = "toolCall" | "toolResult" | "promptRequest" | "promptResponse";
|
|
73
|
+
export interface EvaluateOptions {
|
|
74
|
+
args?: Record<string, unknown>;
|
|
75
|
+
output?: string;
|
|
76
|
+
prompt?: string;
|
|
77
|
+
response?: string;
|
|
78
|
+
history?: unknown[];
|
|
79
|
+
provider?: string;
|
|
80
|
+
model?: string;
|
|
81
|
+
durationMs?: number;
|
|
82
|
+
callId?: string;
|
|
83
|
+
source?: string;
|
|
84
|
+
serverName?: string;
|
|
85
|
+
serverUrl?: string;
|
|
86
|
+
callerPid?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface Verdict {
|
|
89
|
+
action: "allow" | "block" | "require_approval";
|
|
90
|
+
reason?: string;
|
|
91
|
+
redacted?: boolean;
|
|
92
|
+
content?: string;
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run one hook event through the in-process scanner + AIDR pipeline.
|
|
97
|
+
*
|
|
98
|
+
* action:
|
|
99
|
+
* "toolCall" pre-tool, may block (args)
|
|
100
|
+
* "toolResult" post-tool, fire-and-forget (output)
|
|
101
|
+
* "promptRequest" LLM prompt before send (prompt, history)
|
|
102
|
+
* "promptResponse" LLM response before use (response)
|
|
103
|
+
*
|
|
104
|
+
* Fails open ({action: "allow"}) if the scanner is not yet running.
|
|
105
|
+
*/
|
|
106
|
+
export declare function evaluate(action: Action, toolName?: string, opts?: EvaluateOptions): Verdict;
|
|
107
|
+
/** Convenience pre-tool gate. Returns {allowed, reason}. */
|
|
108
|
+
export declare function isAllowed(toolName: string, args?: Record<string, unknown>, opts?: Pick<EvaluateOptions, "callId" | "source" | "serverUrl" | "callerPid">): {
|
|
109
|
+
allowed: boolean;
|
|
110
|
+
reason: string;
|
|
111
|
+
};
|
|
112
|
+
/** Convenience post-tool observability report (never blocks). */
|
|
113
|
+
export declare function reportResult(toolName: string, output: string, opts?: Pick<EvaluateOptions, "callId" | "source" | "callerPid">): void;
|
|
114
|
+
/**
|
|
115
|
+
* Gate an outbound LLM prompt before it is sent to the model.
|
|
116
|
+
*
|
|
117
|
+
* Returns {allowed, reason, redactedPrompt}. redactedPrompt is non-null when
|
|
118
|
+
* the scanner sanitized the prompt — the caller should send that text instead.
|
|
119
|
+
*/
|
|
120
|
+
export declare function checkPrompt(prompt: string, opts?: Pick<EvaluateOptions, "history" | "provider" | "model" | "callId" | "source" | "callerPid">): {
|
|
121
|
+
allowed: boolean;
|
|
122
|
+
reason: string;
|
|
123
|
+
redactedPrompt: string | null;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Gate an LLM response before the agent consumes it.
|
|
127
|
+
*
|
|
128
|
+
* Returns {allowed, reason, redactedResponse}. redactedResponse is non-null
|
|
129
|
+
* when the scanner sanitized the response — the caller should use that text.
|
|
130
|
+
*/
|
|
131
|
+
export declare function checkResponse(response: string, opts?: Pick<EvaluateOptions, "prompt" | "provider" | "model" | "durationMs" | "callId" | "source" | "callerPid">): {
|
|
132
|
+
allowed: boolean;
|
|
133
|
+
reason: string;
|
|
134
|
+
redactedResponse: string | null;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* One-call, fire-and-forget governance for any Node agent framework.
|
|
138
|
+
*
|
|
139
|
+
* Unlike a framework-specific adapter (which hooks that framework's tool-call
|
|
140
|
+
* boundary), govern() just starts the embedded client with advanced
|
|
141
|
+
* governance on — the agent's own LLM traffic, egress, and subprocess
|
|
142
|
+
* behavior are inspected in-process regardless of which framework issues
|
|
143
|
+
* them. Idempotent (no-op if already running). Config comes from NETZILO_*
|
|
144
|
+
* env vars, overridable via opts.
|
|
145
|
+
*
|
|
146
|
+
* import { govern } from "netzilo";
|
|
147
|
+
* await govern();
|
|
148
|
+
*/
|
|
149
|
+
export declare function govern(opts?: StartOptions): Promise<number>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
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.DEFAULT_CONFIG_PATH = void 0;
|
|
37
|
+
exports.start = start;
|
|
38
|
+
exports.mcpGatewayPort = mcpGatewayPort;
|
|
39
|
+
exports.socks5Port = socks5Port;
|
|
40
|
+
exports.stop = stop;
|
|
41
|
+
exports.flush = flush;
|
|
42
|
+
exports.snapshot = snapshot;
|
|
43
|
+
exports.isRunning = isRunning;
|
|
44
|
+
exports.integrityLevel = integrityLevel;
|
|
45
|
+
exports.evaluate = evaluate;
|
|
46
|
+
exports.isAllowed = isAllowed;
|
|
47
|
+
exports.reportResult = reportResult;
|
|
48
|
+
exports.checkPrompt = checkPrompt;
|
|
49
|
+
exports.checkResponse = checkResponse;
|
|
50
|
+
exports.govern = govern;
|
|
51
|
+
/**
|
|
52
|
+
* netzilo — embed the full Netzilo client in-process for AI agent governance.
|
|
53
|
+
*
|
|
54
|
+
* The core module loads the bundled Netzilo shared library and exposes an API
|
|
55
|
+
* that mirrors the C exports (Start/Stop/IsRunning):
|
|
56
|
+
*
|
|
57
|
+
* import * as netzilo from "netzilo";
|
|
58
|
+
* netzilo.start({ server: "...", setupKey: "..." }); // non-blocking
|
|
59
|
+
* netzilo.stop(); // graceful stop
|
|
60
|
+
* netzilo.isRunning(); // boolean
|
|
61
|
+
* await netzilo.evaluate(...); // per-tool-call verdict, in-process (no HTTP)
|
|
62
|
+
* await netzilo.isAllowed(...); // convenience: {allowed, reason}
|
|
63
|
+
*
|
|
64
|
+
* Framework adapters live in their own entry points under the same package —
|
|
65
|
+
* no separate installs — e.g. `import { govern } from "netzilo/langchain"`.
|
|
66
|
+
* They build on evaluate()/isAllowed() to gate tool calls at the framework
|
|
67
|
+
* boundary. With enableAdvancedGovernance=true, start() additionally governs
|
|
68
|
+
* the agent's own outbound traffic in depth (see probe.ts / trust.ts).
|
|
69
|
+
*/
|
|
70
|
+
const net = __importStar(require("net"));
|
|
71
|
+
const os = __importStar(require("os"));
|
|
72
|
+
const path = __importStar(require("path"));
|
|
73
|
+
const loader_1 = require("./loader");
|
|
74
|
+
// Default config location — callers shouldn't have to specify it.
|
|
75
|
+
exports.DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".netzilo-sdk", "config.json");
|
|
76
|
+
// Ports actually used by the running client, filled in by start(). Exposed via
|
|
77
|
+
// mcpGatewayPort()/socks5Port() so the host can route traffic / introspect.
|
|
78
|
+
const resolvedPorts = { mcpGatewayPort: 0, socks5Port: 0 };
|
|
79
|
+
// Config directory of the running client (dirname of configPath), set by
|
|
80
|
+
// start(). Used by the advanced-governance setup to locate per-install state.
|
|
81
|
+
let configDir = "";
|
|
82
|
+
let exitHookRegistered = false;
|
|
83
|
+
async function freePorts(n) {
|
|
84
|
+
// Pick n distinct currently-free localhost TCP ports. Servers are held
|
|
85
|
+
// listening simultaneously so the returned ports can't collide with each
|
|
86
|
+
// other, then closed for the client to bind.
|
|
87
|
+
const servers = [];
|
|
88
|
+
try {
|
|
89
|
+
for (let i = 0; i < n; i++) {
|
|
90
|
+
const server = net.createServer();
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
server.once("error", reject);
|
|
93
|
+
server.listen(0, "127.0.0.1", () => resolve());
|
|
94
|
+
});
|
|
95
|
+
servers.push(server);
|
|
96
|
+
}
|
|
97
|
+
return servers.map((s) => s.address().port);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
await Promise.all(servers.map((s) => new Promise((resolve) => {
|
|
101
|
+
s.close(() => resolve());
|
|
102
|
+
})));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Start the embedded client. Non-blocking; the client runs in a background
|
|
107
|
+
* goroutine inside the shared library.
|
|
108
|
+
*
|
|
109
|
+
* Returns 0 on launch, 1 if already running, 2 on bad config.
|
|
110
|
+
*/
|
|
111
|
+
async function start(opts = {}) {
|
|
112
|
+
const cfg = { config_path: opts.configPath || exports.DEFAULT_CONFIG_PATH };
|
|
113
|
+
const server = opts.server ?? opts.managementUrl;
|
|
114
|
+
if (server) {
|
|
115
|
+
cfg.management_url = server;
|
|
116
|
+
}
|
|
117
|
+
if (opts.pat)
|
|
118
|
+
cfg.pat = opts.pat;
|
|
119
|
+
if (opts.setupKey)
|
|
120
|
+
cfg.setup_key = opts.setupKey;
|
|
121
|
+
if (opts.agentName)
|
|
122
|
+
cfg.agent_name = opts.agentName;
|
|
123
|
+
if (opts.enableAdvancedGovernance !== undefined) {
|
|
124
|
+
cfg.enable_advanced_governance = opts.enableAdvancedGovernance;
|
|
125
|
+
}
|
|
126
|
+
if (opts.webServPort !== undefined)
|
|
127
|
+
cfg.web_serv_port = opts.webServPort;
|
|
128
|
+
if (opts.logLevel)
|
|
129
|
+
cfg.log_level = opts.logLevel;
|
|
130
|
+
if (opts.logFile)
|
|
131
|
+
cfg.log_file = opts.logFile;
|
|
132
|
+
// Auto-pick free ports for the scanner gateway and the advanced-governance
|
|
133
|
+
// egress when unset, so they never collide with Netzilo.app or a second SDK
|
|
134
|
+
// process. Pick both at once so they can't clash with each other. Pass an
|
|
135
|
+
// explicit port to override, or mcpGatewayPort: 0 to disable governance.
|
|
136
|
+
const needMcp = opts.mcpGatewayPort === undefined;
|
|
137
|
+
const needSocks = opts.socks5Port === undefined;
|
|
138
|
+
if (needMcp || needSocks) {
|
|
139
|
+
const picked = await freePorts((needMcp ? 1 : 0) + (needSocks ? 1 : 0));
|
|
140
|
+
let i = 0;
|
|
141
|
+
cfg.mcp_gateway_port = needMcp ? picked[i++] : opts.mcpGatewayPort;
|
|
142
|
+
cfg.socks5_port = needSocks ? picked[i++] : opts.socks5Port;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
cfg.mcp_gateway_port = opts.mcpGatewayPort;
|
|
146
|
+
cfg.socks5_port = opts.socks5Port;
|
|
147
|
+
}
|
|
148
|
+
// Advanced governance: publish the security-probe socket path BEFORE the
|
|
149
|
+
// native client starts so its receiver binds the exact path the probe
|
|
150
|
+
// connects to (no startup race). Linux-only; no-op otherwise. Gated on
|
|
151
|
+
// advanced mode — with it off, the probe socket is never primed and no
|
|
152
|
+
// hooks are installed.
|
|
153
|
+
if (cfg.enable_advanced_governance) {
|
|
154
|
+
const { primeSocketEnv } = await Promise.resolve().then(() => __importStar(require("./probe")));
|
|
155
|
+
primeSocketEnv(path.dirname(cfg.config_path));
|
|
156
|
+
}
|
|
157
|
+
const lib = (0, loader_1.load)();
|
|
158
|
+
const rc = lib.Start(JSON.stringify(cfg));
|
|
159
|
+
if (rc === 0) {
|
|
160
|
+
resolvedPorts.mcpGatewayPort = cfg.mcp_gateway_port || 0;
|
|
161
|
+
resolvedPorts.socks5Port = cfg.socks5_port || 0;
|
|
162
|
+
configDir = path.dirname(cfg.config_path);
|
|
163
|
+
if (!exitHookRegistered) {
|
|
164
|
+
exitHookRegistered = true;
|
|
165
|
+
// process 'exit' handlers must be synchronous; Stop() is a synchronous
|
|
166
|
+
// FFI call via koffi, so a plain handler here is sufficient.
|
|
167
|
+
process.on("exit", () => {
|
|
168
|
+
stop();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (cfg.enable_advanced_governance) {
|
|
172
|
+
await enableAdvancedGovernance();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return rc;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Internal: prepare the process for deep inspection of the agent's own
|
|
179
|
+
* traffic. Idempotent; never throws (must not break the agent).
|
|
180
|
+
*/
|
|
181
|
+
async function enableAdvancedGovernance() {
|
|
182
|
+
if (!configDir) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const { installTrust, installRouting } = await Promise.resolve().then(() => __importStar(require("./trust")));
|
|
187
|
+
await installTrust(configDir);
|
|
188
|
+
installRouting(resolvedPorts.socks5Port);
|
|
189
|
+
// Inject the userspace security probe (Linux only) so the agent's process
|
|
190
|
+
// + subprocess activity is reported to the native client's receiver,
|
|
191
|
+
// mirroring the macOS Endpoint Security extension. Only reached under
|
|
192
|
+
// advanced governance.
|
|
193
|
+
const { install: installProbe } = await Promise.resolve().then(() => __importStar(require("./probe")));
|
|
194
|
+
installProbe(configDir);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
// Advanced-governance setup must never break the agent.
|
|
198
|
+
// eslint-disable-next-line no-console
|
|
199
|
+
console.warn("netzilo: advanced governance setup failed:", err);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Port the in-process scanner (MCP) gateway is listening on. Auto-picked at
|
|
204
|
+
* start() unless one was passed explicitly. Returns 0 if not started or
|
|
205
|
+
* governance is disabled (mcpGatewayPort: 0).
|
|
206
|
+
*/
|
|
207
|
+
function mcpGatewayPort() {
|
|
208
|
+
return resolvedPorts.mcpGatewayPort;
|
|
209
|
+
}
|
|
210
|
+
/** Egress port used by advanced governance. Returns 0 if not started. */
|
|
211
|
+
function socks5Port() {
|
|
212
|
+
return resolvedPorts.socks5Port;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Gracefully stop the client (context cancel). Safe to call repeatedly.
|
|
216
|
+
*
|
|
217
|
+
* Stop() flushes any buffered governance events to management first, so a
|
|
218
|
+
* short-lived embedded run delivers its events before teardown.
|
|
219
|
+
*/
|
|
220
|
+
function stop() {
|
|
221
|
+
const lib = (0, loader_1.load)();
|
|
222
|
+
const rc = lib.Stop();
|
|
223
|
+
resolvedPorts.mcpGatewayPort = 0;
|
|
224
|
+
resolvedPorts.socks5Port = 0;
|
|
225
|
+
// Undo advanced-governance traffic routing so a host process that keeps
|
|
226
|
+
// running after stop() doesn't route to the now-closed egress port.
|
|
227
|
+
try {
|
|
228
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
229
|
+
const { uninstallRouting } = require("./trust");
|
|
230
|
+
uninstallRouting();
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// stop() must never throw
|
|
234
|
+
}
|
|
235
|
+
// Stop injecting the probe into subprocesses spawned after stop().
|
|
236
|
+
try {
|
|
237
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
238
|
+
const { uninstall: uninstallProbe } = require("./probe");
|
|
239
|
+
uninstallProbe();
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// stop() must never throw
|
|
243
|
+
}
|
|
244
|
+
return rc;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Force an immediate push of buffered governance events to management.
|
|
248
|
+
*
|
|
249
|
+
* Events are normally delivered in the background (near-real-time in
|
|
250
|
+
* embedded mode); call this to guarantee delivery on demand. No-op on older
|
|
251
|
+
* bundled libraries without the export.
|
|
252
|
+
*/
|
|
253
|
+
function flush() {
|
|
254
|
+
(0, loader_1.load)().FlushEvents();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Force an immediate AIDR session-graph snapshot + delivery to management.
|
|
258
|
+
*
|
|
259
|
+
* The behavior graph (agents -> tools -> prompts -> redactions) is otherwise
|
|
260
|
+
* only uploaded hourly and at shutdown. No-op on older bundled libraries.
|
|
261
|
+
*/
|
|
262
|
+
function snapshot() {
|
|
263
|
+
(0, loader_1.load)().FlushSnapshot();
|
|
264
|
+
}
|
|
265
|
+
/** Whether the embedded client is currently running. */
|
|
266
|
+
function isRunning() {
|
|
267
|
+
return (0, loader_1.load)().IsRunning() !== 0;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Current agent integrity level: 0=breached, 1=low, 2=medium (default), 3=high.
|
|
271
|
+
*
|
|
272
|
+
* Reflects whatever posture rules (set_integrity_level) or host integrity
|
|
273
|
+
* checks have set in-process.
|
|
274
|
+
*/
|
|
275
|
+
function integrityLevel() {
|
|
276
|
+
return (0, loader_1.load)().GetIntegrityLevel();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Run one hook event through the in-process scanner + AIDR pipeline.
|
|
280
|
+
*
|
|
281
|
+
* action:
|
|
282
|
+
* "toolCall" pre-tool, may block (args)
|
|
283
|
+
* "toolResult" post-tool, fire-and-forget (output)
|
|
284
|
+
* "promptRequest" LLM prompt before send (prompt, history)
|
|
285
|
+
* "promptResponse" LLM response before use (response)
|
|
286
|
+
*
|
|
287
|
+
* Fails open ({action: "allow"}) if the scanner is not yet running.
|
|
288
|
+
*/
|
|
289
|
+
function evaluate(action, toolName = "", opts = {}) {
|
|
290
|
+
const lib = (0, loader_1.load)();
|
|
291
|
+
const source = opts.source ?? "agentsdk";
|
|
292
|
+
const req = {
|
|
293
|
+
action,
|
|
294
|
+
tool_name: toolName,
|
|
295
|
+
server_name: opts.serverName ?? source,
|
|
296
|
+
call_id: opts.callId ?? "",
|
|
297
|
+
source,
|
|
298
|
+
server_url: opts.serverUrl ?? "",
|
|
299
|
+
caller_pid: opts.callerPid || process.pid,
|
|
300
|
+
};
|
|
301
|
+
if (opts.args !== undefined) {
|
|
302
|
+
req.args = opts.args;
|
|
303
|
+
}
|
|
304
|
+
if (opts.output !== undefined)
|
|
305
|
+
req.output = opts.output;
|
|
306
|
+
if (opts.prompt !== undefined)
|
|
307
|
+
req.prompt = opts.prompt;
|
|
308
|
+
if (opts.response !== undefined)
|
|
309
|
+
req.response = opts.response;
|
|
310
|
+
if (opts.history && opts.history.length) {
|
|
311
|
+
req.history = opts.history.map((h) => String(h));
|
|
312
|
+
}
|
|
313
|
+
if (opts.provider)
|
|
314
|
+
req.provider = opts.provider;
|
|
315
|
+
if (opts.model)
|
|
316
|
+
req.model = opts.model;
|
|
317
|
+
if (opts.durationMs)
|
|
318
|
+
req.duration_ms = opts.durationMs;
|
|
319
|
+
const raw = lib.Evaluate(JSON.stringify(req));
|
|
320
|
+
return raw ? JSON.parse(raw) : { action: "allow" };
|
|
321
|
+
}
|
|
322
|
+
/** Convenience pre-tool gate. Returns {allowed, reason}. */
|
|
323
|
+
function isAllowed(toolName, args = {}, opts = {}) {
|
|
324
|
+
const verdict = evaluate("toolCall", toolName, { ...opts, args });
|
|
325
|
+
const action = verdict.action ?? "allow";
|
|
326
|
+
return { allowed: action === "allow", reason: verdict.reason ?? "" };
|
|
327
|
+
}
|
|
328
|
+
/** Convenience post-tool observability report (never blocks). */
|
|
329
|
+
function reportResult(toolName, output, opts = {}) {
|
|
330
|
+
evaluate("toolResult", toolName, { ...opts, output });
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Gate an outbound LLM prompt before it is sent to the model.
|
|
334
|
+
*
|
|
335
|
+
* Returns {allowed, reason, redactedPrompt}. redactedPrompt is non-null when
|
|
336
|
+
* the scanner sanitized the prompt — the caller should send that text instead.
|
|
337
|
+
*/
|
|
338
|
+
function checkPrompt(prompt, opts = {}) {
|
|
339
|
+
const v = evaluate("promptRequest", "", { ...opts, prompt });
|
|
340
|
+
const allowed = (v.action ?? "allow") === "allow";
|
|
341
|
+
const redactedPrompt = v.redacted ? v.content ?? null : null;
|
|
342
|
+
return { allowed, reason: v.reason ?? "", redactedPrompt };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Gate an LLM response before the agent consumes it.
|
|
346
|
+
*
|
|
347
|
+
* Returns {allowed, reason, redactedResponse}. redactedResponse is non-null
|
|
348
|
+
* when the scanner sanitized the response — the caller should use that text.
|
|
349
|
+
*/
|
|
350
|
+
function checkResponse(response, opts = {}) {
|
|
351
|
+
const v = evaluate("promptResponse", "", { ...opts, response });
|
|
352
|
+
const allowed = (v.action ?? "allow") === "allow";
|
|
353
|
+
const redactedResponse = v.redacted ? v.content ?? null : null;
|
|
354
|
+
return { allowed, reason: v.reason ?? "", redactedResponse };
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* One-call, fire-and-forget governance for any Node agent framework.
|
|
358
|
+
*
|
|
359
|
+
* Unlike a framework-specific adapter (which hooks that framework's tool-call
|
|
360
|
+
* boundary), govern() just starts the embedded client with advanced
|
|
361
|
+
* governance on — the agent's own LLM traffic, egress, and subprocess
|
|
362
|
+
* behavior are inspected in-process regardless of which framework issues
|
|
363
|
+
* them. Idempotent (no-op if already running). Config comes from NETZILO_*
|
|
364
|
+
* env vars, overridable via opts.
|
|
365
|
+
*
|
|
366
|
+
* import { govern } from "netzilo";
|
|
367
|
+
* await govern();
|
|
368
|
+
*/
|
|
369
|
+
async function govern(opts = {}) {
|
|
370
|
+
if (isRunning()) {
|
|
371
|
+
return 1;
|
|
372
|
+
}
|
|
373
|
+
const cfg = {
|
|
374
|
+
server: process.env.NETZILO_SERVER || "https://srv.netzilo.com",
|
|
375
|
+
agentName: process.env.NETZILO_AGENT_NAME,
|
|
376
|
+
enableAdvancedGovernance: process.env.NETZILO_ADVANCED !== "0",
|
|
377
|
+
logLevel: process.env.NETZILO_LOG_LEVEL || "info",
|
|
378
|
+
logFile: "console",
|
|
379
|
+
...(process.env.NETZILO_SETUP_KEY ? { setupKey: process.env.NETZILO_SETUP_KEY } : {}),
|
|
380
|
+
...(process.env.NETZILO_PAT ? { pat: process.env.NETZILO_PAT } : {}),
|
|
381
|
+
...opts,
|
|
382
|
+
};
|
|
383
|
+
return start(cfg);
|
|
384
|
+
}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface NetziloLib {
|
|
2
|
+
Start(configJSON: string): number;
|
|
3
|
+
Stop(): number;
|
|
4
|
+
IsRunning(): number;
|
|
5
|
+
/**
|
|
6
|
+
* Returns the decoded JSON string directly, or null. Evaluate's C return
|
|
7
|
+
* value is a heap-allocated char* that must be released with FreeString —
|
|
8
|
+
* bound as a koffi "disposable type" (see below), so koffi calls FreeString
|
|
9
|
+
* on the original pointer automatically right after decoding it to a JS
|
|
10
|
+
* string. Callers never see or manage the pointer.
|
|
11
|
+
*/
|
|
12
|
+
Evaluate(reqJSON: string): string | null;
|
|
13
|
+
/** Absent on older bundled libraries; guarded with a no-op fallback. */
|
|
14
|
+
FlushEvents(): void;
|
|
15
|
+
FlushSnapshot(): void;
|
|
16
|
+
GetIntegrityLevel(): number;
|
|
17
|
+
}
|
|
18
|
+
export declare function load(): NetziloLib;
|
|
19
|
+
/** Test-only: forget the cached binding so a fresh NETZILO_LIB_PATH can be picked up. */
|
|
20
|
+
export declare function _resetForTests(): void;
|
|
21
|
+
export declare function tmpDir(): string;
|