@safekb/mcp 0.13.0 → 0.14.1
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 +4 -0
- package/dist/safekb-mcp.js +119 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -377,6 +377,10 @@ custom fields and the key name of every custom field (sealed ones too) stay read
|
|
|
377
377
|
sealed credentials start with the tenant default (Settings → Sealed credentials; `use_only` until
|
|
378
378
|
set). Only you make anything more visible, in the SafeKB app; `set_sealed_credential_llm_visibility`
|
|
379
379
|
only tightens. The client enforces this: a client older than 0.11.0 reveals every sealed credential, and 0.11.0 ignores field overrides.
|
|
380
|
+
- Entering a secret without the agent seeing it: `insert_sealed_credential` and
|
|
381
|
+
`update_sealed_credential` take `typed_at_sidecar`, e.g. `["custom:number", "custom:cvc", "password"]`.
|
|
382
|
+
The call waits while you type each field at the sidecar (`safekb-mcp --sidecar`), hidden and twice;
|
|
383
|
+
the agent passes only the visible fields. Or enter it in the SafeKB web app.
|
|
380
384
|
- Using a secret without showing it: SafeKB only provides the value. `secret_exec` runs your own
|
|
381
385
|
program with it in env, stdin or a temp file (`files`, mode 0600, deleted afterwards), never argv;
|
|
382
386
|
every integration (HTTP API, browser form, `adb shell input text`, ssh, wallet, `cryptsetup`, …) is
|
package/dist/safekb-mcp.js
CHANGED
|
@@ -2581,7 +2581,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2581
2581
|
// package.json
|
|
2582
2582
|
var package_default = {
|
|
2583
2583
|
name: "@safekb/mcp",
|
|
2584
|
-
version: "0.
|
|
2584
|
+
version: "0.14.1",
|
|
2585
2585
|
description: "SafeKB local MCP client: proxies the hosted SafeKB tools and adds zero-knowledge sealed documents.",
|
|
2586
2586
|
license: "SEE LICENSE IN LICENSE",
|
|
2587
2587
|
author: "Mustafa Öztürk",
|
|
@@ -9895,6 +9895,47 @@ class CredentialMigration {
|
|
|
9895
9895
|
}
|
|
9896
9896
|
}
|
|
9897
9897
|
|
|
9898
|
+
// src/sealed/typedFields.ts
|
|
9899
|
+
var NAMED = ["password", "totp", "sealed_notes"];
|
|
9900
|
+
var CUSTOM_PREFIX3 = customFieldName("");
|
|
9901
|
+
var NOTHING_TYPED = { named: {}, custom: [] };
|
|
9902
|
+
function checked(fields2, passed) {
|
|
9903
|
+
if (new Set(fields2).size !== fields2.length) {
|
|
9904
|
+
throw new ClientError("typed_at_sidecar names a field twice");
|
|
9905
|
+
}
|
|
9906
|
+
for (const field of fields2) {
|
|
9907
|
+
if (NAMED.includes(field)) {
|
|
9908
|
+
if (passed[field] !== undefined) {
|
|
9909
|
+
throw new ClientError(`${field} is both passed and typed_at_sidecar: pass it one way only`);
|
|
9910
|
+
}
|
|
9911
|
+
} else if (field.startsWith(CUSTOM_PREFIX3) && field.length > CUSTOM_PREFIX3.length) {
|
|
9912
|
+
const key = field.slice(CUSTOM_PREFIX3.length);
|
|
9913
|
+
if (passed.custom?.some((item) => item.key === key)) {
|
|
9914
|
+
throw new ClientError(`custom field '${key}' is both in custom and typed_at_sidecar: pass it one way only`);
|
|
9915
|
+
}
|
|
9916
|
+
} else {
|
|
9917
|
+
throw new ClientError(`typed_at_sidecar: '${field}' is not a sealed field. Use password, totp, sealed_notes or custom:{key}.`);
|
|
9918
|
+
}
|
|
9919
|
+
}
|
|
9920
|
+
}
|
|
9921
|
+
async function typeAtSidecar(entry, credential2, fields2, passed) {
|
|
9922
|
+
checked(fields2, passed);
|
|
9923
|
+
const typed = { named: {}, custom: [] };
|
|
9924
|
+
for (const field of fields2) {
|
|
9925
|
+
const value = await entry.secret(credential2, field);
|
|
9926
|
+
if (field.startsWith(CUSTOM_PREFIX3)) {
|
|
9927
|
+
typed.custom.push({ key: field.slice(CUSTOM_PREFIX3.length), value });
|
|
9928
|
+
} else {
|
|
9929
|
+
typed.named[field] = value;
|
|
9930
|
+
}
|
|
9931
|
+
}
|
|
9932
|
+
return typed;
|
|
9933
|
+
}
|
|
9934
|
+
function withTypedCustom(sealed, typed) {
|
|
9935
|
+
const keys = new Set(typed.custom.map(({ key }) => key));
|
|
9936
|
+
return [...sealed.filter(({ key }) => !keys.has(key)), ...typed.custom];
|
|
9937
|
+
}
|
|
9938
|
+
|
|
9898
9939
|
// src/sealed/credentialService.ts
|
|
9899
9940
|
var NOT_FOUND2 = 404;
|
|
9900
9941
|
var VISIBLE_NAMES = [
|
|
@@ -10006,6 +10047,7 @@ class CredentialService {
|
|
|
10006
10047
|
const id = newObjectId();
|
|
10007
10048
|
const custom = splitCustom(args.custom ?? []);
|
|
10008
10049
|
const { dek, encDek } = await this.access.newDek(args.root_id, id);
|
|
10050
|
+
const typed = await this.typed(args.title, args);
|
|
10009
10051
|
const save = await this.access.seal(id, dek, {
|
|
10010
10052
|
title: args.title,
|
|
10011
10053
|
username: textOrNull(args.username, null),
|
|
@@ -10014,10 +10056,10 @@ class CredentialService {
|
|
|
10014
10056
|
notes: textOrNull(args.notes, null),
|
|
10015
10057
|
custom: custom.visible
|
|
10016
10058
|
}, {
|
|
10017
|
-
password: textOrNull(args.password, null),
|
|
10018
|
-
totp: this.checkedTotp(textOrNull(args.totp, null)),
|
|
10019
|
-
sealed_notes: textOrNull(args.sealed_notes, null),
|
|
10020
|
-
custom: custom.sealed
|
|
10059
|
+
password: typed.named.password ?? textOrNull(args.password, null),
|
|
10060
|
+
totp: this.checkedTotp(typed.named.totp ?? textOrNull(args.totp, null)),
|
|
10061
|
+
sealed_notes: typed.named.sealed_notes ?? textOrNull(args.sealed_notes, null),
|
|
10062
|
+
custom: withTypedCustom(custom.sealed, typed)
|
|
10021
10063
|
});
|
|
10022
10064
|
const sigs = await this.access.sign({
|
|
10023
10065
|
op: "credential_create",
|
|
@@ -10051,6 +10093,7 @@ class CredentialService {
|
|
|
10051
10093
|
throw new ClientError(`${attestation.warning} ${describeChanges(attestation.changed_visible_fields, current)} ` + "Show this to the user. Only after they have reviewed it, repeat with accept_visible_changes: true (and pass the right values for any field to correct).");
|
|
10052
10094
|
}
|
|
10053
10095
|
const custom = args.custom === undefined ? { visible: current.custom, sealed: values.custom } : splitCustom(args.custom);
|
|
10096
|
+
const typed = await this.typed(args.title ?? current.title, args);
|
|
10054
10097
|
const save = await this.access.seal(credential2.id, dek, {
|
|
10055
10098
|
title: args.title ?? current.title,
|
|
10056
10099
|
username: textOrNull(args.username, current.username),
|
|
@@ -10059,10 +10102,10 @@ class CredentialService {
|
|
|
10059
10102
|
notes: textOrNull(args.notes, current.notes),
|
|
10060
10103
|
custom: custom.visible
|
|
10061
10104
|
}, {
|
|
10062
|
-
password: textOrNull(args.password, values.password),
|
|
10063
|
-
totp: this.checkedTotp(textOrNull(args.totp, values.totp)),
|
|
10064
|
-
sealed_notes: textOrNull(args.sealed_notes, values.sealed_notes),
|
|
10065
|
-
custom: custom.sealed
|
|
10105
|
+
password: typed.named.password ?? textOrNull(args.password, values.password),
|
|
10106
|
+
totp: this.checkedTotp(typed.named.totp ?? textOrNull(args.totp, values.totp)),
|
|
10107
|
+
sealed_notes: typed.named.sealed_notes ?? textOrNull(args.sealed_notes, values.sealed_notes),
|
|
10108
|
+
custom: withTypedCustom(custom.sealed, typed)
|
|
10066
10109
|
});
|
|
10067
10110
|
const sigs = await this.access.sign({
|
|
10068
10111
|
op: "credential_update",
|
|
@@ -10120,6 +10163,10 @@ class CredentialService {
|
|
|
10120
10163
|
});
|
|
10121
10164
|
return { id: credentialId, deleted: true };
|
|
10122
10165
|
}
|
|
10166
|
+
typed(title, args) {
|
|
10167
|
+
const atSidecar = args.typed_at_sidecar;
|
|
10168
|
+
return atSidecar === undefined || atSidecar.fields.length === 0 ? Promise.resolve(NOTHING_TYPED) : typeAtSidecar(atSidecar.entry, title, atSidecar.fields, args);
|
|
10169
|
+
}
|
|
10123
10170
|
checkedTotp(value) {
|
|
10124
10171
|
if (value !== null)
|
|
10125
10172
|
parseTotp(value);
|
|
@@ -31313,7 +31360,7 @@ function decodeDirective(html) {
|
|
|
31313
31360
|
|
|
31314
31361
|
// ../shared/js/canvas-markdown/src/html.ts
|
|
31315
31362
|
var ENTITY = /&(#x[0-9a-fA-F]+|#[0-9]+|amp|lt|gt|quot|apos);/g;
|
|
31316
|
-
var
|
|
31363
|
+
var NAMED2 = {
|
|
31317
31364
|
amp: "&",
|
|
31318
31365
|
lt: "<",
|
|
31319
31366
|
gt: ">",
|
|
@@ -31329,7 +31376,7 @@ function unescapeHtmlText(text2) {
|
|
|
31329
31376
|
return String.fromCodePoint(Number.parseInt(body.slice(2), 16));
|
|
31330
31377
|
if (body.startsWith("#"))
|
|
31331
31378
|
return String.fromCodePoint(Number(body.slice(1)));
|
|
31332
|
-
return
|
|
31379
|
+
return NAMED2[body];
|
|
31333
31380
|
});
|
|
31334
31381
|
}
|
|
31335
31382
|
|
|
@@ -31660,12 +31707,12 @@ class BlockParser {
|
|
|
31660
31707
|
}
|
|
31661
31708
|
item(taskable) {
|
|
31662
31709
|
this.pos++;
|
|
31663
|
-
const
|
|
31710
|
+
const checked2 = taskable ? this.takeTaskMarker() : null;
|
|
31664
31711
|
const children = this.blocks((token) => token.type === "list_item_close");
|
|
31665
31712
|
this.pos++;
|
|
31666
31713
|
return {
|
|
31667
|
-
task:
|
|
31668
|
-
checked:
|
|
31714
|
+
task: checked2 !== null,
|
|
31715
|
+
checked: checked2 === true,
|
|
31669
31716
|
children: children.length > 0 ? children : [node("paragraph")]
|
|
31670
31717
|
};
|
|
31671
31718
|
}
|
|
@@ -34474,7 +34521,7 @@ import { connect } from "node:net";
|
|
|
34474
34521
|
|
|
34475
34522
|
// src/sidecar/protocol.ts
|
|
34476
34523
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
34477
|
-
var SIDECAR_PROTOCOL_VERSION =
|
|
34524
|
+
var SIDECAR_PROTOCOL_VERSION = 3;
|
|
34478
34525
|
var SIDECAR_SOCKET_NAME = "sidecar.sock";
|
|
34479
34526
|
var SIDECAR_SUPPORTED = process.platform !== "win32";
|
|
34480
34527
|
function defaultSidecarSocket(configPath) {
|
|
@@ -34484,12 +34531,16 @@ function defaultSidecarSocket(configPath) {
|
|
|
34484
34531
|
// src/sidecar/client.ts
|
|
34485
34532
|
var SIDECAR_PROMPT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
34486
34533
|
var SIDECAR_QUICK_TIMEOUT_MS = 3000;
|
|
34487
|
-
var
|
|
34488
|
-
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
|
|
34534
|
+
var WAITS_FOR_USER = {
|
|
34535
|
+
holds: false,
|
|
34536
|
+
lookup: true,
|
|
34537
|
+
new: true,
|
|
34538
|
+
discard: false,
|
|
34539
|
+
settle: false,
|
|
34540
|
+
passphrase: true,
|
|
34541
|
+
secret: true,
|
|
34542
|
+
confirm: true
|
|
34543
|
+
};
|
|
34493
34544
|
var NOT_RUNNING_CODES = new Set(["ENOENT", "ECONNREFUSED"]);
|
|
34494
34545
|
|
|
34495
34546
|
class SidecarNotRunningError extends ClientError {
|
|
@@ -34529,7 +34580,7 @@ class SidecarClient {
|
|
|
34529
34580
|
this.quickTimeoutMs = quickTimeoutMs;
|
|
34530
34581
|
}
|
|
34531
34582
|
async request(request) {
|
|
34532
|
-
const text2 = await send(this.socketPath, request,
|
|
34583
|
+
const text2 = await send(this.socketPath, request, WAITS_FOR_USER[request.op] ? this.promptTimeoutMs : this.quickTimeoutMs);
|
|
34533
34584
|
let response;
|
|
34534
34585
|
try {
|
|
34535
34586
|
response = JSON.parse(text2);
|
|
@@ -34619,6 +34670,10 @@ class SidecarQuestions {
|
|
|
34619
34670
|
const { passphrase } = await this.asking("The passphrase", () => this.client.request({ op: "passphrase", of }));
|
|
34620
34671
|
return passphrase;
|
|
34621
34672
|
}
|
|
34673
|
+
async secret(credential2, field2) {
|
|
34674
|
+
const { value } = await this.asking(`The value of ${field2}`, () => this.client.request({ op: "secret", credential: credential2, field: field2 }));
|
|
34675
|
+
return value;
|
|
34676
|
+
}
|
|
34622
34677
|
async ask(notice, question) {
|
|
34623
34678
|
const { typed: typed2 } = await this.asking("The confirmation", () => this.client.request({ op: "confirm", notice, question }));
|
|
34624
34679
|
return typed2;
|
|
@@ -34648,6 +34703,9 @@ class NoSidecarQuestions {
|
|
|
34648
34703
|
ask() {
|
|
34649
34704
|
return this.refuse();
|
|
34650
34705
|
}
|
|
34706
|
+
secret() {
|
|
34707
|
+
return Promise.reject(new ClientError("Typing a secret at the sidecar is not available on this platform. Ask the user to enter it in the SafeKB web app (Credentials)."));
|
|
34708
|
+
}
|
|
34651
34709
|
refuse() {
|
|
34652
34710
|
return Promise.reject(new ClientError(`This needs the user to type at the sidecar, which is not available on this platform. Ask them to run it at a terminal: ${this.commandLine}`));
|
|
34653
34711
|
}
|
|
@@ -34721,6 +34779,15 @@ The MCP client asks for the passphrase of ${printable2(of)}. It is used for this
|
|
|
34721
34779
|
return typed2.length === 0 ? null : typed2;
|
|
34722
34780
|
});
|
|
34723
34781
|
}
|
|
34782
|
+
secret({ credential: credential2, field: field2 }, signal) {
|
|
34783
|
+
return this.inTurn(async () => {
|
|
34784
|
+
signal.throwIfAborted();
|
|
34785
|
+
this.reader.write(`
|
|
34786
|
+
The MCP client asks for ${printable2(field2)} of credential "${printable2(credential2)}". It is sealed on this machine; the model never sees it.
|
|
34787
|
+
`);
|
|
34788
|
+
return this.readTwice(`${printable2(field2)} (not shown; empty to decline): `, signal);
|
|
34789
|
+
});
|
|
34790
|
+
}
|
|
34724
34791
|
typed(notice, question, signal) {
|
|
34725
34792
|
return this.inTurn(async () => {
|
|
34726
34793
|
signal.throwIfAborted();
|
|
@@ -34852,9 +34919,12 @@ ${printableLines(notice)}`);
|
|
|
34852
34919
|
` : `Forgotten.
|
|
34853
34920
|
`);
|
|
34854
34921
|
}
|
|
34855
|
-
|
|
34922
|
+
readNewPassphrase(signal) {
|
|
34923
|
+
return this.readTwice("New passphrase (not shown; empty to decline): ", signal);
|
|
34924
|
+
}
|
|
34925
|
+
async readTwice(prompt, signal) {
|
|
34856
34926
|
for (;; ) {
|
|
34857
|
-
const first = await this.reader.read(
|
|
34927
|
+
const first = await this.reader.read(prompt, { hidden: true, signal });
|
|
34858
34928
|
if (first.length === 0)
|
|
34859
34929
|
return null;
|
|
34860
34930
|
const second = await this.reader.read("Repeat it: ", {
|
|
@@ -35118,6 +35188,8 @@ class SidecarServer {
|
|
|
35118
35188
|
return {};
|
|
35119
35189
|
case "passphrase":
|
|
35120
35190
|
return { passphrase: await this.passphrase(request, clientGone) };
|
|
35191
|
+
case "secret":
|
|
35192
|
+
return { value: await this.secret(request, clientGone) };
|
|
35121
35193
|
case "confirm":
|
|
35122
35194
|
return {
|
|
35123
35195
|
typed: await this.prompter.typed(requireText(request.notice, "notice"), requireText(request.question, "question"), clientGone)
|
|
@@ -35144,6 +35216,16 @@ class SidecarServer {
|
|
|
35144
35216
|
}
|
|
35145
35217
|
return typed2;
|
|
35146
35218
|
}
|
|
35219
|
+
async secret(request, clientGone) {
|
|
35220
|
+
const credential2 = requireText(request.credential, "credential");
|
|
35221
|
+
const field2 = requireText(request.field, "field");
|
|
35222
|
+
const typed2 = await this.prompter.secret({ credential: credential2, field: field2 }, clientGone);
|
|
35223
|
+
clientGone.throwIfAborted();
|
|
35224
|
+
if (typed2 === null) {
|
|
35225
|
+
throw new ClientError(`The sidecar's user entered no ${field2} for credential "${credential2}"`);
|
|
35226
|
+
}
|
|
35227
|
+
return typed2;
|
|
35228
|
+
}
|
|
35147
35229
|
async create(rootId, label2, clientGone) {
|
|
35148
35230
|
const chosen = await this.prompter.newSecret(rootId, label2, clientGone);
|
|
35149
35231
|
clientGone.throwIfAborted();
|
|
@@ -36097,13 +36179,15 @@ var sealedFields = {
|
|
|
36097
36179
|
password: exports_external.string().describe(`Sealed. ${CLEARABLE}`).optional(),
|
|
36098
36180
|
totp: exports_external.string().describe(`Sealed TOTP seed (base32) or otpauth://totp URI. ${CLEARABLE}`).optional(),
|
|
36099
36181
|
sealed_notes: exports_external.string().describe(`Sealed notes. ${CLEARABLE}`).optional(),
|
|
36182
|
+
typed_at_sidecar: exports_external.array(exports_external.string().min(1)).describe(`Sealed fields the user types at the sidecar instead of you passing them: password, totp, sealed_notes or custom:{key} (a new or replaced sealed custom field). The call waits while they type each one, hidden and twice. ${SIDECAR_TYPING} Use this for any secret the user has not already given you, e.g. a card number, CVC or PIN; set the visible fields (title, holder name, last 4 digits) yourself.`).optional(),
|
|
36100
36183
|
custom: exports_external.array(exports_external.strictObject({
|
|
36101
36184
|
key: exports_external.string().min(1).describe("Field name. Always stored in clear, also for a sealed field: never put a secret in it."),
|
|
36102
36185
|
value: exports_external.string(),
|
|
36103
36186
|
sealed: exports_external.boolean().describe("true: the value is encrypted on this machine (the key name is not). false: visible to the server.")
|
|
36104
36187
|
})).describe("The complete list of custom fields; it replaces the stored list.").optional()
|
|
36105
36188
|
};
|
|
36106
|
-
function credentialTools(credentials, migration, names) {
|
|
36189
|
+
function credentialTools(credentials, migration, names, entry) {
|
|
36190
|
+
const atSidecar = (fields2) => fields2 === undefined ? undefined : { fields: fields2, entry };
|
|
36107
36191
|
return [
|
|
36108
36192
|
defineTool("reveal_sealed_credential", `Decrypt one sealed field of a sealed credential on this machine. The server only sees that the ciphertext was fetched. Find credentials and their visible fields with the standard credential tools (search_credentials, get_credential). Refused unless its llm_visibility is visible. ${EXPOSURE_NOTE}`, {
|
|
36109
36193
|
credential_id: credentialId,
|
|
@@ -36123,7 +36207,8 @@ function credentialTools(credentials, migration, names) {
|
|
|
36123
36207
|
...sealedFields
|
|
36124
36208
|
}, async (args) => credentials.insert({
|
|
36125
36209
|
...args,
|
|
36126
|
-
root_id: await names.resolve(args.root_id)
|
|
36210
|
+
root_id: await names.resolve(args.root_id),
|
|
36211
|
+
typed_at_sidecar: atSidecar(args.typed_at_sidecar)
|
|
36127
36212
|
})),
|
|
36128
36213
|
defineTool("update_sealed_credential", `Change a sealed credential with its seal root's signature. Pass only the fields to change; the save also attests the visible fields as they are now. If visible fields were changed without the key since the last sealed save, the call fails and names them until accept_visible_changes is true. ${SECRET_ARGUMENT_NOTE}`, {
|
|
36129
36214
|
credential_id: credentialId,
|
|
@@ -36132,7 +36217,10 @@ function credentialTools(credentials, migration, names) {
|
|
|
36132
36217
|
title: exports_external.string().min(1).describe("Visible to the server.").optional(),
|
|
36133
36218
|
...visibleFields,
|
|
36134
36219
|
...sealedFields
|
|
36135
|
-
}, (args) => credentials.update(
|
|
36220
|
+
}, (args) => credentials.update({
|
|
36221
|
+
...args,
|
|
36222
|
+
typed_at_sidecar: atSidecar(args.typed_at_sidecar)
|
|
36223
|
+
})),
|
|
36136
36224
|
defineTool("move_sealed_credential", "Move a sealed credential to another seal root of its space. Both roots must be openable here: its key is re-wrapped under the new root and both roots sign. Afterwards only the new root's secret opens it. Whoever held the old root's secret keeps what they already read.", {
|
|
36137
36225
|
credential_id: credentialId,
|
|
36138
36226
|
expected_version: expectedVersion,
|
|
@@ -36335,11 +36423,12 @@ The keys of this machine, and backups, are handled by tools that never take or r
|
|
|
36335
36423
|
|
|
36336
36424
|
A credential with \`sealed: true\` belongs to a seal root. Its title, username, url, tags, notes and visible custom fields are readable by the server: find it with the standard credential tools. Its password, TOTP seed, sealed_notes and sealed custom fields open only here: reveal_sealed_credential, get_sealed_totp, insert_sealed_credential, update_sealed_credential.
|
|
36337
36425
|
|
|
36426
|
+
- A secret the user has not given you yet (a card number, a PIN, a new password): never ask for it in the conversation. Call insert_sealed_credential or update_sealed_credential with typed_at_sidecar naming the fields; the user types them at the sidecar (safekb-mcp --sidecar) while the call waits. Or they enter it in the SafeKB web app.
|
|
36338
36427
|
- Reveal a secret only when the task needs it, and prefer get_sealed_totp over revealing a TOTP seed.
|
|
36339
36428
|
- llm_visibility is set per credential, and llm_field_visibility overrides it for single sealed fields. reveal_sealed_credential and get_sealed_totp work only on a field that is visible. Only the user makes anything visible, in the SafeKB app.
|
|
36340
36429
|
- To use a secret without seeing it (visible or use_only fields): secret_exec runs your own program with it in env, stdin or a temp file; every integration (HTTP, browser, adb, ssh, ...) is that program. Name it by handle, {{secret://<credential_id>/<field>}}; a totp handle becomes the current code. Prefer this over revealing, even for a visible field. A human_only field is never used by a tool: ask the user to enter it.
|
|
36341
36430
|
- visible_tampered means the visible fields were changed without the key since the last sealed save. Do not trust the url or username of such a credential until the user has reviewed the change.`;
|
|
36342
|
-
function createSealedTools(ctx) {
|
|
36431
|
+
function createSealedTools(ctx, entry) {
|
|
36343
36432
|
const services = createSealedServices(ctx);
|
|
36344
36433
|
const { names } = ctx;
|
|
36345
36434
|
return [
|
|
@@ -36348,7 +36437,7 @@ function createSealedTools(ctx) {
|
|
|
36348
36437
|
...treeTools(services.tree, names),
|
|
36349
36438
|
...attachmentTools(services.attachments),
|
|
36350
36439
|
...depositTools(services.deposits, names),
|
|
36351
|
-
...credentialTools(services.credentials, services.migration, names),
|
|
36440
|
+
...credentialTools(services.credentials, services.migration, names, entry),
|
|
36352
36441
|
...sinkTools(new ExecSink(services.secrets))
|
|
36353
36442
|
];
|
|
36354
36443
|
}
|
|
@@ -36694,7 +36783,7 @@ async function openTools(options, config3, session, systemKeyring, chosen) {
|
|
|
36694
36783
|
};
|
|
36695
36784
|
const conversions = createConversions(sealed, new KbApi(http), sharedCanvasMarkdown);
|
|
36696
36785
|
const localTools = [
|
|
36697
|
-
...createSealedTools(sealed),
|
|
36786
|
+
...createSealedTools(sealed, userQuestionsOf(options)("")),
|
|
36698
36787
|
...unsealTools(conversions.unseal),
|
|
36699
36788
|
...options.sealedOnly ? [] : sealTools(conversions.seal),
|
|
36700
36789
|
...await adminTools({ options, config: config3, systemKeyring, chosen })
|
package/package.json
CHANGED