@envseal/sdk 0.1.2 → 0.1.4
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/dist/confirm.d.ts +19 -8
- package/dist/confirm.js +62 -60
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -3
- package/package.json +5 -5
package/dist/confirm.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { escapeForDisplay as coreEscapeForDisplay, displayArg as coreDisplayArg, type BrokerOptions, type TargetInfo } from '@envseal/core';
|
|
2
2
|
import type { ManifestEntry, VerifyResult } from '@envseal/protocol';
|
|
3
3
|
import type { Prompter } from '@envseal/prompters';
|
|
4
4
|
/**
|
|
@@ -18,16 +18,16 @@ import type { Prompter } from '@envseal/prompters';
|
|
|
18
18
|
* between a prompt-injected model and arbitrary code holding live
|
|
19
19
|
* credentials. In CI these operations are simply unavailable, and say so.
|
|
20
20
|
*
|
|
21
|
-
* DUPLICATION: this file
|
|
22
|
-
* packages/mcp-server/src/confirm.ts
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* packages test the behaviour independently, so drift shows up as a red test
|
|
27
|
-
* rather than as a binding that quietly stops asking.
|
|
21
|
+
* DUPLICATION: this file remains a hand-maintained twin of
|
|
22
|
+
* packages/mcp-server/src/confirm.ts for the ask/outcome mapping, but the dialog
|
|
23
|
+
* BODY (escaping, truncation caps, fingerprints, warnings) is shared via
|
|
24
|
+
* useConfirmationBody from @envseal/core so the CLI, SDK and MCP surfaces
|
|
25
|
+
* can never drift on what the user is shown.
|
|
28
26
|
*/
|
|
29
27
|
/** Value-entry key name carrying the `env_use` confirmation. */
|
|
30
28
|
export declare const CONFIRM_KEY_USE = "APPROVE";
|
|
29
|
+
/** Value-entry key name carrying the `env_revoke` confirmation. */
|
|
30
|
+
export declare const CONFIRM_KEY_REVOKE = "APPROVE_REVOKE";
|
|
31
31
|
/** Value-entry key name carrying the `env_verify` probe-consent question. */
|
|
32
32
|
export declare const CONFIRM_KEY_PROBE = "APPROVE_PROBE";
|
|
33
33
|
export interface ConfirmSurface {
|
|
@@ -40,11 +40,15 @@ export interface ConfirmSurface {
|
|
|
40
40
|
prompter: () => Promise<Prompter>;
|
|
41
41
|
timeoutMs?: number;
|
|
42
42
|
}
|
|
43
|
+
export declare const escapeForDisplay: typeof coreEscapeForDisplay;
|
|
44
|
+
export declare const displayArg: typeof coreDisplayArg;
|
|
43
45
|
export declare function useConfirmationBody(info: {
|
|
44
46
|
command: string[];
|
|
45
47
|
keys: string[];
|
|
46
48
|
networkEgress: boolean;
|
|
49
|
+
target?: TargetInfo;
|
|
47
50
|
}, projectRoot: string): string;
|
|
51
|
+
export declare function revokeConfirmationBody(keys: string[], projectRoot: string): string;
|
|
48
52
|
export declare function probeConfirmationBody(entry: ManifestEntry): string | null;
|
|
49
53
|
/**
|
|
50
54
|
* `onConfirm` for the Broker: gates `env_use`.
|
|
@@ -55,6 +59,13 @@ export declare function probeConfirmationBody(entry: ManifestEntry): string | nu
|
|
|
55
59
|
* or for a silence — the defect this project already fixed once in the CLI.
|
|
56
60
|
*/
|
|
57
61
|
export declare function createUseConfirm(surface: ConfirmSurface): NonNullable<BrokerOptions['onConfirm']>;
|
|
62
|
+
/**
|
|
63
|
+
* `onRevokeConfirm` for the Broker: gates `env_revoke`.
|
|
64
|
+
*
|
|
65
|
+
* Same ask/outcome mapping as createUseConfirm. Throws rather than returning
|
|
66
|
+
* false when no human could be asked or when the ask expired unanswered.
|
|
67
|
+
*/
|
|
68
|
+
export declare function createRevokeConfirm(surface: ConfirmSurface): NonNullable<BrokerOptions['onRevokeConfirm']>;
|
|
58
69
|
/**
|
|
59
70
|
* `onApprovalNeeded` for the Broker: PLAN.md §6.4 probe consent for
|
|
60
71
|
* `env_verify` against a host that is not registry-allowlisted.
|
package/dist/confirm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { useConfirmationBody as sharedUseConfirmationBody, revokeConfirmationBody as sharedRevokeConfirmationBody, escapeForDisplay as coreEscapeForDisplay, displayArg as coreDisplayArg, } from '@envseal/core';
|
|
2
3
|
import { SepError, zero } from '@envseal/protocol';
|
|
3
4
|
import { makeDisplayNonce } from '@envseal/prompters';
|
|
4
5
|
/**
|
|
@@ -18,21 +19,19 @@ import { makeDisplayNonce } from '@envseal/prompters';
|
|
|
18
19
|
* between a prompt-injected model and arbitrary code holding live
|
|
19
20
|
* credentials. In CI these operations are simply unavailable, and say so.
|
|
20
21
|
*
|
|
21
|
-
* DUPLICATION: this file
|
|
22
|
-
* packages/mcp-server/src/confirm.ts
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* packages test the behaviour independently, so drift shows up as a red test
|
|
27
|
-
* rather than as a binding that quietly stops asking.
|
|
22
|
+
* DUPLICATION: this file remains a hand-maintained twin of
|
|
23
|
+
* packages/mcp-server/src/confirm.ts for the ask/outcome mapping, but the dialog
|
|
24
|
+
* BODY (escaping, truncation caps, fingerprints, warnings) is shared via
|
|
25
|
+
* useConfirmationBody from @envseal/core so the CLI, SDK and MCP surfaces
|
|
26
|
+
* can never drift on what the user is shown.
|
|
28
27
|
*/
|
|
29
28
|
/** Value-entry key name carrying the `env_use` confirmation. */
|
|
30
29
|
export const CONFIRM_KEY_USE = 'APPROVE';
|
|
30
|
+
/** Value-entry key name carrying the `env_revoke` confirmation. */
|
|
31
|
+
export const CONFIRM_KEY_REVOKE = 'APPROVE_REVOKE';
|
|
31
32
|
/** Value-entry key name carrying the `env_verify` probe-consent question. */
|
|
32
33
|
export const CONFIRM_KEY_PROBE = 'APPROVE_PROBE';
|
|
33
34
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
34
|
-
/** Per-argument display cap; longer arguments are shown truncated, and said to be. */
|
|
35
|
-
const MAX_ARG_CHARS = 300;
|
|
36
35
|
/** Whole-dialog cap. Past this we refuse rather than ask about something unreadable. */
|
|
37
36
|
const MAX_BODY_CHARS = 16 * 1024;
|
|
38
37
|
const INSTRUCTION = 'Type yes to approve, or submit an empty box to deny.';
|
|
@@ -41,57 +40,16 @@ const INSTRUCTION = 'Type yes to approve, or submit an empty box to deny.';
|
|
|
41
40
|
* `env_use` in a loop and stack up dialogs until one gets clicked through.
|
|
42
41
|
*/
|
|
43
42
|
let confirmationOpen = false;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
function escapeForDisplay(value) {
|
|
51
|
-
let out = '';
|
|
52
|
-
for (const ch of value) {
|
|
53
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
54
|
-
if (code < 0x20 || (code >= 0x7f && code <= 0x9f)) {
|
|
55
|
-
out += `<0x${code.toString(16).padStart(2, '0')}>`;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
out += ch;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
function displayArg(arg) {
|
|
64
|
-
const escaped = escapeForDisplay(arg);
|
|
65
|
-
if (escaped.length <= MAX_ARG_CHARS) {
|
|
66
|
-
return escaped;
|
|
67
|
-
}
|
|
68
|
-
const hidden = escaped.length - MAX_ARG_CHARS;
|
|
69
|
-
return `${escaped.slice(0, MAX_ARG_CHARS)}[... ${hidden} more characters, not shown]`;
|
|
70
|
-
}
|
|
43
|
+
// Re-exported so existing importers of the twins keep working; the
|
|
44
|
+
// implementations live in @envseal/core/display.ts and are shared across
|
|
45
|
+
// every binding (CLI, SDK and MCP alike).
|
|
46
|
+
export const escapeForDisplay = coreEscapeForDisplay;
|
|
47
|
+
export const displayArg = coreDisplayArg;
|
|
71
48
|
export function useConfirmationBody(info, projectRoot) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
` keys: ${info.keys.length > 0 ? info.keys.map(escapeForDisplay).join(', ') : '(none)'}`,
|
|
77
|
-
'',
|
|
78
|
-
' command, one argument per line, exactly as it will be run (no shell):',
|
|
79
|
-
];
|
|
80
|
-
info.command.forEach((arg, index) => {
|
|
81
|
-
lines.push(` [${index}] ${displayArg(arg)}`);
|
|
82
|
-
});
|
|
83
|
-
lines.push('');
|
|
84
|
-
if (info.networkEgress) {
|
|
85
|
-
lines.push(' WARNING: this command can reach the network, so it could send these', ' values somewhere. Only continue if you trust it.');
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
// Honest about what the check is worth: NETWORK_TOOLS plus a URL scan is a
|
|
89
|
-
// heuristic, and claiming more would be the kind of overstatement this
|
|
90
|
-
// project has already had to walk back once.
|
|
91
|
-
lines.push(' No network tool or URL was recognised in this command. That is a', ' heuristic, not a guarantee: any program can open a socket.');
|
|
92
|
-
}
|
|
93
|
-
lines.push('', `${INSTRUCTION} Nothing runs unless you approve.`);
|
|
94
|
-
return lines.join('\n');
|
|
49
|
+
return sharedUseConfirmationBody(info, projectRoot);
|
|
50
|
+
}
|
|
51
|
+
export function revokeConfirmationBody(keys, projectRoot) {
|
|
52
|
+
return sharedRevokeConfirmationBody(keys, projectRoot);
|
|
95
53
|
}
|
|
96
54
|
export function probeConfirmationBody(entry) {
|
|
97
55
|
const probe = entry.verify;
|
|
@@ -207,6 +165,50 @@ export function createUseConfirm(surface) {
|
|
|
207
165
|
}
|
|
208
166
|
};
|
|
209
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* `onRevokeConfirm` for the Broker: gates `env_revoke`.
|
|
170
|
+
*
|
|
171
|
+
* Same ask/outcome mapping as createUseConfirm. Throws rather than returning
|
|
172
|
+
* false when no human could be asked or when the ask expired unanswered.
|
|
173
|
+
*/
|
|
174
|
+
export function createRevokeConfirm(surface) {
|
|
175
|
+
return async (keys) => {
|
|
176
|
+
const outcome = await ask(surface, CONFIRM_KEY_REVOKE, 'Approve removing stored credentials? Nothing has been removed yet.', revokeConfirmationBody(keys, surface.projectRoot));
|
|
177
|
+
switch (outcome) {
|
|
178
|
+
case 'approved':
|
|
179
|
+
return true;
|
|
180
|
+
case 'denied':
|
|
181
|
+
return false;
|
|
182
|
+
case 'timed-out':
|
|
183
|
+
throw new SepError({
|
|
184
|
+
code: 'SEP_TICKET_EXPIRED',
|
|
185
|
+
userMessage: 'The env_revoke confirmation closed after its timeout with nobody answering it. Nothing was ' +
|
|
186
|
+
'removed. This is not a denial: ask the user to approve it, then call env_revoke again.',
|
|
187
|
+
});
|
|
188
|
+
case 'no-surface':
|
|
189
|
+
throw new SepError({
|
|
190
|
+
code: 'SEP_NO_INTERACTIVE_SURFACE',
|
|
191
|
+
userMessage: 'env_revoke needs the user to confirm before stored credentials are removed, ' +
|
|
192
|
+
'but there is no interactive surface here to ask on (this is what CI looks like to envseal). ' +
|
|
193
|
+
'Nothing was removed. ' +
|
|
194
|
+
'There is no flag or environment variable that skips this prompt in this binding: the request ' +
|
|
195
|
+
'came from a model, and the confirmation is the only control on it. ' +
|
|
196
|
+
'Run `envseal revoke` yourself in a session that has a browser or a terminal.',
|
|
197
|
+
});
|
|
198
|
+
case 'busy':
|
|
199
|
+
throw new SepError({
|
|
200
|
+
code: 'SEP_RATE_LIMITED',
|
|
201
|
+
userMessage: 'Another envseal confirmation is already open. Answer that one first, then call env_revoke again.',
|
|
202
|
+
});
|
|
203
|
+
case 'too-large':
|
|
204
|
+
throw new SepError({
|
|
205
|
+
code: 'SEP_FORMAT_INVALID',
|
|
206
|
+
userMessage: 'This revoke request is too large to display in a confirmation dialog, and envseal will not ask ' +
|
|
207
|
+
'anyone to approve something it cannot show them. Revoke fewer keys at once.',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
210
212
|
/**
|
|
211
213
|
* `onApprovalNeeded` for the Broker: PLAN.md §6.4 probe consent for
|
|
212
214
|
* `env_verify` against a host that is not registry-allowlisted.
|
|
@@ -219,7 +221,7 @@ export function createUseConfirm(surface) {
|
|
|
219
221
|
*/
|
|
220
222
|
export function createProbeApproval(surface) {
|
|
221
223
|
return async (entry) => {
|
|
222
|
-
const outcome = await ask(surface, CONFIRM_KEY_PROBE, `Approve sending ${entry.key} to a host that is not on envseal's allowlist? Nothing has been sent yet.`, probeConfirmationBody(entry));
|
|
224
|
+
const outcome = await ask(surface, CONFIRM_KEY_PROBE, `Approve sending ${escapeForDisplay(entry.key)} to a host that is not on envseal's allowlist? Nothing has been sent yet.`, probeConfirmationBody(entry));
|
|
223
225
|
return outcome === 'approved';
|
|
224
226
|
};
|
|
225
227
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,6 @@ export type Dialect = 'openai' | 'anthropic' | 'gemini';
|
|
|
12
12
|
export declare function toolsFor(dialect: Dialect): unknown[];
|
|
13
13
|
export declare function dispatch(broker: Broker, name: string, args: unknown): Promise<unknown>;
|
|
14
14
|
export { SEP_TOOL_NAMES } from '@envseal/protocol';
|
|
15
|
-
export { createUseConfirm, createProbeApproval, annotateVerifyResults, CONFIRM_KEY_USE, CONFIRM_KEY_PROBE, } from './confirm.js';
|
|
15
|
+
export { createUseConfirm, createRevokeConfirm, createProbeApproval, annotateVerifyResults, CONFIRM_KEY_USE, CONFIRM_KEY_REVOKE, CONFIRM_KEY_PROBE, } from './confirm.js';
|
|
16
16
|
export type { ConfirmSurface } from './confirm.js';
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Broker } from '@envseal/core';
|
|
|
3
3
|
import { selectPrompter } from '@envseal/prompters';
|
|
4
4
|
import { SEP_TOOL_NAMES, INPUT_SCHEMAS, isSepError, } from '@envseal/protocol';
|
|
5
5
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
6
|
-
import { annotateVerifyResults, createProbeApproval, createUseConfirm } from './confirm.js';
|
|
6
|
+
import { annotateVerifyResults, createProbeApproval, createRevokeConfirm, createUseConfirm } from './confirm.js';
|
|
7
7
|
export function createBroker(opts) {
|
|
8
8
|
const root = opts?.root ?? findProjectRoot(process.cwd());
|
|
9
9
|
// Resolved lazily and memoised: `selectPrompter()` is async and this factory
|
|
@@ -25,6 +25,7 @@ export function createBroker(opts) {
|
|
|
25
25
|
// reports the absent callback as SEP_CONFIRMATION_DENIED — blaming a user
|
|
26
26
|
// who was never asked. See confirm.ts.
|
|
27
27
|
onConfirm: createUseConfirm(surface),
|
|
28
|
+
onRevokeConfirm: createRevokeConfirm(surface),
|
|
28
29
|
onApprovalNeeded: createProbeApproval(surface),
|
|
29
30
|
};
|
|
30
31
|
return new Broker(brokerOpts);
|
|
@@ -71,7 +72,7 @@ const TOOL_DESCRIPTIONS = {
|
|
|
71
72
|
'It will NOT print the secrets to you, will NOT export them into your own environment, and refuses to ' +
|
|
72
73
|
'run without explicit user confirmation. ' +
|
|
73
74
|
'To check whether a key exists instead of running a command, call env_describe.',
|
|
74
|
-
env_revoke: 'Removes stored credentials. Records in the audit log and emits the provider ' +
|
|
75
|
+
env_revoke: 'Removes stored credentials after user confirmation. Records in the audit log and emits the provider ' +
|
|
75
76
|
'rotation URL so you can help the user invalidate the old key.',
|
|
76
77
|
};
|
|
77
78
|
export function toolsFor(dialect) {
|
|
@@ -219,5 +220,5 @@ export async function dispatch(broker, name, args) {
|
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
export { SEP_TOOL_NAMES } from '@envseal/protocol';
|
|
222
|
-
export { createUseConfirm, createProbeApproval, annotateVerifyResults, CONFIRM_KEY_USE, CONFIRM_KEY_PROBE, } from './confirm.js';
|
|
223
|
+
export { createUseConfirm, createRevokeConfirm, createProbeApproval, annotateVerifyResults, CONFIRM_KEY_USE, CONFIRM_KEY_REVOKE, CONFIRM_KEY_PROBE, } from './confirm.js';
|
|
223
224
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envseal/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"zod-to-json-schema": "^3.24.1",
|
|
24
|
-
"@envseal/protocol": "0.1.
|
|
25
|
-
"@envseal/
|
|
26
|
-
"@envseal/
|
|
27
|
-
"@envseal/
|
|
24
|
+
"@envseal/protocol": "0.1.4",
|
|
25
|
+
"@envseal/core": "0.1.4",
|
|
26
|
+
"@envseal/registry": "0.1.4",
|
|
27
|
+
"@envseal/prompters": "0.1.4"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"vitest": "^2.1.8"
|