@openparachute/vault 0.7.6-rc.1 → 0.7.6-rc.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/vault",
3
- "version": "0.7.6-rc.1",
3
+ "version": "0.7.6-rc.2",
4
4
  "description": "Agent-native knowledge graph. Notes, tags, links over MCP.",
5
5
  "module": "src/cli.ts",
6
6
  "type": "module",
package/src/mcp-tools.ts CHANGED
@@ -883,9 +883,13 @@ function overrideVaultInfo(
883
883
  * model error), the cap is the backstop. Operators wanting long-lived
884
884
  * tokens mint a hub-issued JWT via the hub mint-token flow (the REST
885
885
  * /vault/<name>/tokens endpoint was removed with the pvt_* drop, vault#282).
886
+ * The `long_lived: true` opt-in deliberately relaxes this to a 90-day
887
+ * ceiling for standing credentials; the caller's admin JWT could already
888
+ * mint longer directly from the hub, so the flag adds no new authority.
886
889
  */
887
890
  const MANAGE_TOKEN_DEFAULT_TTL_SECONDS = 900; // 15 minutes
888
891
  const MANAGE_TOKEN_MAX_TTL_SECONDS = 3600; // 1 hour
892
+ const MANAGE_TOKEN_LONG_TTL_MAX_SECONDS = 7776000; // 90 days; opt-in ceiling for `long_lived: true`, matching hub's API_MINT_TOKEN_DEFAULT_TTL_SECONDS
889
893
 
890
894
  /**
891
895
  * Resolve the bare hub origin for the mint/revoke proxy calls. Reuses
@@ -952,8 +956,10 @@ function buildManageTokenTool(
952
956
  "escalate. Minting requires a hub-JWT session holding 'vault:" + vaultName +
953
957
  ":admin'. List + revoke are scoped to tokens this session minted; " +
954
958
  "CLI/REST-minted tokens are not surfaced here.\n\n" +
959
+ "`long_lived: true` raises the mint TTL cap to 90 days; a 90-day token is a " +
960
+ "standing credential and meaningfully increases blast radius if leaked/misused.\n\n" +
955
961
  "Actions (discriminator: `action`):\n" +
956
- "- `mint` — { scope: string|string[], ttl_seconds?: number, description?: string } → { action: \"mint\", token, jti, expires_at, scopes, scoped_tags, vault_name } (vault#555: scopes/scoped_tags/vault_name were previously undocumented here)\n" +
962
+ "- `mint` — { scope: string|string[], ttl_seconds?: number, long_lived?: boolean, description?: string } → { action: \"mint\", token, jti, expires_at, scopes, scoped_tags, vault_name } (vault#555: scopes/scoped_tags/vault_name were previously undocumented here)\n" +
957
963
  "- `revoke` — { jti: string } → { action: \"revoke\", ok: boolean, already_revoked?: boolean } — idempotent; a jti not in this session's ledger, or already revoked, still returns ok:true. A genuine failure additionally carries error/message (and, for a hub-side rejection, hub_status).\n" +
958
964
  "- `list` — (no inputs) → { action: \"list\", tokens: [...] }",
959
965
  inputSchema: {
@@ -974,7 +980,11 @@ function buildManageTokenTool(
974
980
  },
975
981
  ttl_seconds: {
976
982
  type: "number",
977
- description: `(action=mint) Token lifetime in seconds. Default ${MANAGE_TOKEN_DEFAULT_TTL_SECONDS} (15 min), max ${MANAGE_TOKEN_MAX_TTL_SECONDS} (1 hour). Values outside (0, ${MANAGE_TOKEN_MAX_TTL_SECONDS}] are rejected.`,
983
+ description: `(action=mint) Token lifetime in seconds. Default ${MANAGE_TOKEN_DEFAULT_TTL_SECONDS} (15 min), max ${MANAGE_TOKEN_MAX_TTL_SECONDS} (1 hour); with long_lived=true, max ${MANAGE_TOKEN_LONG_TTL_MAX_SECONDS} (90 days). Values outside the applicable (0, max] range are rejected.`,
984
+ },
985
+ long_lived: {
986
+ type: "boolean",
987
+ description: "(action=mint, optional) Set true to raise the TTL cap from 1 hour to 90 days. A 90-day token is a standing credential: long-lived and meaningfully increases blast radius if leaked/misused.",
978
988
  },
979
989
  description: {
980
990
  type: "string",
@@ -1107,9 +1117,20 @@ async function mintAction(
1107
1117
  }
1108
1118
 
1109
1119
  // TTL bounds. Default 900 (15 min); explicit values must satisfy
1110
- // `0 < ttl <= MANAGE_TOKEN_MAX_TTL_SECONDS`. Zero, negative, NaN, and
1111
- // beyond-max all reject the cap is the safety backstop if revoke fails,
1112
- // so it must be strict.
1120
+ // `0 < ttl <= MANAGE_TOKEN_MAX_TTL_SECONDS`, or
1121
+ // `0 < ttl <= MANAGE_TOKEN_LONG_TTL_MAX_SECONDS` with `long_lived: true`.
1122
+ // Zero, negative, NaN, and beyond-max all reject — the cap is the safety
1123
+ // backstop if revoke fails, so it must be strict.
1124
+ if (params.long_lived !== undefined && typeof params.long_lived !== "boolean") {
1125
+ return {
1126
+ action: "mint",
1127
+ error: "invalid_request",
1128
+ message: "manage-token mint: long_lived must be a boolean.",
1129
+ };
1130
+ }
1131
+ const maxTtl = params.long_lived === true
1132
+ ? MANAGE_TOKEN_LONG_TTL_MAX_SECONDS
1133
+ : MANAGE_TOKEN_MAX_TTL_SECONDS;
1113
1134
  let ttl = MANAGE_TOKEN_DEFAULT_TTL_SECONDS;
1114
1135
  if (params.ttl_seconds !== undefined && params.ttl_seconds !== null) {
1115
1136
  if (typeof params.ttl_seconds !== "number" || !Number.isFinite(params.ttl_seconds)) {
@@ -1119,11 +1140,11 @@ async function mintAction(
1119
1140
  message: "manage-token mint: ttl_seconds must be a finite number.",
1120
1141
  };
1121
1142
  }
1122
- if (params.ttl_seconds <= 0 || params.ttl_seconds > MANAGE_TOKEN_MAX_TTL_SECONDS) {
1143
+ if (params.ttl_seconds <= 0 || params.ttl_seconds > maxTtl) {
1123
1144
  return {
1124
1145
  action: "mint",
1125
1146
  error: "invalid_request",
1126
- message: `manage-token mint: ttl_seconds must be in (0, ${MANAGE_TOKEN_MAX_TTL_SECONDS}]; got ${params.ttl_seconds}.`,
1147
+ message: `manage-token mint: ttl_seconds must be in (0, ${maxTtl}]; got ${params.ttl_seconds}.`,
1127
1148
  };
1128
1149
  }
1129
1150
  ttl = params.ttl_seconds;
package/src/vault.test.ts CHANGED
@@ -7598,6 +7598,23 @@ describe("manage-token MCP tool (vault#403, MGT — hub-JWT attenuation proxy)",
7598
7598
  closeAllStores();
7599
7599
  });
7600
7600
 
7601
+ test("long_lived=true permits a 30-day TTL", async () => {
7602
+ installHubStub();
7603
+ const { vaultName, auth } = await setupAdminSession("mint-long-lived");
7604
+ const { closeAllStores } = await import("./vault-store.ts");
7605
+ const { parsed } = await callTool(vaultName, auth, "manage-token", {
7606
+ action: "mint",
7607
+ scope: "vault:read",
7608
+ ttl_seconds: 2592000,
7609
+ long_lived: true,
7610
+ });
7611
+ expect(parsed.action).toBe("mint");
7612
+ expect(parsed.error).toBeUndefined();
7613
+ const mint = hubCalls.find((c) => c.url.endsWith("/api/auth/mint-token"));
7614
+ expect(mint!.body.expires_in).toBe(2592000);
7615
+ closeAllStores();
7616
+ });
7617
+
7601
7618
  test("tag-scoped caller's mint includes permissions.scoped_tags", async () => {
7602
7619
  installHubStub();
7603
7620
  const { vaultName, auth } = await setupAdminSession("mint-scoped", ["task", "project"]);
@@ -7625,6 +7642,52 @@ describe("manage-token MCP tool (vault#403, MGT — hub-JWT attenuation proxy)",
7625
7642
  const { closeAllStores } = await import("./vault-store.ts");
7626
7643
  const { parsed } = await callTool(vaultName, auth, "manage-token", { action: "mint", scope: "vault:read", ttl_seconds: 3601 });
7627
7644
  expect(parsed.error).toBe("invalid_request");
7645
+ expect(parsed.message).toBe("manage-token mint: ttl_seconds must be in (0, 3600]; got 3601.");
7646
+ expect(hubCalls.length).toBe(0);
7647
+ closeAllStores();
7648
+ });
7649
+
7650
+ test("long_lived=true rejects TTL above the 90-day cap locally", async () => {
7651
+ installHubStub();
7652
+ const { vaultName, auth } = await setupAdminSession("mint-long-over");
7653
+ const { closeAllStores } = await import("./vault-store.ts");
7654
+ const { parsed } = await callTool(vaultName, auth, "manage-token", {
7655
+ action: "mint",
7656
+ scope: "vault:read",
7657
+ ttl_seconds: 7776001,
7658
+ long_lived: true,
7659
+ });
7660
+ expect(parsed.error).toBe("invalid_request");
7661
+ expect(parsed.message).toContain("7776000");
7662
+ expect(hubCalls.length).toBe(0);
7663
+ closeAllStores();
7664
+ });
7665
+
7666
+ test("long_lived=false keeps the 1-hour cap", async () => {
7667
+ installHubStub();
7668
+ const { vaultName, auth } = await setupAdminSession("mint-long-false");
7669
+ const { closeAllStores } = await import("./vault-store.ts");
7670
+ const { parsed } = await callTool(vaultName, auth, "manage-token", {
7671
+ action: "mint",
7672
+ scope: "vault:read",
7673
+ ttl_seconds: 7200,
7674
+ long_lived: false,
7675
+ });
7676
+ expect(parsed.error).toBe("invalid_request");
7677
+ expect(hubCalls.length).toBe(0);
7678
+ closeAllStores();
7679
+ });
7680
+
7681
+ test("omitted long_lived keeps the 1-hour cap", async () => {
7682
+ installHubStub();
7683
+ const { vaultName, auth } = await setupAdminSession("mint-long-omitted");
7684
+ const { closeAllStores } = await import("./vault-store.ts");
7685
+ const { parsed } = await callTool(vaultName, auth, "manage-token", {
7686
+ action: "mint",
7687
+ scope: "vault:read",
7688
+ ttl_seconds: 7200,
7689
+ });
7690
+ expect(parsed.error).toBe("invalid_request");
7628
7691
  expect(hubCalls.length).toBe(0);
7629
7692
  closeAllStores();
7630
7693
  });