enclave-link 0.2.3 → 0.2.5
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
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</picture>
|
|
6
6
|
</h1>
|
|
7
7
|
|
|
8
|
-
Your AI agent needs your Netflix password. Instead of asking in chat, it sends you a link. You
|
|
8
|
+
Your AI agent needs your Netflix password. Instead of asking in chat, it sends you a link. You type the password (or your password manager fills it) and tap **Send**. It's locked in your browser before it leaves, so only your agent can open it. The agent keeps it in a vault of its own, next to 2FA codes and passkeys, and uses it later without seeing it.
|
|
9
9
|
|
|
10
10
|
<p>
|
|
11
11
|
<picture>
|
|
@@ -34,7 +34,7 @@ It works out of the box with a hosted relay. No accounts, no keys to set up, not
|
|
|
34
34
|
npx skills add AVGVSTVS96/enclave
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
It runs enclave with `npx`, so there's nothing else to install.
|
|
38
38
|
|
|
39
39
|
**Agents that use MCP** get the same thing as tools. Add the server below, and add this line to the agent's instructions, because tools alone don't stop a model from asking in chat:
|
|
40
40
|
|
|
@@ -63,7 +63,7 @@ claude mcp add enclave -- npx -y enclave-link mcp
|
|
|
63
63
|
codex mcp add enclave -- npx -y enclave-link mcp
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Codex stops a tool after 60 seconds by default
|
|
66
|
+
Codex stops a tool after 60 seconds by default, and `wait_for_secret` can wait 25 minutes, so add this under `[mcp_servers.enclave]` in `~/.codex/config.toml`:
|
|
67
67
|
|
|
68
68
|
```toml
|
|
69
69
|
tool_timeout_sec = 1800
|
|
@@ -188,7 +188,7 @@ Run `goose configure`, choose **Add Extension → Command-line Extension**, ente
|
|
|
188
188
|
|
|
189
189
|
</details>
|
|
190
190
|
|
|
191
|
-
**ChatGPT, Claude.ai and the Gemini and Grok apps** can't use enclave yet
|
|
191
|
+
**ChatGPT, Claude.ai and the Gemini and Grok apps** can't use enclave yet: they run in the cloud, and the vault lives on the computer where the agent runs. Their desktop and coding-agent versions above work.
|
|
192
192
|
|
|
193
193
|
**Your own code** can use the TypeScript SDK; see [SDK](#sdk).
|
|
194
194
|
|
|
@@ -232,11 +232,9 @@ A field named `passkey` holds a passkey, and the agent signs in with it in Chrom
|
|
|
232
232
|
enclave passkey GitHub # then click the site's passkey sign-in
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
`enclave passkey` arms the passkey for one sign-in, for up to 5 minutes. When the site asks for a passkey, enclave's Chrome extension hands the request to `enclave sign`, which signs it inside the vault and disarms the passkey. The private key never
|
|
235
|
+
`enclave passkey` arms the passkey for one sign-in, for up to 5 minutes. When the site asks for a passkey, enclave's Chrome extension hands the request to `enclave sign`, which signs it inside the vault and disarms the passkey. The private key never leaves the vault: `enclave get` and `enclave run` refuse passkey fields.
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
A passkey's private key never comes out: `enclave get` and `enclave run` refuse passkey fields, and only `enclave sign` uses them.
|
|
237
|
+
A passkey keeps working in Proton Pass and enclave side by side.
|
|
240
238
|
|
|
241
239
|
Install the extension once per machine, from a clone of this repo, with Google Chrome on Linux. It's force-installed through Chrome's policy, so it loads in every profile:
|
|
242
240
|
|
|
@@ -252,9 +250,9 @@ enclave import proton # every vault
|
|
|
252
250
|
enclave import proton Work # or just the ones you name
|
|
253
251
|
```
|
|
254
252
|
|
|
255
|
-
copies [Proton Pass](https://proton.me/pass) in through [pass-cli](https://github.com/protonpass/pass-cli): logins, 2FA, passkeys, notes, cards, SSH keys and custom fields, one item per Proton item, named by its title
|
|
253
|
+
copies [Proton Pass](https://proton.me/pass) in through [pass-cli](https://github.com/protonpass/pass-cli): logins, 2FA, passkeys, notes, cards, SSH keys and custom fields, one item per Proton item, named by its title. Proton keeps everything. Run it again to bring in what changed: names stay the same, renames follow, items deleted in Proton leave enclave too, and anything you saved yourself is kept.
|
|
256
254
|
|
|
257
|
-
2FA comes in as a `totp` field, so `enclave get <name> totp` gives the current code
|
|
255
|
+
2FA comes in as a `totp` field, so `enclave get <name> totp` gives the current code, and passkeys as a `passkey` field.
|
|
258
256
|
|
|
259
257
|
## CLI
|
|
260
258
|
|
|
@@ -329,7 +327,7 @@ The vault keeps each item encrypted in `~/.enclave/items`, sealed to the vault's
|
|
|
329
327
|
|
|
330
328
|
What enclave can't protect against:
|
|
331
329
|
|
|
332
|
-
- **Whoever runs the relay sends you the page.** Your browser downloads the page, including the code that locks your password, from the relay each time you open a link. What the relay stores can't be read, but if someone took over the relay's Convex account, they could change the page to copy what you type before locking it.
|
|
330
|
+
- **Whoever runs the relay sends you the page.** Your browser downloads the page, including the code that locks your password, from the relay each time you open a link. What the relay stores can't be read, but if someone took over the relay's Convex account, they could change the page to copy what you type before locking it. The page's script is 128 lines in [`page/page.ts`](page/page.ts) plus the encryption in [`src/hpke.ts`](src/hpke.ts), served as one file with a strict Content Security Policy, so you can read exactly what it runs. If you'd rather trust nobody's relay, [host your own](#host-your-own).
|
|
333
331
|
- **Anyone with the link can answer it first.** They still can't read anything, but they could send a fake answer. If that happens, the page says "This link doesn't work anymore" when you open it.
|
|
334
332
|
- **Phishing.** A link can say anyone is asking. Only send secrets to agents you asked for help.
|
|
335
333
|
- **An agent that wants to see a secret can.** enclave keeps secrets out of the chat and out of tool results, and `run` hides their exact values in output. But an agent with a shell on the same computer could still read its own vault. enclave stops secrets from leaking by accident, not a model that's trying.
|
|
@@ -11857,8 +11857,8 @@ function openVault({
|
|
|
11857
11857
|
if (!target) continue;
|
|
11858
11858
|
const item = toItem(entry.content);
|
|
11859
11859
|
const previous = before[entry.id];
|
|
11860
|
-
const
|
|
11861
|
-
const
|
|
11860
|
+
const existing = await store.get(previous?.name ?? target.name) ?? {};
|
|
11861
|
+
const proton = new Set(previous?.fields ?? (saved ? Object.keys(existing) : []));
|
|
11862
11862
|
const added = Object.entries(existing).filter(([field]) => !proton.has(field) && !(field in item));
|
|
11863
11863
|
imported.set(target.name, { ...Object.fromEntries(added), ...item });
|
|
11864
11864
|
target.fields = Object.keys(item);
|
package/dist/enclave.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
signRequest,
|
|
4
4
|
vault
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PB2UBW7U.js";
|
|
6
6
|
import "./chunk-7QIF4PCL.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { text } from "node:stream/consumers";
|
|
10
|
-
var usage = `enclave: ask a person for a secret
|
|
10
|
+
var usage = `enclave: ask a person for a secret by one-time link, and use it without seeing it
|
|
11
11
|
|
|
12
12
|
enclave ask <name> [field...] print a link asking for <name> (fields default to password)
|
|
13
13
|
enclave wait <name> wait for the answer and save it to the vault
|
|
@@ -72,7 +72,7 @@ try {
|
|
|
72
72
|
} else if (command === "rm" && name) {
|
|
73
73
|
await vault.remove(name);
|
|
74
74
|
} else if (command === "mcp") {
|
|
75
|
-
const { serve } = await import("./mcp-
|
|
75
|
+
const { serve } = await import("./mcp-LJPUESU6.js");
|
|
76
76
|
await serve(vault);
|
|
77
77
|
} else {
|
|
78
78
|
console.error(usage);
|
package/dist/index.js
CHANGED
|
@@ -16914,8 +16914,8 @@ var StdioServerTransport = class {
|
|
|
16914
16914
|
// package.json
|
|
16915
16915
|
var package_default = {
|
|
16916
16916
|
name: "enclave-link",
|
|
16917
|
-
version: "0.2.
|
|
16918
|
-
description: "
|
|
16917
|
+
version: "0.2.5",
|
|
16918
|
+
description: "Your agent asks for a password by one-time link, not in chat, and uses it without seeing it.",
|
|
16919
16919
|
license: "MIT",
|
|
16920
16920
|
bin: {
|
|
16921
16921
|
enclave: "dist/enclave.js"
|
|
@@ -16968,7 +16968,7 @@ async function serve(vault) {
|
|
|
16968
16968
|
"request_secret",
|
|
16969
16969
|
{
|
|
16970
16970
|
title: "Ask for a secret",
|
|
16971
|
-
description: "Use this whenever you need a password, API key, 2FA key or
|
|
16971
|
+
description: "Use this whenever you need a password, API key, 2FA key or other secret from the person, instead of asking in chat. It returns a one-time link; what they type there is locked in their browser for this vault. Then call wait_for_secret.",
|
|
16972
16972
|
inputSchema: {
|
|
16973
16973
|
name: string().describe('What the secret is for. The person sees it as the title, like "Netflix" or "OpenAI API key"'),
|
|
16974
16974
|
fields: array(string()).optional().describe(
|
|
@@ -16981,7 +16981,7 @@ async function serve(vault) {
|
|
|
16981
16981
|
const { url } = await vault.ask(name, fields);
|
|
16982
16982
|
if (server.server.getClientCapabilities()?.elicitation?.url) {
|
|
16983
16983
|
const elicitationId = randomUUID();
|
|
16984
|
-
const message = `Open this
|
|
16984
|
+
const message = `Open this link to enter ${name}. It's locked on this device before it's sent.`;
|
|
16985
16985
|
const { action } = await server.server.elicitInput({ mode: "url", elicitationId, url, message });
|
|
16986
16986
|
if (action === "accept") {
|
|
16987
16987
|
elicitations.set(name, elicitationId);
|
|
@@ -17019,7 +17019,7 @@ It works once, for 24 hours. Then call wait_for_secret with name "${name}".`
|
|
|
17019
17019
|
"run_with_secrets",
|
|
17020
17020
|
{
|
|
17021
17021
|
title: "Run a command with secrets",
|
|
17022
|
-
description: 'Run a program with saved secrets in its environment. Their values are replaced with [hidden VAR] in the output, so you can use a secret without seeing it. Example: {"command": ["sh", "-c", "curl -H \\"Authorization: Bearer $KEY\\" https://api.example.com/me"], "secrets": {"KEY": "Example/api key"}}',
|
|
17022
|
+
description: 'Run a program with saved secrets in its environment. Their values are replaced with [hidden VAR] in the output, so you can use a secret without seeing it. A "totp" field gives its current 2FA code. Example: {"command": ["sh", "-c", "curl -H \\"Authorization: Bearer $KEY\\" https://api.example.com/me"], "secrets": {"KEY": "Example/api key"}}',
|
|
17023
17023
|
inputSchema: {
|
|
17024
17024
|
command: array(string()).min(1).describe(
|
|
17025
17025
|
'The program and its arguments. Use ["sh", "-c", "\u2026"] for shell syntax, and refer to secrets as $VAR inside it.'
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enclave-link",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Your agent asks for a password by one-time link, not in chat, and uses it without seeing it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
7
7
|
"enclave": "dist/enclave.js"
|