@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 +1 -1
- package/src/mcp-tools.ts +28 -7
- package/src/vault.test.ts +63 -0
package/package.json
CHANGED
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,
|
|
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
|
|
1111
|
-
//
|
|
1112
|
-
//
|
|
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 >
|
|
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, ${
|
|
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
|
});
|