@safekb/mcp 0.10.1 → 0.11.0

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
@@ -371,6 +371,11 @@ custom fields and the key name of every custom field (sealed ones too) stay read
371
371
  space takes its credentials along (a credential's group in the old space is left behind).
372
372
  - `reveal_sealed_credential` and `get_sealed_totp` decrypt locally. The hosted `reveal_credential` and
373
373
  `get_credential_totp` cannot open a sealed credential.
374
+ - AI access (`llm_visibility`): `visible`, `use_only` or `human_only`. `reveal_sealed_credential` and
375
+ `get_sealed_totp` work only on `visible` credentials. New and migrated sealed credentials start
376
+ `use_only`; only you make one `visible`, in the SafeKB app. `set_sealed_credential_llm_visibility`
377
+ only tightens it. The client enforces this: a client older than 0.11.0 still reveals every sealed
378
+ credential.
374
379
  - Visible fields can be edited without the secret. Such an edit is flagged (`visible_tampered`) with the
375
380
  changed fields until a signed save accepts it (`accept_visible_changes`).
376
381
 
@@ -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.10.1",
2584
+ version: "0.11.0",
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",
@@ -3276,6 +3276,11 @@ class SealedApi {
3276
3276
  fetchSealedCredential(id) {
3277
3277
  return this.http.getJson(`/credentials/${id}/sealed`);
3278
3278
  }
3279
+ setLlmVisibility(id, llmVisibility) {
3280
+ return this.http.putJson(`/credentials/${id}/sealed/llm-visibility`, {
3281
+ llm_visibility: llmVisibility
3282
+ });
3283
+ }
3279
3284
  lastAttestedVisibleFields(id) {
3280
3285
  return this.http.getJson(`/credentials/${id}/sealed/attested`);
3281
3286
  }
@@ -9289,7 +9294,17 @@ class CredentialAccess {
9289
9294
  return serializeEnvelope(await wrapDek(keys, credentialId, dek));
9290
9295
  }
9291
9296
  async open(credentialId) {
9297
+ return this.decrypt(await this.ctx.api.fetchSealedCredential(credentialId));
9298
+ }
9299
+ async openForReveal(credentialId) {
9292
9300
  const credential2 = await this.ctx.api.fetchSealedCredential(credentialId);
9301
+ if (credential2.llm_visibility !== "visible") {
9302
+ throw new ClientError(`Credential ${credentialId} (${credential2.title}) is ${credential2.llm_visibility}: its secrets are never shown to an LLM. No tool uses a secret without showing it yet: ask the user to enter it. Only the user can change this, in the SafeKB app.`);
9303
+ }
9304
+ return this.decrypt(credential2);
9305
+ }
9306
+ async decrypt(credential2) {
9307
+ const credentialId = credential2.id;
9293
9308
  const { keys } = await this.ctx.session.open(credential2.seal_root_id);
9294
9309
  const dek = await unwrapDek(keys, credentialId, parseEnvelope(credential2.enc_dek));
9295
9310
  const values = await unsealCredentialFields(dek, this.context(credentialId), credential2.enc_fields);
@@ -9736,7 +9751,7 @@ class CredentialService {
9736
9751
  this.access = access;
9737
9752
  }
9738
9753
  async reveal(credentialId, field) {
9739
- const open5 = await this.access.open(credentialId);
9754
+ const open5 = await this.access.openForReveal(credentialId);
9740
9755
  const value = this.valueOf(open5, field);
9741
9756
  return {
9742
9757
  id: credentialId,
@@ -9747,7 +9762,7 @@ class CredentialService {
9747
9762
  };
9748
9763
  }
9749
9764
  async totp(credentialId, nowMs = Date.now()) {
9750
- const open5 = await this.access.open(credentialId);
9765
+ const open5 = await this.access.openForReveal(credentialId);
9751
9766
  const code = await totp(parseTotp(this.valueOf(open5, "totp")), nowMs);
9752
9767
  return {
9753
9768
  id: credentialId,
@@ -9756,6 +9771,10 @@ class CredentialService {
9756
9771
  ...await this.attestation(open5.credential)
9757
9772
  };
9758
9773
  }
9774
+ async setLlmVisibility(credentialId, llmVisibility) {
9775
+ const credential2 = await this.ctx.api.setLlmVisibility(credentialId, llmVisibility);
9776
+ return { id: credential2.id, llm_visibility: credential2.llm_visibility };
9777
+ }
9759
9778
  valueOf(open5, field) {
9760
9779
  const { values, credential: credential2 } = open5;
9761
9780
  const value = field.startsWith(CUSTOM_PREFIX2) ? values.custom.find((item) => item.key === field.slice(CUSTOM_PREFIX2.length))?.value : SEALED_CREDENTIAL_FIELDS.includes(field) ? values[field] : undefined;
@@ -35750,12 +35769,16 @@ var sealedFields = {
35750
35769
  };
35751
35770
  function credentialTools(credentials, migration, names) {
35752
35771
  return [
35753
- 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). ${EXPOSURE_NOTE}`, {
35772
+ 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}`, {
35754
35773
  credential_id: credentialId,
35755
35774
  field: exports_external.string().min(1).describe("password, totp, sealed_notes, or custom:{key}.")
35756
35775
  }, (args) => credentials.reveal(args.credential_id, args.field)),
35757
- defineTool("get_sealed_totp", `Compute the current TOTP code of a sealed credential on this machine, without revealing the seed. ${EXPOSURE_NOTE}`, { credential_id: credentialId }, (args) => credentials.totp(args.credential_id)),
35758
- defineTool("insert_sealed_credential", `Create a sealed credential in a seal root. It is kept in the space of that root. password, totp, sealed_notes and the values of sealed custom fields are encrypted on this machine; title, username, url, tags, notes, visible custom fields and the key name of every custom field (sealed ones too) stay readable by the server so search and duplicate detection work. ${SECRET_ARGUMENT_NOTE}`, {
35776
+ defineTool("get_sealed_totp", `Compute the current TOTP code of a sealed credential on this machine, without revealing the seed. Refused unless its llm_visibility is visible. ${EXPOSURE_NOTE}`, { credential_id: credentialId }, (args) => credentials.totp(args.credential_id)),
35777
+ defineTool("set_sealed_credential_llm_visibility", "Make a sealed credential's secrets less visible to LLMs: use_only (never revealed to an LLM) or human_only (never revealed, entered by a person). It only tightens: human_only cannot go back to use_only from here, and only the user makes a credential visible, in the SafeKB app. The value is not signed and does not change the credential's version.", {
35778
+ credential_id: credentialId,
35779
+ llm_visibility: exports_external.enum(["use_only", "human_only"]).describe("human_only is the more restrictive.")
35780
+ }, (args) => credentials.setLlmVisibility(args.credential_id, args.llm_visibility)),
35781
+ defineTool("insert_sealed_credential", `Create a sealed credential in a seal root. It is kept in the space of that root. It starts use_only: its secrets cannot be revealed to an LLM until the user makes it visible in the SafeKB app. password, totp, sealed_notes and the values of sealed custom fields are encrypted on this machine; title, username, url, tags, notes, visible custom fields and the key name of every custom field (sealed ones too) stay readable by the server so search and duplicate detection work. ${SECRET_ARGUMENT_NOTE}`, {
35759
35782
  root_id: rootRef("The seal root that will own it."),
35760
35783
  title: exports_external.string().min(1).describe("Visible to the server."),
35761
35784
  folder_id: exports_external.string().min(1).describe("Group it belongs to.").optional(),
@@ -35946,6 +35969,7 @@ The keys of this machine, and backups, are handled by tools that never take or r
35946
35969
  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.
35947
35970
 
35948
35971
  - Reveal a secret only when the task needs it, and prefer get_sealed_totp over revealing a TOTP seed.
35972
+ - llm_visibility: reveal_sealed_credential and get_sealed_totp work only on visible credentials; on use_only and human_only ones, ask the user to enter the secret. New sealed credentials start use_only, and only the user makes one visible, in the SafeKB app.
35949
35973
  - 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.`;
35950
35974
  function createSealedTools(ctx) {
35951
35975
  const services = createSealedServices(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safekb/mcp",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "SafeKB local MCP client: proxies the hosted SafeKB tools and adds zero-knowledge sealed documents.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Mustafa Öztürk",