@kutrion/cli 1.2.12 → 1.2.16
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 +20 -1
- package/dist/index.js +1709 -462
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10417,22 +10417,22 @@ var require_formdata = __commonJS({
|
|
|
10417
10417
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10418
10418
|
value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "value", { strict: false }) : webidl.converters.USVString(value, prefix, "value");
|
|
10419
10419
|
filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "filename") : void 0;
|
|
10420
|
-
const
|
|
10421
|
-
this[kState].push(
|
|
10420
|
+
const entry2 = makeEntry(name, value, filename);
|
|
10421
|
+
this[kState].push(entry2);
|
|
10422
10422
|
}
|
|
10423
10423
|
delete(name) {
|
|
10424
10424
|
webidl.brandCheck(this, _FormData);
|
|
10425
10425
|
const prefix = "FormData.delete";
|
|
10426
10426
|
webidl.argumentLengthCheck(arguments, 1, prefix);
|
|
10427
10427
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10428
|
-
this[kState] = this[kState].filter((
|
|
10428
|
+
this[kState] = this[kState].filter((entry2) => entry2.name !== name);
|
|
10429
10429
|
}
|
|
10430
10430
|
get(name) {
|
|
10431
10431
|
webidl.brandCheck(this, _FormData);
|
|
10432
10432
|
const prefix = "FormData.get";
|
|
10433
10433
|
webidl.argumentLengthCheck(arguments, 1, prefix);
|
|
10434
10434
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10435
|
-
const idx = this[kState].findIndex((
|
|
10435
|
+
const idx = this[kState].findIndex((entry2) => entry2.name === name);
|
|
10436
10436
|
if (idx === -1) {
|
|
10437
10437
|
return null;
|
|
10438
10438
|
}
|
|
@@ -10443,14 +10443,14 @@ var require_formdata = __commonJS({
|
|
|
10443
10443
|
const prefix = "FormData.getAll";
|
|
10444
10444
|
webidl.argumentLengthCheck(arguments, 1, prefix);
|
|
10445
10445
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10446
|
-
return this[kState].filter((
|
|
10446
|
+
return this[kState].filter((entry2) => entry2.name === name).map((entry2) => entry2.value);
|
|
10447
10447
|
}
|
|
10448
10448
|
has(name) {
|
|
10449
10449
|
webidl.brandCheck(this, _FormData);
|
|
10450
10450
|
const prefix = "FormData.has";
|
|
10451
10451
|
webidl.argumentLengthCheck(arguments, 1, prefix);
|
|
10452
10452
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10453
|
-
return this[kState].findIndex((
|
|
10453
|
+
return this[kState].findIndex((entry2) => entry2.name === name) !== -1;
|
|
10454
10454
|
}
|
|
10455
10455
|
set(name, value, filename = void 0) {
|
|
10456
10456
|
webidl.brandCheck(this, _FormData);
|
|
@@ -10464,16 +10464,16 @@ var require_formdata = __commonJS({
|
|
|
10464
10464
|
name = webidl.converters.USVString(name, prefix, "name");
|
|
10465
10465
|
value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "name", { strict: false }) : webidl.converters.USVString(value, prefix, "name");
|
|
10466
10466
|
filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "name") : void 0;
|
|
10467
|
-
const
|
|
10468
|
-
const idx = this[kState].findIndex((
|
|
10467
|
+
const entry2 = makeEntry(name, value, filename);
|
|
10468
|
+
const idx = this[kState].findIndex((entry3) => entry3.name === name);
|
|
10469
10469
|
if (idx !== -1) {
|
|
10470
10470
|
this[kState] = [
|
|
10471
10471
|
...this[kState].slice(0, idx),
|
|
10472
|
-
|
|
10473
|
-
...this[kState].slice(idx + 1).filter((
|
|
10472
|
+
entry2,
|
|
10473
|
+
...this[kState].slice(idx + 1).filter((entry3) => entry3.name !== name)
|
|
10474
10474
|
];
|
|
10475
10475
|
} else {
|
|
10476
|
-
this[kState].push(
|
|
10476
|
+
this[kState].push(entry2);
|
|
10477
10477
|
}
|
|
10478
10478
|
}
|
|
10479
10479
|
[nodeUtil.inspect.custom](depth, options) {
|
|
@@ -13753,16 +13753,16 @@ var require_env_http_proxy_agent = __commonJS({
|
|
|
13753
13753
|
return false;
|
|
13754
13754
|
}
|
|
13755
13755
|
for (let i = 0; i < this.#noProxyEntries.length; i++) {
|
|
13756
|
-
const
|
|
13757
|
-
if (
|
|
13756
|
+
const entry2 = this.#noProxyEntries[i];
|
|
13757
|
+
if (entry2.port && entry2.port !== port) {
|
|
13758
13758
|
continue;
|
|
13759
13759
|
}
|
|
13760
|
-
if (!/^[.*]/.test(
|
|
13761
|
-
if (hostname3 ===
|
|
13760
|
+
if (!/^[.*]/.test(entry2.hostname)) {
|
|
13761
|
+
if (hostname3 === entry2.hostname) {
|
|
13762
13762
|
return false;
|
|
13763
13763
|
}
|
|
13764
13764
|
} else {
|
|
13765
|
-
if (hostname3.endsWith(
|
|
13765
|
+
if (hostname3.endsWith(entry2.hostname.replace(/^\*/, ""))) {
|
|
13766
13766
|
return false;
|
|
13767
13767
|
}
|
|
13768
13768
|
}
|
|
@@ -13774,13 +13774,13 @@ var require_env_http_proxy_agent = __commonJS({
|
|
|
13774
13774
|
const noProxySplit = noProxyValue.split(/[,\s]/);
|
|
13775
13775
|
const noProxyEntries = [];
|
|
13776
13776
|
for (let i = 0; i < noProxySplit.length; i++) {
|
|
13777
|
-
const
|
|
13778
|
-
if (!
|
|
13777
|
+
const entry2 = noProxySplit[i];
|
|
13778
|
+
if (!entry2) {
|
|
13779
13779
|
continue;
|
|
13780
13780
|
}
|
|
13781
|
-
const parsed =
|
|
13781
|
+
const parsed = entry2.match(/^(.+):(\d+)$/);
|
|
13782
13782
|
noProxyEntries.push({
|
|
13783
|
-
hostname: (parsed ? parsed[1] :
|
|
13783
|
+
hostname: (parsed ? parsed[1] : entry2).toLowerCase(),
|
|
13784
13784
|
port: parsed ? Number.parseInt(parsed[2], 10) : 0
|
|
13785
13785
|
});
|
|
13786
13786
|
}
|
|
@@ -13828,7 +13828,7 @@ var require_headers = __commonJS({
|
|
|
13828
13828
|
while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i;
|
|
13829
13829
|
return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j);
|
|
13830
13830
|
}
|
|
13831
|
-
function
|
|
13831
|
+
function fill2(headers, object) {
|
|
13832
13832
|
if (Array.isArray(object)) {
|
|
13833
13833
|
for (let i = 0; i < object.length; ++i) {
|
|
13834
13834
|
const header = object[i];
|
|
@@ -14053,7 +14053,7 @@ var require_headers = __commonJS({
|
|
|
14053
14053
|
this.#guard = "none";
|
|
14054
14054
|
if (init !== void 0) {
|
|
14055
14055
|
init = webidl.converters.HeadersInit(init, "Headers contructor", "init");
|
|
14056
|
-
|
|
14056
|
+
fill2(this, init);
|
|
14057
14057
|
}
|
|
14058
14058
|
}
|
|
14059
14059
|
// https://fetch.spec.whatwg.org/#dom-headers-append
|
|
@@ -14233,7 +14233,7 @@ var require_headers = __commonJS({
|
|
|
14233
14233
|
});
|
|
14234
14234
|
};
|
|
14235
14235
|
module2.exports = {
|
|
14236
|
-
fill,
|
|
14236
|
+
fill: fill2,
|
|
14237
14237
|
// for test.
|
|
14238
14238
|
compareHeaderName,
|
|
14239
14239
|
Headers: Headers2,
|
|
@@ -14251,7 +14251,7 @@ var require_response = __commonJS({
|
|
|
14251
14251
|
"node_modules/undici/lib/web/fetch/response.js"(exports2, module2) {
|
|
14252
14252
|
"use strict";
|
|
14253
14253
|
init_define_KT_HUB_FILES();
|
|
14254
|
-
var { Headers: Headers2, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require_headers();
|
|
14254
|
+
var { Headers: Headers2, HeadersList, fill: fill2, getHeadersGuard, setHeadersGuard, setHeadersList } = require_headers();
|
|
14255
14255
|
var { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require_body();
|
|
14256
14256
|
var util = require_util();
|
|
14257
14257
|
var nodeUtil = require("node:util");
|
|
@@ -14552,7 +14552,7 @@ var require_response = __commonJS({
|
|
|
14552
14552
|
response[kState].statusText = init.statusText;
|
|
14553
14553
|
}
|
|
14554
14554
|
if ("headers" in init && init.headers != null) {
|
|
14555
|
-
|
|
14555
|
+
fill2(response[kHeaders], init.headers);
|
|
14556
14556
|
}
|
|
14557
14557
|
if (body) {
|
|
14558
14558
|
if (nullBodyStatus.includes(response.status)) {
|
|
@@ -23900,7 +23900,7 @@ async function completeEmailCodeLogin(d) {
|
|
|
23900
23900
|
}
|
|
23901
23901
|
|
|
23902
23902
|
// src/index.ts
|
|
23903
|
-
var
|
|
23903
|
+
var import_fs7 = require("fs");
|
|
23904
23904
|
|
|
23905
23905
|
// src/owner-only.ts
|
|
23906
23906
|
init_define_KT_HUB_FILES();
|
|
@@ -23921,7 +23921,7 @@ function writeOwnerOnly(path, data) {
|
|
|
23921
23921
|
|
|
23922
23922
|
// src/index.ts
|
|
23923
23923
|
var import_os5 = require("os");
|
|
23924
|
-
var
|
|
23924
|
+
var import_path7 = require("path");
|
|
23925
23925
|
var import_readline2 = require("readline");
|
|
23926
23926
|
var import_net = require("net");
|
|
23927
23927
|
|
|
@@ -24054,6 +24054,152 @@ function auditResultLabel(raw) {
|
|
|
24054
24054
|
return humanizeAction(raw);
|
|
24055
24055
|
}
|
|
24056
24056
|
|
|
24057
|
+
// ../packages/shared/src/refresh-refusal-copy.ts
|
|
24058
|
+
init_define_KT_HUB_FILES();
|
|
24059
|
+
|
|
24060
|
+
// ../packages/shared/src/security-copy.ts
|
|
24061
|
+
init_define_KT_HUB_FILES();
|
|
24062
|
+
var SECURITY_COPY_EN = {
|
|
24063
|
+
"destinationRefusal.title": "Destination not authorized",
|
|
24064
|
+
"destinationRefusal.title.fault": "This destination could not be checked",
|
|
24065
|
+
"destinationRefusal.title.connection": "This destination cannot be authorized as written",
|
|
24066
|
+
"destinationRefusal.summary.named": 'Kutrion did not connect to {destination} and did not send the credential "{credential}".',
|
|
24067
|
+
"destinationRefusal.summary.namedSource": 'Kutrion did not connect to {destination} as the source of this transfer and did not send the credential "{credential}".',
|
|
24068
|
+
"destinationRefusal.summary.namedDest": 'Kutrion did not connect to {destination} as the destination of this transfer and did not send the credential "{credential}".',
|
|
24069
|
+
"destinationRefusal.summary.unnamed": "Kutrion did not connect to {destination} and did not send this credential.",
|
|
24070
|
+
"destinationRefusal.summary.unnamedSource": "Kutrion did not connect to {destination} as the source of this transfer and did not send this credential.",
|
|
24071
|
+
"destinationRefusal.summary.unnamedDest": "Kutrion did not connect to {destination} as the destination of this transfer and did not send this credential.",
|
|
24072
|
+
"destinationRefusal.allowed.list": "It may be presented to these destinations only:",
|
|
24073
|
+
"destinationRefusal.allowed.listInline": "It may be presented to these destinations only: {destinations}.",
|
|
24074
|
+
"destinationRefusal.allowed.noneFault": "Kutrion could not check where this credential may be presented.",
|
|
24075
|
+
"destinationRefusal.allowed.none": "No authorized destinations are recorded for this credential.",
|
|
24076
|
+
"destinationRefusal.action.allowList": "Where this credential may be presented is set on the credential itself. Changing it needs write access to the vault holding the credential, and the credential's current value.",
|
|
24077
|
+
"destinationRefusal.action.authorizeBelow": "Authorize this destination below.",
|
|
24078
|
+
"destinationRefusal.action.host": "The host recorded on this connection cannot be expressed as an authorized destination, so no entry on the credential can match it. A destination host must be a plain ASCII hostname, an IPv4 address, or an IPv6 address in square brackets, and must not carry a scheme, a port, a user name or a path. Correct the host on the connection, then authorize the corrected destination on the credential.",
|
|
24079
|
+
"destinationRefusal.action.domain": "The Windows domain recorded on this connection cannot be expressed as an authorized destination, so no entry on the credential can match it. A domain must be a plain ASCII name, either a short name such as CORP or a full name such as corp.example.com, entered on its own without a backslash, a user name or an address, or left empty if this host does not use one. Correct the domain on the connection, then authorize the corrected destination on the credential.",
|
|
24080
|
+
"destinationRefusal.action.fault": "This is a fault in Kutrion, not a permission you are missing. No change to the credential or the connection will resolve it. Report it with the time, the connection and the destination shown above.",
|
|
24081
|
+
"destinationRefusal.notARemedy": "Re-entering the credential's value here does not change where it may be presented.",
|
|
24082
|
+
"destinationRefusal.locator.named": "Open Kutrion on the web, go to the vault, open this credential and choose Destinations to allow {destination}. Or run `kt cred dest add`.",
|
|
24083
|
+
"destinationRefusal.locator.unnamed": "Open Kutrion on the web, go to the vault, open this credential and choose Destinations to allow this destination. Or run `kt cred dest add`.",
|
|
24084
|
+
"tlsDowngrade.title": "Transport security cannot be weakened",
|
|
24085
|
+
"tlsDowngrade.summary": "An existing connection's transport security cannot be weakened.",
|
|
24086
|
+
"tlsDowngrade.action": "Keep TLS and certificate checking on, or use a credential that has no stronger connection to this host.",
|
|
24087
|
+
"destinationNeed.notice": "Saved. Kutrion will refuse this connection until this credential is authorized for {target}.",
|
|
24088
|
+
"destinationNeed.who": "An editor, admin or owner of the vault {vault} can authorize it. It is listed as waiting under that credential's Destinations.",
|
|
24089
|
+
"destinationNeed.nonWriterPresave": "Kutrion will refuse this connection until this credential is authorized for this destination. An editor, admin or owner of the vault {vault} can authorize it. It is listed as waiting under that credential's Destinations.",
|
|
24090
|
+
"destinationNeed.badge": "Needs authorization",
|
|
24091
|
+
"destinationNeed.cleartext.ftp": "Plain FTP sends this password unencrypted.",
|
|
24092
|
+
"destinationNeed.cleartext.other": "This connection is not encrypted.",
|
|
24093
|
+
"destinationNeed.cleartext.ftpReaches": "This credential already reaches {host} over {protocols}. Plain FTP sends this password unencrypted.",
|
|
24094
|
+
"destinationNeed.cleartext.otherReaches": "This credential already reaches {host} over {protocols}. This connection is not encrypted.",
|
|
24095
|
+
"destinationNeed.publicLinks": "{count} share link(s) or drop zone(s) on this connection will start working for anyone who holds them.",
|
|
24096
|
+
"destinationNeed.firstClaim": "This is the credential's first destination. After this, Kutrion presents it only to the destinations listed for it.",
|
|
24097
|
+
"authorizeSheet.action": "Authorize this destination",
|
|
24098
|
+
"authorizeSheet.alsoAuthorize": "Also authorize this credential for {target}",
|
|
24099
|
+
"authorizeSheet.typedHint": "Changing where a credential may be sent needs the credential's own value, not just write access to the vault.",
|
|
24100
|
+
"authorizeSheet.secretMismatch": "That is not this credential's current value.",
|
|
24101
|
+
"authorizeSheet.injectOnly": "This credential is injection-only, so it cannot be authorized for a destination here.",
|
|
24102
|
+
"authorizeSheet.unsupported": "This server cannot authorize with the saved value. Enter the value instead.",
|
|
24103
|
+
"authorizeSheet.failed": "This destination could not be authorized.",
|
|
24104
|
+
"waiting.requestedBy": "Requested by {requester} ({role}).",
|
|
24105
|
+
"refreshRefusal.noun.app": "app",
|
|
24106
|
+
"refreshRefusal.noun.browser": "browser",
|
|
24107
|
+
"refreshRefusal.noun.device": "device",
|
|
24108
|
+
"refreshRefusal.signedOut.title": "This session was signed out",
|
|
24109
|
+
"refreshRefusal.refresh_hub_signed_out.why": "You signed out in Kutrion Hub.",
|
|
24110
|
+
"refreshRefusal.refresh_hub_revoked.why": "This {app} was signed out because its Kutrion Hub was removed.",
|
|
24111
|
+
"refreshRefusal.refresh_credentials_changed.title": "Your password changed",
|
|
24112
|
+
"refreshRefusal.refresh_credentials_changed.why": "Your password changed, so Kutrion signed this {app} out.",
|
|
24113
|
+
"refreshRefusal.refresh_credentials_changed.action": "Sign in again with your new password.",
|
|
24114
|
+
"refreshRefusal.device_revoked.title": "This device was signed out",
|
|
24115
|
+
"refreshRefusal.device_revoked.why": "This {app}'s access was withdrawn from your Kutrion account.",
|
|
24116
|
+
"refreshRefusal.device_revoked.action": "Sign in again to re-register it.",
|
|
24117
|
+
"refreshRefusal.account_inactive.title": "This account has ended",
|
|
24118
|
+
"refreshRefusal.account_inactive.why": "Your Kutrion account is no longer active, so this {app} was signed out.",
|
|
24119
|
+
"refreshRefusal.accountEnded.action": "Your administrator can restore it. Signing in again won't work until they do.",
|
|
24120
|
+
"refreshRefusal.account_expired.title": "This account has expired",
|
|
24121
|
+
"refreshRefusal.account_expired.why": "Your Kutrion account has expired, so this {app} was signed out.",
|
|
24122
|
+
"refreshRefusal.org_mismatch.why": "You're no longer a member of the organization this {app} was signed in to.",
|
|
24123
|
+
"refreshRefusal.org_mismatch.action": "Sign in again to continue with your current organization.",
|
|
24124
|
+
"privateTarget.connect.connectorAvailable": "{host} is on a private network, which hosted Kutrion cannot reach directly. Install the Kutrion Connector on that network, or use self-hosted Kutrion.",
|
|
24125
|
+
"privateTarget.connect.connectorPending": "{host} is on a private network, which hosted Kutrion cannot reach directly. Self-hosted Kutrion connects to hosts on private networks by default. The Kutrion Connector is not available yet.",
|
|
24126
|
+
"privateTarget.save.connectorAvailable": "{host} is on a private network, which hosted Kutrion cannot reach directly. Connecting to it will be refused until the Kutrion Connector is installed on that network, or you use self-hosted Kutrion.",
|
|
24127
|
+
"privateTarget.save.connectorPending": "{host} is on a private network, which hosted Kutrion cannot reach directly. Connecting to it will be refused on hosted Kutrion. Self-hosted Kutrion connects to hosts on private networks by default. The Kutrion Connector is not available yet.",
|
|
24128
|
+
"privateTarget.short.connectorAvailable": "{host} is on a private network that hosted Kutrion cannot reach directly. Install the Kutrion Connector there, or use self-hosted Kutrion.",
|
|
24129
|
+
"privateTarget.short.connectorPending": "{host} is on a private network that hosted Kutrion cannot reach directly. Use self-hosted Kutrion; the Kutrion Connector is not available yet.",
|
|
24130
|
+
"privateTarget.orgPolicy": "This organization's policy does not allow connections to {host}. Ask an organization owner or admin."
|
|
24131
|
+
};
|
|
24132
|
+
var fill = (template, values) => values ? template.replace(/\{(\w+)\}/g, (m, name) => name in values ? String(values[name]) : m) : template;
|
|
24133
|
+
var englishT = (key, values) => fill(SECURITY_COPY_EN[key], values);
|
|
24134
|
+
function securityCopy(key, t, values) {
|
|
24135
|
+
if (t) {
|
|
24136
|
+
try {
|
|
24137
|
+
const out = t(key, values);
|
|
24138
|
+
if (out && out !== key) return out;
|
|
24139
|
+
} catch {
|
|
24140
|
+
}
|
|
24141
|
+
}
|
|
24142
|
+
return englishT(key, values);
|
|
24143
|
+
}
|
|
24144
|
+
|
|
24145
|
+
// ../packages/shared/src/refresh-refusal-copy.ts
|
|
24146
|
+
var CODES = [
|
|
24147
|
+
"refresh_hub_signed_out",
|
|
24148
|
+
"refresh_hub_revoked",
|
|
24149
|
+
"refresh_credentials_changed",
|
|
24150
|
+
"device_revoked",
|
|
24151
|
+
"account_inactive",
|
|
24152
|
+
"user_inactive",
|
|
24153
|
+
"account_expired",
|
|
24154
|
+
"org_mismatch"
|
|
24155
|
+
];
|
|
24156
|
+
var en = SECURITY_COPY_EN;
|
|
24157
|
+
var KEY_CODE = { user_inactive: "account_inactive" };
|
|
24158
|
+
var TITLE_KEY = {
|
|
24159
|
+
refresh_hub_signed_out: "signedOut",
|
|
24160
|
+
refresh_hub_revoked: "signedOut",
|
|
24161
|
+
org_mismatch: "signedOut"
|
|
24162
|
+
};
|
|
24163
|
+
var ACTION_KEY = { account_inactive: "accountEnded", account_expired: "accountEnded" };
|
|
24164
|
+
var keyFor = (code, part) => {
|
|
24165
|
+
const base = KEY_CODE[code] ?? code;
|
|
24166
|
+
const shared = part === "title" ? TITLE_KEY[base] : part === "action" ? ACTION_KEY[base] : void 0;
|
|
24167
|
+
return `refreshRefusal.${shared ?? base}.${part}`;
|
|
24168
|
+
};
|
|
24169
|
+
var entry = (code) => {
|
|
24170
|
+
const action = en[keyFor(code, "action")];
|
|
24171
|
+
return { title: en[keyFor(code, "title")], why: en[keyFor(code, "why")], ...action ? { action } : {} };
|
|
24172
|
+
};
|
|
24173
|
+
var REFRESH_REFUSAL_COPY = Object.fromEntries(
|
|
24174
|
+
CODES.map((code) => [code, entry(code)])
|
|
24175
|
+
);
|
|
24176
|
+
var NOUNS = ["app", "browser", "device"];
|
|
24177
|
+
function refreshRefusalCopy(code, noun = "app", t) {
|
|
24178
|
+
if (typeof code !== "string" || !Object.prototype.hasOwnProperty.call(REFRESH_REFUSAL_COPY, code)) return null;
|
|
24179
|
+
const key = (part) => keyFor(code, part);
|
|
24180
|
+
const word = NOUNS.includes(noun) ? securityCopy(`refreshRefusal.noun.${noun}`, t) : noun;
|
|
24181
|
+
const values = { app: word };
|
|
24182
|
+
const hasAction = keyFor(code, "action") in SECURITY_COPY_EN;
|
|
24183
|
+
return {
|
|
24184
|
+
title: securityCopy(key("title"), t),
|
|
24185
|
+
why: securityCopy(key("why"), t, values),
|
|
24186
|
+
...hasAction ? { action: securityCopy(key("action"), t, values) } : {}
|
|
24187
|
+
};
|
|
24188
|
+
}
|
|
24189
|
+
|
|
24190
|
+
// ../packages/shared/src/private-target-refusal.ts
|
|
24191
|
+
init_define_KT_HUB_FILES();
|
|
24192
|
+
var CONNECTOR_AVAILABLE = false;
|
|
24193
|
+
var privateTargetReason = (v) => v === "org_policy" ? "org_policy" : "deployment";
|
|
24194
|
+
function privateTargetKey(surface, reason, connectorAvailable = CONNECTOR_AVAILABLE) {
|
|
24195
|
+
if (reason === "org_policy") return "privateTarget.orgPolicy";
|
|
24196
|
+
const variant = connectorAvailable ? "connectorAvailable" : "connectorPending";
|
|
24197
|
+
return `privateTarget.${surface}.${variant}`;
|
|
24198
|
+
}
|
|
24199
|
+
function privateTargetSentence(o, t) {
|
|
24200
|
+
return securityCopy(privateTargetKey(o.surface, privateTargetReason(o.reason), o.connectorAvailable), t, { host: o.host });
|
|
24201
|
+
}
|
|
24202
|
+
|
|
24057
24203
|
// ../packages/shared/src/client.ts
|
|
24058
24204
|
init_define_KT_HUB_FILES();
|
|
24059
24205
|
|
|
@@ -27621,6 +27767,9 @@ var ApiError = class extends Error {
|
|
|
27621
27767
|
this.headers = headers;
|
|
27622
27768
|
this.name = "ApiError";
|
|
27623
27769
|
}
|
|
27770
|
+
// #3445: the `error` code /auth/refresh gave when it ended the session behind this 401, set only by
|
|
27771
|
+
// the SDK's own refresh-then-replay path. `body` and `message` stay the original request's.
|
|
27772
|
+
refreshCode;
|
|
27624
27773
|
// #2747: the error class slug — the server's `error` code, or the code the SDK minted itself — or
|
|
27625
27774
|
// null when the reply carried none (the message is then a status phrase, not a code).
|
|
27626
27775
|
get slug() {
|
|
@@ -27803,9 +27952,13 @@ async function withTimeout(inner, ms, input, init) {
|
|
|
27803
27952
|
var REFRESH_BODY_MS = 3e3;
|
|
27804
27953
|
var REFRESH_STORE_ATTEMPTS = 3;
|
|
27805
27954
|
var REFRESH_STORE_TIMEOUT_MS = 1e4;
|
|
27955
|
+
var SIGNATURE_REQUIRED_COOLDOWN_MS = 3e4;
|
|
27806
27956
|
function isRefreshSignatureRefusal(body) {
|
|
27807
27957
|
const error = body?.error;
|
|
27808
|
-
return error === "bad_signature" || error === "device_key_required";
|
|
27958
|
+
return error === "bad_signature" || error === "device_key_required" || error === "signature_required";
|
|
27959
|
+
}
|
|
27960
|
+
function isSignatureRequired(body) {
|
|
27961
|
+
return body?.error === "signature_required";
|
|
27809
27962
|
}
|
|
27810
27963
|
async function readBodyWithin(res, ms) {
|
|
27811
27964
|
let timer;
|
|
@@ -27964,6 +28117,8 @@ var KutrionClient = class {
|
|
|
27964
28117
|
// `next` is presented in its place: `spent` is already rotated, and presenting it reads as reuse.
|
|
27965
28118
|
heldRefresh = null;
|
|
27966
28119
|
refreshing = null;
|
|
28120
|
+
// performance.now() time, so a wall-clock step cannot lengthen or cut the cooldown.
|
|
28121
|
+
signatureRequiredUntil = 0;
|
|
27967
28122
|
// VT-405 (#153): per-session key for decrypting sealed response payloads. Agreed lazily over ECDH
|
|
27968
28123
|
// and held in memory only (re-agreed on demand after a reload / worker restart).
|
|
27969
28124
|
sessionKey = null;
|
|
@@ -28043,6 +28198,7 @@ var KutrionClient = class {
|
|
|
28043
28198
|
async tryRefresh() {
|
|
28044
28199
|
const rt = this.presentableRefreshToken();
|
|
28045
28200
|
if (!rt) return { outcome: "no_token" };
|
|
28201
|
+
if (!this.refreshing && performance.now() < this.signatureRequiredUntil) return { outcome: "indeterminate", status: 401 };
|
|
28046
28202
|
if (!this.refreshing) {
|
|
28047
28203
|
this.refreshing = (async () => {
|
|
28048
28204
|
try {
|
|
@@ -28053,15 +28209,23 @@ var KutrionClient = class {
|
|
|
28053
28209
|
});
|
|
28054
28210
|
const { body: signed, signerThrew } = await this.signedRefreshBody(rt);
|
|
28055
28211
|
let res = await post(signed);
|
|
28056
|
-
|
|
28057
|
-
|
|
28058
|
-
|
|
28212
|
+
let refusalBody;
|
|
28213
|
+
if (res.status === 401 && signed.signature) {
|
|
28214
|
+
refusalBody = await readBodyWithin(res, REFRESH_BODY_MS);
|
|
28215
|
+
if (isRefreshSignatureRefusal(refusalBody)) {
|
|
28216
|
+
res = await post(this.refreshBody(rt));
|
|
28217
|
+
refusalBody = void 0;
|
|
28218
|
+
if (res.ok) this.refreshSigningRefused = true;
|
|
28219
|
+
}
|
|
28059
28220
|
}
|
|
28060
28221
|
if (res.status === 401) {
|
|
28061
|
-
if (
|
|
28222
|
+
if (refusalBody === void 0) refusalBody = await readBodyWithin(res, REFRESH_BODY_MS);
|
|
28223
|
+
if (signerThrew && !signed.signature && isRefreshSignatureRefusal(refusalBody) || isSignatureRequired(refusalBody)) {
|
|
28224
|
+
if (!signerThrew && isSignatureRequired(refusalBody)) this.signatureRequiredUntil = performance.now() + SIGNATURE_REQUIRED_COOLDOWN_MS;
|
|
28062
28225
|
return { outcome: "indeterminate", status: 401 };
|
|
28063
28226
|
}
|
|
28064
|
-
|
|
28227
|
+
const code = refusalBody?.error;
|
|
28228
|
+
return typeof code === "string" && code ? { outcome: "rejected", status: 401, code } : { outcome: "rejected", status: 401 };
|
|
28065
28229
|
}
|
|
28066
28230
|
if (!res.ok) {
|
|
28067
28231
|
const body = res.status === 403 ? await readBodyWithin(res, REFRESH_BODY_MS) : void 0;
|
|
@@ -28147,6 +28311,7 @@ var KutrionClient = class {
|
|
|
28147
28311
|
data = JSON.parse(await decryptEnvelope(this.sessionKey, data.enc));
|
|
28148
28312
|
}
|
|
28149
28313
|
if (!res.ok) {
|
|
28314
|
+
let refreshCode;
|
|
28150
28315
|
if (res.status === 401 && auth) {
|
|
28151
28316
|
if (_retry) {
|
|
28152
28317
|
this.onUnauthorized?.();
|
|
@@ -28156,6 +28321,9 @@ var KutrionClient = class {
|
|
|
28156
28321
|
case "refreshed":
|
|
28157
28322
|
return this.req(method, path, body, auth, true);
|
|
28158
28323
|
case "rejected":
|
|
28324
|
+
refreshCode = r.code;
|
|
28325
|
+
this.onUnauthorized?.(r.code);
|
|
28326
|
+
break;
|
|
28159
28327
|
case "no_token":
|
|
28160
28328
|
this.onUnauthorized?.();
|
|
28161
28329
|
break;
|
|
@@ -28170,7 +28338,9 @@ var KutrionClient = class {
|
|
|
28170
28338
|
}
|
|
28171
28339
|
}
|
|
28172
28340
|
}
|
|
28173
|
-
|
|
28341
|
+
const err = new ApiError(res.status, apiErrorMessage(res.status, data), data, res.headers);
|
|
28342
|
+
if (refreshCode) err.refreshCode = refreshCode;
|
|
28343
|
+
throw err;
|
|
28174
28344
|
}
|
|
28175
28345
|
return data;
|
|
28176
28346
|
}
|
|
@@ -28420,7 +28590,7 @@ var KutrionClient = class {
|
|
|
28420
28590
|
// #2578: clears a factor's pending-approval mark. A step-up-pending session is refused with 403
|
|
28421
28591
|
// step_up_required; `approved: false` means the factor was already approved.
|
|
28422
28592
|
approveFactor(kind, id) {
|
|
28423
|
-
return this.req("POST", `/auth/factors/${kind}/${encodeURIComponent(id)}/approve`);
|
|
28593
|
+
return this.req("POST", `/auth/factors/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/approve`);
|
|
28424
28594
|
}
|
|
28425
28595
|
mfaGenerateBackupCodes() {
|
|
28426
28596
|
return this.req("POST", "/auth/mfa/backup-codes/generate");
|
|
@@ -28440,14 +28610,17 @@ var KutrionClient = class {
|
|
|
28440
28610
|
// losing its keys; the server revokes that row if it is the caller's and ignores it otherwise.
|
|
28441
28611
|
async registerDevice(input) {
|
|
28442
28612
|
const reg = await this.req("POST", "/auth/devices/register", input);
|
|
28443
|
-
if (reg.refresh_key_bound)
|
|
28613
|
+
if (reg.refresh_key_bound) {
|
|
28614
|
+
this.refreshSigningRefused = false;
|
|
28615
|
+
this.signatureRequiredUntil = 0;
|
|
28616
|
+
}
|
|
28444
28617
|
return reg;
|
|
28445
28618
|
}
|
|
28446
28619
|
// #301: mint a second refresh-token family for an existing device, owned by a companion process
|
|
28447
28620
|
// (the iOS AutoFill extension) so it rotates independently of the app and never trips the app's
|
|
28448
28621
|
// reuse-detection. The extension persists its own rotations; the app must not share its token.
|
|
28449
28622
|
provisionCompanionSession(deviceId) {
|
|
28450
|
-
return this.req("POST", `/auth/devices/${deviceId}/companion`, {});
|
|
28623
|
+
return this.req("POST", `/auth/devices/${encodeURIComponent(deviceId)}/companion`, {});
|
|
28451
28624
|
}
|
|
28452
28625
|
// #768: `drain_limits` is what each device's `drain.state` may and may not be said to prove. The
|
|
28453
28626
|
// server ships the sentence so three clients cannot each invent their own; optional here only
|
|
@@ -28456,7 +28629,7 @@ var KutrionClient = class {
|
|
|
28456
28629
|
return this.req("GET", "/auth/devices");
|
|
28457
28630
|
}
|
|
28458
28631
|
revokeDevice(id) {
|
|
28459
|
-
return this.req("DELETE", `/auth/devices/${id}`, void 0);
|
|
28632
|
+
return this.req("DELETE", `/auth/devices/${encodeURIComponent(id)}`, void 0);
|
|
28460
28633
|
}
|
|
28461
28634
|
// Rotate tokens explicitly (req() also does this transparently on 401).
|
|
28462
28635
|
async refreshTokens(refreshToken) {
|
|
@@ -28464,8 +28637,11 @@ var KutrionClient = class {
|
|
|
28464
28637
|
try {
|
|
28465
28638
|
return await this.req("POST", "/auth/refresh", body, false);
|
|
28466
28639
|
} catch (e) {
|
|
28467
|
-
if (
|
|
28468
|
-
throw new ApiError(0, "
|
|
28640
|
+
if (e instanceof ApiError && e.status === 401) {
|
|
28641
|
+
if (isSignatureRequired(e.body) && !signerThrew) throw new ApiError(0, "signature_required");
|
|
28642
|
+
if (signerThrew && !body.signature && isRefreshSignatureRefusal(e.body) || isSignatureRequired(e.body)) {
|
|
28643
|
+
throw new ApiError(0, "signer_unavailable");
|
|
28644
|
+
}
|
|
28469
28645
|
}
|
|
28470
28646
|
throw e;
|
|
28471
28647
|
}
|
|
@@ -28485,7 +28661,10 @@ var KutrionClient = class {
|
|
|
28485
28661
|
refreshKey ? { device_id: deviceId, signature, refresh_key: refreshKey } : { device_id: deviceId, signature },
|
|
28486
28662
|
false
|
|
28487
28663
|
);
|
|
28488
|
-
if (tokens.refresh_key_bound)
|
|
28664
|
+
if (tokens.refresh_key_bound) {
|
|
28665
|
+
this.refreshSigningRefused = false;
|
|
28666
|
+
this.signatureRequiredUntil = 0;
|
|
28667
|
+
}
|
|
28489
28668
|
return tokens;
|
|
28490
28669
|
}
|
|
28491
28670
|
// --- local vault cache: keys (#709, epic #708) ---
|
|
@@ -28493,7 +28672,7 @@ var KutrionClient = class {
|
|
|
28493
28672
|
// nowhere else afterwards, so a caller that drops it must either re-issue (same key while it lives)
|
|
28494
28673
|
// or rotate — and rotating deliberately kills whatever the old key was protecting.
|
|
28495
28674
|
issueCacheKey(deviceId, opts2) {
|
|
28496
|
-
return this.req("POST", `/auth/devices/${deviceId}/cache-key`, { rotate: opts2?.rotate === true });
|
|
28675
|
+
return this.req("POST", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`, { rotate: opts2?.rotate === true });
|
|
28497
28676
|
}
|
|
28498
28677
|
// #712: drain this device's local reveal journal. The batch is MAC'd under the cache KEK; a broken
|
|
28499
28678
|
// chain answers 409 with the head the server holds, never a silent success.
|
|
@@ -28508,10 +28687,10 @@ var KutrionClient = class {
|
|
|
28508
28687
|
return this.req("POST", "/audit/journal-discarded", input);
|
|
28509
28688
|
}
|
|
28510
28689
|
cacheKeyStatus(deviceId) {
|
|
28511
|
-
return this.req("GET", `/auth/devices/${deviceId}/cache-key`);
|
|
28690
|
+
return this.req("GET", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`);
|
|
28512
28691
|
}
|
|
28513
28692
|
revokeCacheKey(deviceId) {
|
|
28514
|
-
return this.req("DELETE", `/auth/devices/${deviceId}/cache-key`);
|
|
28693
|
+
return this.req("DELETE", `/auth/devices/${encodeURIComponent(deviceId)}/cache-key`);
|
|
28515
28694
|
}
|
|
28516
28695
|
listCacheKeys() {
|
|
28517
28696
|
return this.req("GET", "/auth/cache-keys");
|
|
@@ -28532,21 +28711,24 @@ var KutrionClient = class {
|
|
|
28532
28711
|
return this.req("GET", "/auth/webauthn");
|
|
28533
28712
|
}
|
|
28534
28713
|
deletePasskey(id) {
|
|
28535
|
-
return this.req("DELETE", `/auth/webauthn/${id}`);
|
|
28714
|
+
return this.req("DELETE", `/auth/webauthn/${encodeURIComponent(id)}`);
|
|
28715
|
+
}
|
|
28716
|
+
renamePasskey(id, label) {
|
|
28717
|
+
return this.req("PATCH", `/auth/webauthn/${encodeURIComponent(id)}`, { label });
|
|
28536
28718
|
}
|
|
28537
28719
|
// VT-120: linked sign-in methods (SSO/social identities) + set a first password for social-only accounts.
|
|
28538
28720
|
listIdentities() {
|
|
28539
28721
|
return this.req("GET", "/auth/identities");
|
|
28540
28722
|
}
|
|
28541
28723
|
unlinkIdentity(id) {
|
|
28542
|
-
return this.req("DELETE", `/auth/identities/${id}`);
|
|
28724
|
+
return this.req("DELETE", `/auth/identities/${encodeURIComponent(id)}`);
|
|
28543
28725
|
}
|
|
28544
28726
|
// #2678: begin linking a platform provider. The browser then navigates to `url`; the callback hands
|
|
28545
28727
|
// back a handle that completeIdentityLink attaches from this same session. #2847: a native app passes
|
|
28546
28728
|
// its registered client and the challenge for a verifier it keeps, then completes with that verifier.
|
|
28547
28729
|
startIdentityLink(ref, binding, currentPassword) {
|
|
28548
28730
|
const how = typeof binding === "string" ? { cb: binding } : { native: true, native_client: binding.nativeClient, native_challenge: binding.challenge };
|
|
28549
|
-
return this.req("POST", `/auth/sso/${ref}/link`, { ...how, ...currentPassword ? { current_password: currentPassword } : {} });
|
|
28731
|
+
return this.req("POST", `/auth/sso/${encodeURIComponent(ref)}/link`, { ...how, ...currentPassword ? { current_password: currentPassword } : {} });
|
|
28550
28732
|
}
|
|
28551
28733
|
completeIdentityLink(handle, verifier) {
|
|
28552
28734
|
return this.req("POST", "/auth/sso/link/complete", { handle, ...verifier ? { verifier } : {} });
|
|
@@ -28587,15 +28769,15 @@ var KutrionClient = class {
|
|
|
28587
28769
|
// Works for both OIDC and SAML providers — /start dispatches on the provider's protocol. `ref`
|
|
28588
28770
|
// is the provider id (preferred, unambiguous across orgs) or a platform provider's slug.
|
|
28589
28771
|
ssoStartUrl(ref) {
|
|
28590
|
-
return `${this.baseUrl}/auth/sso/${ref}/start`;
|
|
28772
|
+
return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/start`;
|
|
28591
28773
|
}
|
|
28592
28774
|
// SP metadata URL to hand a SAML IdP (entityID + ACS endpoint). Keyed by provider id.
|
|
28593
28775
|
samlMetadataUrl(ref) {
|
|
28594
|
-
return `${this.baseUrl}/auth/sso/${ref}/saml/metadata`;
|
|
28776
|
+
return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/saml/metadata`;
|
|
28595
28777
|
}
|
|
28596
28778
|
// OIDC redirect URI to register at the IdP, keyed by provider id (per-org safe).
|
|
28597
28779
|
ssoCallbackUrl(ref) {
|
|
28598
|
-
return `${this.baseUrl}/auth/sso/${ref}/callback`;
|
|
28780
|
+
return `${this.baseUrl}/auth/sso/${encodeURIComponent(ref)}/callback`;
|
|
28599
28781
|
}
|
|
28600
28782
|
// --- SSO provider admin (MT-3, org admin/owner only; secrets are write-only) ---
|
|
28601
28783
|
listSsoProvidersAdmin() {
|
|
@@ -28686,10 +28868,10 @@ var KutrionClient = class {
|
|
|
28686
28868
|
return this.req("POST", "/workspaces", input);
|
|
28687
28869
|
}
|
|
28688
28870
|
updateWorkspace(id, input) {
|
|
28689
|
-
return this.req("PUT", `/workspaces/${id}`, input);
|
|
28871
|
+
return this.req("PUT", `/workspaces/${encodeURIComponent(id)}`, input);
|
|
28690
28872
|
}
|
|
28691
28873
|
deleteWorkspace(id) {
|
|
28692
|
-
return this.req("DELETE", `/workspaces/${id}`);
|
|
28874
|
+
return this.req("DELETE", `/workspaces/${encodeURIComponent(id)}`);
|
|
28693
28875
|
}
|
|
28694
28876
|
// --- relay (#458 vault-synced API-client documents; #464 team grants) ---
|
|
28695
28877
|
listRelayDocuments() {
|
|
@@ -28705,7 +28887,7 @@ var KutrionClient = class {
|
|
|
28705
28887
|
return this.req("POST", `/relay/documents/${encodeURIComponent(clientId)}/grants`, { team_id, role });
|
|
28706
28888
|
}
|
|
28707
28889
|
relayUnshare(clientId, teamId) {
|
|
28708
|
-
return this.req("DELETE", `/relay/documents/${encodeURIComponent(clientId)}/grants/${teamId}`);
|
|
28890
|
+
return this.req("DELETE", `/relay/documents/${encodeURIComponent(clientId)}/grants/${encodeURIComponent(teamId)}`);
|
|
28709
28891
|
}
|
|
28710
28892
|
// --- vault ---
|
|
28711
28893
|
listVaults() {
|
|
@@ -28715,13 +28897,13 @@ var KutrionClient = class {
|
|
|
28715
28897
|
return this.req("POST", "/vault/create", { name, team_id });
|
|
28716
28898
|
}
|
|
28717
28899
|
renameVault(vaultId, name) {
|
|
28718
|
-
return this.req("PATCH", `/vault/${vaultId}`, { name });
|
|
28900
|
+
return this.req("PATCH", `/vault/${encodeURIComponent(vaultId)}`, { name });
|
|
28719
28901
|
}
|
|
28720
28902
|
deleteVault(vaultId) {
|
|
28721
|
-
return this.req("DELETE", `/vault/${vaultId}`);
|
|
28903
|
+
return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}`);
|
|
28722
28904
|
}
|
|
28723
28905
|
listCredentials(vaultId) {
|
|
28724
|
-
return this.req("GET", `/vault/${vaultId}/credentials`);
|
|
28906
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials`);
|
|
28725
28907
|
}
|
|
28726
28908
|
// #1122: listVaults + every listCredentials in one round trip, with the same permissions gating.
|
|
28727
28909
|
// Metadata only — revealing a secret is still the audited per-credential call.
|
|
@@ -28747,19 +28929,19 @@ var KutrionClient = class {
|
|
|
28747
28929
|
return this.req("GET", `/vault/host-matches?host=${encodeURIComponent(host2)}`);
|
|
28748
28930
|
}
|
|
28749
28931
|
getCredential(vaultId, cid) {
|
|
28750
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}`);
|
|
28932
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}`);
|
|
28751
28933
|
}
|
|
28752
28934
|
// #717: `mutation_id` and `base_updated_at` are what a replayed offline write adds — the first
|
|
28753
28935
|
// makes a retry a no-op, the second lets the server tell an edit from an overwrite.
|
|
28754
28936
|
storeCredential(vaultId, input) {
|
|
28755
|
-
return this.req("POST", `/vault/${vaultId}/credentials`, input);
|
|
28937
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials`, input);
|
|
28756
28938
|
}
|
|
28757
28939
|
updateCredential(vaultId, cid, input) {
|
|
28758
|
-
return this.req("PATCH", `/vault/${vaultId}/credentials/${cid}`, input);
|
|
28940
|
+
return this.req("PATCH", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}`, input);
|
|
28759
28941
|
}
|
|
28760
28942
|
deleteCredential(vaultId, cid, opts2) {
|
|
28761
28943
|
const q = opts2?.mutation_id ? `?mutation_id=${encodeURIComponent(opts2.mutation_id)}` : "";
|
|
28762
|
-
return this.req("DELETE", `/vault/${vaultId}/credentials/${cid}
|
|
28944
|
+
return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}` + q);
|
|
28763
28945
|
}
|
|
28764
28946
|
// #2622: snippets. A brokered shell runs one over its socket (snippetFrame); useSnippet is for a
|
|
28765
28947
|
// terminal the server does not broker, and records the use the same way.
|
|
@@ -28787,23 +28969,23 @@ var KutrionClient = class {
|
|
|
28787
28969
|
return this.req("GET", `/vault/conflicts${opts2?.open === false ? "?open=false" : ""}`);
|
|
28788
28970
|
}
|
|
28789
28971
|
acknowledgeConflict(id) {
|
|
28790
|
-
return this.req("POST", `/vault/conflicts/${id}/ack`);
|
|
28972
|
+
return this.req("POST", `/vault/conflicts/${encodeURIComponent(id)}/ack`);
|
|
28791
28973
|
}
|
|
28792
28974
|
conflictPrevious(id) {
|
|
28793
|
-
return this.req("GET", `/vault/conflicts/${id}/previous`);
|
|
28975
|
+
return this.req("GET", `/vault/conflicts/${encodeURIComponent(id)}/previous`);
|
|
28794
28976
|
}
|
|
28795
28977
|
// VT-487: group RBAC on a vault (collection) + item folders.
|
|
28796
28978
|
listVaultGrants(vaultId) {
|
|
28797
|
-
return this.req("GET", `/vault/${vaultId}/grants`);
|
|
28979
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/grants`);
|
|
28798
28980
|
}
|
|
28799
28981
|
grantVaultToTeam(vaultId, input) {
|
|
28800
|
-
return this.req("POST", `/vault/${vaultId}/grants`, input);
|
|
28982
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/grants`, input);
|
|
28801
28983
|
}
|
|
28802
28984
|
revokeVaultGrant(vaultId, grantId) {
|
|
28803
|
-
return this.req("DELETE", `/vault/${vaultId}/grants/${grantId}`);
|
|
28985
|
+
return this.req("DELETE", `/vault/${encodeURIComponent(vaultId)}/grants/${encodeURIComponent(grantId)}`);
|
|
28804
28986
|
}
|
|
28805
28987
|
listVaultFolders(vaultId) {
|
|
28806
|
-
return this.req("GET", `/vault/${vaultId}/folders`);
|
|
28988
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/folders`);
|
|
28807
28989
|
}
|
|
28808
28990
|
// #1169: the credential's destination allow-list — where its secret may be presented. The routes
|
|
28809
28991
|
// live in their own module mounted on /vault (server/src/index.ts:310), so the paths hang off the
|
|
@@ -28820,36 +29002,45 @@ var KutrionClient = class {
|
|
|
28820
29002
|
// No write response carries the three list-only fields; re-list rather than deriving them.
|
|
28821
29003
|
// #2634: who revealed or used one credential. Vault owners and admins only; never carries the value.
|
|
28822
29004
|
credentialProvenance(vaultId, cid) {
|
|
28823
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/provenance`);
|
|
29005
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/provenance`);
|
|
28824
29006
|
}
|
|
28825
29007
|
// #3074: password history. The list carries no values; revealing one is a gated read like getCredential.
|
|
28826
29008
|
listCredentialHistory(vaultId, cid) {
|
|
28827
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/history`);
|
|
29009
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history`);
|
|
28828
29010
|
}
|
|
28829
29011
|
revealCredentialHistory(vaultId, cid, hid) {
|
|
28830
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/history/${hid}`);
|
|
29012
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history/${encodeURIComponent(hid)}`);
|
|
28831
29013
|
}
|
|
28832
29014
|
restoreCredentialHistory(vaultId, cid, hid) {
|
|
28833
|
-
return this.req("POST", `/vault/${vaultId}/credentials/${cid}/history/${hid}/restore`);
|
|
29015
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/history/${encodeURIComponent(hid)}/restore`);
|
|
28834
29016
|
}
|
|
28835
29017
|
listCredentialDestinations(vaultId, cid) {
|
|
28836
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/destinations`);
|
|
29018
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations`);
|
|
28837
29019
|
}
|
|
28838
29020
|
// `port` omitted means this protocol's default, resolved and stored server-side — NOT a wildcard, and
|
|
28839
29021
|
// not something to fill in here: a client-supplied default would come back as a chosen port.
|
|
28840
29022
|
//
|
|
28841
|
-
//
|
|
28842
|
-
//
|
|
28843
|
-
// the
|
|
28844
|
-
//
|
|
29023
|
+
// Write on the vault gates who may ask; the proof gates whether they may move the credential. It is
|
|
29024
|
+
// one of two (#3466):
|
|
29025
|
+
// - `secret`, the credential's current plaintext. A wrong one answers 403 `secret_mismatch` and is
|
|
29026
|
+
// rate-limited to 429 after 10.
|
|
29027
|
+
// - `use_saved_value: true`, for a caller who could reveal the value right now. It answers every
|
|
29028
|
+
// refusal reveal would, plus 403 `step_up_required` without a fresh step-up on this session. An
|
|
29029
|
+
// older server answers it 400 `invalid_input` with an issue at path ["secret"].
|
|
29030
|
+
// Injection-only credentials satisfy neither and answer 403 `inject_only`. Either proof may answer
|
|
29031
|
+
// 403 `step_up_required` from the adaptive gate, or 403 `device_posture_required`.
|
|
28845
29032
|
//
|
|
28846
29033
|
// #1170: `domain` is RDP-only and omitting it means the credential is presented with NO domain — not
|
|
28847
29034
|
// "any domain". It is the third member of the RDP destination tuple because it selects which
|
|
28848
29035
|
// directory authenticates the credential, so a binding that omits it does not authorise one that
|
|
28849
29036
|
// names it. Sent on any other protocol it answers 400 `domain_not_supported`; unexpressible values
|
|
28850
29037
|
// answer 400 `invalid_domain`.
|
|
29038
|
+
//
|
|
29039
|
+
// #3466: `use_saved_value` in place of `secret` asks the server to vouch for the stored value itself; a
|
|
29040
|
+
// server without that mode answers 400 `invalid_input` on `secret`. `authorizeDestination` below is the
|
|
29041
|
+
// one caller that should choose between the two.
|
|
28851
29042
|
addCredentialDestination(vaultId, cid, input) {
|
|
28852
|
-
return this.req("POST", `/vault/${vaultId}/credentials/${cid}/destinations`, input);
|
|
29043
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations`, input);
|
|
28853
29044
|
}
|
|
28854
29045
|
// Gated on the secret for the same reason adding is, but NOT because removal widens — #1281 reversed
|
|
28855
29046
|
// that. Emptying a protocol while rows survive elsewhere leaves the credential claimed, so that
|
|
@@ -28861,7 +29052,7 @@ var KutrionClient = class {
|
|
|
28861
29052
|
removeCredentialDestination(vaultId, cid, destinationId, secret) {
|
|
28862
29053
|
return this.req(
|
|
28863
29054
|
"DELETE",
|
|
28864
|
-
`/vault/${vaultId}/credentials/${cid}/destinations/${destinationId}`,
|
|
29055
|
+
`/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations/${encodeURIComponent(destinationId)}`,
|
|
28865
29056
|
{ secret }
|
|
28866
29057
|
);
|
|
28867
29058
|
}
|
|
@@ -28901,20 +29092,20 @@ var KutrionClient = class {
|
|
|
28901
29092
|
return this.req("POST", "/vault/breach-alerts/scan");
|
|
28902
29093
|
}
|
|
28903
29094
|
updateBreachAlert(id, status) {
|
|
28904
|
-
return this.req("PATCH", `/vault/breach-alerts/${id}`, { status });
|
|
29095
|
+
return this.req("PATCH", `/vault/breach-alerts/${encodeURIComponent(id)}`, { status });
|
|
28905
29096
|
}
|
|
28906
29097
|
setBreachMonitor(enabled) {
|
|
28907
29098
|
return this.req("PUT", "/vault/breach-alerts/settings", { enabled });
|
|
28908
29099
|
}
|
|
28909
29100
|
importEnv(vaultId, name, dotenv) {
|
|
28910
|
-
return this.req("POST", `/vault/${vaultId}/env-import`, { name, dotenv });
|
|
29101
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/env-import`, { name, dotenv });
|
|
28911
29102
|
}
|
|
28912
29103
|
readEnv(vaultId, cid) {
|
|
28913
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/env`);
|
|
29104
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/env`);
|
|
28914
29105
|
}
|
|
28915
29106
|
// #96: live TOTP code for a TOTP credential (secret stays on the broker).
|
|
28916
29107
|
getTotpCode(vaultId, cid) {
|
|
28917
|
-
return this.req("GET", `/vault/${vaultId}/credentials/${cid}/totp`);
|
|
29108
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/totp`);
|
|
28918
29109
|
}
|
|
28919
29110
|
// --- passkey provider (VT-405g): Kutrion as a roaming WebAuthn authenticator for OTHER sites
|
|
28920
29111
|
// (distinct from listPasskeys/deletePasskey above, which are passkeys for logging into Kutrion). ---
|
|
@@ -28935,7 +29126,7 @@ var KutrionClient = class {
|
|
|
28935
29126
|
return this.req("POST", "/share", { credential_id: credentialId, ...opts2 });
|
|
28936
29127
|
}
|
|
28937
29128
|
redeemShare(token) {
|
|
28938
|
-
return this.req("GET", `/share/${token}`, void 0, false);
|
|
29129
|
+
return this.req("GET", `/share/${encodeURIComponent(token)}`, void 0, false);
|
|
28939
29130
|
}
|
|
28940
29131
|
// #869: with a credential id, every link issued for that credential (needs read on it); without
|
|
28941
29132
|
// one, only the links the caller issued.
|
|
@@ -28943,14 +29134,14 @@ var KutrionClient = class {
|
|
|
28943
29134
|
return this.req("GET", `/share${credentialId ? `?credential_id=${encodeURIComponent(credentialId)}` : ""}`);
|
|
28944
29135
|
}
|
|
28945
29136
|
revokeShare(id) {
|
|
28946
|
-
return this.req("DELETE", `/share/${id}`);
|
|
29137
|
+
return this.req("DELETE", `/share/${encodeURIComponent(id)}`);
|
|
28947
29138
|
}
|
|
28948
29139
|
// --- import / export ---
|
|
28949
29140
|
exportVault(vaultId) {
|
|
28950
|
-
return this.req("GET", `/vault/${vaultId}/export`);
|
|
29141
|
+
return this.req("GET", `/vault/${encodeURIComponent(vaultId)}/export`);
|
|
28951
29142
|
}
|
|
28952
29143
|
importVault(vaultId, items) {
|
|
28953
|
-
return this.req("POST", `/vault/${vaultId}/import`, { items });
|
|
29144
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/import`, { items });
|
|
28954
29145
|
}
|
|
28955
29146
|
importProviders() {
|
|
28956
29147
|
return this.req("GET", "/vault/import/providers");
|
|
@@ -28988,7 +29179,7 @@ var KutrionClient = class {
|
|
|
28988
29179
|
* a move's `enabled:false` after the caller's fields precisely so one request can never both repoint a
|
|
28989
29180
|
* partner and turn it back on, and a client that batched them would be asking to be refused. */
|
|
28990
29181
|
setAs2PartnerEnabled(id, enabled) {
|
|
28991
|
-
return this.req("PATCH", `/as2/partners/${id}`, { enabled });
|
|
29182
|
+
return this.req("PATCH", `/as2/partners/${encodeURIComponent(id)}`, { enabled });
|
|
28992
29183
|
}
|
|
28993
29184
|
listAs2Messages() {
|
|
28994
29185
|
return this.req("GET", "/as2/messages");
|
|
@@ -29001,33 +29192,33 @@ var KutrionClient = class {
|
|
|
29001
29192
|
return this.req("POST", "/teams", { name });
|
|
29002
29193
|
}
|
|
29003
29194
|
listMembers(teamId) {
|
|
29004
|
-
return this.req("GET", `/teams/${teamId}/members`);
|
|
29195
|
+
return this.req("GET", `/teams/${encodeURIComponent(teamId)}/members`);
|
|
29005
29196
|
}
|
|
29006
29197
|
addMember(teamId, email, role) {
|
|
29007
|
-
return this.req("POST", `/teams/${teamId}/members`, { email, role });
|
|
29198
|
+
return this.req("POST", `/teams/${encodeURIComponent(teamId)}/members`, { email, role });
|
|
29008
29199
|
}
|
|
29009
29200
|
removeMember(teamId, userId) {
|
|
29010
|
-
return this.req("DELETE", `/teams/${teamId}/members/${userId}`);
|
|
29201
|
+
return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`);
|
|
29011
29202
|
}
|
|
29012
29203
|
// #2775: org owner or admin only. 409 team_has_vaults or scim_managed.
|
|
29013
29204
|
deleteTeam(teamId) {
|
|
29014
|
-
return this.req("DELETE", `/teams/${teamId}`);
|
|
29205
|
+
return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}`);
|
|
29015
29206
|
}
|
|
29016
29207
|
// --- team invitations (VT-102) ---
|
|
29017
29208
|
inviteMember(teamId, email, role) {
|
|
29018
|
-
return this.req("POST", `/teams/${teamId}/invitations`, { email, role });
|
|
29209
|
+
return this.req("POST", `/teams/${encodeURIComponent(teamId)}/invitations`, { email, role });
|
|
29019
29210
|
}
|
|
29020
29211
|
listInvitations(teamId) {
|
|
29021
|
-
return this.req("GET", `/teams/${teamId}/invitations`);
|
|
29212
|
+
return this.req("GET", `/teams/${encodeURIComponent(teamId)}/invitations`);
|
|
29022
29213
|
}
|
|
29023
29214
|
revokeInvitation(teamId, invId) {
|
|
29024
|
-
return this.req("DELETE", `/teams/${teamId}/invitations/${invId}`);
|
|
29215
|
+
return this.req("DELETE", `/teams/${encodeURIComponent(teamId)}/invitations/${encodeURIComponent(invId)}`);
|
|
29025
29216
|
}
|
|
29026
29217
|
invitePreview(token) {
|
|
29027
|
-
return this.req("GET", `/teams/invitations/${token}`, void 0, false);
|
|
29218
|
+
return this.req("GET", `/teams/invitations/${encodeURIComponent(token)}`, void 0, false);
|
|
29028
29219
|
}
|
|
29029
29220
|
acceptInvitation(token) {
|
|
29030
|
-
return this.req("POST", `/teams/invitations/${token}/accept`);
|
|
29221
|
+
return this.req("POST", `/teams/invitations/${encodeURIComponent(token)}/accept`);
|
|
29031
29222
|
}
|
|
29032
29223
|
// --- org onboarding (MT-4 #115): admin/owner-gated unless noted ---
|
|
29033
29224
|
getOrg() {
|
|
@@ -29088,10 +29279,10 @@ var KutrionClient = class {
|
|
|
29088
29279
|
return this.req("POST", "/emergency/contacts", input);
|
|
29089
29280
|
}
|
|
29090
29281
|
removeEmergencyContact(id) {
|
|
29091
|
-
return this.req("DELETE", `/emergency/contacts/${id}`);
|
|
29282
|
+
return this.req("DELETE", `/emergency/contacts/${encodeURIComponent(id)}`);
|
|
29092
29283
|
}
|
|
29093
29284
|
denyEmergencyRequest(id) {
|
|
29094
|
-
return this.req("POST", `/emergency/requests/${id}/deny`);
|
|
29285
|
+
return this.req("POST", `/emergency/requests/${encodeURIComponent(id)}/deny`);
|
|
29095
29286
|
}
|
|
29096
29287
|
listEmergencyGrants() {
|
|
29097
29288
|
return this.req("GET", "/emergency/grants");
|
|
@@ -29100,10 +29291,10 @@ var KutrionClient = class {
|
|
|
29100
29291
|
return this.req("POST", "/emergency/grants/accept", { token });
|
|
29101
29292
|
}
|
|
29102
29293
|
requestEmergencyAccess(grantId) {
|
|
29103
|
-
return this.req("POST", `/emergency/grants/${grantId}/request`);
|
|
29294
|
+
return this.req("POST", `/emergency/grants/${encodeURIComponent(grantId)}/request`);
|
|
29104
29295
|
}
|
|
29105
29296
|
completeEmergencyAccess(requestId) {
|
|
29106
|
-
return this.req("POST", `/emergency/requests/${requestId}/complete`);
|
|
29297
|
+
return this.req("POST", `/emergency/requests/${encodeURIComponent(requestId)}/complete`);
|
|
29107
29298
|
}
|
|
29108
29299
|
// VT-443: the user-facing announcement feed (filtered server-side by the org's tier/status).
|
|
29109
29300
|
listAnnouncements() {
|
|
@@ -29193,7 +29384,7 @@ var KutrionClient = class {
|
|
|
29193
29384
|
// #1959: org-admin surface grants for one member of the caller's own org. Distinct from
|
|
29194
29385
|
// selfGrantSurface, which only ever acts on the caller.
|
|
29195
29386
|
listUserSurfaceGrants(userId) {
|
|
29196
|
-
return this.req("GET", `/surfaces/users/${userId}`);
|
|
29387
|
+
return this.req("GET", `/surfaces/users/${encodeURIComponent(userId)}`);
|
|
29197
29388
|
}
|
|
29198
29389
|
grantUserSurface(userId, surface, opts2) {
|
|
29199
29390
|
return this.req("POST", "/surfaces/grants", {
|
|
@@ -29294,15 +29485,15 @@ var KutrionClient = class {
|
|
|
29294
29485
|
return this.req("GET", "/org/members");
|
|
29295
29486
|
}
|
|
29296
29487
|
setOrgMemberRole(id, org_role) {
|
|
29297
|
-
return this.req("PATCH", `/org/members/${id}`, { org_role });
|
|
29488
|
+
return this.req("PATCH", `/org/members/${encodeURIComponent(id)}`, { org_role });
|
|
29298
29489
|
}
|
|
29299
29490
|
removeOrgMember(id) {
|
|
29300
|
-
return this.req("DELETE", `/org/members/${id}`);
|
|
29491
|
+
return this.req("DELETE", `/org/members/${encodeURIComponent(id)}`);
|
|
29301
29492
|
}
|
|
29302
29493
|
// VT-483: admin-mediated recovery — email a locked-out member a password-reset link/code. The admin
|
|
29303
29494
|
// never sets or learns the new password.
|
|
29304
29495
|
resetOrgMemberPassword(id) {
|
|
29305
|
-
return this.req("POST", `/org/members/${id}/password-reset`);
|
|
29496
|
+
return this.req("POST", `/org/members/${encodeURIComponent(id)}/password-reset`);
|
|
29306
29497
|
}
|
|
29307
29498
|
// #1433: admin-mediated ACCOUNT RECOVERY, for a member who cannot sign in AT ALL. Distinct from the
|
|
29308
29499
|
// password reset above, which refuses exactly the accounts that most need it: an SSO- or
|
|
@@ -29314,7 +29505,7 @@ var KutrionClient = class {
|
|
|
29314
29505
|
// step_up_required), refuses a suspended member (409 member_inactive), and refuses a non-owner admin
|
|
29315
29506
|
// who targets an owner (403 owner_recovery_requires_owner).
|
|
29316
29507
|
recoverOrgMember(id) {
|
|
29317
|
-
return this.req("POST", `/org/members/${id}/recover`);
|
|
29508
|
+
return this.req("POST", `/org/members/${encodeURIComponent(id)}/recover`);
|
|
29318
29509
|
}
|
|
29319
29510
|
inviteToOrg(email, org_role) {
|
|
29320
29511
|
return this.req("POST", "/org/invitations", { email, org_role });
|
|
@@ -29323,13 +29514,13 @@ var KutrionClient = class {
|
|
|
29323
29514
|
return this.req("GET", "/org/invitations");
|
|
29324
29515
|
}
|
|
29325
29516
|
revokeOrgInvitation(id) {
|
|
29326
|
-
return this.req("DELETE", `/org/invitations/${id}`);
|
|
29517
|
+
return this.req("DELETE", `/org/invitations/${encodeURIComponent(id)}`);
|
|
29327
29518
|
}
|
|
29328
29519
|
orgInvitePreview(token) {
|
|
29329
|
-
return this.req("GET", `/org/invitations/${token}`, void 0, false);
|
|
29520
|
+
return this.req("GET", `/org/invitations/${encodeURIComponent(token)}`, void 0, false);
|
|
29330
29521
|
}
|
|
29331
29522
|
async acceptOrgInvitation(token) {
|
|
29332
|
-
const r = await this.req("POST", `/org/invitations/${token}/accept`);
|
|
29523
|
+
const r = await this.req("POST", `/org/invitations/${encodeURIComponent(token)}/accept`);
|
|
29333
29524
|
this.onToken?.(r.token);
|
|
29334
29525
|
return r;
|
|
29335
29526
|
}
|
|
@@ -29342,18 +29533,18 @@ var KutrionClient = class {
|
|
|
29342
29533
|
return this.req("POST", "/msp/clients", { client_email, role, ...expires_days ? { expires_days } : {} });
|
|
29343
29534
|
}
|
|
29344
29535
|
revokeMspClient(id) {
|
|
29345
|
-
return this.req("POST", `/msp/clients/${id}/revoke`);
|
|
29536
|
+
return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/revoke`);
|
|
29346
29537
|
}
|
|
29347
29538
|
// Mint a read-only "view as client" scoped session (#415c). The token is used as the active bearer;
|
|
29348
29539
|
// the existing impersonation guard makes it read-only. Revoking the relationship kills it instantly.
|
|
29349
29540
|
viewAsMspClient(id) {
|
|
29350
|
-
return this.req("POST", `/msp/clients/${id}/view-as`);
|
|
29541
|
+
return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/view-as`);
|
|
29351
29542
|
}
|
|
29352
29543
|
// #432: enter a WRITE-capable "act as client" session at the clamped effective role. Requires a fresh
|
|
29353
29544
|
// step-up; writes are bounded by the deny-by-default server allowlist. Returns 403 step_up_required if
|
|
29354
29545
|
// the operator has not recently verified a passkey/MFA, 409 read_only_relationship for a VIEWER ceiling.
|
|
29355
29546
|
actAsMspClient(id, role = "EDITOR") {
|
|
29356
|
-
return this.req("POST", `/msp/clients/${id}/act-as`, { role });
|
|
29547
|
+
return this.req("POST", `/msp/clients/${encodeURIComponent(id)}/act-as`, { role });
|
|
29357
29548
|
}
|
|
29358
29549
|
// #415d: the MSP's own access ledger across managed clients (sessions opened, last access).
|
|
29359
29550
|
mspClientsActivity() {
|
|
@@ -29377,13 +29568,13 @@ var KutrionClient = class {
|
|
|
29377
29568
|
return this.req("GET", "/msp/providers");
|
|
29378
29569
|
}
|
|
29379
29570
|
acceptMspProvider(id) {
|
|
29380
|
-
return this.req("POST", `/msp/providers/${id}/accept`);
|
|
29571
|
+
return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/accept`);
|
|
29381
29572
|
}
|
|
29382
29573
|
declineMspProvider(id) {
|
|
29383
|
-
return this.req("POST", `/msp/providers/${id}/decline`);
|
|
29574
|
+
return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/decline`);
|
|
29384
29575
|
}
|
|
29385
29576
|
revokeMspProvider(id) {
|
|
29386
|
-
return this.req("POST", `/msp/providers/${id}/revoke`);
|
|
29577
|
+
return this.req("POST", `/msp/providers/${encodeURIComponent(id)}/revoke`);
|
|
29387
29578
|
}
|
|
29388
29579
|
// #434: what my providers did in my org (read-only-session opens etc.), for client-side transparency.
|
|
29389
29580
|
mspProviderActivity() {
|
|
@@ -29417,13 +29608,13 @@ var KutrionClient = class {
|
|
|
29417
29608
|
setMemberActive(memberId, active, reason) {
|
|
29418
29609
|
return this.req(
|
|
29419
29610
|
"PATCH",
|
|
29420
|
-
`/org/members/${memberId}/active`,
|
|
29611
|
+
`/org/members/${encodeURIComponent(memberId)}/active`,
|
|
29421
29612
|
{ active, ...reason ? { reason } : {} }
|
|
29422
29613
|
);
|
|
29423
29614
|
}
|
|
29424
29615
|
// Offboarding: kill every offline copy a member holds, from the admin side.
|
|
29425
29616
|
revokeMemberCacheKeys(memberId) {
|
|
29426
|
-
return this.req("DELETE", `/org/members/${memberId}/cache-keys`);
|
|
29617
|
+
return this.req("DELETE", `/org/members/${encodeURIComponent(memberId)}/cache-keys`);
|
|
29427
29618
|
}
|
|
29428
29619
|
// #815: the other half of that revoke — which devices came back and were told to wipe, and which
|
|
29429
29620
|
// have not been seen since. Org-admin only.
|
|
@@ -29439,7 +29630,7 @@ var KutrionClient = class {
|
|
|
29439
29630
|
return this.req("POST", "/org/equivalent-domains", { domains, note: note ?? null });
|
|
29440
29631
|
}
|
|
29441
29632
|
removeEquivalentDomains(id) {
|
|
29442
|
-
return this.req("DELETE", `/org/equivalent-domains/${id}`);
|
|
29633
|
+
return this.req("DELETE", `/org/equivalent-domains/${encodeURIComponent(id)}`);
|
|
29443
29634
|
}
|
|
29444
29635
|
// #904: the caller's OWN remembered host bindings. Per user and per (credential, host) — never per
|
|
29445
29636
|
// org, which would make phishing one member a lateral-movement primitive. Creation is screened
|
|
@@ -29449,10 +29640,10 @@ var KutrionClient = class {
|
|
|
29449
29640
|
return this.req("GET", "/vault/site-bindings");
|
|
29450
29641
|
}
|
|
29451
29642
|
createSiteBinding(vaultId, cid, targetUrl, sourceUrl) {
|
|
29452
|
-
return this.req("POST", `/vault/${vaultId}/credentials/${cid}/site-bindings`, { target_url: targetUrl, source_url: sourceUrl });
|
|
29643
|
+
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/site-bindings`, { target_url: targetUrl, source_url: sourceUrl });
|
|
29453
29644
|
}
|
|
29454
29645
|
revokeSiteBinding(id) {
|
|
29455
|
-
return this.req("DELETE", `/vault/site-bindings/${id}`);
|
|
29646
|
+
return this.req("DELETE", `/vault/site-bindings/${encodeURIComponent(id)}`);
|
|
29456
29647
|
}
|
|
29457
29648
|
listOrgDomains() {
|
|
29458
29649
|
return this.req("GET", "/org/domains");
|
|
@@ -29461,10 +29652,10 @@ var KutrionClient = class {
|
|
|
29461
29652
|
return this.req("POST", "/org/domains", { domain });
|
|
29462
29653
|
}
|
|
29463
29654
|
verifyOrgDomain(id) {
|
|
29464
|
-
return this.req("POST", `/org/domains/${id}/verify`);
|
|
29655
|
+
return this.req("POST", `/org/domains/${encodeURIComponent(id)}/verify`);
|
|
29465
29656
|
}
|
|
29466
29657
|
removeOrgDomain(id) {
|
|
29467
|
-
return this.req("DELETE", `/org/domains/${id}`);
|
|
29658
|
+
return this.req("DELETE", `/org/domains/${encodeURIComponent(id)}`);
|
|
29468
29659
|
}
|
|
29469
29660
|
// VT-485/486: enterprise API tokens for SCIM provisioning and SIEM event export. The secret is
|
|
29470
29661
|
// returned only on create; thereafter only the prefix is shown.
|
|
@@ -29475,7 +29666,7 @@ var KutrionClient = class {
|
|
|
29475
29666
|
return this.req("POST", "/api-tokens", input);
|
|
29476
29667
|
}
|
|
29477
29668
|
revokeApiToken(id) {
|
|
29478
|
-
return this.req("DELETE", `/api-tokens/${id}`);
|
|
29669
|
+
return this.req("DELETE", `/api-tokens/${encodeURIComponent(id)}`);
|
|
29479
29670
|
}
|
|
29480
29671
|
// #662 agent access plane: non-human identities (#664), their activity (#665) and the kill
|
|
29481
29672
|
// switch (#675). The credential is returned only on create; thereafter only the prefix is shown.
|
|
@@ -29490,23 +29681,29 @@ var KutrionClient = class {
|
|
|
29490
29681
|
return this.req("POST", "/agents", input);
|
|
29491
29682
|
}
|
|
29492
29683
|
renewAgent(id, ttl_minutes) {
|
|
29493
|
-
return this.req("POST", `/agents/${id}/renew`, { ttl_minutes });
|
|
29684
|
+
return this.req("POST", `/agents/${encodeURIComponent(id)}/renew`, { ttl_minutes });
|
|
29494
29685
|
}
|
|
29495
29686
|
revokeAgent(id) {
|
|
29496
|
-
return this.req("DELETE", `/agents/${id}`);
|
|
29687
|
+
return this.req("DELETE", `/agents/${encodeURIComponent(id)}`);
|
|
29497
29688
|
}
|
|
29498
29689
|
// #2700: PUT replaces the whole resource scope; an omitted or empty list means unrestricted.
|
|
29499
29690
|
agentScopeOptions() {
|
|
29500
29691
|
return this.req("GET", "/agents/scope-options");
|
|
29501
29692
|
}
|
|
29502
29693
|
setAgentResourceScope(id, scope) {
|
|
29503
|
-
return this.req("PUT", `/agents/${id}/resource-scope`, scope);
|
|
29694
|
+
return this.req("PUT", `/agents/${encodeURIComponent(id)}/resource-scope`, scope);
|
|
29504
29695
|
}
|
|
29505
29696
|
listAgentOidcTrusts() {
|
|
29506
29697
|
return this.req("GET", "/agents/oidc/trusts");
|
|
29507
29698
|
}
|
|
29699
|
+
createAgentOidcTrust(input) {
|
|
29700
|
+
return this.req("POST", "/agents/oidc/trusts", input);
|
|
29701
|
+
}
|
|
29702
|
+
deleteAgentOidcTrust(id) {
|
|
29703
|
+
return this.req("DELETE", `/agents/oidc/trusts/${encodeURIComponent(id)}`);
|
|
29704
|
+
}
|
|
29508
29705
|
setAgentOidcTrustResourceScope(id, scope, opts2 = {}) {
|
|
29509
|
-
return this.req("PUT", `/agents/oidc/trusts/${id}/resource-scope`, { ...scope, ...opts2 });
|
|
29706
|
+
return this.req("PUT", `/agents/oidc/trusts/${encodeURIComponent(id)}/resource-scope`, { ...scope, ...opts2 });
|
|
29510
29707
|
}
|
|
29511
29708
|
revokeAgents(input) {
|
|
29512
29709
|
return this.req(
|
|
@@ -29523,7 +29720,7 @@ var KutrionClient = class {
|
|
|
29523
29720
|
return this.req("GET", qs ? `/agents/sessions?${qs}` : "/agents/sessions");
|
|
29524
29721
|
}
|
|
29525
29722
|
getAgentSession(id) {
|
|
29526
|
-
return this.req("GET", `/agents/sessions/${id}`);
|
|
29723
|
+
return this.req("GET", `/agents/sessions/${encodeURIComponent(id)}`);
|
|
29527
29724
|
}
|
|
29528
29725
|
listAgentActivity(opts2 = {}) {
|
|
29529
29726
|
const q = new URLSearchParams();
|
|
@@ -29533,7 +29730,7 @@ var KutrionClient = class {
|
|
|
29533
29730
|
}
|
|
29534
29731
|
/** Relative path for the auditor export; the caller downloads it with its own auth. */
|
|
29535
29732
|
agentSessionExportPath(id) {
|
|
29536
|
-
return `/agents/sessions/${id}/export`;
|
|
29733
|
+
return `/agents/sessions/${encodeURIComponent(id)}/export`;
|
|
29537
29734
|
}
|
|
29538
29735
|
// #669: the human half of just-in-time agent approval. These routes are deliberately unreachable
|
|
29539
29736
|
// from an MCP client — the agent opens a request and settles nothing, a person in an authenticated
|
|
@@ -29544,13 +29741,13 @@ var KutrionClient = class {
|
|
|
29544
29741
|
return this.req("GET", "/agent-approvals");
|
|
29545
29742
|
}
|
|
29546
29743
|
getAgentApproval(id) {
|
|
29547
|
-
return this.req("GET", `/agent-approvals/${id}`);
|
|
29744
|
+
return this.req("GET", `/agent-approvals/${encodeURIComponent(id)}`);
|
|
29548
29745
|
}
|
|
29549
29746
|
approveAgentApproval(id, note) {
|
|
29550
|
-
return this.req("POST", `/agent-approvals/${id}/approve`, note ? { note } : {});
|
|
29747
|
+
return this.req("POST", `/agent-approvals/${encodeURIComponent(id)}/approve`, note ? { note } : {});
|
|
29551
29748
|
}
|
|
29552
29749
|
denyAgentApproval(id, note) {
|
|
29553
|
-
return this.req("POST", `/agent-approvals/${id}/deny`, note ? { note } : {});
|
|
29750
|
+
return this.req("POST", `/agent-approvals/${encodeURIComponent(id)}/deny`, note ? { note } : {});
|
|
29554
29751
|
}
|
|
29555
29752
|
// #670 (epic #662): the outbound MCP gateway's control plane. Curation is ORG ADMIN; only the
|
|
29556
29753
|
// report-what-I-run call is open to any member, and that asymmetry is deliberate — the machines
|
|
@@ -29573,14 +29770,14 @@ var KutrionClient = class {
|
|
|
29573
29770
|
/** Aim a credential at an approved entry. Authorised by WRITE on the credential's own vault, not by
|
|
29574
29771
|
* org admin — binding is an act of delegation, so 403 here is about the vault and not the console. */
|
|
29575
29772
|
bindMcpCredential(entryId, credential_id) {
|
|
29576
|
-
return this.req("POST", `/mcp/gateway/servers/${entryId}/credential`, { credential_id });
|
|
29773
|
+
return this.req("POST", `/mcp/gateway/servers/${encodeURIComponent(entryId)}/credential`, { credential_id });
|
|
29577
29774
|
}
|
|
29578
29775
|
unbindMcpCredential(entryId) {
|
|
29579
|
-
return this.req("DELETE", `/mcp/gateway/servers/${entryId}/credential`);
|
|
29776
|
+
return this.req("DELETE", `/mcp/gateway/servers/${encodeURIComponent(entryId)}/credential`);
|
|
29580
29777
|
}
|
|
29581
29778
|
/** Stops traffic on the next call, with nothing cached in between. */
|
|
29582
29779
|
revokeMcpServer(entryId) {
|
|
29583
|
-
return this.req("DELETE", `/mcp/gateway/servers/${entryId}`);
|
|
29780
|
+
return this.req("DELETE", `/mcp/gateway/servers/${encodeURIComponent(entryId)}`);
|
|
29584
29781
|
}
|
|
29585
29782
|
listMcpObserved() {
|
|
29586
29783
|
return this.req("GET", "/mcp/gateway/observed");
|
|
@@ -29613,10 +29810,10 @@ var KutrionClient = class {
|
|
|
29613
29810
|
// which leaves it as it is — a connection could otherwise never be made direct again.
|
|
29614
29811
|
// #2621: `vault_id` shares a personal host into that vault, one way; its credential must live there.
|
|
29615
29812
|
updateConnection(connId, input) {
|
|
29616
|
-
return this.req("PATCH", `/ssh/connections/${connId}`, input);
|
|
29813
|
+
return this.req("PATCH", `/ssh/connections/${encodeURIComponent(connId)}`, input);
|
|
29617
29814
|
}
|
|
29618
29815
|
deleteConnection(connId) {
|
|
29619
|
-
return this.req("DELETE", `/ssh/connections/${connId}`);
|
|
29816
|
+
return this.req("DELETE", `/ssh/connections/${encodeURIComponent(connId)}`);
|
|
29620
29817
|
}
|
|
29621
29818
|
// #414: self-serve secure-session demo. status drives the empty-state affordance; provision is
|
|
29622
29819
|
// idempotent (reattaches to an existing demo connection); reset removes the demo artifacts.
|
|
@@ -29631,8 +29828,9 @@ var KutrionClient = class {
|
|
|
29631
29828
|
}
|
|
29632
29829
|
// Pre-flight risk guardrail (#141): a high-risk command 403s with { error: "step_up_required" }
|
|
29633
29830
|
// until the session completes WebAuthn step-up; retry after stepping up.
|
|
29634
|
-
|
|
29635
|
-
|
|
29831
|
+
// #3515: `stdinB64` is streamed to the command and then closed; the server acknowledges it with `stdin`.
|
|
29832
|
+
exec(connId, command, stdinB64) {
|
|
29833
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/exec`, stdinB64 === void 0 ? { command } : { command, stdin_b64: stdinB64 });
|
|
29636
29834
|
}
|
|
29637
29835
|
validateCommand(command) {
|
|
29638
29836
|
return this.req("POST", "/ssh/validate", { command });
|
|
@@ -29649,7 +29847,7 @@ var KutrionClient = class {
|
|
|
29649
29847
|
*
|
|
29650
29848
|
* `rdp.tunnel` is deliberately not mintable here — the RDP broker returns its own on the
|
|
29651
29849
|
* session-open response (`tunnel.ws_ticket`), and this route refuses the scope so a caller without
|
|
29652
|
-
* the `rdp_broker` entitlement cannot mint against that socket (server/src/modules/ssh.ts:
|
|
29850
|
+
* the `rdp_broker` entitlement cannot mint against that socket (server/src/modules/ssh.ts:665).
|
|
29653
29851
|
*/
|
|
29654
29852
|
mintWsTicket(scope, resourceId) {
|
|
29655
29853
|
return this.req(
|
|
@@ -29715,7 +29913,7 @@ var KutrionClient = class {
|
|
|
29715
29913
|
}
|
|
29716
29914
|
/** 403 step_up_required until the session has a fresh passkey/MFA check; retry after stepping up. */
|
|
29717
29915
|
clearSshHostKey(id) {
|
|
29718
|
-
return this.req("DELETE", `/ssh/host-keys/${id}`);
|
|
29916
|
+
return this.req("DELETE", `/ssh/host-keys/${encodeURIComponent(id)}`);
|
|
29719
29917
|
}
|
|
29720
29918
|
/**
|
|
29721
29919
|
* #2743: pins the key a host_key_unconfirmed refusal showed this caller. For a first-use refusal the server accepts only the
|
|
@@ -29733,7 +29931,7 @@ var KutrionClient = class {
|
|
|
29733
29931
|
}
|
|
29734
29932
|
/** 404 when the session already ended, or is not in the caller's org. */
|
|
29735
29933
|
endLiveSession(kind, id) {
|
|
29736
|
-
return this.req("DELETE", `/org/live-sessions/${kind}/${encodeURIComponent(id)}`);
|
|
29934
|
+
return this.req("DELETE", `/org/live-sessions/${encodeURIComponent(kind)}/${encodeURIComponent(id)}`);
|
|
29737
29935
|
}
|
|
29738
29936
|
// --- SSH certificate authority (VT-484): mint short-lived user certs from a per-org internal CA ---
|
|
29739
29937
|
getSshCa() {
|
|
@@ -29828,29 +30026,29 @@ var KutrionClient = class {
|
|
|
29828
30026
|
// It does NOT mean "the whole directory": the routes default to limit=1000 and report `hasMore`.
|
|
29829
30027
|
// --- SFTP file transfer (VT-112) ---
|
|
29830
30028
|
sftpList(connId, path = ".", page) {
|
|
29831
|
-
return this.req("GET", `/ssh/connections/${connId}/sftp/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
30029
|
+
return this.req("GET", `/ssh/connections/${encodeURIComponent(connId)}/sftp/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
29832
30030
|
}
|
|
29833
30031
|
sftpMkdir(connId, path) {
|
|
29834
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/mkdir`, { path });
|
|
30032
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/mkdir`, { path });
|
|
29835
30033
|
}
|
|
29836
30034
|
sftpRename(connId, from, to) {
|
|
29837
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/rename`, { from, to });
|
|
30035
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/rename`, { from, to });
|
|
29838
30036
|
}
|
|
29839
30037
|
sftpDelete(connId, path, isDir) {
|
|
29840
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/delete`, { path, is_dir: isDir });
|
|
30038
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/delete`, { path, is_dir: isDir });
|
|
29841
30039
|
}
|
|
29842
30040
|
// #587: SFTP checksums + duplicate detection (mirror the file-connection routes).
|
|
29843
30041
|
sftpChecksum(connId, path, algos = ["sha256"]) {
|
|
29844
|
-
return this.req("GET", `/ssh/connections/${connId}/sftp/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
|
|
30042
|
+
return this.req("GET", `/ssh/connections/${encodeURIComponent(connId)}/sftp/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
|
|
29845
30043
|
}
|
|
29846
30044
|
sftpDuplicates(connId, path = ".", opts2) {
|
|
29847
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
|
|
30045
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
|
|
29848
30046
|
}
|
|
29849
30047
|
// Download a remote file as a Blob (carries the auth token). With { redact: true } the server
|
|
29850
30048
|
// routes the bytes through the #142 redaction gate (text only; binary → 415) before sending (#320c).
|
|
29851
30049
|
async sftpDownload(connId, path, opts2) {
|
|
29852
30050
|
const tok = this.getToken();
|
|
29853
|
-
const res = await this.f(`${this.baseUrl}/ssh/connections/${connId}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
|
|
30051
|
+
const res = await this.f(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
|
|
29854
30052
|
headers: this.rawHeaders(tok)
|
|
29855
30053
|
});
|
|
29856
30054
|
if (!res.ok) {
|
|
@@ -29906,12 +30104,12 @@ var KutrionClient = class {
|
|
|
29906
30104
|
// Stream a remote file's bytes instead of buffering them into a Blob. `opts.start` resumes from an
|
|
29907
30105
|
// offset via Range (#555); read `resumedAt` off the result rather than assuming the server obeyed.
|
|
29908
30106
|
sftpDownloadStream(connId, path, opts2) {
|
|
29909
|
-
return this.downloadStream(`${this.baseUrl}/ssh/connections/${connId}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
30107
|
+
return this.downloadStream(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
29910
30108
|
}
|
|
29911
30109
|
// Upload from a byte source without materialising it. `streamed` on the result reports whether this
|
|
29912
30110
|
// runtime actually streamed the request body or had to collect it first.
|
|
29913
30111
|
sftpUploadStream(connId, path, body, opts2) {
|
|
29914
|
-
return this.uploadStream(`${this.baseUrl}/ssh/connections/${connId}/sftp/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
30112
|
+
return this.uploadStream(`${this.baseUrl}/ssh/connections/${encodeURIComponent(connId)}/sftp/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
29915
30113
|
}
|
|
29916
30114
|
// #2556: throws UploadTooLargeToInspectError when this org inspects uploads and `size` is over what the
|
|
29917
30115
|
// inspection can hold, so the file is refused before any of it is sent. A policy that cannot be read
|
|
@@ -29926,7 +30124,7 @@ var KutrionClient = class {
|
|
|
29926
30124
|
throw new UploadTooLargeToInspectError(size, insp.max_bytes, insp.enabled ? "too_large_for_scan" : "too_large_for_policy");
|
|
29927
30125
|
}
|
|
29928
30126
|
sftpUploadResumable(connId, path, source, opts2) {
|
|
29929
|
-
return this.uploadResumable(`/ssh/connections/${connId}/sftp/upload`, path, source, opts2);
|
|
30127
|
+
return this.uploadResumable(`/ssh/connections/${encodeURIComponent(connId)}/sftp/upload`, path, source, opts2);
|
|
29930
30128
|
}
|
|
29931
30129
|
// #2556: one request per UPLOAD_CHUNK_BYTES over the #1566 resume protocol, so a file of any size
|
|
29932
30130
|
// crosses an edge that caps a request body, and a dropped chunk is re-sent from the offset the
|
|
@@ -30051,30 +30249,41 @@ var KutrionClient = class {
|
|
|
30051
30249
|
listFileConnections() {
|
|
30052
30250
|
return this.req("GET", "/files/connections");
|
|
30053
30251
|
}
|
|
30252
|
+
// #3466: what a create with these fields would report as `destination_need`, before creating it.
|
|
30253
|
+
// `connection_id` counts the public links on that connection, and only when it is the caller's own.
|
|
30254
|
+
// `secure` is optional and defaults as create does: true only when protocol is WEBDAV.
|
|
30255
|
+
fileConnectionDestinationNeed(q) {
|
|
30256
|
+
const qs = new URLSearchParams({ credential_id: q.credential_id, protocol: q.protocol, port: String(q.port) });
|
|
30257
|
+
if (q.host !== void 0) qs.set("host", q.host);
|
|
30258
|
+
if (q.secure !== void 0) qs.set("secure", String(q.secure));
|
|
30259
|
+
if (q.connection_id !== void 0) qs.set("connection_id", q.connection_id);
|
|
30260
|
+
return this.req("GET", `/files/connections/destination-need?${qs}`);
|
|
30261
|
+
}
|
|
30054
30262
|
createFileConnection(input) {
|
|
30055
30263
|
return this.req("POST", "/files/connections", input);
|
|
30056
30264
|
}
|
|
30265
|
+
// #3456: `username: null` switches the connection to inherit the credential's username.
|
|
30057
30266
|
updateFileConnection(id, input) {
|
|
30058
|
-
return this.req("PATCH", `/files/connections/${id}`, input);
|
|
30267
|
+
return this.req("PATCH", `/files/connections/${encodeURIComponent(id)}`, input);
|
|
30059
30268
|
}
|
|
30060
30269
|
deleteFileConnection(id) {
|
|
30061
|
-
return this.req("DELETE", `/files/connections/${id}`);
|
|
30270
|
+
return this.req("DELETE", `/files/connections/${encodeURIComponent(id)}`);
|
|
30062
30271
|
}
|
|
30063
30272
|
fileList(connId, path = "/", page) {
|
|
30064
|
-
return this.req("GET", `/files/connections/${connId}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
30273
|
+
return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
30065
30274
|
}
|
|
30066
30275
|
// #587: checksums + duplicate detection.
|
|
30067
30276
|
fileChecksum(connId, path, algos = ["sha256"]) {
|
|
30068
|
-
return this.req("GET", `/files/connections/${connId}/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
|
|
30277
|
+
return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/checksum?path=${encodeURIComponent(path)}&algos=${algos.join(",")}`);
|
|
30069
30278
|
}
|
|
30070
30279
|
fileDuplicates(connId, path = "/", opts2) {
|
|
30071
|
-
return this.req("POST", `/files/connections/${connId}/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
|
|
30280
|
+
return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/duplicates`, { path, maxFiles: opts2?.maxFiles, maxBytesPerFile: opts2?.maxBytesPerFile });
|
|
30072
30281
|
}
|
|
30073
30282
|
// #586: recursive CONTENT search under `path`. Bytes are read on the server and only path/line/
|
|
30074
30283
|
// snippet come back. `glob` filters which files are opened by name; it does not match names into
|
|
30075
30284
|
// the result. Check `truncated` before presenting the matches as the whole answer.
|
|
30076
30285
|
fileSearch(connId, path, query, opts2) {
|
|
30077
|
-
return this.req("POST", `/files/connections/${connId}/search`, {
|
|
30286
|
+
return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/search`, {
|
|
30078
30287
|
path,
|
|
30079
30288
|
query,
|
|
30080
30289
|
glob: opts2?.glob,
|
|
@@ -30084,39 +30293,39 @@ var KutrionClient = class {
|
|
|
30084
30293
|
}
|
|
30085
30294
|
// #588: time-limited presigned direct-download URL (S3 only).
|
|
30086
30295
|
filePresign(connId, path, expiresSec = 3600) {
|
|
30087
|
-
return this.req("GET", `/files/connections/${connId}/presign?path=${encodeURIComponent(path)}&expires=${expiresSec}`);
|
|
30296
|
+
return this.req("GET", `/files/connections/${encodeURIComponent(connId)}/presign?path=${encodeURIComponent(path)}&expires=${expiresSec}`);
|
|
30088
30297
|
}
|
|
30089
30298
|
// #574: OAuth cloud connectors (Dropbox/Drive/OneDrive/Box). Tokens stay server-side.
|
|
30090
30299
|
cloudProviders() {
|
|
30091
30300
|
return this.req("GET", "/cloud/providers");
|
|
30092
30301
|
}
|
|
30093
30302
|
cloudAuthorize(provider, name) {
|
|
30094
|
-
return this.req("POST", `/cloud/${provider}/authorize`, { name });
|
|
30303
|
+
return this.req("POST", `/cloud/${encodeURIComponent(provider)}/authorize`, { name });
|
|
30095
30304
|
}
|
|
30096
30305
|
listCloudConnections() {
|
|
30097
30306
|
return this.req("GET", "/cloud/connections");
|
|
30098
30307
|
}
|
|
30099
30308
|
deleteCloudConnection(id) {
|
|
30100
|
-
return this.req("DELETE", `/cloud/connections/${id}`);
|
|
30309
|
+
return this.req("DELETE", `/cloud/connections/${encodeURIComponent(id)}`);
|
|
30101
30310
|
}
|
|
30102
30311
|
cloudList(connId, path = "/", page) {
|
|
30103
|
-
return this.req("GET", `/cloud/connections/${connId}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
30312
|
+
return this.req("GET", `/cloud/connections/${encodeURIComponent(connId)}/list?path=${encodeURIComponent(path)}${listPageQuery(page)}`);
|
|
30104
30313
|
}
|
|
30105
30314
|
// #608: list + pin the connection's browse root (Shared Drive / SharePoint site / Box folder).
|
|
30106
30315
|
cloudRoots(connId) {
|
|
30107
|
-
return this.req("GET", `/cloud/connections/${connId}/roots`);
|
|
30316
|
+
return this.req("GET", `/cloud/connections/${encodeURIComponent(connId)}/roots`);
|
|
30108
30317
|
}
|
|
30109
30318
|
setCloudRoot(connId, root) {
|
|
30110
|
-
return this.req("PUT", `/cloud/connections/${connId}/root`, { root });
|
|
30319
|
+
return this.req("PUT", `/cloud/connections/${encodeURIComponent(connId)}/root`, { root });
|
|
30111
30320
|
}
|
|
30112
30321
|
cloudMkdir(connId, path) {
|
|
30113
|
-
return this.req("POST", `/cloud/connections/${connId}/mkdir`, { path });
|
|
30322
|
+
return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/mkdir`, { path });
|
|
30114
30323
|
}
|
|
30115
30324
|
cloudRename(connId, from, to) {
|
|
30116
|
-
return this.req("POST", `/cloud/connections/${connId}/rename`, { from, to });
|
|
30325
|
+
return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/rename`, { from, to });
|
|
30117
30326
|
}
|
|
30118
30327
|
cloudDelete(connId, path, isDir) {
|
|
30119
|
-
return this.req("POST", `/cloud/connections/${connId}/delete`, { path, is_dir: isDir });
|
|
30328
|
+
return this.req("POST", `/cloud/connections/${encodeURIComponent(connId)}/delete`, { path, is_dir: isDir });
|
|
30120
30329
|
}
|
|
30121
30330
|
// #1627: `redact` is the same opt-in the /files and /ssh download helpers take, and the cloud route
|
|
30122
30331
|
// has honoured it since #1562 (server/src/modules/cloud.ts:461). It only ever ADDS masking — the
|
|
@@ -30124,7 +30333,7 @@ var KutrionClient = class {
|
|
|
30124
30333
|
// a client whose users cannot ask for less, not one that is safer.
|
|
30125
30334
|
async cloudDownload(connId, path, opts2) {
|
|
30126
30335
|
const tok = this.getToken();
|
|
30127
|
-
const res = await this.f(`${this.baseUrl}/cloud/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, { headers: this.rawHeaders(tok) });
|
|
30336
|
+
const res = await this.f(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, { headers: this.rawHeaders(tok) });
|
|
30128
30337
|
if (!res.ok) {
|
|
30129
30338
|
const errBody = await res.json().catch(() => void 0);
|
|
30130
30339
|
throw new ApiError(res.status, apiErrorMessage(res.status, errBody), errBody);
|
|
@@ -30134,7 +30343,7 @@ var KutrionClient = class {
|
|
|
30134
30343
|
// #2768: one request, so `signal` cancels it but there is no per-piece progress to report.
|
|
30135
30344
|
async cloudUpload(connId, path, data, opts2) {
|
|
30136
30345
|
const tok = this.getToken();
|
|
30137
|
-
const res = await this.fUpload(`${this.baseUrl}/cloud/connections/${connId}/upload?path=${encodeURIComponent(path)}${opts2?.ifAbsent ? "&if_absent=1" : ""}`, {
|
|
30346
|
+
const res = await this.fUpload(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}${opts2?.ifAbsent ? "&if_absent=1" : ""}`, {
|
|
30138
30347
|
method: "POST",
|
|
30139
30348
|
headers: this.rawHeaders(tok, { "Content-Type": "application/octet-stream" }),
|
|
30140
30349
|
body: data,
|
|
@@ -30155,27 +30364,27 @@ var KutrionClient = class {
|
|
|
30155
30364
|
// (cloud.ts:453) — downloadStream only ever sends the open-ended form, so that refusal is
|
|
30156
30365
|
// unreachable from here.
|
|
30157
30366
|
cloudDownloadStream(connId, path, opts2) {
|
|
30158
|
-
return this.downloadStream(`${this.baseUrl}/cloud/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
30367
|
+
return this.downloadStream(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
30159
30368
|
}
|
|
30160
30369
|
// POST /cloud/connections/:id/upload (cloud.ts:537) answers `{ ok }` plus the staging keys and NOT
|
|
30161
30370
|
// `size` — it returns `uploadProgress()` alone, where the file and sftp routes add `path`/`size`
|
|
30162
30371
|
// (files.ts:1428, ssh.ts:1070). Typed to what this route actually sends: claiming `size: number`
|
|
30163
30372
|
// here would hand every caller a number that is undefined at runtime.
|
|
30164
30373
|
cloudUploadStream(connId, path, body, opts2) {
|
|
30165
|
-
return this.uploadStream(`${this.baseUrl}/cloud/connections/${connId}/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
30374
|
+
return this.uploadStream(`${this.baseUrl}/cloud/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
30166
30375
|
}
|
|
30167
30376
|
fileMkdir(connId, path) {
|
|
30168
|
-
return this.req("POST", `/files/connections/${connId}/mkdir`, { path });
|
|
30377
|
+
return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/mkdir`, { path });
|
|
30169
30378
|
}
|
|
30170
30379
|
fileRename(connId, from, to) {
|
|
30171
|
-
return this.req("POST", `/files/connections/${connId}/rename`, { from, to });
|
|
30380
|
+
return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/rename`, { from, to });
|
|
30172
30381
|
}
|
|
30173
30382
|
fileDelete(connId, path, isDir) {
|
|
30174
|
-
return this.req("POST", `/files/connections/${connId}/delete`, { path, is_dir: isDir });
|
|
30383
|
+
return this.req("POST", `/files/connections/${encodeURIComponent(connId)}/delete`, { path, is_dir: isDir });
|
|
30175
30384
|
}
|
|
30176
30385
|
async fileDownload(connId, path, opts2) {
|
|
30177
30386
|
const tok = this.getToken();
|
|
30178
|
-
const res = await this.f(`${this.baseUrl}/files/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
|
|
30387
|
+
const res = await this.f(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, {
|
|
30179
30388
|
headers: this.rawHeaders(tok)
|
|
30180
30389
|
});
|
|
30181
30390
|
if (!res.ok) {
|
|
@@ -30186,13 +30395,13 @@ var KutrionClient = class {
|
|
|
30186
30395
|
}
|
|
30187
30396
|
// #1542: streaming + resumable counterparts of fileDownload/fileUpload. See downloadStream above.
|
|
30188
30397
|
fileDownloadStream(connId, path, opts2) {
|
|
30189
|
-
return this.downloadStream(`${this.baseUrl}/files/connections/${connId}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
30398
|
+
return this.downloadStream(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/download?path=${encodeURIComponent(path)}${opts2?.redact ? "&redact=1" : ""}`, opts2);
|
|
30190
30399
|
}
|
|
30191
30400
|
fileUploadStream(connId, path, body, opts2) {
|
|
30192
|
-
return this.uploadStream(`${this.baseUrl}/files/connections/${connId}/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
30401
|
+
return this.uploadStream(`${this.baseUrl}/files/connections/${encodeURIComponent(connId)}/upload?path=${encodeURIComponent(path)}`, body, opts2);
|
|
30193
30402
|
}
|
|
30194
30403
|
fileUploadResumable(connId, path, source, opts2) {
|
|
30195
|
-
return this.uploadResumable(`/files/connections/${connId}/upload`, path, source, opts2);
|
|
30404
|
+
return this.uploadResumable(`/files/connections/${encodeURIComponent(connId)}/upload`, path, source, opts2);
|
|
30196
30405
|
}
|
|
30197
30406
|
// #325: download a selection of files/folders as one zip, built server-side (works for every protocol).
|
|
30198
30407
|
async fileArchive(input) {
|
|
@@ -30221,7 +30430,7 @@ var KutrionClient = class {
|
|
|
30221
30430
|
return this.req("GET", "/files/shares");
|
|
30222
30431
|
}
|
|
30223
30432
|
revokeFileShare(id) {
|
|
30224
|
-
return this.req("DELETE", `/files/shares/${id}`);
|
|
30433
|
+
return this.req("DELETE", `/files/shares/${encodeURIComponent(id)}`);
|
|
30225
30434
|
}
|
|
30226
30435
|
// #1636: the anonymous side of a Vault Drop — the recipient is outside the org and has no account.
|
|
30227
30436
|
//
|
|
@@ -30263,12 +30472,16 @@ var KutrionClient = class {
|
|
|
30263
30472
|
return this.req("GET", "/files/jobs");
|
|
30264
30473
|
}
|
|
30265
30474
|
getTransferJob(id) {
|
|
30266
|
-
return this.req("GET", `/files/jobs/${id}`);
|
|
30475
|
+
return this.req("GET", `/files/jobs/${encodeURIComponent(id)}`);
|
|
30267
30476
|
}
|
|
30268
30477
|
// #583: reprioritise or cancel a job that is still QUEUED. A running or finished job answers 409
|
|
30269
30478
|
// not_queued — the runner has already leased it and there is nothing left to reorder.
|
|
30270
30479
|
patchTransferJob(id, input) {
|
|
30271
|
-
return this.req("PATCH", `/files/jobs/${id}`, input);
|
|
30480
|
+
return this.req("PATCH", `/files/jobs/${encodeURIComponent(id)}`, input);
|
|
30481
|
+
}
|
|
30482
|
+
// #3549: cancel a queued or running job. A finished one answers 409 not_active.
|
|
30483
|
+
cancelTransferJob(id) {
|
|
30484
|
+
return this.req("POST", `/files/jobs/${encodeURIComponent(id)}/cancel`, {});
|
|
30272
30485
|
}
|
|
30273
30486
|
// #563: recurring transfer schedules (interval or daily UTC time).
|
|
30274
30487
|
createSchedule(input) {
|
|
@@ -30278,10 +30491,10 @@ var KutrionClient = class {
|
|
|
30278
30491
|
return this.req("GET", "/files/schedules");
|
|
30279
30492
|
}
|
|
30280
30493
|
setScheduleEnabled(id, enabled) {
|
|
30281
|
-
return this.req("PATCH", `/files/schedules/${id}`, { enabled });
|
|
30494
|
+
return this.req("PATCH", `/files/schedules/${encodeURIComponent(id)}`, { enabled });
|
|
30282
30495
|
}
|
|
30283
30496
|
deleteSchedule(id) {
|
|
30284
|
-
return this.req("DELETE", `/files/schedules/${id}`);
|
|
30497
|
+
return this.req("DELETE", `/files/schedules/${encodeURIComponent(id)}`);
|
|
30285
30498
|
}
|
|
30286
30499
|
// #614: folder watchers — poll a source directory and fire a transfer job on new/changed, size-stable
|
|
30287
30500
|
// files matching the filters. The transfer runs as a durable job.
|
|
@@ -30292,10 +30505,10 @@ var KutrionClient = class {
|
|
|
30292
30505
|
return this.req("GET", "/files/folder-watchers");
|
|
30293
30506
|
}
|
|
30294
30507
|
setFolderWatcherEnabled(id, enabled) {
|
|
30295
|
-
return this.req("PATCH", `/files/folder-watchers/${id}`, { enabled });
|
|
30508
|
+
return this.req("PATCH", `/files/folder-watchers/${encodeURIComponent(id)}`, { enabled });
|
|
30296
30509
|
}
|
|
30297
30510
|
deleteFolderWatcher(id) {
|
|
30298
|
-
return this.req("DELETE", `/files/folder-watchers/${id}`);
|
|
30511
|
+
return this.req("DELETE", `/files/folder-watchers/${encodeURIComponent(id)}`);
|
|
30299
30512
|
}
|
|
30300
30513
|
// #627: workflow templates. A saved edit is a NEW VERSION, not a PATCH — 0150's trigger refuses an
|
|
30301
30514
|
// UPDATE, because a run names the version it used and reproducibility depends on a stored revision
|
|
@@ -30304,19 +30517,19 @@ var KutrionClient = class {
|
|
|
30304
30517
|
return this.req("GET", "/files/workflow-templates");
|
|
30305
30518
|
}
|
|
30306
30519
|
getWorkflowTemplate(id) {
|
|
30307
|
-
return this.req("GET", `/files/workflow-templates/${id}`);
|
|
30520
|
+
return this.req("GET", `/files/workflow-templates/${encodeURIComponent(id)}`);
|
|
30308
30521
|
}
|
|
30309
30522
|
createWorkflowTemplate(input) {
|
|
30310
30523
|
return this.req("POST", "/files/workflow-templates", input);
|
|
30311
30524
|
}
|
|
30312
30525
|
addWorkflowTemplateVersion(id, input) {
|
|
30313
|
-
return this.req("POST", `/files/workflow-templates/${id}/versions`, input);
|
|
30526
|
+
return this.req("POST", `/files/workflow-templates/${encodeURIComponent(id)}/versions`, input);
|
|
30314
30527
|
}
|
|
30315
30528
|
deleteWorkflowTemplate(id) {
|
|
30316
|
-
return this.req("DELETE", `/files/workflow-templates/${id}`);
|
|
30529
|
+
return this.req("DELETE", `/files/workflow-templates/${encodeURIComponent(id)}`);
|
|
30317
30530
|
}
|
|
30318
30531
|
instantiateWorkflowTemplate(id, input) {
|
|
30319
|
-
return this.req("POST", `/files/workflow-templates/${id}/instantiate`, input);
|
|
30532
|
+
return this.req("POST", `/files/workflow-templates/${encodeURIComponent(id)}/instantiate`, input);
|
|
30320
30533
|
}
|
|
30321
30534
|
// #559: directory sync / mirror. dry_run returns the plan without writing; omit it to apply.
|
|
30322
30535
|
// #585: `two_way` and `mirror` are mutually exclusive and the server refuses the pair outright
|
|
@@ -30366,13 +30579,13 @@ var KutrionClient = class {
|
|
|
30366
30579
|
}
|
|
30367
30580
|
// #557: SFTP POSIX metadata (chmod / set mtime / create symlink).
|
|
30368
30581
|
sftpChmod(connId, path, mode) {
|
|
30369
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/chmod`, { path, mode });
|
|
30582
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/chmod`, { path, mode });
|
|
30370
30583
|
}
|
|
30371
30584
|
sftpChtime(connId, path, mtime) {
|
|
30372
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/chtime`, { path, mtime });
|
|
30585
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/chtime`, { path, mtime });
|
|
30373
30586
|
}
|
|
30374
30587
|
sftpSymlink(connId, target, link) {
|
|
30375
|
-
return this.req("POST", `/ssh/connections/${connId}/sftp/symlink`, { target, link });
|
|
30588
|
+
return this.req("POST", `/ssh/connections/${encodeURIComponent(connId)}/sftp/symlink`, { target, link });
|
|
30376
30589
|
}
|
|
30377
30590
|
// #2556: resumable, as sftpUpload.
|
|
30378
30591
|
async fileUpload(connId, path, data, opts2) {
|
|
@@ -30391,7 +30604,7 @@ var KutrionClient = class {
|
|
|
30391
30604
|
return this.req("GET", "/ssh/recordings");
|
|
30392
30605
|
}
|
|
30393
30606
|
getRecording(id) {
|
|
30394
|
-
return this.req("GET", `/ssh/recordings/${id}`);
|
|
30607
|
+
return this.req("GET", `/ssh/recordings/${encodeURIComponent(id)}`);
|
|
30395
30608
|
}
|
|
30396
30609
|
/** #2631: what was stored for one of the caller's recorded shell sessions, once it has ended. */
|
|
30397
30610
|
shellSessionReceipt(sessionId) {
|
|
@@ -30406,11 +30619,11 @@ var KutrionClient = class {
|
|
|
30406
30619
|
return this.req("DELETE", `/ssh/shell-sessions/${encodeURIComponent(sessionId)}`);
|
|
30407
30620
|
}
|
|
30408
30621
|
deleteRecording(id) {
|
|
30409
|
-
return this.req("DELETE", `/ssh/recordings/${id}`);
|
|
30622
|
+
return this.req("DELETE", `/ssh/recordings/${encodeURIComponent(id)}`);
|
|
30410
30623
|
}
|
|
30411
30624
|
// VT-143: generate (or fetch cached) an AI session summary for a recording. regenerate forces recompute.
|
|
30412
30625
|
summarizeRecording(id, regenerate = false) {
|
|
30413
|
-
return this.req("POST", `/ssh/recordings/${id}/summary${regenerate ? "?regenerate=1" : ""}`);
|
|
30626
|
+
return this.req("POST", `/ssh/recordings/${encodeURIComponent(id)}/summary${regenerate ? "?regenerate=1" : ""}`);
|
|
30414
30627
|
}
|
|
30415
30628
|
// --- #2628: org recording review and retention, org owners and admins only ---
|
|
30416
30629
|
getRecordingPolicy() {
|
|
@@ -30477,10 +30690,10 @@ var KutrionClient = class {
|
|
|
30477
30690
|
return this.req("GET", `/exposure${s ? `?${s}` : ""}`);
|
|
30478
30691
|
}
|
|
30479
30692
|
resolveExposure(id, resolution) {
|
|
30480
|
-
return this.req("POST", `/exposure/${id}/resolve`, { resolution });
|
|
30693
|
+
return this.req("POST", `/exposure/${encodeURIComponent(id)}/resolve`, { resolution });
|
|
30481
30694
|
}
|
|
30482
30695
|
suppressExposure(id, patterns = []) {
|
|
30483
|
-
return this.req("POST", `/exposure/${id}/suppress`, { patterns });
|
|
30696
|
+
return this.req("POST", `/exposure/${encodeURIComponent(id)}/suppress`, { patterns });
|
|
30484
30697
|
}
|
|
30485
30698
|
// --- integrations hub: outbound alert + SIEM connectors (VT-310 / VT-485) ---
|
|
30486
30699
|
integrationCatalog() {
|
|
@@ -30493,16 +30706,16 @@ var KutrionClient = class {
|
|
|
30493
30706
|
return this.req("POST", "/integrations", input);
|
|
30494
30707
|
}
|
|
30495
30708
|
updateIntegration(id, input) {
|
|
30496
|
-
return this.req("PATCH", `/integrations/${id}`, input);
|
|
30709
|
+
return this.req("PATCH", `/integrations/${encodeURIComponent(id)}`, input);
|
|
30497
30710
|
}
|
|
30498
30711
|
deleteIntegration(id) {
|
|
30499
|
-
return this.req("DELETE", `/integrations/${id}`);
|
|
30712
|
+
return this.req("DELETE", `/integrations/${encodeURIComponent(id)}`);
|
|
30500
30713
|
}
|
|
30501
30714
|
testIntegration(id) {
|
|
30502
|
-
return this.req("POST", `/integrations/${id}/test`);
|
|
30715
|
+
return this.req("POST", `/integrations/${encodeURIComponent(id)}/test`);
|
|
30503
30716
|
}
|
|
30504
30717
|
listIntegrationDeliveries(id, limit = 50) {
|
|
30505
|
-
return this.req("GET", `/integrations/${id}/deliveries?limit=${limit}`);
|
|
30718
|
+
return this.req("GET", `/integrations/${encodeURIComponent(id)}/deliveries?limit=${limit}`);
|
|
30506
30719
|
}
|
|
30507
30720
|
// --- HashiCorp Vault secret sync (VT-306): ENTERPRISE-gated (secret_sync). Auth secret is write-only. ---
|
|
30508
30721
|
listHcvConnections() {
|
|
@@ -30512,28 +30725,28 @@ var KutrionClient = class {
|
|
|
30512
30725
|
return this.req("POST", "/hashicorp/connections", input);
|
|
30513
30726
|
}
|
|
30514
30727
|
updateHcvConnection(id, input) {
|
|
30515
|
-
return this.req("PATCH", `/hashicorp/connections/${id}`, input);
|
|
30728
|
+
return this.req("PATCH", `/hashicorp/connections/${encodeURIComponent(id)}`, input);
|
|
30516
30729
|
}
|
|
30517
30730
|
deleteHcvConnection(id) {
|
|
30518
|
-
return this.req("DELETE", `/hashicorp/connections/${id}`);
|
|
30731
|
+
return this.req("DELETE", `/hashicorp/connections/${encodeURIComponent(id)}`);
|
|
30519
30732
|
}
|
|
30520
30733
|
testHcvConnection(id) {
|
|
30521
|
-
return this.req("POST", `/hashicorp/connections/${id}/test`);
|
|
30734
|
+
return this.req("POST", `/hashicorp/connections/${encodeURIComponent(id)}/test`);
|
|
30522
30735
|
}
|
|
30523
30736
|
listHcvMappings(connId) {
|
|
30524
|
-
return this.req("GET", `/hashicorp/connections/${connId}/mappings`);
|
|
30737
|
+
return this.req("GET", `/hashicorp/connections/${encodeURIComponent(connId)}/mappings`);
|
|
30525
30738
|
}
|
|
30526
30739
|
createHcvMapping(connId, input) {
|
|
30527
|
-
return this.req("POST", `/hashicorp/connections/${connId}/mappings`, input);
|
|
30740
|
+
return this.req("POST", `/hashicorp/connections/${encodeURIComponent(connId)}/mappings`, input);
|
|
30528
30741
|
}
|
|
30529
30742
|
deleteHcvMapping(mappingId) {
|
|
30530
|
-
return this.req("DELETE", `/hashicorp/mappings/${mappingId}`);
|
|
30743
|
+
return this.req("DELETE", `/hashicorp/mappings/${encodeURIComponent(mappingId)}`);
|
|
30531
30744
|
}
|
|
30532
30745
|
syncHcvMapping(mappingId, resolve2) {
|
|
30533
|
-
return this.req("POST", `/hashicorp/mappings/${mappingId}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
|
|
30746
|
+
return this.req("POST", `/hashicorp/mappings/${encodeURIComponent(mappingId)}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
|
|
30534
30747
|
}
|
|
30535
30748
|
syncHcvConnection(connId, resolve2) {
|
|
30536
|
-
return this.req("POST", `/hashicorp/connections/${connId}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
|
|
30749
|
+
return this.req("POST", `/hashicorp/connections/${encodeURIComponent(connId)}/sync${resolve2 ? `?resolve=${resolve2}` : ""}`);
|
|
30537
30750
|
}
|
|
30538
30751
|
// --- rdp broker (VT-490) ---
|
|
30539
30752
|
listRdpConnections() {
|
|
@@ -30543,28 +30756,28 @@ var KutrionClient = class {
|
|
|
30543
30756
|
return this.req("POST", "/rdp/connections", input);
|
|
30544
30757
|
}
|
|
30545
30758
|
updateRdpConnection(id, input) {
|
|
30546
|
-
return this.req("PATCH", `/rdp/connections/${id}`, input);
|
|
30759
|
+
return this.req("PATCH", `/rdp/connections/${encodeURIComponent(id)}`, input);
|
|
30547
30760
|
}
|
|
30548
30761
|
deleteRdpConnection(id) {
|
|
30549
|
-
return this.req("DELETE", `/rdp/connections/${id}`);
|
|
30762
|
+
return this.req("DELETE", `/rdp/connections/${encodeURIComponent(id)}`);
|
|
30550
30763
|
}
|
|
30551
30764
|
testRdpConnection(id) {
|
|
30552
|
-
return this.req("POST", `/rdp/connections/${id}/test`);
|
|
30765
|
+
return this.req("POST", `/rdp/connections/${encodeURIComponent(id)}/test`);
|
|
30553
30766
|
}
|
|
30554
30767
|
startRdpSession(id) {
|
|
30555
|
-
return this.req("POST", `/rdp/connections/${id}/session`);
|
|
30768
|
+
return this.req("POST", `/rdp/connections/${encodeURIComponent(id)}/session`);
|
|
30556
30769
|
}
|
|
30557
30770
|
listRdpSessions() {
|
|
30558
30771
|
return this.req("GET", "/rdp/sessions");
|
|
30559
30772
|
}
|
|
30560
30773
|
endRdpSession(sessionId) {
|
|
30561
|
-
return this.req("DELETE", `/rdp/sessions/${sessionId}`);
|
|
30774
|
+
return this.req("DELETE", `/rdp/sessions/${encodeURIComponent(sessionId)}`);
|
|
30562
30775
|
}
|
|
30563
30776
|
// Stream a brokered session's recording (broker proxies it from the gateway). Binary, so it bypasses
|
|
30564
30777
|
// the JSON req() helper — same shape as sftpDownload/fileDownload.
|
|
30565
30778
|
async getRdpRecording(sessionId) {
|
|
30566
30779
|
const tok = this.getToken();
|
|
30567
|
-
const res = await this.f(`${this.baseUrl}/rdp/sessions/${sessionId}/recording`, {
|
|
30780
|
+
const res = await this.f(`${this.baseUrl}/rdp/sessions/${encodeURIComponent(sessionId)}/recording`, {
|
|
30568
30781
|
headers: this.rawHeaders(tok)
|
|
30569
30782
|
});
|
|
30570
30783
|
if (!res.ok) throw new ApiError(res.status, apiErrorMessage(res.status));
|
|
@@ -30607,7 +30820,7 @@ var KutrionClient = class {
|
|
|
30607
30820
|
}
|
|
30608
30821
|
// Files AI (#325): build a filename regex from natural language, summarize a listing, or propose a
|
|
30609
30822
|
// rename plan. All run LAN-first; only file NAMES/metadata are sent (never file content), through the
|
|
30610
|
-
// redaction gate. The returned regex is server-validated to compile and
|
|
30823
|
+
// redaction gate. The returned regex is server-validated to compile and to lack the most obvious ReDoS shapes only (isSafeRegex is a heuristic: (a|a)+ passes); clients must run it under a time budget.
|
|
30611
30824
|
aiFilePattern(input) {
|
|
30612
30825
|
return this.req("POST", "/ai/files/pattern", input);
|
|
30613
30826
|
}
|
|
@@ -30639,16 +30852,16 @@ var KutrionClient = class {
|
|
|
30639
30852
|
return this.req("GET", "/jit/requests");
|
|
30640
30853
|
}
|
|
30641
30854
|
getJitRequest(id) {
|
|
30642
|
-
return this.req("GET", `/jit/requests/${id}`);
|
|
30855
|
+
return this.req("GET", `/jit/requests/${encodeURIComponent(id)}`);
|
|
30643
30856
|
}
|
|
30644
30857
|
approveJitRequest(id, note) {
|
|
30645
|
-
return this.req("POST", `/jit/requests/${id}/approve`, { note });
|
|
30858
|
+
return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/approve`, { note });
|
|
30646
30859
|
}
|
|
30647
30860
|
denyJitRequest(id, note) {
|
|
30648
|
-
return this.req("POST", `/jit/requests/${id}/deny`, { note });
|
|
30861
|
+
return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/deny`, { note });
|
|
30649
30862
|
}
|
|
30650
30863
|
revokeJitRequest(id) {
|
|
30651
|
-
return this.req("POST", `/jit/requests/${id}/revoke`);
|
|
30864
|
+
return this.req("POST", `/jit/requests/${encodeURIComponent(id)}/revoke`);
|
|
30652
30865
|
}
|
|
30653
30866
|
// --- rotation copilot (VT-145) ---
|
|
30654
30867
|
rotationServices() {
|
|
@@ -30672,13 +30885,13 @@ var KutrionClient = class {
|
|
|
30672
30885
|
return this.req("POST", "/fleet/plan", input);
|
|
30673
30886
|
}
|
|
30674
30887
|
executeFleetRun(id) {
|
|
30675
|
-
return this.req("POST", `/fleet/runs/${id}/execute`, { confirm: true });
|
|
30888
|
+
return this.req("POST", `/fleet/runs/${encodeURIComponent(id)}/execute`, { confirm: true });
|
|
30676
30889
|
}
|
|
30677
30890
|
fleetRuns() {
|
|
30678
30891
|
return this.req("GET", "/fleet/runs");
|
|
30679
30892
|
}
|
|
30680
30893
|
fleetRun(id) {
|
|
30681
|
-
return this.req("GET", `/fleet/runs/${id}`);
|
|
30894
|
+
return this.req("GET", `/fleet/runs/${encodeURIComponent(id)}`);
|
|
30682
30895
|
}
|
|
30683
30896
|
// --- anomaly baseline (VT-204) ---
|
|
30684
30897
|
refreshBaseline() {
|
|
@@ -30896,50 +31109,65 @@ function parseDestinationRefusal(body) {
|
|
|
30896
31109
|
reason: DESTINATION_REFUSAL_REASONS.includes(b.reason) ? b.reason : null
|
|
30897
31110
|
};
|
|
30898
31111
|
}
|
|
30899
|
-
var DESTINATION_REFUSAL_TITLE = "
|
|
30900
|
-
var
|
|
30901
|
-
function destinationRefusalSummary(r) {
|
|
30902
|
-
const
|
|
30903
|
-
|
|
30904
|
-
}
|
|
30905
|
-
|
|
30906
|
-
|
|
30907
|
-
if (r.
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
|
|
30911
|
-
var
|
|
30912
|
-
var
|
|
30913
|
-
var
|
|
30914
|
-
var
|
|
30915
|
-
var
|
|
30916
|
-
var
|
|
30917
|
-
|
|
31112
|
+
var DESTINATION_REFUSAL_TITLE = SECURITY_COPY_EN["destinationRefusal.title"];
|
|
31113
|
+
var usSpelling = (s) => s.replace(/\b(A|a)uthoris/g, "$1uthoriz");
|
|
31114
|
+
function destinationRefusalSummary(r, t) {
|
|
31115
|
+
const side = r.side === "source" ? "Source" : r.side === "dest" ? "Dest" : "";
|
|
31116
|
+
const key = `destinationRefusal.summary.${r.credential ? "named" : "unnamed"}${side}`;
|
|
31117
|
+
return securityCopy(key, t, { destination: r.destination, credential: r.credential ?? "" });
|
|
31118
|
+
}
|
|
31119
|
+
function destinationRefusalAllowedLabel(r, t) {
|
|
31120
|
+
if (r.allowedDestinations.length) return securityCopy("destinationRefusal.allowed.list", t);
|
|
31121
|
+
if (r.detail) return usSpelling(r.detail);
|
|
31122
|
+
return securityCopy(destinationRefusalFault(r) === "kutrion" ? "destinationRefusal.allowed.noneFault" : "destinationRefusal.allowed.none", t);
|
|
31123
|
+
}
|
|
31124
|
+
var DESTINATION_REFUSAL_ACTION = SECURITY_COPY_EN["destinationRefusal.action.allowList"];
|
|
31125
|
+
var isAllowListRemedy = (r) => r.reason !== "host_not_normalizable" && r.reason !== "domain_not_normalizable" && destinationRefusalFault(r) !== "kutrion";
|
|
31126
|
+
var DESTINATION_REFUSAL_NOT_A_REMEDY = SECURITY_COPY_EN["destinationRefusal.notARemedy"];
|
|
31127
|
+
var DESTINATION_REFUSAL_FAULT_TITLE = SECURITY_COPY_EN["destinationRefusal.title.fault"];
|
|
31128
|
+
var DESTINATION_REFUSAL_FAULT_ACTION = SECURITY_COPY_EN["destinationRefusal.action.fault"];
|
|
31129
|
+
var DESTINATION_REFUSAL_CONNECTION_TITLE = SECURITY_COPY_EN["destinationRefusal.title.connection"];
|
|
31130
|
+
var DESTINATION_REFUSAL_HOST_ACTION = SECURITY_COPY_EN["destinationRefusal.action.host"];
|
|
31131
|
+
var DESTINATION_REFUSAL_DOMAIN_ACTION = SECURITY_COPY_EN["destinationRefusal.action.domain"];
|
|
31132
|
+
function destinationRefusalTitle(r, t) {
|
|
30918
31133
|
if (r.reason === "host_not_normalizable" || r.reason === "domain_not_normalizable") {
|
|
30919
|
-
return
|
|
31134
|
+
return securityCopy("destinationRefusal.title.connection", t);
|
|
30920
31135
|
}
|
|
30921
|
-
return destinationRefusalFault(r) === "kutrion" ?
|
|
31136
|
+
return securityCopy(destinationRefusalFault(r) === "kutrion" ? "destinationRefusal.title.fault" : "destinationRefusal.title", t);
|
|
30922
31137
|
}
|
|
30923
|
-
function destinationRefusalAction(r) {
|
|
30924
|
-
if (r
|
|
30925
|
-
if (r.reason === "
|
|
30926
|
-
|
|
31138
|
+
function destinationRefusalAction(r, o, t) {
|
|
31139
|
+
if (o?.inlineAction && isAllowListRemedy(r)) return securityCopy("destinationRefusal.action.authorizeBelow", t);
|
|
31140
|
+
if (r.reason === "host_not_normalizable") return securityCopy("destinationRefusal.action.host", t);
|
|
31141
|
+
if (r.reason === "domain_not_normalizable") return securityCopy("destinationRefusal.action.domain", t);
|
|
31142
|
+
return securityCopy(destinationRefusalFault(r) === "kutrion" ? "destinationRefusal.action.fault" : "destinationRefusal.action.allowList", t);
|
|
30927
31143
|
}
|
|
30928
|
-
function destinationRefusalLines(r, indent = " ") {
|
|
31144
|
+
function destinationRefusalLines(r, indent = " ", t) {
|
|
30929
31145
|
return [
|
|
30930
|
-
destinationRefusalTitle(r),
|
|
31146
|
+
destinationRefusalTitle(r, t),
|
|
30931
31147
|
"",
|
|
30932
|
-
destinationRefusalSummary(r),
|
|
31148
|
+
destinationRefusalSummary(r, t),
|
|
30933
31149
|
"",
|
|
30934
|
-
destinationRefusalAllowedLabel(r),
|
|
31150
|
+
destinationRefusalAllowedLabel(r, t),
|
|
30935
31151
|
...r.allowedDestinations.map((d) => `${indent}${d}`),
|
|
30936
31152
|
"",
|
|
30937
|
-
destinationRefusalAction(r),
|
|
31153
|
+
destinationRefusalAction(r, void 0, t),
|
|
30938
31154
|
// Only true of a refusal that turned on where the credential may go. On a fault of ours the secret
|
|
30939
31155
|
// was never the subject, and the line reads as one more thing the user got wrong.
|
|
30940
|
-
...destinationRefusalFault(r) === "kutrion" ? [] : [
|
|
31156
|
+
...destinationRefusalFault(r) === "kutrion" ? [] : [securityCopy("destinationRefusal.notARemedy", t)]
|
|
30941
31157
|
];
|
|
30942
31158
|
}
|
|
31159
|
+
var TLS_DOWNGRADE_REFUSAL_TITLE = SECURITY_COPY_EN["tlsDowngrade.title"];
|
|
31160
|
+
var TLS_DOWNGRADE_REFUSAL_SUMMARY = SECURITY_COPY_EN["tlsDowngrade.summary"];
|
|
31161
|
+
var TLS_DOWNGRADE_REFUSAL_ACTION = SECURITY_COPY_EN["tlsDowngrade.action"];
|
|
31162
|
+
var DESTINATION_NEED_BADGE = SECURITY_COPY_EN["destinationNeed.badge"];
|
|
31163
|
+
var DESTINATION_REFUSAL_AUTHORIZE_BELOW = SECURITY_COPY_EN["destinationRefusal.action.authorizeBelow"];
|
|
31164
|
+
var DESTINATION_AUTHORIZE_ACTION = SECURITY_COPY_EN["authorizeSheet.action"];
|
|
31165
|
+
var DESTINATION_TYPED_HINT = SECURITY_COPY_EN["authorizeSheet.typedHint"];
|
|
31166
|
+
var DESTINATION_SECRET_MISMATCH = SECURITY_COPY_EN["authorizeSheet.secretMismatch"];
|
|
31167
|
+
var DESTINATION_INJECT_ONLY = SECURITY_COPY_EN["authorizeSheet.injectOnly"];
|
|
31168
|
+
var DESTINATION_UNSUPPORTED = SECURITY_COPY_EN["authorizeSheet.unsupported"];
|
|
31169
|
+
var DESTINATION_FAILED = SECURITY_COPY_EN["authorizeSheet.failed"];
|
|
31170
|
+
var DESTINATION_FIRST_CLAIM_WARNING = SECURITY_COPY_EN["destinationNeed.firstClaim"];
|
|
30943
31171
|
|
|
30944
31172
|
// ../packages/shared/src/credential-size-refusal.ts
|
|
30945
31173
|
init_define_KT_HUB_FILES();
|
|
@@ -31101,6 +31329,16 @@ function platformFlagRefusalText(code) {
|
|
|
31101
31329
|
return code && Object.prototype.hasOwnProperty.call(PLATFORM_FLAG_REFUSAL_COPY, code) ? PLATFORM_FLAG_REFUSAL_COPY[code] : null;
|
|
31102
31330
|
}
|
|
31103
31331
|
|
|
31332
|
+
// ../packages/shared/src/remote-auth-refusal.ts
|
|
31333
|
+
init_define_KT_HUB_FILES();
|
|
31334
|
+
var REMOTE_AUTH_FAILED_TEXT = "The server rejected the sign-in. Check the username and password or access key saved for this connection.";
|
|
31335
|
+
var BASIC_OVER_HTTP_REFUSED_TEXT = "Kutrion did not send the password. The server did not offer a protected sign-in over this unencrypted connection, so the password would have been readable to anyone on the network path.";
|
|
31336
|
+
var BASIC_AFTER_DIGEST_REFUSED_TEXT = "Kutrion did not send the password. This server asked for Digest sign-in earlier and now asks for Basic, which would expose the password.";
|
|
31337
|
+
var PLAIN_HTTP_OPT_IN_LABEL = "Allow sign-in over plain HTTP";
|
|
31338
|
+
var BASIC_OVER_HTTP_REFUSED_NEXT = `Switch this connection to HTTPS, or tick "${PLAIN_HTTP_OPT_IN_LABEL}" on it.`;
|
|
31339
|
+
var CREDENTIAL_CONTROL_CHARS_TEXT = "Kutrion did not send the sign-in. The username or password saved for this connection contains a line break or NUL character, which this protocol cannot carry.";
|
|
31340
|
+
var CREDENTIAL_CONTROL_CHARS_NEXT = "Edit the credential in the vault so it has no line break, then open the connection again.";
|
|
31341
|
+
|
|
31104
31342
|
// ../packages/shared/src/ssh-refusal.ts
|
|
31105
31343
|
init_define_KT_HUB_FILES();
|
|
31106
31344
|
var SSH_REFUSAL_CLASSIFICATION = {
|
|
@@ -31222,6 +31460,9 @@ var SSH_REFUSAL_CLASSIFICATION = {
|
|
|
31222
31460
|
// Something answered on that port and it was not an SSH server; what is listening has to change
|
|
31223
31461
|
// first, even though the user is who can change it.
|
|
31224
31462
|
protocol_error: { family: "failed", retry: "pointless" },
|
|
31463
|
+
// #3419: the login was accepted and the host refused a PTY or a shell. The account is set up that
|
|
31464
|
+
// way on the host, so every attempt earns the same answer.
|
|
31465
|
+
shell_refused: { family: "failed", retry: "pointless" },
|
|
31225
31466
|
// The rest are genuine retry candidates: a transport fact can be different a second later.
|
|
31226
31467
|
connection_refused: { family: "failed", retry: "possible" },
|
|
31227
31468
|
host_not_found: { family: "failed", retry: "possible" },
|
|
@@ -31343,12 +31584,12 @@ function parseJumpRefusal(body) {
|
|
|
31343
31584
|
const code = str4(b.code) ?? str4(b.error);
|
|
31344
31585
|
if (!isJumpRefusalCode(code)) return null;
|
|
31345
31586
|
const nested = b.detail && typeof b.detail === "object" ? b.detail : {};
|
|
31346
|
-
const
|
|
31347
|
-
const idx =
|
|
31348
|
-
const allowed =
|
|
31587
|
+
const pick3 = (k) => b[k] !== void 0 ? b[k] : nested[k];
|
|
31588
|
+
const idx = pick3("hop_index");
|
|
31589
|
+
const allowed = pick3("allowed_destinations");
|
|
31349
31590
|
return {
|
|
31350
31591
|
code,
|
|
31351
|
-
hopConnectionId: str4(
|
|
31592
|
+
hopConnectionId: str4(pick3("hop_connection_id")),
|
|
31352
31593
|
hopIndex: typeof idx === "number" && Number.isFinite(idx) ? idx : null,
|
|
31353
31594
|
detail: typeof b.detail === "string" ? str4(b.detail) : str4(nested.detail) ?? str4(b.error_description),
|
|
31354
31595
|
allowedDestinations: Array.isArray(allowed) ? allowed.filter((x) => typeof x === "string" && x.trim() !== "") : null
|
|
@@ -31407,12 +31648,12 @@ function parseJumpHostInUse(body) {
|
|
|
31407
31648
|
var JUMP_HOST_IN_USE_TITLE = "Other connections are reached through this one";
|
|
31408
31649
|
function jumpHostInUseSummary(r, deletingName) {
|
|
31409
31650
|
const name = str4(deletingName);
|
|
31410
|
-
const
|
|
31651
|
+
const subject = name ? `\u201C${name}\u201D` : "this connection";
|
|
31411
31652
|
const n = r.usedBy.length;
|
|
31412
31653
|
if (!n) {
|
|
31413
|
-
return `${
|
|
31654
|
+
return `${subject} is used as a jump host by other connections, so deleting it would leave them with no route.`;
|
|
31414
31655
|
}
|
|
31415
|
-
return `${
|
|
31656
|
+
return `${subject} is the jump host for ${n === 1 ? "another connection" : `${n} other connections`}. Deleting it would leave ` + (n === 1 ? "it" : "them") + " with no route to the target, so it was not deleted.";
|
|
31416
31657
|
}
|
|
31417
31658
|
var JUMP_HOST_IN_USE_ACTION = "Point each of these at a different jump host, or clear the jump host to reach it directly, and then delete this connection.";
|
|
31418
31659
|
|
|
@@ -32502,6 +32743,12 @@ function subtle() {
|
|
|
32502
32743
|
if (!s || typeof globalThis.crypto.getRandomValues !== "function") throw new VaultKeyError("crypto_unavailable");
|
|
32503
32744
|
return s;
|
|
32504
32745
|
}
|
|
32746
|
+
var random = (n) => globalThis.crypto.getRandomValues(new Uint8Array(n));
|
|
32747
|
+
function toB64(bytes) {
|
|
32748
|
+
let s = "";
|
|
32749
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
32750
|
+
return btoa(s);
|
|
32751
|
+
}
|
|
32505
32752
|
function fromB64(b64) {
|
|
32506
32753
|
const s = atob(b64);
|
|
32507
32754
|
const out = new Uint8Array(s.length);
|
|
@@ -32547,6 +32794,10 @@ async function derivePassphraseKek(passphrase, kdf, onProgress) {
|
|
|
32547
32794
|
out.fill(0);
|
|
32548
32795
|
}
|
|
32549
32796
|
}
|
|
32797
|
+
function generateVaultKey() {
|
|
32798
|
+
subtle();
|
|
32799
|
+
return random(VAULT_KEY_BYTES);
|
|
32800
|
+
}
|
|
32550
32801
|
async function unwrapVaultKey(wrap, kek, purpose) {
|
|
32551
32802
|
let bytes;
|
|
32552
32803
|
try {
|
|
@@ -32582,6 +32833,15 @@ async function unlockVaultKey(record, passphrase, onProgress) {
|
|
|
32582
32833
|
}
|
|
32583
32834
|
var dataKeyAad = (vaultId) => enc.encode(`kutrion-vault-key-v1:data-key:${vaultId.toLowerCase()}`);
|
|
32584
32835
|
var TAG_BYTES = TAG_BITS / 8;
|
|
32836
|
+
async function wrapVaultDataKey(dataKey, vaultKey, vaultId) {
|
|
32837
|
+
if (dataKey.length !== VAULT_KEY_BYTES) throw new VaultKeyError("malformed_wrap", "A vault data key is 32 bytes.");
|
|
32838
|
+
const iv = random(IV_BYTES);
|
|
32839
|
+
const ct = new Uint8Array(await subtle().encrypt({ name: "AES-GCM", iv: bs2(iv), additionalData: bs2(dataKeyAad(vaultId)), tagLength: TAG_BITS }, vaultKey, bs2(dataKey)));
|
|
32840
|
+
const out = new Uint8Array(IV_BYTES + ct.length);
|
|
32841
|
+
out.set(iv, 0);
|
|
32842
|
+
out.set(ct, IV_BYTES);
|
|
32843
|
+
return toB64(out);
|
|
32844
|
+
}
|
|
32585
32845
|
async function unwrapVaultDataKey(wrap, vaultKey, vaultId) {
|
|
32586
32846
|
let bytes;
|
|
32587
32847
|
try {
|
|
@@ -32605,6 +32865,14 @@ async function unwrapVaultDataKey(wrap, vaultKey, vaultId) {
|
|
|
32605
32865
|
}
|
|
32606
32866
|
}
|
|
32607
32867
|
var itemAad = (vaultId, itemId, purpose) => enc.encode(`kutrion-vault-key-v1:item:${vaultId.toLowerCase()}:${itemId.toLowerCase()}${purpose ? `:${purpose}` : ""}`);
|
|
32868
|
+
async function sealItemField(dataKey, plaintext, vaultId, itemId, purpose) {
|
|
32869
|
+
const iv = random(IV_BYTES);
|
|
32870
|
+
const ctTag = new Uint8Array(await subtle().encrypt({ name: "AES-GCM", iv: bs2(iv), additionalData: bs2(itemAad(vaultId, itemId, purpose)), tagLength: TAG_BITS }, dataKey, bs2(enc.encode(plaintext))));
|
|
32871
|
+
const blob = new Uint8Array(ctTag.length);
|
|
32872
|
+
blob.set(ctTag.subarray(ctTag.length - TAG_BYTES), 0);
|
|
32873
|
+
blob.set(ctTag.subarray(0, ctTag.length - TAG_BYTES), TAG_BYTES);
|
|
32874
|
+
return { iv: toB64(iv), blob: toB64(blob) };
|
|
32875
|
+
}
|
|
32608
32876
|
async function openItemField(dataKey, iv, blob, vaultId, itemId, purpose) {
|
|
32609
32877
|
let ivBytes, b;
|
|
32610
32878
|
try {
|
|
@@ -32663,6 +32931,40 @@ async function openPrivateItem(item, sealed, vaultKey) {
|
|
|
32663
32931
|
]);
|
|
32664
32932
|
return { value: value ?? "", notes, custom_fields: fields ? JSON.parse(fields) : [], passphrase, totp };
|
|
32665
32933
|
}
|
|
32934
|
+
async function privateItemDataKey(current, vaultKey, vaultId, enrolledAt) {
|
|
32935
|
+
if (current) {
|
|
32936
|
+
return { dataKey: await unwrapVaultDataKey(current.client_wrapped_data_key, vaultKey, vaultId), key: { key_id: current.key_id } };
|
|
32937
|
+
}
|
|
32938
|
+
const raw = generateVaultKey();
|
|
32939
|
+
try {
|
|
32940
|
+
const [client_wrapped_data_key, dataKey] = await Promise.all([
|
|
32941
|
+
wrapVaultDataKey(raw, vaultKey, vaultId),
|
|
32942
|
+
subtle().importKey("raw", bs2(raw), { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"])
|
|
32943
|
+
]);
|
|
32944
|
+
return { dataKey, key: { client_wrapped_data_key, vault_key_created_at: enrolledAt } };
|
|
32945
|
+
} finally {
|
|
32946
|
+
raw.fill(0);
|
|
32947
|
+
}
|
|
32948
|
+
}
|
|
32949
|
+
async function sealPrivateItem(item, plain, dataKey) {
|
|
32950
|
+
const seal = (s, purpose) => sealItemField(dataKey, s, item.vault_id, item.id, purpose);
|
|
32951
|
+
const opt = async (s, purpose) => s === void 0 ? void 0 : s === null || s === "" ? null : seal(s, purpose);
|
|
32952
|
+
const fields = plain.custom_fields === void 0 ? void 0 : plain.custom_fields && plain.custom_fields.length ? JSON.stringify(plain.custom_fields) : null;
|
|
32953
|
+
const [value, notes, custom_fields, passphrase, totp] = await Promise.all([
|
|
32954
|
+
plain.value === void 0 ? void 0 : seal(plain.value),
|
|
32955
|
+
opt(plain.notes),
|
|
32956
|
+
opt(fields),
|
|
32957
|
+
opt(plain.passphrase),
|
|
32958
|
+
opt(plain.totp?.trim() ?? plain.totp, "totp")
|
|
32959
|
+
]);
|
|
32960
|
+
const out = {};
|
|
32961
|
+
if (value !== void 0) out.value = value;
|
|
32962
|
+
if (notes !== void 0) out.notes = notes;
|
|
32963
|
+
if (custom_fields !== void 0) out.custom_fields = custom_fields;
|
|
32964
|
+
if (passphrase !== void 0) out.passphrase = passphrase;
|
|
32965
|
+
if (totp !== void 0) out.totp = totp;
|
|
32966
|
+
return out;
|
|
32967
|
+
}
|
|
32666
32968
|
|
|
32667
32969
|
// ../packages/shared/src/private-vault-copy.ts
|
|
32668
32970
|
init_define_KT_HUB_FILES();
|
|
@@ -32747,6 +33049,7 @@ var PRIVATE_VAULT_COPY = {
|
|
|
32747
33049
|
|
|
32748
33050
|
// src/args.ts
|
|
32749
33051
|
init_define_KT_HUB_FILES();
|
|
33052
|
+
var REPEATABLE = /* @__PURE__ */ new Set(["vault", "connection", "scope-vault", "scope-conn"]);
|
|
32750
33053
|
function parseForwardSpec(spec) {
|
|
32751
33054
|
const m = spec.match(/^(\d{1,5}):(.+):(\d{1,5})$/);
|
|
32752
33055
|
if (!m) return null;
|
|
@@ -32758,10 +33061,12 @@ function parseForwardSpec(spec) {
|
|
|
32758
33061
|
return { localPort, dstHost, dstPort };
|
|
32759
33062
|
}
|
|
32760
33063
|
var GLOBAL_FLAGS = { base: "value", json: "boolean" };
|
|
32761
|
-
var SWITCHES = /* @__PURE__ */ new Set(["browser", "reveal", "password-stdin", "value-stdin", "vault-passphrase-stdin", "fallback", "dry-run"]);
|
|
33064
|
+
var SWITCHES = /* @__PURE__ */ new Set(["browser", "reveal", "password-stdin", "value-stdin", "vault-passphrase-stdin", "fallback", "dry-run", "allow-insecure-lan", "stdin"]);
|
|
32762
33065
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
32763
33066
|
"action",
|
|
32764
33067
|
"agent-forward",
|
|
33068
|
+
"allow-insecure-lan",
|
|
33069
|
+
"audience",
|
|
32765
33070
|
"base",
|
|
32766
33071
|
"browser",
|
|
32767
33072
|
"client",
|
|
@@ -32773,6 +33078,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32773
33078
|
"conflict",
|
|
32774
33079
|
"conn",
|
|
32775
33080
|
"conns",
|
|
33081
|
+
"connection",
|
|
32776
33082
|
"cred",
|
|
32777
33083
|
"current",
|
|
32778
33084
|
"daily-at",
|
|
@@ -32788,7 +33094,9 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32788
33094
|
"fingerprint",
|
|
32789
33095
|
"format",
|
|
32790
33096
|
"from",
|
|
33097
|
+
"help",
|
|
32791
33098
|
"host",
|
|
33099
|
+
"issuer",
|
|
32792
33100
|
"insert",
|
|
32793
33101
|
"intent",
|
|
32794
33102
|
"json",
|
|
@@ -32825,12 +33133,16 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32825
33133
|
"reveal",
|
|
32826
33134
|
"role",
|
|
32827
33135
|
"scope",
|
|
33136
|
+
"scope-conn",
|
|
33137
|
+
"scope-vault",
|
|
32828
33138
|
"scopes",
|
|
32829
33139
|
"secret",
|
|
32830
33140
|
"service",
|
|
32831
33141
|
"session",
|
|
32832
33142
|
"since",
|
|
32833
33143
|
"status",
|
|
33144
|
+
"stdin",
|
|
33145
|
+
"subject",
|
|
32834
33146
|
"target",
|
|
32835
33147
|
"throttle",
|
|
32836
33148
|
"timeout",
|
|
@@ -32848,19 +33160,28 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32848
33160
|
"verify",
|
|
32849
33161
|
"views",
|
|
32850
33162
|
"wait",
|
|
33163
|
+
"write-env",
|
|
32851
33164
|
"yes"
|
|
32852
33165
|
]);
|
|
32853
33166
|
function unknownFlags(flags) {
|
|
32854
33167
|
return Object.keys(flags).filter((k) => !KNOWN_FLAGS.has(k));
|
|
32855
33168
|
}
|
|
32856
|
-
function
|
|
33169
|
+
function repeatedFlag(argv2, name) {
|
|
33170
|
+
const out = [];
|
|
33171
|
+
for (let i = 0; i < argv2.length - 1; i++) {
|
|
33172
|
+
if (argv2[i] === `--${name}` && !argv2[i + 1].startsWith("--")) out.push(argv2[i + 1]);
|
|
33173
|
+
}
|
|
33174
|
+
return out;
|
|
33175
|
+
}
|
|
33176
|
+
function parseArgs(argv2) {
|
|
32857
33177
|
const flags = {};
|
|
33178
|
+
const multi = {};
|
|
32858
33179
|
let at = 0;
|
|
32859
|
-
while (at <
|
|
32860
|
-
const key =
|
|
33180
|
+
while (at < argv2.length && argv2[at].startsWith("--")) {
|
|
33181
|
+
const key = argv2[at].slice(2);
|
|
32861
33182
|
const kind = GLOBAL_FLAGS[key];
|
|
32862
33183
|
if (!kind) break;
|
|
32863
|
-
const next =
|
|
33184
|
+
const next = argv2[at + 1];
|
|
32864
33185
|
if (kind === "value" && next !== void 0 && !next.startsWith("--")) {
|
|
32865
33186
|
flags[key] = next;
|
|
32866
33187
|
at += 2;
|
|
@@ -32869,16 +33190,22 @@ function parseArgs(argv) {
|
|
|
32869
33190
|
at += 1;
|
|
32870
33191
|
}
|
|
32871
33192
|
}
|
|
32872
|
-
const cmd =
|
|
32873
|
-
const rest =
|
|
33193
|
+
const cmd = argv2[at];
|
|
33194
|
+
const rest = argv2.slice(at + 1);
|
|
32874
33195
|
const positionals = [];
|
|
33196
|
+
if (cmd === "exec") return { cmd, positionals: parseExecArgs(rest, flags), flags, multi };
|
|
32875
33197
|
for (let i = 0; i < rest.length; i++) {
|
|
32876
33198
|
const a = rest[i];
|
|
33199
|
+
if (a === "--") {
|
|
33200
|
+
positionals.push(...rest.slice(i + 1));
|
|
33201
|
+
break;
|
|
33202
|
+
}
|
|
32877
33203
|
if (a.startsWith("--")) {
|
|
32878
33204
|
const key = a.slice(2);
|
|
32879
33205
|
const next = rest[i + 1];
|
|
32880
33206
|
if (!SWITCHES.has(key) && next !== void 0 && !next.startsWith("--")) {
|
|
32881
33207
|
flags[key] = next;
|
|
33208
|
+
if (REPEATABLE.has(key)) (multi[key] ??= []).push(next);
|
|
32882
33209
|
i++;
|
|
32883
33210
|
} else {
|
|
32884
33211
|
flags[key] = true;
|
|
@@ -32887,7 +33214,134 @@ function parseArgs(argv) {
|
|
|
32887
33214
|
positionals.push(a);
|
|
32888
33215
|
}
|
|
32889
33216
|
}
|
|
32890
|
-
return { cmd: cmd ?? "help", positionals, flags };
|
|
33217
|
+
return { cmd: cmd ?? "help", positionals, flags, multi };
|
|
33218
|
+
}
|
|
33219
|
+
function parseExecArgs(rest, flags) {
|
|
33220
|
+
const positionals = [];
|
|
33221
|
+
let i = 0;
|
|
33222
|
+
for (; i < rest.length; i++) {
|
|
33223
|
+
const a = rest[i];
|
|
33224
|
+
if (a === "--") {
|
|
33225
|
+
i++;
|
|
33226
|
+
if (positionals.length) break;
|
|
33227
|
+
continue;
|
|
33228
|
+
}
|
|
33229
|
+
if (a.startsWith("--")) {
|
|
33230
|
+
const key = a.slice(2);
|
|
33231
|
+
const next = rest[i + 1];
|
|
33232
|
+
if (!SWITCHES.has(key) && key !== "json" && next !== void 0 && !next.startsWith("--")) {
|
|
33233
|
+
flags[key] = next;
|
|
33234
|
+
i++;
|
|
33235
|
+
} else {
|
|
33236
|
+
flags[key] = true;
|
|
33237
|
+
}
|
|
33238
|
+
} else if (!positionals.length) {
|
|
33239
|
+
positionals.push(a);
|
|
33240
|
+
} else {
|
|
33241
|
+
break;
|
|
33242
|
+
}
|
|
33243
|
+
}
|
|
33244
|
+
positionals.push(...rest.slice(i));
|
|
33245
|
+
return positionals;
|
|
33246
|
+
}
|
|
33247
|
+
|
|
33248
|
+
// src/base-fallback.ts
|
|
33249
|
+
init_define_KT_HUB_FILES();
|
|
33250
|
+
var import_fs2 = require("fs");
|
|
33251
|
+
var norm = (u) => u.trim().replace(/\/+$/, "");
|
|
33252
|
+
function splitBases(parts) {
|
|
33253
|
+
const out = [];
|
|
33254
|
+
for (const p of parts) for (const raw of p.split(",")) {
|
|
33255
|
+
const u = norm(raw);
|
|
33256
|
+
if (u && !out.includes(u)) out.push(u);
|
|
33257
|
+
}
|
|
33258
|
+
return out;
|
|
33259
|
+
}
|
|
33260
|
+
function fallbackRefusal(f) {
|
|
33261
|
+
let u;
|
|
33262
|
+
try {
|
|
33263
|
+
u = new URL(f.url);
|
|
33264
|
+
} catch {
|
|
33265
|
+
return `not a valid server URL: ${f.url}`;
|
|
33266
|
+
}
|
|
33267
|
+
if (u.protocol === "https:") return null;
|
|
33268
|
+
if (u.protocol === "http:") {
|
|
33269
|
+
return f.insecure ? null : `${f.url} is plain http; credentials would cross it unencrypted. Use https, or opt this base in with --allow-insecure-lan`;
|
|
33270
|
+
}
|
|
33271
|
+
return `unsupported scheme for ${f.url}`;
|
|
33272
|
+
}
|
|
33273
|
+
var CONNECTION_CODES = /* @__PURE__ */ new Set([
|
|
33274
|
+
"ECONNREFUSED",
|
|
33275
|
+
"ECONNRESET",
|
|
33276
|
+
"ENOTFOUND",
|
|
33277
|
+
"EAI_AGAIN",
|
|
33278
|
+
"ETIMEDOUT",
|
|
33279
|
+
"EHOSTUNREACH",
|
|
33280
|
+
"ENETUNREACH",
|
|
33281
|
+
"EHOSTDOWN",
|
|
33282
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
33283
|
+
"UND_ERR_SOCKET"
|
|
33284
|
+
]);
|
|
33285
|
+
function isConnectionFailure(e) {
|
|
33286
|
+
for (let cur = e, depth = 0; cur && depth < 4; cur = cur.cause, depth++) {
|
|
33287
|
+
if (typeof cur.code === "string" && CONNECTION_CODES.has(cur.code)) return true;
|
|
33288
|
+
if (cur.name === "TimeoutError") return true;
|
|
33289
|
+
}
|
|
33290
|
+
return false;
|
|
33291
|
+
}
|
|
33292
|
+
var isEdgeFailure = (status) => status >= 520 && status <= 529;
|
|
33293
|
+
var replayable = (b) => b == null || typeof b === "string" || b instanceof URLSearchParams || b instanceof ArrayBuffer || ArrayBuffer.isView(b);
|
|
33294
|
+
function fallbackFetch(inner, primary, fallbacks, warn = (l) => console.error(l)) {
|
|
33295
|
+
const p = norm(primary);
|
|
33296
|
+
const bases = [{ url: p }];
|
|
33297
|
+
for (const f of fallbacks) {
|
|
33298
|
+
const why2 = fallbackRefusal(f);
|
|
33299
|
+
if (why2) {
|
|
33300
|
+
warn(`warning: ignoring fallback base: ${why2}`);
|
|
33301
|
+
continue;
|
|
33302
|
+
}
|
|
33303
|
+
if (norm(f.url) !== p) bases.push({ ...f, url: norm(f.url) });
|
|
33304
|
+
}
|
|
33305
|
+
if (bases.length === 1) return inner;
|
|
33306
|
+
let sticky = 0;
|
|
33307
|
+
return async (input, init) => {
|
|
33308
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : null;
|
|
33309
|
+
if (url === null || !(url === p || url.startsWith(`${p}/`) || url.startsWith(`${p}?`)) || !replayable(init?.body)) {
|
|
33310
|
+
return inner(input, init);
|
|
33311
|
+
}
|
|
33312
|
+
const suffix = url.slice(p.length);
|
|
33313
|
+
const order = [sticky, ...bases.map((_, i) => i).filter((i) => i !== sticky)];
|
|
33314
|
+
let firstErr;
|
|
33315
|
+
for (let n = 0; n < order.length; n++) {
|
|
33316
|
+
const i = order[n];
|
|
33317
|
+
const b = bases[i];
|
|
33318
|
+
if (b.insecure) warn(`warning: ${b.url} is plain http (opted in with --allow-insecure-lan); this request, with its credentials, is not encrypted`);
|
|
33319
|
+
const more = n < order.length - 1;
|
|
33320
|
+
try {
|
|
33321
|
+
const res = await inner(b.url + suffix, init);
|
|
33322
|
+
if (isEdgeFailure(res.status) && more) {
|
|
33323
|
+
firstErr ??= new Error(`${b.url} answered ${res.status}`);
|
|
33324
|
+
continue;
|
|
33325
|
+
}
|
|
33326
|
+
if (!isEdgeFailure(res.status)) sticky = i;
|
|
33327
|
+
return res;
|
|
33328
|
+
} catch (e) {
|
|
33329
|
+
if (init?.signal?.aborted || !isConnectionFailure(e)) throw e;
|
|
33330
|
+
firstErr ??= e;
|
|
33331
|
+
if (!more) throw firstErr;
|
|
33332
|
+
}
|
|
33333
|
+
}
|
|
33334
|
+
throw firstErr;
|
|
33335
|
+
};
|
|
33336
|
+
}
|
|
33337
|
+
function readFallbacks(configPath, base) {
|
|
33338
|
+
try {
|
|
33339
|
+
const f = JSON.parse((0, import_fs2.readFileSync)(configPath, "utf8"));
|
|
33340
|
+
if (typeof f.base !== "string" || norm(f.base) !== norm(base) || !Array.isArray(f.fallbacks)) return [];
|
|
33341
|
+
return f.fallbacks.filter((x) => x && typeof x.url === "string").map((x) => ({ url: norm(x.url), ...x.insecure === true ? { insecure: true } : {} }));
|
|
33342
|
+
} catch {
|
|
33343
|
+
return [];
|
|
33344
|
+
}
|
|
32891
33345
|
}
|
|
32892
33346
|
|
|
32893
33347
|
// src/history.ts
|
|
@@ -32940,7 +33394,7 @@ function exitForStatus(status) {
|
|
|
32940
33394
|
init_define_KT_HUB_FILES();
|
|
32941
33395
|
var import_child_process2 = require("child_process");
|
|
32942
33396
|
var import_crypto = require("crypto");
|
|
32943
|
-
var
|
|
33397
|
+
var import_fs3 = require("fs");
|
|
32944
33398
|
var import_os = require("os");
|
|
32945
33399
|
var import_path2 = require("path");
|
|
32946
33400
|
|
|
@@ -33038,7 +33492,7 @@ function run(bin, args, store, verb, input) {
|
|
|
33038
33492
|
var SECRET_TOOL_DIRS = ["/usr/bin", "/bin", "/usr/local/bin", "/run/current-system/sw/bin"];
|
|
33039
33493
|
var isExecutable = (p) => {
|
|
33040
33494
|
try {
|
|
33041
|
-
(0,
|
|
33495
|
+
(0, import_fs3.accessSync)(p, import_fs3.constants.X_OK);
|
|
33042
33496
|
return true;
|
|
33043
33497
|
} catch {
|
|
33044
33498
|
return false;
|
|
@@ -33157,7 +33611,7 @@ function why(r, ...secrets) {
|
|
|
33157
33611
|
function fileStore(file = FILE) {
|
|
33158
33612
|
let raw;
|
|
33159
33613
|
try {
|
|
33160
|
-
raw = (0,
|
|
33614
|
+
raw = (0, import_fs3.readFileSync)(file, "utf8");
|
|
33161
33615
|
} catch (e) {
|
|
33162
33616
|
if (e.code === "ENOENT") return {};
|
|
33163
33617
|
throw new Error(`cannot read the token file ${file}: ${e.message}`);
|
|
@@ -33174,9 +33628,9 @@ function fileStore(file = FILE) {
|
|
|
33174
33628
|
var dirLocked = false;
|
|
33175
33629
|
function ensureFileDir() {
|
|
33176
33630
|
const dir = (0, import_path2.dirname)(FILE);
|
|
33177
|
-
(0,
|
|
33631
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
33178
33632
|
if ((0, import_os.platform)() !== "win32") try {
|
|
33179
|
-
(0,
|
|
33633
|
+
(0, import_fs3.chmodSync)(dir, 448);
|
|
33180
33634
|
} catch {
|
|
33181
33635
|
}
|
|
33182
33636
|
else if (!dirLocked) {
|
|
@@ -33205,32 +33659,32 @@ function withLockFile(lock, fn, afterJudge) {
|
|
|
33205
33659
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
33206
33660
|
for (; ; ) {
|
|
33207
33661
|
try {
|
|
33208
|
-
const fd = (0,
|
|
33662
|
+
const fd = (0, import_fs3.openSync)(lock, "wx", 384);
|
|
33209
33663
|
try {
|
|
33210
|
-
(0,
|
|
33664
|
+
(0, import_fs3.writeSync)(fd, owner);
|
|
33211
33665
|
} finally {
|
|
33212
|
-
(0,
|
|
33666
|
+
(0, import_fs3.closeSync)(fd);
|
|
33213
33667
|
}
|
|
33214
33668
|
break;
|
|
33215
33669
|
} catch (e) {
|
|
33216
33670
|
const code = e.code;
|
|
33217
33671
|
if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") throw e;
|
|
33218
|
-
if (code !== "EEXIST" && !(0,
|
|
33672
|
+
if (code !== "EEXIST" && !(0, import_fs3.existsSync)(lock)) throw new Error(`cannot write ${(0, import_path2.dirname)(lock)}: ${e.message}`);
|
|
33219
33673
|
try {
|
|
33220
|
-
if (Date.now() - (0,
|
|
33221
|
-
const judged = (0,
|
|
33674
|
+
if (Date.now() - (0, import_fs3.statSync)(lock).mtimeMs > LOCK_STALE_MS) {
|
|
33675
|
+
const judged = (0, import_fs3.readFileSync)(lock, "utf8");
|
|
33222
33676
|
afterJudge?.();
|
|
33223
33677
|
const gone = `${lock}.${process.pid}.${(0, import_crypto.randomBytes)(4).toString("hex")}`;
|
|
33224
|
-
(0,
|
|
33225
|
-
if ((0,
|
|
33226
|
-
(0,
|
|
33678
|
+
(0, import_fs3.renameSync)(lock, gone);
|
|
33679
|
+
if ((0, import_fs3.readFileSync)(gone, "utf8") === judged) {
|
|
33680
|
+
(0, import_fs3.rmSync)(gone, { force: true });
|
|
33227
33681
|
continue;
|
|
33228
33682
|
}
|
|
33229
33683
|
try {
|
|
33230
|
-
(0,
|
|
33684
|
+
(0, import_fs3.linkSync)(gone, lock);
|
|
33231
33685
|
} catch {
|
|
33232
33686
|
}
|
|
33233
|
-
(0,
|
|
33687
|
+
(0, import_fs3.rmSync)(gone, { force: true });
|
|
33234
33688
|
}
|
|
33235
33689
|
} catch {
|
|
33236
33690
|
}
|
|
@@ -33242,7 +33696,7 @@ function withLockFile(lock, fn, afterJudge) {
|
|
|
33242
33696
|
return fn();
|
|
33243
33697
|
} finally {
|
|
33244
33698
|
try {
|
|
33245
|
-
if ((0,
|
|
33699
|
+
if ((0, import_fs3.readFileSync)(lock, "utf8") === owner) (0, import_fs3.rmSync)(lock, { force: true });
|
|
33246
33700
|
} catch {
|
|
33247
33701
|
}
|
|
33248
33702
|
}
|
|
@@ -33251,10 +33705,10 @@ var withFileLock = (fn) => withLockFile(`${FILE}.lock`, fn);
|
|
|
33251
33705
|
function sweepStaleTemps(file) {
|
|
33252
33706
|
const dir = (0, import_path2.dirname)(file), prefix = `${(0, import_path2.basename)(file)}.`;
|
|
33253
33707
|
try {
|
|
33254
|
-
for (const n of (0,
|
|
33708
|
+
for (const n of (0, import_fs3.readdirSync)(dir)) {
|
|
33255
33709
|
if (!n.startsWith(prefix) || !n.endsWith(".tmp")) continue;
|
|
33256
33710
|
try {
|
|
33257
|
-
if (Date.now() - (0,
|
|
33711
|
+
if (Date.now() - (0, import_fs3.statSync)((0, import_path2.join)(dir, n)).mtimeMs > LOCK_STALE_MS) (0, import_fs3.rmSync)((0, import_path2.join)(dir, n), { force: true });
|
|
33258
33712
|
} catch {
|
|
33259
33713
|
}
|
|
33260
33714
|
}
|
|
@@ -33264,16 +33718,16 @@ function sweepStaleTemps(file) {
|
|
|
33264
33718
|
function writeFileStore(store) {
|
|
33265
33719
|
const tmp = `${FILE}.${process.pid}.${(0, import_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
33266
33720
|
try {
|
|
33267
|
-
(0,
|
|
33721
|
+
(0, import_fs3.closeSync)((0, import_fs3.openSync)(tmp, "wx", 384));
|
|
33268
33722
|
if ((0, import_os.platform)() === "win32") lockDownWin(tmp, "file");
|
|
33269
33723
|
else try {
|
|
33270
|
-
(0,
|
|
33724
|
+
(0, import_fs3.chmodSync)(tmp, 384);
|
|
33271
33725
|
} catch {
|
|
33272
33726
|
}
|
|
33273
|
-
(0,
|
|
33727
|
+
(0, import_fs3.writeFileSync)(tmp, JSON.stringify(store));
|
|
33274
33728
|
for (let i = 0; ; i++) {
|
|
33275
33729
|
try {
|
|
33276
|
-
(0,
|
|
33730
|
+
(0, import_fs3.renameSync)(tmp, FILE);
|
|
33277
33731
|
break;
|
|
33278
33732
|
} catch (e) {
|
|
33279
33733
|
const code = e.code;
|
|
@@ -33282,7 +33736,7 @@ function writeFileStore(store) {
|
|
|
33282
33736
|
}
|
|
33283
33737
|
}
|
|
33284
33738
|
} catch (e) {
|
|
33285
|
-
(0,
|
|
33739
|
+
(0, import_fs3.rmSync)(tmp, { force: true });
|
|
33286
33740
|
throw e;
|
|
33287
33741
|
}
|
|
33288
33742
|
}
|
|
@@ -33293,7 +33747,7 @@ function updateFileStore(mutate) {
|
|
|
33293
33747
|
const store = fileStore();
|
|
33294
33748
|
if (!mutate(store)) return;
|
|
33295
33749
|
if (Object.keys(store).length) writeFileStore(store);
|
|
33296
|
-
else (0,
|
|
33750
|
+
else (0, import_fs3.rmSync)(FILE, { force: true });
|
|
33297
33751
|
});
|
|
33298
33752
|
}
|
|
33299
33753
|
function dropFileEntry(base) {
|
|
@@ -34029,15 +34483,18 @@ async function borrowAndEnrol(deps) {
|
|
|
34029
34483
|
if (enrolment === "device_clients_disabled" || enrolment === "satellite_update_required" || enrolment === "hub_not_bound") return { ok: false, reason: enrolment };
|
|
34030
34484
|
return enrolment ? { ok: true, enrolment, hubPublicKey: borrowed.hubPublicKey } : { ok: false, reason: "enrol_refused" };
|
|
34031
34485
|
}
|
|
34032
|
-
function createBorrowSession(store, client, resolveDeps) {
|
|
34486
|
+
function createBorrowSession(store, client, resolveDeps, otherSigninHeld = () => false) {
|
|
34033
34487
|
let pending = null;
|
|
34034
34488
|
let held = null;
|
|
34035
34489
|
let pin = null;
|
|
34036
34490
|
let running = false;
|
|
34491
|
+
let autoTried = false;
|
|
34037
34492
|
return {
|
|
34038
|
-
begin() {
|
|
34039
|
-
if (
|
|
34040
|
-
if (
|
|
34493
|
+
begin(auto = false) {
|
|
34494
|
+
if (auto && autoTried) return { ok: false, reason: "auto_done" };
|
|
34495
|
+
if (auto) autoTried = true;
|
|
34496
|
+
if (running) return { ok: false, reason: "busy" };
|
|
34497
|
+
if (held || otherSigninHeld()) return { ok: false, reason: "signin_in_progress" };
|
|
34041
34498
|
const p = prepareBorrow(store, client);
|
|
34042
34499
|
pending = p.ok ? p : null;
|
|
34043
34500
|
return p.ok ? { ok: true, fingerprint: p.fingerprint } : { ok: false, reason: p.reason };
|
|
@@ -34064,6 +34521,9 @@ function createBorrowSession(store, client, resolveDeps) {
|
|
|
34064
34521
|
pin = deps.pin;
|
|
34065
34522
|
return { ok: true, deviceId: out.deviceId, firstContact: out.held !== null };
|
|
34066
34523
|
},
|
|
34524
|
+
suppressAuto() {
|
|
34525
|
+
autoTried = true;
|
|
34526
|
+
},
|
|
34067
34527
|
cancel() {
|
|
34068
34528
|
pending = null;
|
|
34069
34529
|
},
|
|
@@ -34241,14 +34701,29 @@ function joinedAccount(user, lastAccount) {
|
|
|
34241
34701
|
accountChanged: lastAccount !== null && lastAccount !== user.id
|
|
34242
34702
|
};
|
|
34243
34703
|
}
|
|
34704
|
+
var HELD_TTL_MS = 3e5;
|
|
34244
34705
|
function createBorrowConfirm(deps) {
|
|
34706
|
+
const ttl = deps.ttlMs ?? HELD_TTL_MS;
|
|
34707
|
+
const now = deps.now ?? Date.now;
|
|
34245
34708
|
let account = null;
|
|
34246
34709
|
let heldId = null;
|
|
34247
34710
|
let heldName = null;
|
|
34711
|
+
let heldAt = 0;
|
|
34712
|
+
let timer = null;
|
|
34248
34713
|
const remember = (user) => {
|
|
34249
34714
|
if (deps.lastAccount.get() !== user.id) deps.lastAccount.set(user.id);
|
|
34250
34715
|
};
|
|
34716
|
+
const expired = () => account !== null && now() - heldAt >= ttl;
|
|
34717
|
+
const reap = () => {
|
|
34718
|
+
if (!expired()) return false;
|
|
34719
|
+
void reject().then(() => deps.onExpired?.());
|
|
34720
|
+
return true;
|
|
34721
|
+
};
|
|
34251
34722
|
const reject = async () => {
|
|
34723
|
+
if (timer) {
|
|
34724
|
+
clearTimeout(timer);
|
|
34725
|
+
timer = null;
|
|
34726
|
+
}
|
|
34252
34727
|
account = null;
|
|
34253
34728
|
heldId = null;
|
|
34254
34729
|
heldName = null;
|
|
@@ -34262,19 +34737,28 @@ function createBorrowConfirm(deps) {
|
|
|
34262
34737
|
account = user;
|
|
34263
34738
|
heldId = deviceId;
|
|
34264
34739
|
heldName = deviceName ?? null;
|
|
34740
|
+
heldAt = now();
|
|
34741
|
+
if (timer) clearTimeout(timer);
|
|
34742
|
+
timer = setTimeout(reap, ttl);
|
|
34743
|
+
timer.unref?.();
|
|
34265
34744
|
},
|
|
34266
|
-
pending: () => account ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName } : null,
|
|
34745
|
+
pending: () => account && !reap() ? { ...joinedAccount(account, deps.lastAccount.get()), deviceName: heldName, deviceId: heldId } : null,
|
|
34267
34746
|
seen(user) {
|
|
34268
34747
|
if (!account && user) remember(user);
|
|
34269
34748
|
},
|
|
34270
|
-
async confirm() {
|
|
34749
|
+
async confirm(deviceId) {
|
|
34750
|
+
if (reap()) return false;
|
|
34271
34751
|
const user = account;
|
|
34272
34752
|
const id = heldId;
|
|
34273
|
-
if (!user || !id) return false;
|
|
34753
|
+
if (!user || !id || id !== deviceId) return false;
|
|
34274
34754
|
if (!deps.borrow.commit(id)) {
|
|
34275
34755
|
await reject();
|
|
34276
34756
|
return false;
|
|
34277
34757
|
}
|
|
34758
|
+
if (timer) {
|
|
34759
|
+
clearTimeout(timer);
|
|
34760
|
+
timer = null;
|
|
34761
|
+
}
|
|
34278
34762
|
account = null;
|
|
34279
34763
|
heldId = null;
|
|
34280
34764
|
remember(user);
|
|
@@ -34476,6 +34960,10 @@ function cliHubStores(base, kc, keychainAvailable) {
|
|
|
34476
34960
|
};
|
|
34477
34961
|
return { store, pin, latch, lastAccount, failedWrite: () => failed2 ? { deviceId, bearer } : null };
|
|
34478
34962
|
}
|
|
34963
|
+
var lastAccountId = (base, kc) => read(kc, slot.lastAccount(base));
|
|
34964
|
+
function recordLastAccountId(base, kc, user) {
|
|
34965
|
+
if (user && typeof user.id === "string" && user.id) write(kc, slot.lastAccount(base), user.id);
|
|
34966
|
+
}
|
|
34479
34967
|
function borrowedDeviceKey(base, kc) {
|
|
34480
34968
|
return readDevice(kc, base).key;
|
|
34481
34969
|
}
|
|
@@ -34576,6 +35064,7 @@ async function hubLogin(deps) {
|
|
|
34576
35064
|
if (!deps.keychainAvailable()) return { kind: "fallback", note: null, reason: "no_keychain" };
|
|
34577
35065
|
if (locationFault(deps.location)) return { kind: "fallback", note: null, reason: "hub_not_running" };
|
|
34578
35066
|
const { store, pin, latch, lastAccount, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
35067
|
+
if (deps.pairedOnly && !pin.hubPublicKey) return { kind: "fallback", note: null, reason: "not_paired" };
|
|
34579
35068
|
const revoke = (deviceId, bearer) => endServerSession(deps.call, deviceId, bearer);
|
|
34580
35069
|
const session = createBorrowSession(store, "cli", () => ({
|
|
34581
35070
|
location: deps.location,
|
|
@@ -34627,11 +35116,36 @@ async function hubLogin(deps) {
|
|
|
34627
35116
|
const verdict = await presentJoined(confirm, deps.out, deps.ask);
|
|
34628
35117
|
if (verdict === "not_kept") return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
|
|
34629
35118
|
if (verdict === "not_you") return { kind: "fallback", note: HUB_COPY.notYou, reason: "not_you" };
|
|
34630
|
-
if (!await confirm.confirm()) return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
|
|
35119
|
+
if (!await confirm.confirm(done.deviceId)) return { kind: "fallback", note: HUB_COPY.notKept, reason: "not_kept" };
|
|
34631
35120
|
const lost = await unkept();
|
|
34632
35121
|
if (lost) return lost;
|
|
34633
35122
|
return { kind: "signed_in", user, firstContact: true };
|
|
34634
35123
|
}
|
|
35124
|
+
var canPromptYes = (stdin, stderr) => Boolean(stdin.isTTY && stderr.isTTY);
|
|
35125
|
+
var reborrowFromHub = (deps) => hubLogin({ ...deps, pairedOnly: true });
|
|
35126
|
+
var loginTriesHub = (flags, accountRoot) => flags.email === void 0 && flags["password-stdin"] !== true && accountRoot !== null;
|
|
35127
|
+
var HubAccountSwitchedError = class extends Error {
|
|
35128
|
+
constructor(email) {
|
|
35129
|
+
super(`Signed in through Kutrion Hub as ${email === null ? "an account it could not name" : printable(email)}, which is not the account this command started on. Run it again to use that account.`);
|
|
35130
|
+
this.name = "HubAccountSwitchedError";
|
|
35131
|
+
}
|
|
35132
|
+
};
|
|
35133
|
+
async function retryAfterHubReborrow(run2, deps) {
|
|
35134
|
+
try {
|
|
35135
|
+
return await run2();
|
|
35136
|
+
} catch (e) {
|
|
35137
|
+
if (!deps.refusedSession(e) || deps.wrote()) throw e;
|
|
35138
|
+
const before = deps.lastAccount();
|
|
35139
|
+
const hub = await deps.reborrow();
|
|
35140
|
+
if (hub.kind !== "signed_in") {
|
|
35141
|
+
if (hub.note) deps.out(hub.note);
|
|
35142
|
+
throw e;
|
|
35143
|
+
}
|
|
35144
|
+
if (!before || !hub.user || hub.user.id !== before) throw new HubAccountSwitchedError(hub.user ? hub.user.email : null);
|
|
35145
|
+
deps.out(`Signed in again with the Kutrion app as ${printable(hub.user.email)}; running the command again.`);
|
|
35146
|
+
}
|
|
35147
|
+
return run2();
|
|
35148
|
+
}
|
|
34635
35149
|
|
|
34636
35150
|
// src/hub/hub-seed.ts
|
|
34637
35151
|
init_define_KT_HUB_FILES();
|
|
@@ -34855,9 +35369,9 @@ function installLocationFromRegistry(regOutput) {
|
|
|
34855
35369
|
}
|
|
34856
35370
|
function underInstallRoot(dir, roots) {
|
|
34857
35371
|
if (!import_node_path3.win32.isAbsolute(dir)) return false;
|
|
34858
|
-
const
|
|
34859
|
-
const d =
|
|
34860
|
-
return roots.some((r) => r && import_node_path3.win32.isAbsolute(r) && d.startsWith(
|
|
35372
|
+
const norm2 = (p) => import_node_path3.win32.resolve(p).toLowerCase().replace(/[\\/]+$/, "");
|
|
35373
|
+
const d = norm2(dir);
|
|
35374
|
+
return roots.some((r) => r && import_node_path3.win32.isAbsolute(r) && d.startsWith(norm2(r) + "\\"));
|
|
34861
35375
|
}
|
|
34862
35376
|
function hubLauncher(env = {}) {
|
|
34863
35377
|
const platform3 = env.platform ?? process.platform;
|
|
@@ -35009,13 +35523,14 @@ var host = () => (0, import_os3.hostname)().trim().replace(/\.local$/, "") || "t
|
|
|
35009
35523
|
var ownDeviceName = () => `Kutrion CLI on ${host()}`.slice(0, 255);
|
|
35010
35524
|
async function registerOwnDevice(deps) {
|
|
35011
35525
|
const tty = deps.tty ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
35012
|
-
|
|
35526
|
+
const previous = deps.previousDeviceId ?? null;
|
|
35527
|
+
if (!previous && seedSkipReason(deps.env ?? process.env, deps.platform ?? process.platform, tty)) return { kind: "skipped", note: null };
|
|
35013
35528
|
if (!deps.bearer || !deps.keychainAvailable()) return { kind: "skipped", note: null };
|
|
35014
35529
|
const { store, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
35015
35530
|
const key = resolveDeviceKey(store);
|
|
35016
35531
|
const res = await deps.post(
|
|
35017
35532
|
"/auth/devices/register",
|
|
35018
|
-
{ name: ownDeviceName(), platform: "desktop", client: "cli", public_key: key.publicSpkiB64 },
|
|
35533
|
+
{ name: ownDeviceName(), platform: "desktop", client: "cli", public_key: key.publicSpkiB64, ...previous ? { previous_device_id: previous } : {} },
|
|
35019
35534
|
deps.bearer
|
|
35020
35535
|
);
|
|
35021
35536
|
if (!res.ok) {
|
|
@@ -35185,16 +35700,16 @@ function redactProxyUrls(text2) {
|
|
|
35185
35700
|
function bypassesProxy(host2, port, no) {
|
|
35186
35701
|
if (!no) return false;
|
|
35187
35702
|
for (const raw of no.split(/[\s,]+/)) {
|
|
35188
|
-
let
|
|
35189
|
-
if (!
|
|
35190
|
-
if (
|
|
35191
|
-
const colon =
|
|
35703
|
+
let entry2 = raw.trim().toLowerCase();
|
|
35704
|
+
if (!entry2) continue;
|
|
35705
|
+
if (entry2 === "*") return true;
|
|
35706
|
+
const colon = entry2.match(/^(.*):(\d+)$/);
|
|
35192
35707
|
if (colon) {
|
|
35193
35708
|
if (colon[2] !== port) continue;
|
|
35194
|
-
|
|
35709
|
+
entry2 = colon[1];
|
|
35195
35710
|
}
|
|
35196
|
-
|
|
35197
|
-
if (host2 ===
|
|
35711
|
+
entry2 = entry2.replace(/^\*?\./, "");
|
|
35712
|
+
if (host2 === entry2 || host2.endsWith(`.${entry2}`)) return true;
|
|
35198
35713
|
}
|
|
35199
35714
|
return false;
|
|
35200
35715
|
}
|
|
@@ -35348,8 +35863,8 @@ function firstBindingWarning(name, group, host2, port) {
|
|
|
35348
35863
|
...group.protocols.length > 1 ? ["", "Authorizing SSH also authorizes SFTP. They are one destination."] : []
|
|
35349
35864
|
];
|
|
35350
35865
|
}
|
|
35351
|
-
function removalWarning(name, group,
|
|
35352
|
-
const pairNote =
|
|
35866
|
+
function removalWarning(name, group, entry2, remainingAfter, sameGroupRemaining) {
|
|
35867
|
+
const pairNote = entry2.rows.length > 1 ? ["", "Both the SSH and the SFTP authorization for this host are removed."] : [];
|
|
35353
35868
|
if (remainingAfter === 0) {
|
|
35354
35869
|
return [
|
|
35355
35870
|
`This is the only destination authorized on "${name}".`,
|
|
@@ -35368,7 +35883,7 @@ function removalWarning(name, group, entry, remainingAfter, sameGroupRemaining)
|
|
|
35368
35883
|
];
|
|
35369
35884
|
}
|
|
35370
35885
|
return [
|
|
35371
|
-
`"${name}" will no longer be accepted at ${
|
|
35886
|
+
`"${name}" will no longer be accepted at ${entry2.host}:${entry2.port} over ${group.protocols.map((p) => PROTOCOL_LABEL[p]).join(" or ")}.`,
|
|
35372
35887
|
"",
|
|
35373
35888
|
`Its other ${group.label} destinations are unchanged.`,
|
|
35374
35889
|
...pairNote
|
|
@@ -35517,6 +36032,7 @@ async function confirmChainedHostKey(deps, connId, k, own) {
|
|
|
35517
36032
|
}
|
|
35518
36033
|
|
|
35519
36034
|
// src/refusal.ts
|
|
36035
|
+
var refreshWhy = (code, noun = "session") => refreshRefusalCopy(code, noun)?.why ?? code;
|
|
35520
36036
|
var REFUSALS = {
|
|
35521
36037
|
// #1538 authoring time. Safe to key here even though `jump_cycle` and `jump_too_deep` are also
|
|
35522
36038
|
// WebSocket codes: this table is read only by refusalLines() on the HTTP path, and wsRefusalLines()
|
|
@@ -35538,16 +36054,22 @@ var REFUSALS = {
|
|
|
35538
36054
|
why: "A jump host must be one of your own SSH connections, and no connection of yours has that id.",
|
|
35539
36055
|
action: "Run `kt conns` and pass an id from the first column to --jump."
|
|
35540
36056
|
},
|
|
35541
|
-
//
|
|
35542
|
-
//
|
|
35543
|
-
|
|
35544
|
-
|
|
35545
|
-
|
|
35546
|
-
|
|
36057
|
+
// #3528: modules/ssh.ts sftpFail and modules/files.ts send this for a remote path, not a record id, so
|
|
36058
|
+
// the 404 line about an invisible id would point at the wrong thing.
|
|
36059
|
+
path_not_found: {
|
|
36060
|
+
title: "No such path",
|
|
36061
|
+
why: "The connection was reached, but nothing exists at that path on the remote side.",
|
|
36062
|
+
action: "Check the path. `kt sftp ls <connId>` with no path lists the connection's starting directory."
|
|
36063
|
+
},
|
|
36064
|
+
// lib/auth-guard.ts blockStepUpPending (social-session gate) and enforceAdaptiveAuth (adaptive), plus
|
|
36065
|
+
// the rotation, jit, fleet, agents and ssh modules. Step-up freshness is a per-SESSION Redis proof with
|
|
36066
|
+
// a short TTL (server/src/lib/step-up.ts), so a step-up in the web app does not clear this CLI's
|
|
36067
|
+
// session; `kt step-up` proves it for this one. The refusal does not say which factors the account
|
|
36068
|
+
// holds, so the action names a completable path for each case (#3539).
|
|
35547
36069
|
step_up_required: {
|
|
35548
36070
|
title: "Step-up verification required",
|
|
35549
36071
|
why: "This action needs a recent passkey or app-MFA check, and this session does not have one.",
|
|
35550
|
-
action: "
|
|
36072
|
+
action: "Run `kt step-up` and enter a code from your authenticator app, then re-run this command within a few minutes. With no authenticator app on the account (none yet, or a passkey only, which a terminal cannot verify), enrol one first with `kt mfa setup` and `kt mfa verify --code <code>`, then run `kt step-up`. If you signed in through SSO or a social provider, `kt login --email <e> --password <p> --code <2fa>` also mints a session that is not gated."
|
|
35551
36073
|
},
|
|
35552
36074
|
// #2225 AC5, and the one shape this table exists for: the server owns the REASON, this client adds
|
|
35553
36075
|
// the ROUTE. The server's hint names what ended the session (server/src/index.ts for a credential
|
|
@@ -35568,6 +36090,49 @@ var REFUSALS = {
|
|
|
35568
36090
|
why: "The token stored for this server is no longer accepted, so every command will be refused until this machine signs in again.",
|
|
35569
36091
|
action: "Run `kt login` to sign in again."
|
|
35570
36092
|
},
|
|
36093
|
+
// #3444/#3445: POST /auth/refresh terminal verdicts (server/src/modules/devices.ts). Each ends the
|
|
36094
|
+
// stored session, like device_revoked, so the only way forward is a fresh `kt login`. The Hub pair
|
|
36095
|
+
// reuses the extension's sentences word for word.
|
|
36096
|
+
refresh_credentials_changed: {
|
|
36097
|
+
title: "Your password changed",
|
|
36098
|
+
why: refreshWhy("refresh_credentials_changed"),
|
|
36099
|
+
action: "Run `kt login` with your new password."
|
|
36100
|
+
},
|
|
36101
|
+
refresh_hub_signed_out: {
|
|
36102
|
+
title: "This session was signed out",
|
|
36103
|
+
why: refreshWhy("refresh_hub_signed_out"),
|
|
36104
|
+
action: "Run `kt login` to sign in again."
|
|
36105
|
+
},
|
|
36106
|
+
refresh_hub_revoked: {
|
|
36107
|
+
title: "This session was signed out",
|
|
36108
|
+
why: refreshWhy("refresh_hub_revoked", "app"),
|
|
36109
|
+
action: "Run `kt login` to sign in again."
|
|
36110
|
+
},
|
|
36111
|
+
device_revoked: {
|
|
36112
|
+
title: "This device was signed out",
|
|
36113
|
+
why: refreshWhy("device_revoked", "device"),
|
|
36114
|
+
action: "Run `kt login` to sign in again and register it anew."
|
|
36115
|
+
},
|
|
36116
|
+
account_inactive: {
|
|
36117
|
+
title: "This account has ended",
|
|
36118
|
+
why: refreshWhy("account_inactive"),
|
|
36119
|
+
action: "Your administrator can restore the account. Signing in again will not work until they do."
|
|
36120
|
+
},
|
|
36121
|
+
account_expired: {
|
|
36122
|
+
title: "This account has expired",
|
|
36123
|
+
why: refreshWhy("account_expired"),
|
|
36124
|
+
action: "Your administrator can extend the account. Signing in again will not work until they do."
|
|
36125
|
+
},
|
|
36126
|
+
user_inactive: {
|
|
36127
|
+
title: "This account has ended",
|
|
36128
|
+
why: refreshWhy("user_inactive"),
|
|
36129
|
+
action: "Your administrator can restore the account. Signing in again will not work until they do."
|
|
36130
|
+
},
|
|
36131
|
+
org_mismatch: {
|
|
36132
|
+
title: "This session was signed out",
|
|
36133
|
+
why: refreshWhy("org_mismatch"),
|
|
36134
|
+
action: "Run `kt login` to sign in again with your current organization."
|
|
36135
|
+
},
|
|
35571
36136
|
// auth-guard.ts:164. The distinguishing fact is that this one CANNOT be stepped up out of — it is
|
|
35572
36137
|
// sent precisely when the account holds no second factor to step up with.
|
|
35573
36138
|
access_blocked: {
|
|
@@ -35649,7 +36214,7 @@ var REFUSALS = {
|
|
|
35649
36214
|
why: "Promoting now would leave those hosts refusing every session, because they do not yet accept a certificate from the next CA.",
|
|
35650
36215
|
action: "Install or verify each host listed by `kt broker-ca status`, or revoke its enrollment, then promote again."
|
|
35651
36216
|
},
|
|
35652
|
-
// authz.ts:69, connection-import.ts:
|
|
36217
|
+
// authz.ts:69, connection-import.ts:164. Overage freeze after a downgrade — read stays, write stops.
|
|
35653
36218
|
vault_frozen: {
|
|
35654
36219
|
title: "Vault is read-only",
|
|
35655
36220
|
why: "This vault is frozen for writes because the plan's vault limit was exceeded after a downgrade.",
|
|
@@ -35675,7 +36240,7 @@ var REFUSALS = {
|
|
|
35675
36240
|
why: "This organization keeps AI on its own network, so only agents declared `lan` may act for it.",
|
|
35676
36241
|
action: 'Mint with `--egress lan` only for a model the organization hosts on its own network, or ask an owner to turn off "Keep AI on our network".'
|
|
35677
36242
|
},
|
|
35678
|
-
// files.ts:
|
|
36243
|
+
// server/src/modules/files.ts:353.
|
|
35679
36244
|
tls_downgrade_refused: {
|
|
35680
36245
|
title: "Transport security cannot be weakened",
|
|
35681
36246
|
why: "The change would turn TLS off, or allow an unverified certificate, on a connection that already has it on.",
|
|
@@ -35731,7 +36296,7 @@ var REFUSALS = {
|
|
|
35731
36296
|
why: "The code was valid for a limited time and that time has passed.",
|
|
35732
36297
|
action: "Run `kt login` again; a new code is emailed when the password is accepted."
|
|
35733
36298
|
},
|
|
35734
|
-
// vault.ts:1105/1363/1387, files.ts:
|
|
36299
|
+
// vault.ts:1105/1363/1387, server/src/modules/files.ts:369/574, shares.ts:43, file-broker.ts:99. #954: the wire field is
|
|
35735
36300
|
// `inject_only`; the name the product uses is "No-reveal", and the second half is stated positively
|
|
35736
36301
|
// so the credential does not read as broken.
|
|
35737
36302
|
inject_only: {
|
|
@@ -35739,12 +36304,42 @@ var REFUSALS = {
|
|
|
35739
36304
|
why: "Kutrion does not display, copy, export or share this credential's value. It is still used to authenticate connections and integrations.",
|
|
35740
36305
|
action: "Use the credential rather than reading it \u2014 `kt ssh <connId>` or `kt exec <connId> <command>` broker a session with it without the value passing through this terminal."
|
|
35741
36306
|
},
|
|
35742
|
-
// modules/ssh.ts:
|
|
36307
|
+
// modules/ssh.ts:478, modules/rdp.ts:151.
|
|
35743
36308
|
inject_only_requires_write: {
|
|
35744
36309
|
title: "Write access required for a No-reveal credential",
|
|
35745
36310
|
why: "Pointing a connection at a No-reveal credential needs write access to the vault holding it, because the person doing it decides where the credential gets presented.",
|
|
35746
36311
|
action: "Ask an owner or editor of that vault to make the change, or to grant you write access to it."
|
|
35747
36312
|
},
|
|
36313
|
+
// lib/untrusted-cert.ts, sent by files.ts fail() and cloud.ts fail() as a 503 (#3418); admitted past the 5xx
|
|
36314
|
+
// gate by SERVER_REFUSALS. Wording matches Kutrion Transfer's entry.
|
|
36315
|
+
// lib/remote-auth.ts, sent by files.ts fail() and ssh.ts sftpFail() as a 422 (#3451). Wording shared with Kutrion Transfer.
|
|
36316
|
+
remote_auth_failed: {
|
|
36317
|
+
title: "The server rejected the sign-in",
|
|
36318
|
+
why: REMOTE_AUTH_FAILED_TEXT,
|
|
36319
|
+
action: "Update the credential this connection uses in the vault, then retry."
|
|
36320
|
+
},
|
|
36321
|
+
// lib/remote-auth.ts, sent by files.ts fail() as a 400 (#3462). Nothing was sent to the host. Wording shared with Kutrion Transfer.
|
|
36322
|
+
basic_over_http_refused: {
|
|
36323
|
+
title: "The password was not sent over plain HTTP",
|
|
36324
|
+
why: BASIC_OVER_HTTP_REFUSED_TEXT,
|
|
36325
|
+
action: "Switch this connection to HTTPS, then retry."
|
|
36326
|
+
},
|
|
36327
|
+
basic_after_digest_refused: {
|
|
36328
|
+
title: "The server's sign-in method changed",
|
|
36329
|
+
why: BASIC_AFTER_DIGEST_REFUSED_TEXT,
|
|
36330
|
+
action: "Kutrion refused because someone between you and the server may be removing the safer sign-in option. Ask whoever runs the server whether they changed its sign-in settings. If the change is genuine, delete this connection and add it again, or, if you own it, edit it to the server's new address (this needs step-up). Only adding it again resets the remembered sign-in type for the same address."
|
|
36331
|
+
},
|
|
36332
|
+
// lib/remote-auth.ts, sent by files.ts fail() as a 400 (#3484). Nothing was sent to the host.
|
|
36333
|
+
credential_control_characters: {
|
|
36334
|
+
title: "The saved sign-in cannot be sent",
|
|
36335
|
+
why: CREDENTIAL_CONTROL_CHARS_TEXT,
|
|
36336
|
+
action: CREDENTIAL_CONTROL_CHARS_NEXT
|
|
36337
|
+
},
|
|
36338
|
+
tls_certificate_untrusted: {
|
|
36339
|
+
title: "This host's certificate is not trusted",
|
|
36340
|
+
why: "The host presented a certificate that no authority vouches for, often a self-signed one, so the connection was not made. Nothing was sent to the host.",
|
|
36341
|
+
action: "Ask an admin to review the certificate or the network path to this host."
|
|
36342
|
+
},
|
|
35748
36343
|
// access-policy.ts:264, reached from a root preHandler over the file family; reason values from AccessDecision.reason. The server sends
|
|
35749
36344
|
// a bare code here and no hint, so translating `reason` is the whole value this entry adds.
|
|
35750
36345
|
blocked_by_access_policy: {
|
|
@@ -35752,7 +36347,7 @@ var REFUSALS = {
|
|
|
35752
36347
|
why: "This organization restricts where its file connections may be reached from, and this request came from outside that.",
|
|
35753
36348
|
action: "Connect from an allowed network or country, or ask an organization administrator to widen the policy. The `reason` below says which rule refused it."
|
|
35754
36349
|
},
|
|
35755
|
-
// files.ts:
|
|
36350
|
+
// server/src/modules/files.ts:56/1054, file-requests.ts:280. `path` and `rule` name the file and the rule.
|
|
35756
36351
|
blocked_by_dlp_policy: {
|
|
35757
36352
|
title: "Blocked by a data-loss-prevention rule",
|
|
35758
36353
|
why: "The content or the path matched a rule this organization applies to file transfers.",
|
|
@@ -35922,8 +36517,14 @@ function contextLines(body, indent) {
|
|
|
35922
36517
|
const text2 = Array.isArray(v) ? v.filter((x) => x !== null && typeof x !== "object").map((x) => String(x)).join(", ") : String(v);
|
|
35923
36518
|
if (text2.trim()) out.push(`${indent}${f}: ${text2}`);
|
|
35924
36519
|
}
|
|
36520
|
+
const fields = asRecord(asRecord(b.details)?.fieldErrors);
|
|
36521
|
+
for (const [field2, msgs] of Object.entries(fields ?? {})) {
|
|
36522
|
+
const text2 = Array.isArray(msgs) ? msgs.filter((m) => typeof m === "string").map(scrubInput).join("; ") : "";
|
|
36523
|
+
if (text2) out.push(`${indent}${field2}: ${text2}`);
|
|
36524
|
+
}
|
|
35925
36525
|
return out;
|
|
35926
36526
|
}
|
|
36527
|
+
var scrubInput = (m) => m.replace(/,?\s*(?:received|got)\s+(?:'[^']*'|"[^"]*"|\S+)/gi, "").replace(/\s{2,}/g, " ").replace(/[\s,]+$/, "").trim();
|
|
35927
36528
|
var STATUS_ACTION = {
|
|
35928
36529
|
400: "The server could not use the request as sent. Check the command's arguments against `kt --help`.",
|
|
35929
36530
|
401: "This machine's session was not accepted. Run `kt login` to sign in again.",
|
|
@@ -35936,11 +36537,15 @@ var STATUS_ACTION = {
|
|
|
35936
36537
|
422: "The server understood the request but refused its values. Check the values passed against `kt --help`.",
|
|
35937
36538
|
429: "Too many requests in a short period. Wait before retrying rather than looping."
|
|
35938
36539
|
};
|
|
35939
|
-
var
|
|
36540
|
+
var HUB_SIGN_IN_ACTION = {
|
|
36541
|
+
status: "This machine's session was not accepted. Run `kt login`, it signs in through Kutrion Hub.",
|
|
36542
|
+
signed_out: "Run `kt login`, it signs in through Kutrion Hub."
|
|
36543
|
+
};
|
|
36544
|
+
var SERVER_REFUSALS = /* @__PURE__ */ new Set(["payment_unconfirmed", "tls_certificate_untrusted"]);
|
|
35940
36545
|
var sentenceKey = (t) => t.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
35941
36546
|
var sameSentence = (a, b) => b !== void 0 && sentenceKey(a) === sentenceKey(b);
|
|
35942
36547
|
var SIGNIN_CODE_REFUSALS = /* @__PURE__ */ new Set(["email_code_required", "signin_code_cap"]);
|
|
35943
|
-
function refusalLines(e, indent = " ") {
|
|
36548
|
+
function refusalLines(e, indent = " ", opts2 = {}) {
|
|
35944
36549
|
if (!(e instanceof ApiError)) return null;
|
|
35945
36550
|
if (e.status < 400 || e.status >= 500 && !SERVER_REFUSALS.has(refusalCode(e))) return null;
|
|
35946
36551
|
const dest = parseDestinationRefusal(e.body);
|
|
@@ -35963,11 +36568,16 @@ function refusalLines(e, indent = " ") {
|
|
|
35963
36568
|
`${indent}kt conn set <id> --jump none`
|
|
35964
36569
|
];
|
|
35965
36570
|
}
|
|
35966
|
-
const
|
|
36571
|
+
const fromRefresh = e.refreshCode !== void 0 && Object.hasOwn(REFUSALS, e.refreshCode);
|
|
36572
|
+
const code = fromRefresh ? e.refreshCode : refusalCode(e);
|
|
35967
36573
|
const platformFlag = platformFlagRefusalText(code);
|
|
35968
36574
|
if (platformFlag) return [`Turned off on this deployment (${code}, HTTP ${e.status})`, "", platformFlag];
|
|
36575
|
+
const privateHost = code === "blocked_internal_address" ? str6(asRecord(e.body)?.host) : null;
|
|
36576
|
+
if (privateHost) {
|
|
36577
|
+
return [`Refused by the server (${code}, HTTP ${e.status})`, "", privateTargetSentence({ host: privateHost, surface: "short", reason: asRecord(e.body)?.target_reason })];
|
|
36578
|
+
}
|
|
35969
36579
|
const copy = Object.hasOwn(REFUSALS, code) ? REFUSALS[code] : void 0;
|
|
35970
|
-
const server = SIGNIN_CODE_REFUSALS.has(code) ? null : serverText(e.body);
|
|
36580
|
+
const server = fromRefresh || SIGNIN_CODE_REFUSALS.has(code) ? null : serverText(e.body);
|
|
35971
36581
|
const context = contextLines(e.body, indent);
|
|
35972
36582
|
const head = copy ? `${copy.title} (${code}, HTTP ${e.status})` : `Refused by the server (${code}, HTTP ${e.status})`;
|
|
35973
36583
|
const lines = [head, ""];
|
|
@@ -35976,7 +36586,9 @@ function refusalLines(e, indent = " ") {
|
|
|
35976
36586
|
if (!copy && !server) lines.push("The server sent no explanation with this refusal.");
|
|
35977
36587
|
if (context.length) lines.push("", ...context);
|
|
35978
36588
|
lines.push("");
|
|
35979
|
-
|
|
36589
|
+
const hubAction = !opts2.hubSignIn || e.status !== 401 ? null : code === "session_signed_out" ? HUB_SIGN_IN_ACTION.signed_out : copy ? null : HUB_SIGN_IN_ACTION.status;
|
|
36590
|
+
if (hubAction) lines.push(hubAction);
|
|
36591
|
+
else if (copy?.action) lines.push(copy.action);
|
|
35980
36592
|
else if (copy) lines.push("Nothing at this terminal changes this answer.");
|
|
35981
36593
|
else lines.push(STATUS_ACTION[e.status] ?? `Quote the code and status above when reporting this; they are the whole of what the server sent.`);
|
|
35982
36594
|
return lines;
|
|
@@ -36038,7 +36650,9 @@ function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
|
36038
36650
|
const jump = jumpRefusalReport(frame, code, head, jumpCtx, indent);
|
|
36039
36651
|
if (jump) return jump;
|
|
36040
36652
|
const lines = [code && !ourFault ? `${head} (${code})` : head, ""];
|
|
36041
|
-
lines.push(
|
|
36653
|
+
lines.push(
|
|
36654
|
+
code === "blocked_internal_address" && host2 ? privateTargetSentence({ host: host2, surface: "short", reason: d.target_reason }) : str6(frame.error) ?? "The server sent no explanation with this refusal."
|
|
36655
|
+
);
|
|
36042
36656
|
if (host2) lines.push("", `${indent}endpoint: ${port === null ? host2 : `${host2}:${port}`}`);
|
|
36043
36657
|
if (allowed.length) {
|
|
36044
36658
|
lines.push("", destinationRefusalAllowedLabel(dest));
|
|
@@ -36051,6 +36665,17 @@ function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
|
36051
36665
|
}
|
|
36052
36666
|
|
|
36053
36667
|
// src/shell.ts
|
|
36668
|
+
function ttySize(columns, rows) {
|
|
36669
|
+
const known = (n, d) => typeof n === "number" && Number.isFinite(n) && n > 0 ? n : d;
|
|
36670
|
+
return [known(columns, 80), known(rows, 24)];
|
|
36671
|
+
}
|
|
36672
|
+
function detachNotice(frame) {
|
|
36673
|
+
const ms = frame.orphan_ttl_ms;
|
|
36674
|
+
const left = typeof ms === "number" && ms > 0 ? ms >= 6e4 ? `${Math.round(ms / 6e4)} minute${Math.round(ms / 6e4) === 1 ? "" : "s"}` : `${Math.ceil(ms / 1e3)} seconds` : null;
|
|
36675
|
+
const held = left ? `The session keeps running on the server for ${left}, then ends.` : "The session keeps running on the server for a short time, then ends.";
|
|
36676
|
+
if (frame.recorded !== true) return `[Kutrion] Detached. ${held}`;
|
|
36677
|
+
return `[Kutrion] Detached. ${held} Its recording is saved when the session ends, and kt recordings lists it from then. To end a session at once, type exit in it instead of detaching.`;
|
|
36678
|
+
}
|
|
36054
36679
|
function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onChainedHostKey) {
|
|
36055
36680
|
const wsUrl = shellUrl(base, connId);
|
|
36056
36681
|
const ws = new wrapper_default(wsUrl, { headers: { Authorization: `Bearer ${token}` }, agent: wsAgentFor(wsUrl) });
|
|
@@ -36059,12 +36684,12 @@ function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onCha
|
|
|
36059
36684
|
const isTty = Boolean(stdin.isTTY && stdout.isTTY);
|
|
36060
36685
|
const sendResize = () => {
|
|
36061
36686
|
if (ws.readyState !== wrapper_default.OPEN) return;
|
|
36062
|
-
const cols = stdout.columns
|
|
36063
|
-
const rows = stdout.rows ?? 24;
|
|
36687
|
+
const [cols, rows] = ttySize(stdout.columns, stdout.rows);
|
|
36064
36688
|
ws.send(resizeFrame(cols, rows));
|
|
36065
36689
|
};
|
|
36066
36690
|
let exiting = false;
|
|
36067
36691
|
let recordedSessionId = null;
|
|
36692
|
+
let sessionInfo = null;
|
|
36068
36693
|
const printReceipt = async () => {
|
|
36069
36694
|
const id = recordedSessionId;
|
|
36070
36695
|
if (!id) return;
|
|
@@ -36086,6 +36711,9 @@ function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onCha
|
|
|
36086
36711
|
ws.close();
|
|
36087
36712
|
} catch {
|
|
36088
36713
|
}
|
|
36714
|
+
if (!ended && sessionInfo) process.stderr.write(`\r
|
|
36715
|
+
${detachNotice(sessionInfo)}\r
|
|
36716
|
+
`);
|
|
36089
36717
|
void (ended ? printReceipt() : Promise.resolve()).catch(() => {
|
|
36090
36718
|
}).then(() => onExit?.()).catch(() => {
|
|
36091
36719
|
}).then(() => process.exit(code));
|
|
@@ -36126,6 +36754,7 @@ ${wsRefusalLines(frame, " ", jumpCtx).join("\r\n")}\r
|
|
|
36126
36754
|
const sessionFrame = sawSession ? null : parseSessionFrame(text2);
|
|
36127
36755
|
if (sessionFrame) {
|
|
36128
36756
|
sawSession = true;
|
|
36757
|
+
sessionInfo = sessionFrame;
|
|
36129
36758
|
const state = recordingStateLabel(sessionFrame);
|
|
36130
36759
|
if (state) process.stderr.write(`\r
|
|
36131
36760
|
[Kutrion] ${state}\r
|
|
@@ -36203,7 +36832,7 @@ var import_readline = require("readline");
|
|
|
36203
36832
|
|
|
36204
36833
|
// src/mcp-install.ts
|
|
36205
36834
|
init_define_KT_HUB_FILES();
|
|
36206
|
-
var
|
|
36835
|
+
var import_fs4 = require("fs");
|
|
36207
36836
|
var import_os4 = require("os");
|
|
36208
36837
|
var import_path4 = require("path");
|
|
36209
36838
|
var import_child_process3 = require("child_process");
|
|
@@ -36233,6 +36862,18 @@ function refuseBodyRedirects(inner = fetch) {
|
|
|
36233
36862
|
}
|
|
36234
36863
|
};
|
|
36235
36864
|
}
|
|
36865
|
+
function trackWrites(inner) {
|
|
36866
|
+
let wrote = false;
|
|
36867
|
+
return {
|
|
36868
|
+
fetch: async (input, init) => {
|
|
36869
|
+
const res = await inner(input, init);
|
|
36870
|
+
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
36871
|
+
if (res.ok && method !== "GET") wrote = true;
|
|
36872
|
+
return res;
|
|
36873
|
+
},
|
|
36874
|
+
wrote: () => wrote
|
|
36875
|
+
};
|
|
36876
|
+
}
|
|
36236
36877
|
|
|
36237
36878
|
// src/mcp-install.ts
|
|
36238
36879
|
var appData = () => process.env.APPDATA ?? (0, import_path4.join)((0, import_os4.homedir)(), "AppData", "Roaming");
|
|
@@ -36286,35 +36927,35 @@ function bundleCandidates(moduleDir) {
|
|
|
36286
36927
|
function resolveLaunch(base, client, moduleDir = __dirname) {
|
|
36287
36928
|
const which = (0, import_child_process3.spawnSync)("command -v kt", { encoding: "utf8", shell: true });
|
|
36288
36929
|
const onPath = which.status === 0 ? (which.stdout ?? "").trim().split("\n")[0] : "";
|
|
36289
|
-
let
|
|
36930
|
+
let entry2;
|
|
36290
36931
|
if ((0, import_path4.isAbsolute)(onPath)) {
|
|
36291
|
-
|
|
36932
|
+
entry2 = { command: onPath, args: ["mcp"] };
|
|
36292
36933
|
} else {
|
|
36293
|
-
const bundle = bundleCandidates(moduleDir).find((p) => (0,
|
|
36934
|
+
const bundle = bundleCandidates(moduleDir).find((p) => (0, import_fs4.existsSync)(p));
|
|
36294
36935
|
if (!bundle) {
|
|
36295
36936
|
throw new Error(
|
|
36296
36937
|
"no built CLI to launch the MCP bridge with. Run `npm run build` in cli/, or install the CLI so that `kt` is on your PATH, then re-run this command."
|
|
36297
36938
|
);
|
|
36298
36939
|
}
|
|
36299
|
-
|
|
36940
|
+
entry2 = { command: process.execPath, args: [bundle, "mcp"] };
|
|
36300
36941
|
}
|
|
36301
36942
|
const fileStore2 = (ktEnv("TOKEN_STORE") ?? "").trim().toLowerCase() === "file";
|
|
36302
|
-
|
|
36943
|
+
entry2.env = {
|
|
36303
36944
|
KT_API_BASE: base,
|
|
36304
36945
|
...client ? { KT_MCP_CLIENT: client } : {},
|
|
36305
36946
|
...fileStore2 ? { KT_TOKEN_STORE: "file" } : {}
|
|
36306
36947
|
};
|
|
36307
|
-
return
|
|
36948
|
+
return entry2;
|
|
36308
36949
|
}
|
|
36309
36950
|
var STORE_ENV = /* @__PURE__ */ new Set(["TOKEN_STORE", "TOKEN_STORE_TIMEOUT_MS"]);
|
|
36310
|
-
async function verifyLaunch(
|
|
36951
|
+
async function verifyLaunch(entry2, timeoutMs = 15e3) {
|
|
36311
36952
|
const probe = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} });
|
|
36312
36953
|
const inherited = Object.fromEntries(
|
|
36313
36954
|
Object.entries(process.env).filter(([k]) => !/^(KT|VT)_/.test(k) || STORE_ENV.has(k.slice(3)))
|
|
36314
36955
|
);
|
|
36315
|
-
const res = (0, import_child_process3.spawnSync)(
|
|
36956
|
+
const res = (0, import_child_process3.spawnSync)(entry2.command, entry2.args, {
|
|
36316
36957
|
input: probe + "\n",
|
|
36317
|
-
env: { ...inherited, ...
|
|
36958
|
+
env: { ...inherited, ...entry2.env ?? {} },
|
|
36318
36959
|
cwd: (0, import_os4.tmpdir)(),
|
|
36319
36960
|
encoding: "utf8",
|
|
36320
36961
|
timeout: timeoutMs
|
|
@@ -36333,10 +36974,10 @@ async function verifyLaunch(entry, timeoutMs = 15e3) {
|
|
|
36333
36974
|
return { ok: false, error: "the bridge produced output that is not JSON-RPC" };
|
|
36334
36975
|
}
|
|
36335
36976
|
}
|
|
36336
|
-
function mergeConfig(existing, key,
|
|
36977
|
+
function mergeConfig(existing, key, entry2) {
|
|
36337
36978
|
const cfg = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
|
|
36338
36979
|
const servers = cfg[key] && typeof cfg[key] === "object" && !Array.isArray(cfg[key]) ? { ...cfg[key] } : {};
|
|
36339
|
-
servers[SERVER_NAME] =
|
|
36980
|
+
servers[SERVER_NAME] = entry2;
|
|
36340
36981
|
cfg[key] = servers;
|
|
36341
36982
|
return cfg;
|
|
36342
36983
|
}
|
|
@@ -36352,14 +36993,14 @@ function removeFromConfig(existing, key) {
|
|
|
36352
36993
|
}
|
|
36353
36994
|
function readJson(path) {
|
|
36354
36995
|
try {
|
|
36355
|
-
return JSON.parse((0,
|
|
36996
|
+
return JSON.parse((0, import_fs4.readFileSync)(path, "utf8"));
|
|
36356
36997
|
} catch {
|
|
36357
36998
|
return void 0;
|
|
36358
36999
|
}
|
|
36359
37000
|
}
|
|
36360
37001
|
function writeJson(path, value) {
|
|
36361
|
-
(0,
|
|
36362
|
-
(0,
|
|
37002
|
+
(0, import_fs4.mkdirSync)((0, import_path4.dirname)(path), { recursive: true });
|
|
37003
|
+
(0, import_fs4.writeFileSync)(path, JSON.stringify(value, null, 2) + "\n");
|
|
36363
37004
|
}
|
|
36364
37005
|
var agentAccount = (base, client) => client ? `agent:${base}:${client}` : `agent:${base}`;
|
|
36365
37006
|
function getAgentToken(base, client) {
|
|
@@ -36404,7 +37045,7 @@ async function installClient(o) {
|
|
|
36404
37045
|
if (!configPath) {
|
|
36405
37046
|
throw new Error(`${target.label} is not available on this platform (${(0, import_os4.platform)()}).`);
|
|
36406
37047
|
}
|
|
36407
|
-
const
|
|
37048
|
+
const entry2 = resolveLaunch(o.base, o.client);
|
|
36408
37049
|
const mint = () => api2(o.base, "/agents", o.userToken, {
|
|
36409
37050
|
method: "POST",
|
|
36410
37051
|
body: JSON.stringify({
|
|
@@ -36413,7 +37054,8 @@ async function installClient(o) {
|
|
|
36413
37054
|
client: o.client,
|
|
36414
37055
|
scopes: o.scopes,
|
|
36415
37056
|
ttl_minutes: o.ttlDays * 24 * 60,
|
|
36416
|
-
egress_class: o.egressClass ?? "cloud"
|
|
37057
|
+
egress_class: o.egressClass ?? "cloud",
|
|
37058
|
+
...o.resourceScope ? { resource_scope: o.resourceScope } : {}
|
|
36417
37059
|
})
|
|
36418
37060
|
});
|
|
36419
37061
|
let superseded = 0;
|
|
@@ -36454,16 +37096,17 @@ async function installClient(o) {
|
|
|
36454
37096
|
superseded += old.revoked;
|
|
36455
37097
|
revokeFailed.push(...old.revokeFailed);
|
|
36456
37098
|
agentListStatus ??= old.agentListStatus;
|
|
36457
|
-
writeJson(configPath, mergeConfig(readJson(configPath), target.key,
|
|
37099
|
+
writeJson(configPath, mergeConfig(readJson(configPath), target.key, entry2));
|
|
36458
37100
|
const who = await api2(o.base, "/mcp/whoami", agent.token);
|
|
36459
37101
|
return {
|
|
37102
|
+
...o.resourceScope ? { resourceScope: o.resourceScope } : {},
|
|
36460
37103
|
configPath,
|
|
36461
37104
|
principalId: agent.id,
|
|
36462
37105
|
expiresAt: agent.expires_at,
|
|
36463
37106
|
scopes: agent.scopes,
|
|
36464
37107
|
next: target.next,
|
|
36465
37108
|
verified: who.status === 200 && who.body?.principal_id === agent.id,
|
|
36466
|
-
launch: await verifyLaunch(
|
|
37109
|
+
launch: await verifyLaunch(entry2),
|
|
36467
37110
|
superseded,
|
|
36468
37111
|
revokeFailed,
|
|
36469
37112
|
...agentListStatus !== void 0 ? { agentListStatus } : {}
|
|
@@ -36481,7 +37124,7 @@ async function uninstallClient(o) {
|
|
|
36481
37124
|
if (o.client) {
|
|
36482
37125
|
const target = CLIENTS[o.client];
|
|
36483
37126
|
configPath = target.path(o.dir);
|
|
36484
|
-
if (configPath && (0,
|
|
37127
|
+
if (configPath && (0, import_fs4.existsSync)(configPath)) {
|
|
36485
37128
|
writeJson(configPath, removeFromConfig(readJson(configPath), target.key));
|
|
36486
37129
|
}
|
|
36487
37130
|
}
|
|
@@ -36503,6 +37146,7 @@ async function agentStatus(base, token) {
|
|
|
36503
37146
|
principal_id: who.body?.principal_id,
|
|
36504
37147
|
expires_at: who.body?.expires_at,
|
|
36505
37148
|
scopes: who.body?.scopes,
|
|
37149
|
+
resource_scope: who.body?.resource_scope,
|
|
36506
37150
|
rate_limit: who.body?.rate_limit
|
|
36507
37151
|
};
|
|
36508
37152
|
}
|
|
@@ -36518,17 +37162,66 @@ async function mcpStatus(base, dir) {
|
|
|
36518
37162
|
const p = t.path(dir);
|
|
36519
37163
|
let installed = false;
|
|
36520
37164
|
let configuredBase = null;
|
|
36521
|
-
if (p && (0,
|
|
37165
|
+
if (p && (0, import_fs4.existsSync)(p)) {
|
|
36522
37166
|
const cfg = readJson(p);
|
|
36523
|
-
const
|
|
36524
|
-
installed = !!
|
|
36525
|
-
if (typeof
|
|
37167
|
+
const entry2 = cfg?.[t.key]?.[SERVER_NAME];
|
|
37168
|
+
installed = !!entry2;
|
|
37169
|
+
if (typeof entry2?.env?.KT_API_BASE === "string") configuredBase = entry2.env.KT_API_BASE;
|
|
36526
37170
|
}
|
|
36527
37171
|
return { client: id, label: t.label, path: p, installed, configuredBase, agent: await agentStatus(base, getToken(agentAccount(base, id))) };
|
|
36528
37172
|
}));
|
|
36529
37173
|
const legacy = await agentStatus(base, getToken(agentAccount(base)));
|
|
36530
37174
|
return { keychain: backendName(), hasToken: !!legacy || configs.some((c) => c.agent), configs, legacy };
|
|
36531
37175
|
}
|
|
37176
|
+
function pick2(kind, wanted, options, problems) {
|
|
37177
|
+
const ids = [];
|
|
37178
|
+
for (const w of wanted) {
|
|
37179
|
+
const byId = options.find((o) => o.id === w);
|
|
37180
|
+
const byName = options.filter((o) => o.name === w);
|
|
37181
|
+
if (byId) ids.push(byId.id);
|
|
37182
|
+
else if (byName.length === 1) ids.push(byName[0].id);
|
|
37183
|
+
else if (byName.length > 1) problems.push(`${kind} name "${w}" matches ${byName.length} ${kind}s; use an id (${byName.map((o) => o.id).join(", ")})`);
|
|
37184
|
+
else problems.push(`unknown ${kind}: ${w}`);
|
|
37185
|
+
}
|
|
37186
|
+
return [...new Set(ids)];
|
|
37187
|
+
}
|
|
37188
|
+
async function resolveResourceScope(base, userToken, vaults, connections) {
|
|
37189
|
+
if (!vaults.length && !connections.length) return void 0;
|
|
37190
|
+
const r = await api2(base, "/agents/scope-options", userToken);
|
|
37191
|
+
if (r.status !== 200 || !r.body) throw new Error(`could not list the vaults and connections this account can reach (${r.status})`);
|
|
37192
|
+
const problems = [];
|
|
37193
|
+
const vault_ids = pick2("vault", vaults, r.body.vaults ?? [], problems);
|
|
37194
|
+
const connection_ids = pick2("connection", connections, r.body.connections ?? [], problems);
|
|
37195
|
+
if (problems.length) throw new Error(`${problems.join("; ")}. Nothing was minted.`);
|
|
37196
|
+
return { ...vault_ids.length ? { vault_ids } : {}, ...connection_ids.length ? { connection_ids } : {} };
|
|
37197
|
+
}
|
|
37198
|
+
function describeScope(s) {
|
|
37199
|
+
const v = s?.vault_ids?.length ?? 0;
|
|
37200
|
+
const c = s?.connection_ids?.length ?? 0;
|
|
37201
|
+
const parts = [
|
|
37202
|
+
v ? `${v} vault(s): ${s.vault_ids.join(", ")}` : "all vaults its owner can reach",
|
|
37203
|
+
c ? `${c} connection(s): ${s.connection_ids.join(", ")}` : "all connections its owner can reach"
|
|
37204
|
+
];
|
|
37205
|
+
return parts.join("; ");
|
|
37206
|
+
}
|
|
37207
|
+
async function mintAgent(o) {
|
|
37208
|
+
const m = await api2(o.base, "/agents", o.userToken, {
|
|
37209
|
+
method: "POST",
|
|
37210
|
+
body: JSON.stringify({
|
|
37211
|
+
name: o.name,
|
|
37212
|
+
purpose: o.purpose,
|
|
37213
|
+
scopes: o.scopes,
|
|
37214
|
+
ttl_minutes: o.ttlDays * 24 * 60,
|
|
37215
|
+
egress_class: o.egressClass,
|
|
37216
|
+
...o.resourceScope ? { resource_scope: o.resourceScope } : {}
|
|
37217
|
+
})
|
|
37218
|
+
});
|
|
37219
|
+
if (m.status === 402) throw new Error(`your plan does not allow another AI agent (${m.body?.feature ?? "agent_access"}). Revoke one or upgrade.`);
|
|
37220
|
+
if (m.status === 403 && m.body?.error === "on_network_required") throw new Error("this organization requires AI agents to run on its own network. Re-run with --egress lan if the model is self-hosted.");
|
|
37221
|
+
if (m.status !== 201) throw new Error(`could not mint an agent principal (${m.status}): ${JSON.stringify(m.body)}`);
|
|
37222
|
+
if (!m.body?.token || !m.body.id) throw new ApiUnavailableError(m.status, m.body);
|
|
37223
|
+
return { token: m.body.token, id: m.body.id, expires_at: m.body.expires_at, scopes: m.body.scopes };
|
|
37224
|
+
}
|
|
36532
37225
|
|
|
36533
37226
|
// src/mcp.ts
|
|
36534
37227
|
var MODERN = "2026-07-28";
|
|
@@ -37286,9 +37979,23 @@ function recoveryText(rec, s) {
|
|
|
37286
37979
|
"A payment may still be due and your access may still be at risk \u2014 this is a display failure, not a resolved account."
|
|
37287
37980
|
].join("\n");
|
|
37288
37981
|
}
|
|
37982
|
+
function modeString(mode, type) {
|
|
37983
|
+
const lead = type === "dir" ? "d" : type === "link" ? "l" : "-";
|
|
37984
|
+
return lead + [6, 3, 0].map((shift) => {
|
|
37985
|
+
const bits = mode >> shift & 7;
|
|
37986
|
+
return (bits & 4 ? "r" : "-") + (bits & 2 ? "w" : "-") + (bits & 1 ? "x" : "-");
|
|
37987
|
+
}).join("");
|
|
37988
|
+
}
|
|
37989
|
+
function whoamiLines(user) {
|
|
37990
|
+
const org = user.org_name ? `${user.org_name}${user.org_role ? ` (${user.org_role})` : ""}` : user.org_role ?? "none";
|
|
37991
|
+
return `email ${user.email}
|
|
37992
|
+
org ${org}`;
|
|
37993
|
+
}
|
|
37289
37994
|
|
|
37290
37995
|
// src/private-vault.ts
|
|
37291
37996
|
init_define_KT_HUB_FILES();
|
|
37997
|
+
var import_fs5 = require("fs");
|
|
37998
|
+
var import_path5 = require("path");
|
|
37292
37999
|
var PASSPHRASE_STDIN_FLAG = "vault-passphrase-stdin";
|
|
37293
38000
|
var PrivateVaultError = class extends Error {
|
|
37294
38001
|
};
|
|
@@ -37306,6 +38013,9 @@ There is no flag that takes the passphrase itself: a flag lands in shell history
|
|
|
37306
38013
|
return io.ask("vault passphrase: ");
|
|
37307
38014
|
}
|
|
37308
38015
|
async function unlockForCommand(client, io) {
|
|
38016
|
+
return (await unlockVault(client, io)).key;
|
|
38017
|
+
}
|
|
38018
|
+
async function unlockVault(client, io) {
|
|
37309
38019
|
const passphrase = await readVaultPassphrase(io);
|
|
37310
38020
|
if (!passphrase) throw new PrivateVaultError("no vault passphrase was given");
|
|
37311
38021
|
let record;
|
|
@@ -37318,7 +38028,7 @@ async function unlockForCommand(client, io) {
|
|
|
37318
38028
|
throw e;
|
|
37319
38029
|
}
|
|
37320
38030
|
try {
|
|
37321
|
-
return await unlockVaultKey(record, passphrase);
|
|
38031
|
+
return { key: await unlockVaultKey(record, passphrase), enrolledAt: record.created_at };
|
|
37322
38032
|
} catch (e) {
|
|
37323
38033
|
if (e instanceof VaultKeyError && e.code === "wrong_secret") throw new PrivateVaultError(PRIVATE_VAULT_COPY.wrongPassphrase);
|
|
37324
38034
|
throw e;
|
|
@@ -37329,12 +38039,115 @@ async function openSealedItem(client, key, vaultId, credId) {
|
|
|
37329
38039
|
if (!sealed) throw new PrivateVaultError("this item is No-reveal. Kutrion does not display, copy or export its value; it is still used to authenticate connections and integrations.");
|
|
37330
38040
|
try {
|
|
37331
38041
|
const opened = await openPrivateItem({ id: credential.id, vault_id: credential.vault_id }, sealed, key);
|
|
37332
|
-
return { value: opened.value };
|
|
38042
|
+
return { value: opened.value, notes: opened.notes };
|
|
37333
38043
|
} catch (e) {
|
|
37334
38044
|
if (e instanceof VaultKeyError) throw new PrivateVaultError("this item could not be opened with your vault key.");
|
|
37335
38045
|
throw e;
|
|
37336
38046
|
}
|
|
37337
38047
|
}
|
|
38048
|
+
async function probeVaultCustody(client, vaultId) {
|
|
38049
|
+
try {
|
|
38050
|
+
return { private: true, key: (await client.getPrivateDataKey(vaultId)).key };
|
|
38051
|
+
} catch (e) {
|
|
38052
|
+
if (e instanceof ApiError && e.status === 409 && e.slug === "vault_not_private") return { private: false };
|
|
38053
|
+
throw e;
|
|
38054
|
+
}
|
|
38055
|
+
}
|
|
38056
|
+
var PrivateVaultMemory = class {
|
|
38057
|
+
constructor(file) {
|
|
38058
|
+
this.file = file;
|
|
38059
|
+
}
|
|
38060
|
+
read() {
|
|
38061
|
+
try {
|
|
38062
|
+
const parsed = JSON.parse((0, import_fs5.readFileSync)(this.file, "utf8"));
|
|
38063
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
38064
|
+
} catch {
|
|
38065
|
+
return {};
|
|
38066
|
+
}
|
|
38067
|
+
}
|
|
38068
|
+
seen(base, vaultId) {
|
|
38069
|
+
const ids = this.read()[base];
|
|
38070
|
+
return Array.isArray(ids) && ids.includes(vaultId);
|
|
38071
|
+
}
|
|
38072
|
+
remember(base, vaultId) {
|
|
38073
|
+
const all = this.read();
|
|
38074
|
+
const ids = Array.isArray(all[base]) ? all[base] : [];
|
|
38075
|
+
if (ids.includes(vaultId)) return;
|
|
38076
|
+
all[base] = [...ids, vaultId];
|
|
38077
|
+
(0, import_fs5.mkdirSync)((0, import_path5.dirname)(this.file), { recursive: true, mode: 448 });
|
|
38078
|
+
writeOwnerOnly(this.file, JSON.stringify(all, null, 2));
|
|
38079
|
+
}
|
|
38080
|
+
};
|
|
38081
|
+
var custodyChangedMessage = (vaultId) => `the server now says vault ${vaultId} is not Private, but this CLI has seen it Private before. The server's answer changed, so nothing was read or sent. Check the vault in the web app before writing to it.`;
|
|
38082
|
+
var PASSPHRASE_STDIN_NOT_PRIVATE = `--${PASSPHRASE_STDIN_FLAG} was given, but this vault is not Private. Nothing was read from stdin or sent: drop --${PASSPHRASE_STDIN_FLAG} to write to a Connected vault.`;
|
|
38083
|
+
async function resolveVaultCustody(client, vaultId, opts2) {
|
|
38084
|
+
const custody = await probeVaultCustody(client, vaultId);
|
|
38085
|
+
if (custody.private) {
|
|
38086
|
+
try {
|
|
38087
|
+
opts2.memory.remember(opts2.base, vaultId);
|
|
38088
|
+
} catch (e) {
|
|
38089
|
+
console.error(`warning: could not record vault ${vaultId} as Private: ${e.message}`);
|
|
38090
|
+
}
|
|
38091
|
+
return custody;
|
|
38092
|
+
}
|
|
38093
|
+
if (opts2.memory.seen(opts2.base, vaultId)) throw new PrivateVaultError(custodyChangedMessage(vaultId));
|
|
38094
|
+
if (opts2.passphraseStdin) throw new PrivateVaultError(PASSPHRASE_STDIN_NOT_PRIVATE);
|
|
38095
|
+
return custody;
|
|
38096
|
+
}
|
|
38097
|
+
var dataKeyChanged = (e) => e instanceof ApiError && e.status === 409 && e.slug === "data_key_changed";
|
|
38098
|
+
async function createSealedItem(client, vault, vaultId, current, meta, plain) {
|
|
38099
|
+
const id = crypto.randomUUID();
|
|
38100
|
+
const attempt = async (k) => {
|
|
38101
|
+
const { dataKey, key } = await privateItemDataKey(k, vault.key, vaultId, vault.enrolledAt);
|
|
38102
|
+
const fields = await sealPrivateItem({ id, vault_id: vaultId }, plain, dataKey);
|
|
38103
|
+
return client.createPrivateItem(vaultId, { ...meta, id, key, fields });
|
|
38104
|
+
};
|
|
38105
|
+
try {
|
|
38106
|
+
try {
|
|
38107
|
+
return await attempt(current);
|
|
38108
|
+
} catch (e) {
|
|
38109
|
+
if (!dataKeyChanged(e)) throw e;
|
|
38110
|
+
return await attempt(e.body?.key ?? null);
|
|
38111
|
+
}
|
|
38112
|
+
} catch (e) {
|
|
38113
|
+
if (e instanceof VaultKeyError) throw new PrivateVaultError(PRIVATE_VAULT_COPY.wrongPassphrase);
|
|
38114
|
+
throw e;
|
|
38115
|
+
}
|
|
38116
|
+
}
|
|
38117
|
+
async function updateSealedItem(client, vault, vaultId, credId, current, patch) {
|
|
38118
|
+
const plain = {};
|
|
38119
|
+
if (patch.value !== void 0) plain.value = patch.value;
|
|
38120
|
+
if (patch.notes !== void 0) plain.notes = patch.notes.trim() ? patch.notes : null;
|
|
38121
|
+
const meta = patch.name !== void 0 ? { name: patch.name } : {};
|
|
38122
|
+
if (!Object.keys(plain).length) return client.updatePrivateItem(vaultId, credId, meta);
|
|
38123
|
+
if (!vault) throw new PrivateVaultError("a Private item's value and notes can only be changed with the vault unlocked");
|
|
38124
|
+
const sealUnder = async (keyId, wrap) => {
|
|
38125
|
+
const dataKey = await unwrapVaultDataKey(wrap, vault.key, vaultId);
|
|
38126
|
+
const fields = await sealPrivateItem({ id: credId, vault_id: vaultId }, plain, dataKey);
|
|
38127
|
+
return client.updatePrivateItem(vaultId, credId, { ...meta, key_id: keyId, fields });
|
|
38128
|
+
};
|
|
38129
|
+
try {
|
|
38130
|
+
if (current) {
|
|
38131
|
+
try {
|
|
38132
|
+
return await sealUnder(current.key_id, current.client_wrapped_data_key);
|
|
38133
|
+
} catch (e) {
|
|
38134
|
+
if (!dataKeyChanged(e)) throw e;
|
|
38135
|
+
}
|
|
38136
|
+
}
|
|
38137
|
+
const { sealed } = await client.getPrivateItem(vaultId, credId);
|
|
38138
|
+
if (!sealed) throw new PrivateVaultError("this item is No-reveal, so its value cannot be changed from here.");
|
|
38139
|
+
return await sealUnder(sealed.key_id, sealed.client_wrapped_data_key);
|
|
38140
|
+
} catch (e) {
|
|
38141
|
+
if (e instanceof VaultKeyError) throw new PrivateVaultError("this item could not be sealed with your vault key.");
|
|
38142
|
+
throw e;
|
|
38143
|
+
}
|
|
38144
|
+
}
|
|
38145
|
+
function splitPassphraseAndValue(stdin) {
|
|
38146
|
+
const nl = stdin.search(/\r?\n/);
|
|
38147
|
+
if (nl < 0) throw new PrivateVaultError("stdin must carry the vault passphrase on its first line and the value after it");
|
|
38148
|
+
return { passphrase: stdin.slice(0, nl), value: stdin.slice(nl).replace(/^\r?\n/, "") };
|
|
38149
|
+
}
|
|
38150
|
+
var PRIVATE_IMPORT_REFUSED = "this vault is Private, and kt import cannot seal an import yet: the export file is parsed by the server, which would see every value in it. Nothing has been sent. Add each item with `kt cred add --value-stdin`.";
|
|
37338
38151
|
|
|
37339
38152
|
// src/enroll.ts
|
|
37340
38153
|
init_define_KT_HUB_FILES();
|
|
@@ -37415,11 +38228,11 @@ function parsePurposes(v) {
|
|
|
37415
38228
|
}
|
|
37416
38229
|
return out.length ? out : { error: `--purposes is required: one or more of ${ENROLL_PURPOSES.join(", ")}` };
|
|
37417
38230
|
}
|
|
37418
|
-
function parseConfirms(
|
|
38231
|
+
function parseConfirms(argv2) {
|
|
37419
38232
|
const out = /* @__PURE__ */ new Map();
|
|
37420
|
-
for (let i = 0; i <
|
|
37421
|
-
if (
|
|
37422
|
-
const v =
|
|
38233
|
+
for (let i = 0; i < argv2.length; i++) {
|
|
38234
|
+
if (argv2[i] !== "--confirm") continue;
|
|
38235
|
+
const v = argv2[i + 1];
|
|
37423
38236
|
if (v === void 0 || v.startsWith("--")) return { error: "--confirm needs <connId|host>=<fingerprint>" };
|
|
37424
38237
|
i++;
|
|
37425
38238
|
for (const pair of v.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
@@ -37544,10 +38357,47 @@ function parseHostsUnconfirmed(body) {
|
|
|
37544
38357
|
return { count: typeof b.unconfirmedCount === "number" ? b.unconfirmedCount : hosts.length, hosts };
|
|
37545
38358
|
}
|
|
37546
38359
|
|
|
38360
|
+
// src/exec-stdin.ts
|
|
38361
|
+
init_define_KT_HUB_FILES();
|
|
38362
|
+
var EXEC_STDIN_MAX_BYTES = 8 * 1024 * 1024;
|
|
38363
|
+
var StdinTooLargeError = class extends Error {
|
|
38364
|
+
constructor(maxBytes) {
|
|
38365
|
+
super(`stdin is larger than the ${maxBytes} byte limit for kt exec; nothing was sent. Copy larger input over SFTP instead.`);
|
|
38366
|
+
this.maxBytes = maxBytes;
|
|
38367
|
+
}
|
|
38368
|
+
};
|
|
38369
|
+
async function readExecStdin(input, maxBytes = EXEC_STDIN_MAX_BYTES) {
|
|
38370
|
+
const chunks = [];
|
|
38371
|
+
let total = 0;
|
|
38372
|
+
for await (const c of input) {
|
|
38373
|
+
const b = Buffer.isBuffer(c) ? c : Buffer.from(c);
|
|
38374
|
+
total += b.length;
|
|
38375
|
+
if (total > maxBytes) throw new StdinTooLargeError(maxBytes);
|
|
38376
|
+
chunks.push(b);
|
|
38377
|
+
}
|
|
38378
|
+
return total ? Buffer.concat(chunks) : void 0;
|
|
38379
|
+
}
|
|
38380
|
+
async function serverTakesExecStdin(base, fetchImpl = fetch) {
|
|
38381
|
+
try {
|
|
38382
|
+
const res = await fetchImpl(`${base.replace(/\/+$/, "")}/health`, { redirect: "error", signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
|
|
38383
|
+
return (res.headers.get("kutrion-features") ?? "").split(",").some((v) => v.trim().toLowerCase() === "exec-stdin");
|
|
38384
|
+
} catch (e) {
|
|
38385
|
+
if (e instanceof Error && (e.name === "TimeoutError" || e.name === "AbortError")) throw new ExecStdinCheckTimedOut();
|
|
38386
|
+
return false;
|
|
38387
|
+
}
|
|
38388
|
+
}
|
|
38389
|
+
var HEALTH_TIMEOUT_MS = 1e4;
|
|
38390
|
+
var ExecStdinCheckTimedOut = class extends Error {
|
|
38391
|
+
constructor() {
|
|
38392
|
+
super("error: the check for kt exec --stdin support timed out; nothing was run");
|
|
38393
|
+
}
|
|
38394
|
+
};
|
|
38395
|
+
var STDIN_UNSUPPORTED = "error: this server does not support kt exec --stdin (it predates stdin support); nothing was run";
|
|
38396
|
+
|
|
37547
38397
|
// src/transfer.ts
|
|
37548
38398
|
init_define_KT_HUB_FILES();
|
|
37549
|
-
var
|
|
37550
|
-
var
|
|
38399
|
+
var import_fs6 = require("fs");
|
|
38400
|
+
var import_path6 = require("path");
|
|
37551
38401
|
var import_promises = require("stream/promises");
|
|
37552
38402
|
var EXIT_OK = 0;
|
|
37553
38403
|
var EXIT_FAIL = 1;
|
|
@@ -37574,7 +38424,7 @@ function isSideError(s) {
|
|
|
37574
38424
|
var listOn = (c, k, id, p) => k === "sftp" ? c.sftpList(id, p) : c.fileList(id, p);
|
|
37575
38425
|
var mkdirOn = (c, k, id, p) => k === "sftp" ? c.sftpMkdir(id, p) : c.fileMkdir(id, p);
|
|
37576
38426
|
var downloadOn = (c, k, id, p, o) => k === "sftp" ? c.sftpDownloadStream(id, p, o) : c.fileDownloadStream(id, p, o);
|
|
37577
|
-
var uploadOn = (c, k, id, p,
|
|
38427
|
+
var uploadOn = (c, k, id, p, src) => k === "sftp" ? c.sftpUploadResumable(id, p, src) : c.fileUploadResumable(id, p, src);
|
|
37578
38428
|
var abortOnHostKey = (e) => {
|
|
37579
38429
|
if (hostKeyUnconfirmedFromError(e)) throw e;
|
|
37580
38430
|
};
|
|
@@ -37620,6 +38470,8 @@ function exitFor(o) {
|
|
|
37620
38470
|
return o.ok > 0 ? EXIT_PARTIAL : EXIT_FAIL;
|
|
37621
38471
|
}
|
|
37622
38472
|
var msg = (e) => e instanceof Error ? e.message : String(e);
|
|
38473
|
+
async function* emptyChunks() {
|
|
38474
|
+
}
|
|
37623
38475
|
async function* counted(chunks, p, tally) {
|
|
37624
38476
|
for await (const c of chunks) {
|
|
37625
38477
|
tally.bytes += c.byteLength;
|
|
@@ -37633,15 +38485,15 @@ async function saveTo(s, local, append, p) {
|
|
|
37633
38485
|
await (0, import_promises.pipeline)(counted(s.chunks, p, tally), process.stdout, { end: false });
|
|
37634
38486
|
return tally.bytes;
|
|
37635
38487
|
}
|
|
37636
|
-
const dir = (0,
|
|
37637
|
-
if (dir && dir !== "." && !(0,
|
|
37638
|
-
await (0, import_promises.pipeline)(counted(s.chunks, p, tally), (0,
|
|
38488
|
+
const dir = (0, import_path6.dirname)(local);
|
|
38489
|
+
if (dir && dir !== "." && !(0, import_fs6.existsSync)(dir)) (0, import_fs6.mkdirSync)(dir, { recursive: true });
|
|
38490
|
+
await (0, import_promises.pipeline)(counted(s.chunks, p, tally), (0, import_fs6.createWriteStream)(local, append ? { flags: "a" } : {}));
|
|
37639
38491
|
return tally.bytes;
|
|
37640
38492
|
}
|
|
37641
38493
|
async function getOne(c, kind, id, remote, local, opts2) {
|
|
37642
38494
|
let start = 0;
|
|
37643
|
-
if (opts2.resume && local !== "-" && (0,
|
|
37644
|
-
const st = (0,
|
|
38495
|
+
if (opts2.resume && local !== "-" && (0, import_fs6.existsSync)(local)) {
|
|
38496
|
+
const st = (0, import_fs6.statSync)(local);
|
|
37645
38497
|
if (st.isFile()) start = st.size;
|
|
37646
38498
|
}
|
|
37647
38499
|
let s;
|
|
@@ -37664,7 +38516,7 @@ async function getOne(c, kind, id, remote, local, opts2) {
|
|
|
37664
38516
|
opts2.note?.(`${remote}: server ignored the resume range, downloading from the start`);
|
|
37665
38517
|
start = 0;
|
|
37666
38518
|
}
|
|
37667
|
-
const p = opts2.quiet ? NO_PROGRESS : progressBar((0,
|
|
38519
|
+
const p = opts2.quiet ? NO_PROGRESS : progressBar((0, import_path6.basename)(remote) || remote, s.total);
|
|
37668
38520
|
const bytes = await saveTo(s, local, start > 0, p);
|
|
37669
38521
|
p.finish();
|
|
37670
38522
|
return { bytes };
|
|
@@ -37716,13 +38568,13 @@ async function getPath(c, kind, id, remote, local, opts2 = {}) {
|
|
|
37716
38568
|
return out;
|
|
37717
38569
|
}
|
|
37718
38570
|
for await (const item of walkRemote(c, kind, id, remote)) {
|
|
37719
|
-
const dest = (0,
|
|
38571
|
+
const dest = (0, import_path6.join)(local, item.rel);
|
|
37720
38572
|
if (item.link) {
|
|
37721
38573
|
opts2.note?.(`skipped symlink ${item.path}`);
|
|
37722
38574
|
continue;
|
|
37723
38575
|
}
|
|
37724
38576
|
if (item.dir) {
|
|
37725
|
-
(0,
|
|
38577
|
+
(0, import_fs6.mkdirSync)(dest, { recursive: true });
|
|
37726
38578
|
continue;
|
|
37727
38579
|
}
|
|
37728
38580
|
try {
|
|
@@ -37738,8 +38590,8 @@ async function getPath(c, kind, id, remote, local, opts2 = {}) {
|
|
|
37738
38590
|
return out;
|
|
37739
38591
|
}
|
|
37740
38592
|
function* walkLocal(root, rel = "") {
|
|
37741
|
-
for (const e of (0,
|
|
37742
|
-
const abs = (0,
|
|
38593
|
+
for (const e of (0, import_fs6.readdirSync)(root, { withFileTypes: true })) {
|
|
38594
|
+
const abs = (0, import_path6.join)(root, e.name);
|
|
37743
38595
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
37744
38596
|
if (e.isSymbolicLink()) continue;
|
|
37745
38597
|
if (e.isDirectory()) {
|
|
@@ -37751,18 +38603,21 @@ function* walkLocal(root, rel = "") {
|
|
|
37751
38603
|
}
|
|
37752
38604
|
}
|
|
37753
38605
|
async function putOne(c, kind, id, local, remote, opts2) {
|
|
37754
|
-
const total = (0,
|
|
37755
|
-
|
|
37756
|
-
const p = opts2.quiet ? NO_PROGRESS : progressBar((0, import_path5.basename)(local), total);
|
|
38606
|
+
const total = (0, import_fs6.statSync)(local).size;
|
|
38607
|
+
const p = opts2.quiet ? NO_PROGRESS : progressBar((0, import_path6.basename)(local), total);
|
|
37757
38608
|
const tally = { bytes: 0 };
|
|
37758
|
-
const
|
|
38609
|
+
const source = {
|
|
38610
|
+
size: total,
|
|
38611
|
+
slice: (start, end) => counted(end > start ? (0, import_fs6.createReadStream)(local, { start, end: end - 1 }) : emptyChunks(), p, tally)
|
|
38612
|
+
};
|
|
38613
|
+
const r = await uploadOn(c, kind, id, remote, source);
|
|
37759
38614
|
p.finish();
|
|
37760
38615
|
if (!r.streamed) opts2.note?.(`${local}: this runtime could not stream the request body, the file was buffered`);
|
|
37761
38616
|
return r.size ?? tally.bytes;
|
|
37762
38617
|
}
|
|
37763
38618
|
async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
37764
38619
|
const out = empty();
|
|
37765
|
-
const st = (0,
|
|
38620
|
+
const st = (0, import_fs6.existsSync)(local) ? (0, import_fs6.statSync)(local) : null;
|
|
37766
38621
|
if (!st) {
|
|
37767
38622
|
out.failed++;
|
|
37768
38623
|
out.errors.push(`${local}: no such file`);
|
|
@@ -37795,7 +38650,7 @@ async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
|
37795
38650
|
}
|
|
37796
38651
|
continue;
|
|
37797
38652
|
}
|
|
37798
|
-
const parent = (0,
|
|
38653
|
+
const parent = (0, import_path6.dirname)(dest);
|
|
37799
38654
|
if (!made.has(parent)) {
|
|
37800
38655
|
try {
|
|
37801
38656
|
await mkdirOn(c, kind, id, parent);
|
|
@@ -37814,13 +38669,32 @@ async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
|
37814
38669
|
}
|
|
37815
38670
|
return out;
|
|
37816
38671
|
}
|
|
38672
|
+
var JOB_TERMINAL = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled"]);
|
|
38673
|
+
function transientPollError(e) {
|
|
38674
|
+
const status = e?.status;
|
|
38675
|
+
if (typeof status === "number") return status >= 500 || e.name === "ApiUnavailableError";
|
|
38676
|
+
return unreachedApi(e);
|
|
38677
|
+
}
|
|
38678
|
+
function unreachedApi(e) {
|
|
38679
|
+
if (e?.name === "ApiUnavailableError") return true;
|
|
38680
|
+
if (typeof e?.status === "number") return false;
|
|
38681
|
+
return e instanceof TypeError || e instanceof Error && /timed out|aborted|ECONN|ENOTFOUND|EAI_AGAIN|socket|fetch failed/i.test(e.message);
|
|
38682
|
+
}
|
|
37817
38683
|
async function waitForJob(get, id, opts2) {
|
|
37818
38684
|
const interval = opts2.intervalMs ?? 2e3;
|
|
37819
38685
|
const deadline = Date.now() + opts2.timeoutMs;
|
|
37820
38686
|
for (; ; ) {
|
|
37821
|
-
|
|
37822
|
-
|
|
37823
|
-
|
|
38687
|
+
let job;
|
|
38688
|
+
try {
|
|
38689
|
+
({ job } = await get(id));
|
|
38690
|
+
} catch (e) {
|
|
38691
|
+
if (!transientPollError(e)) throw e;
|
|
38692
|
+
opts2.onRetry?.(e);
|
|
38693
|
+
}
|
|
38694
|
+
if (job) {
|
|
38695
|
+
opts2.onTick?.(job);
|
|
38696
|
+
if (JOB_TERMINAL.has(job.status)) return job;
|
|
38697
|
+
}
|
|
37824
38698
|
if (Date.now() + interval > deadline) return null;
|
|
37825
38699
|
await new Promise((r) => setTimeout(r, interval));
|
|
37826
38700
|
}
|
|
@@ -37834,11 +38708,12 @@ function parseDailyAt(v) {
|
|
|
37834
38708
|
}
|
|
37835
38709
|
|
|
37836
38710
|
// src/index.ts
|
|
37837
|
-
var CONFIG = (0,
|
|
38711
|
+
var CONFIG = (0, import_path7.join)((0, import_os5.homedir)(), ".config", "kutrion", "config.json");
|
|
38712
|
+
var PRIVATE_VAULTS = new PrivateVaultMemory((0, import_path7.join)((0, import_os5.homedir)(), ".config", "kutrion", "private-vaults.json"));
|
|
37838
38713
|
function storedBase() {
|
|
37839
38714
|
let file;
|
|
37840
38715
|
try {
|
|
37841
|
-
file = JSON.parse((0,
|
|
38716
|
+
file = JSON.parse((0, import_fs7.readFileSync)(CONFIG, "utf8"));
|
|
37842
38717
|
} catch {
|
|
37843
38718
|
return "";
|
|
37844
38719
|
}
|
|
@@ -37856,9 +38731,17 @@ function storedBase() {
|
|
|
37856
38731
|
}
|
|
37857
38732
|
return base;
|
|
37858
38733
|
}
|
|
37859
|
-
function saveCfg(base) {
|
|
37860
|
-
(0,
|
|
37861
|
-
(0,
|
|
38734
|
+
function saveCfg(base, fallbacks = readFallbacks(CONFIG, base)) {
|
|
38735
|
+
(0, import_fs7.mkdirSync)((0, import_path7.dirname)(CONFIG), { recursive: true });
|
|
38736
|
+
(0, import_fs7.writeFileSync)(CONFIG, JSON.stringify({ base, ...fallbacks.length ? { fallbacks } : {} }, null, 2));
|
|
38737
|
+
}
|
|
38738
|
+
var fallbackInstalled = false;
|
|
38739
|
+
function installBaseFallback(base) {
|
|
38740
|
+
if (fallbackInstalled) return;
|
|
38741
|
+
const list = readFallbacks(CONFIG, base);
|
|
38742
|
+
if (!list.length) return;
|
|
38743
|
+
fallbackInstalled = true;
|
|
38744
|
+
globalThis.fetch = fallbackFetch(globalThis.fetch, base, list);
|
|
37862
38745
|
}
|
|
37863
38746
|
function resolveBase(flag) {
|
|
37864
38747
|
const explicit = typeof flag === "string" ? flag.trim() : "";
|
|
@@ -38005,16 +38888,38 @@ async function readStdin() {
|
|
|
38005
38888
|
function readSecret(q) {
|
|
38006
38889
|
return process.stdin.isTTY ? prompt(q, true) : readStdin();
|
|
38007
38890
|
}
|
|
38891
|
+
var EMPTY_VALUE_MESSAGE = "The credential value is empty, so nothing was sent. Pipe one in with --value-stdin, or leave the flag off and type it at the prompt.";
|
|
38008
38892
|
async function openPrivateItem2(client, fromStdin, vaultId, credId) {
|
|
38009
38893
|
try {
|
|
38010
38894
|
const key = await unlockForCommand(client, { tty: !!process.stdin.isTTY, fromStdin, ask: promptSecret, readStdin });
|
|
38011
|
-
|
|
38895
|
+
const item = await openSealedItem(client, key, vaultId, credId);
|
|
38896
|
+
console.log(item.value);
|
|
38897
|
+
if (item.notes) console.error(`notes: ${item.notes}`);
|
|
38012
38898
|
return null;
|
|
38013
38899
|
} catch (e) {
|
|
38014
38900
|
if (e instanceof PrivateVaultError) return e.message;
|
|
38015
38901
|
throw e;
|
|
38016
38902
|
}
|
|
38017
38903
|
}
|
|
38904
|
+
async function unlockForSealedWrite(client, flags, needValue) {
|
|
38905
|
+
const passStdin = flags[PASSPHRASE_STDIN_FLAG] === true, valueStdin = flags["value-stdin"] === true;
|
|
38906
|
+
const io = { tty: !!process.stdin.isTTY, fromStdin: passStdin, ask: promptSecret, readStdin };
|
|
38907
|
+
if (passStdin && needValue) {
|
|
38908
|
+
if (!valueStdin) throw new PrivateVaultError(`with --${PASSPHRASE_STDIN_FLAG}, pipe the value too with --value-stdin: stdin carries the passphrase on its first line and the value after it`);
|
|
38909
|
+
const piped = splitPassphraseAndValue(await readStdin());
|
|
38910
|
+
if (piped.value === "") throw new PrivateVaultError(EMPTY_VALUE_MESSAGE);
|
|
38911
|
+
return { vault: await unlockVault(client, { ...io, readStdin: async () => piped.passphrase }), value: piped.value };
|
|
38912
|
+
}
|
|
38913
|
+
const vault = await unlockVault(client, io);
|
|
38914
|
+
if (!needValue) return { vault };
|
|
38915
|
+
const value = valueStdin ? await readSecret("value: ") : await prompt("value: ", true);
|
|
38916
|
+
if (value === "") throw new PrivateVaultError(EMPTY_VALUE_MESSAGE);
|
|
38917
|
+
return { vault, value };
|
|
38918
|
+
}
|
|
38919
|
+
var WATCHER_EVERY_MIN = 60;
|
|
38920
|
+
var WATCHER_EVERY_MAX = 86400;
|
|
38921
|
+
var WATCHER_PATTERN_MAX = 256;
|
|
38922
|
+
var withNotesMarker = (c, notes) => notes === void 0 ? c : { ...c, has_notes: notes.length > 0, notes_length: notes.length };
|
|
38018
38923
|
function refuseSecretFlag(flag, instead) {
|
|
38019
38924
|
console.error(`error: --${flag} is not accepted: it puts a credential on the command line, where shell history and the process list keep it.`);
|
|
38020
38925
|
console.error(` ${instead}`);
|
|
@@ -38089,12 +38994,12 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38089
38994
|
const secret = await promptSecret(`current value for "${name}": `);
|
|
38090
38995
|
let added;
|
|
38091
38996
|
try {
|
|
38092
|
-
added = await client.addCredentialDestination(vaultId, cid, {
|
|
38997
|
+
added = await withStepUp(client, () => client.addCredentialDestination(vaultId, cid, {
|
|
38093
38998
|
protocol: group.key,
|
|
38094
38999
|
host: host2,
|
|
38095
39000
|
...portGiven ? { port } : {},
|
|
38096
39001
|
secret
|
|
38097
|
-
});
|
|
39002
|
+
}));
|
|
38098
39003
|
} catch (e) {
|
|
38099
39004
|
const text2 = destRefusalText(e);
|
|
38100
39005
|
if (text2) return die(text2);
|
|
@@ -38117,24 +39022,24 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38117
39022
|
const group = groupFor(row.protocol);
|
|
38118
39023
|
if (!group) return die(`this destination is recorded on protocol ${row.protocol}, which this version of kt cannot withdraw`);
|
|
38119
39024
|
const inGroup = entriesFor(group, before.destinations);
|
|
38120
|
-
const
|
|
38121
|
-
if (!
|
|
39025
|
+
const entry2 = inGroup.find((x) => x.rows.some((r) => r.id === destId));
|
|
39026
|
+
if (!entry2) return die(`no destination ${destId} on "${name}"`);
|
|
38122
39027
|
const sameGroupRows = inGroup.reduce((n, x) => n + x.rows.length, 0);
|
|
38123
|
-
for (const line of removalWarning(name, group,
|
|
39028
|
+
for (const line of removalWarning(name, group, entry2, before.destinations.length - entry2.rows.length, sameGroupRows - entry2.rows.length)) {
|
|
38124
39029
|
console.error(line);
|
|
38125
39030
|
}
|
|
38126
39031
|
console.error("");
|
|
38127
39032
|
if (!await confirmTyped("Type yes to withdraw it: ")) return die("nothing was changed");
|
|
38128
39033
|
const secret = await promptSecret(`current value for "${name}": `);
|
|
38129
39034
|
const gone = [];
|
|
38130
|
-
for (const r of
|
|
39035
|
+
for (const r of entry2.rows) {
|
|
38131
39036
|
try {
|
|
38132
|
-
await client.removeCredentialDestination(vaultId, cid, r.id, secret);
|
|
39037
|
+
await withStepUp(client, () => client.removeCredentialDestination(vaultId, cid, r.id, secret));
|
|
38133
39038
|
gone.push(r.protocol);
|
|
38134
39039
|
} catch (e) {
|
|
38135
39040
|
if (gone.length) {
|
|
38136
|
-
const left =
|
|
38137
|
-
console.error(`Only part of this destination was withdrawn. ${gone.map((pr) => PROTOCOL_LABEL[pr]).join(" and ")} at ${
|
|
39041
|
+
const left = entry2.rows.filter((x) => !gone.includes(x.protocol)).map((x) => PROTOCOL_LABEL[x.protocol]).join(" and ");
|
|
39042
|
+
console.error(`Only part of this destination was withdrawn. ${gone.map((pr) => PROTOCOL_LABEL[pr]).join(" and ")} at ${entry2.host}:${entry2.port} is no longer authorized; ${left} still is. Run the command again.`);
|
|
38138
39043
|
}
|
|
38139
39044
|
const text2 = destRefusalText(e);
|
|
38140
39045
|
if (text2) return die(text2);
|
|
@@ -38143,7 +39048,7 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38143
39048
|
}
|
|
38144
39049
|
const after = await list();
|
|
38145
39050
|
if (!asJson) {
|
|
38146
|
-
console.log(after.destinations.length === 0 ? `"${name}" is no longer restricted. It is accepted at any host on any protocol again.` : `withdrew ${
|
|
39051
|
+
console.log(after.destinations.length === 0 ? `"${name}" is no longer restricted. It is accepted at any host on any protocol again.` : `withdrew ${entry2.host}:${entry2.port}`);
|
|
38147
39052
|
console.log("");
|
|
38148
39053
|
}
|
|
38149
39054
|
return void await printList();
|
|
@@ -38155,10 +39060,37 @@ var ROLES = ["OWNER", "ADMIN", "EDITOR", "VIEWER"];
|
|
|
38155
39060
|
var TOKEN_SCOPES = ["scim", "events"];
|
|
38156
39061
|
var ORG_ROLES = ["owner", "admin", "member"];
|
|
38157
39062
|
var ROTATION_SERVICES = ["linux_password", "postgres_password", "mysql_password"];
|
|
38158
|
-
var VERSION = true ? "1.2.
|
|
39063
|
+
var VERSION = true ? "1.2.16" : "dev";
|
|
39064
|
+
var CMD_HELP = {
|
|
39065
|
+
"ci-login": `kt ci-login --trust <id>
|
|
39066
|
+
|
|
39067
|
+
Exchange this CI job's own OIDC token for a short-lived agent and print its token on stdout. Nothing
|
|
39068
|
+
is stored: export it as KT_AGENT_TOKEN for \`kt run\`.
|
|
39069
|
+
|
|
39070
|
+
The token comes from KT_ID_TOKEN (GitLab id_tokens) or the GitHub Actions token service. The trust
|
|
39071
|
+
named by --trust (or KT_OIDC_TRUST_ID) accepts it only when all three claims match what was registered:
|
|
39072
|
+
iss the issuer: the CI instance URL, for example https://gitlab.example.com
|
|
39073
|
+
aud the audience: the aud your id_tokens block (or GitHub audience) requests
|
|
39074
|
+
sub the subject: matched against the trust's pattern, which names the project and optionally the ref
|
|
39075
|
+
(GitLab: project_path:group/repo:ref_type:branch:ref:main)
|
|
39076
|
+
|
|
39077
|
+
An org owner or admin registers a trust with \`kt trust add\`.
|
|
39078
|
+
Flags: --trust <id>, --json (print the whole exchange result instead of only the token).`,
|
|
39079
|
+
trust: `kt trust add --issuer <url> --audience <aud> --subject <pattern> --vault <id> [--vault <id> ..]
|
|
39080
|
+
[--name <n>] [--scopes vault.list,vault.use] [--egress lan|cloud] [--ttl <min>]
|
|
39081
|
+
kt trust ls
|
|
39082
|
+
kt trust rm <id>
|
|
39083
|
+
|
|
39084
|
+
A trust is the rule \`kt ci-login\` checks a CI job's OIDC token against: issuer, audience and subject
|
|
39085
|
+
pattern. --vault is required and repeatable: a trust reaches only the vaults named, never the whole
|
|
39086
|
+
organization. Org owners and admins only; granting vault.reveal or write scopes asks for step-up.
|
|
39087
|
+
--egress defaults to lan (output stays on the local network); pass --egress cloud to allow cloud AI.
|
|
39088
|
+
A subject pattern that matches every project is refused.`
|
|
39089
|
+
};
|
|
38159
39090
|
var HELP = `kt \u2014 Kutrion CLI
|
|
38160
39091
|
kt login [--email <e>] [--password-stdin] [--code <2fa>] prompts for the password, or reads it from stdin;
|
|
38161
|
-
|
|
39092
|
+
with no --email or --password-stdin, first asks the Kutrion desktop app
|
|
39093
|
+
(without a terminal, only one this CLI has already paired with),
|
|
38162
39094
|
then falls back to your email and password;
|
|
38163
39095
|
at the emailed-code prompt, a blank entry or r sends a new code
|
|
38164
39096
|
kt login --browser not available; exits with an error
|
|
@@ -38172,8 +39104,10 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38172
39104
|
|
|
38173
39105
|
kt creds <vaultId> list credentials in a vault
|
|
38174
39106
|
kt get <vaultId> <credId> [--reveal] [--vault-passphrase-stdin] show a credential (the value only with --reveal); a Private vault asks for its passphrase
|
|
38175
|
-
kt cred add <vaultId> --name <n> [--value-stdin] [--type PASSWORD] [--notes ..] prompts for the value, or reads it from stdin
|
|
38176
|
-
kt cred set <vaultId> <credId> [--name ..] [--value-stdin] [--notes ..]
|
|
39107
|
+
kt cred add <vaultId> --name <n> [--value-stdin] [--vault-passphrase-stdin] [--type PASSWORD] [--notes ..] prompts for the value, or reads it from stdin
|
|
39108
|
+
kt cred set <vaultId> <credId> [--name ..] [--value-stdin] [--vault-passphrase-stdin] [--notes ..]
|
|
39109
|
+
in a Private vault the value and notes are sealed here; with both
|
|
39110
|
+
stdin flags, stdin is the passphrase line, then the value
|
|
38177
39111
|
kt cred rm <vaultId> <credId> delete a credential
|
|
38178
39112
|
kt cred dest ls <vaultId> <credId> where this credential may be presented
|
|
38179
39113
|
kt cred dest add <vaultId> <credId> --host <h> [--protocol SSH] [--port N]
|
|
@@ -38183,10 +39117,30 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38183
39117
|
kt export <vaultId> [file] export a vault's credentials (plaintext, written mode 0600)
|
|
38184
39118
|
|
|
38185
39119
|
kt mcp install --client <id> [--egress lan] connect an AI client (claude-code|claude-desktop|cursor|copilot)
|
|
39120
|
+
[--vault <id|name>]... [--connection <id>]... limit the agent to these vaults/connections (repeatable;
|
|
39121
|
+
none given = everything its owner can reach; unknown names refuse before minting)
|
|
39122
|
+
kt agent mint [--scope-vault <id|name>]... [--scope-conn <id>]... [--write-env <file>]
|
|
39123
|
+
mint an agent for a server install; --write-env writes
|
|
39124
|
+
KT_AGENT_TOKEN/KT_API_BASE to a 0600 file and never prints the token
|
|
38186
39125
|
kt mcp status report each client's agent and which clients are wired
|
|
38187
39126
|
kt mcp uninstall [--client <id>] revoke the agent and remove the client config entry
|
|
38188
39127
|
kt mcp serve MCP over stdio (what the client config invokes)
|
|
39128
|
+
kt config set base-fallback <url>[,<url>...] [--allow-insecure-lan]
|
|
39129
|
+
other addresses for the same server, tried in order only when the
|
|
39130
|
+
primary cannot be reached (connection failure or a 52x from the edge,
|
|
39131
|
+
never an HTTP error from the API). Must be https unless the base is
|
|
39132
|
+
opted in with --allow-insecure-lan, which warns on stderr per request.
|
|
39133
|
+
Tokens carry no host or issuer claim (HS256 against the deployment's
|
|
39134
|
+
signing key), so any address reaching the SAME deployment accepts them and
|
|
39135
|
+
refresh follows whichever base answers; a different deployment refuses
|
|
39136
|
+
them. Websocket sessions (shell, forward) do not fall back.
|
|
39137
|
+
kt config get|unset base-fallback show or clear that list
|
|
38189
39138
|
kt ci-login --trust <id> exchange this CI job's OIDC token for an agent (no stored credential)
|
|
39139
|
+
kt trust add --issuer <url> --audience <aud> --subject <pattern> --vault <id> [--vault <id> ..]
|
|
39140
|
+
register the CI identity ci-login accepts (org owner or admin)
|
|
39141
|
+
[--name <n>] [--scopes vault.list,vault.use] [--egress lan|cloud] [--ttl <min>]
|
|
39142
|
+
kt trust ls list the registered trusts
|
|
39143
|
+
kt trust rm <id> revoke a trust (no new exchange; issued agents run out their lifetime)
|
|
38190
39144
|
|
|
38191
39145
|
kt conns list SSH connections
|
|
38192
39146
|
kt ssh <connId> interactive SSH shell (Ctrl-] to detach)
|
|
@@ -38198,7 +39152,10 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38198
39152
|
--insert places it at the prompt without pressing Enter
|
|
38199
39153
|
kt demo [start] brokered sandbox session, then the audit trail + AI summary
|
|
38200
39154
|
kt demo status | reset demo availability / remove everything it provisioned
|
|
38201
|
-
kt exec <connId> <command...>
|
|
39155
|
+
kt exec <connId> [--stdin] [--] <command...> run one command via the audited broker; --stdin sends
|
|
39156
|
+
this process's stdin (up to 8 MiB) to the command, never
|
|
39157
|
+
to its argv. Without it stdin is not read. kt's flags end at the
|
|
39158
|
+
connId (or the first --); the rest goes to the remote verbatim
|
|
38202
39159
|
kt forward <connId> <localPort>:<dstHost>:<dstPort> (ssh -L style tunnel)
|
|
38203
39160
|
|
|
38204
39161
|
kt sftp ls <connId> [path] list a remote directory
|
|
@@ -38225,7 +39182,8 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38225
39182
|
A "side" below is written <sftp|file|cloud>:<connId>:<path> \u2014 one argument for one endpoint.
|
|
38226
39183
|
|
|
38227
39184
|
kt transfer <from> <to> [--dir] remote-to-remote, brokered by the server
|
|
38228
|
-
[--conflict overwrite|skip|rename] [--throttle <bytes/sec>]
|
|
39185
|
+
[--conflict overwrite|skip|rename] [--throttle <bytes/sec>] [--timeout <sec>]
|
|
39186
|
+
a <to> with no trailing slash whose parent exists names the file
|
|
38229
39187
|
kt sync <from> <to> [--newer-only] directory sync
|
|
38230
39188
|
[--mirror] [--yes] [--dry-run] --mirror DELETES extra files at the destination
|
|
38231
39189
|
|
|
@@ -38234,6 +39192,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38234
39192
|
kt job new <from> <to> [--wait] queue a job (--wait polls to completion)
|
|
38235
39193
|
[--dir] [--verify] [--throttle N] [--newer-only] [--mirror --yes] [--timeout <sec>]
|
|
38236
39194
|
kt job wait <id> [--timeout <sec>] poll an existing job
|
|
39195
|
+
kt job cancel <id> cancel a queued or running job
|
|
38237
39196
|
|
|
38238
39197
|
kt schedule [ls] recurring transfers
|
|
38239
39198
|
kt schedule new <name> <from> <to> --every <sec> or --daily-at HH:MM (UTC)
|
|
@@ -38242,6 +39201,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38242
39201
|
|
|
38243
39202
|
kt watcher [ls] folder watchers (fire on new/changed files)
|
|
38244
39203
|
kt watcher new <name> <source> <dest> [--pattern <glob>] [--every <sec>]
|
|
39204
|
+
--every is 60..86400 seconds (default 300); --pattern is a filename glob (* and ?), up to 256 characters, matched on the name only
|
|
38245
39205
|
[--min-size N] [--max-size N] [--min-age <sec>] [--no-stability]
|
|
38246
39206
|
kt watcher enable|disable|rm <id> pause / resume / delete a watcher
|
|
38247
39207
|
|
|
@@ -38356,7 +39316,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38356
39316
|
[--reporter console|junit|json] [--out <file>] [--dry-run]
|
|
38357
39317
|
with KT_AGENT_TOKEN set, runs as that pipeline principal over /mcp
|
|
38358
39318
|
|
|
38359
|
-
kt history [query] [--limit N] searchable
|
|
39319
|
+
kt history [query] [--limit N] searchable history of brokered exec commands, such as kt exec; not shell input
|
|
38360
39320
|
[--session <id>] [--host <connection id>] [--since <iso>] [--until <iso>]
|
|
38361
39321
|
kt news security news feed
|
|
38362
39322
|
|
|
@@ -38376,6 +39336,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38376
39336
|
kt mfa verify --code <code> finish TOTP enrollment
|
|
38377
39337
|
kt mfa disable turn off TOTP
|
|
38378
39338
|
kt mfa backup-codes regenerate backup codes
|
|
39339
|
+
kt step-up prove app-MFA now (code is prompted), then re-run the refused command
|
|
38379
39340
|
kt passkeys registered passkeys / security keys
|
|
38380
39341
|
kt passkey rm <id> remove a passkey
|
|
38381
39342
|
|
|
@@ -38472,14 +39433,46 @@ function runForward(base, token, connId, spec, ctx = {}) {
|
|
|
38472
39433
|
console.log(`forwarding 127.0.0.1:${localPort} -> ${dstHost}:${dstPort} via ${connId} (Ctrl-C to stop)`);
|
|
38473
39434
|
});
|
|
38474
39435
|
}
|
|
39436
|
+
function hubCallFor(base) {
|
|
39437
|
+
return async (method, path, body, bearer) => {
|
|
39438
|
+
try {
|
|
39439
|
+
const res = await refuseBodyRedirects()(base + path, {
|
|
39440
|
+
method,
|
|
39441
|
+
headers: { Authorization: `Bearer ${bearer}`, Accept: "application/json", ...body === void 0 ? {} : { "Content-Type": "application/json" } },
|
|
39442
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
39443
|
+
signal: AbortSignal.timeout(15e3)
|
|
39444
|
+
});
|
|
39445
|
+
return { ok: res.ok, status: res.status, data: res.ok ? await res.json().catch(() => null) : null };
|
|
39446
|
+
} catch {
|
|
39447
|
+
return { ok: false, status: null, data: null };
|
|
39448
|
+
}
|
|
39449
|
+
};
|
|
39450
|
+
}
|
|
39451
|
+
var OWN_SIGN_IN = /* @__PURE__ */ new Set(["login", "logout", "mcp", "ci-login", "run", "enroll", "share-redeem", "help", "--help", "-h"]);
|
|
39452
|
+
var SESSION_REFUSED = /* @__PURE__ */ new Set(["unauthorized", "session_signed_out"]);
|
|
39453
|
+
var sessionWrites = trackWrites(refuseBodyRedirects());
|
|
39454
|
+
function refusedSavedSession(e, argv2) {
|
|
39455
|
+
if (!(e instanceof ApiError) || e.status !== 401 || !SESSION_REFUSED.has(refusalCode(e)) || OWN_SIGN_IN.has(argv2.cmd)) return false;
|
|
39456
|
+
if (Object.entries(argv2.flags).some(([k, v]) => k.endsWith("-stdin") && v === true)) return false;
|
|
39457
|
+
const base = resolveBase(argv2.flags.base);
|
|
39458
|
+
try {
|
|
39459
|
+
return Boolean(base && getToken(base));
|
|
39460
|
+
} catch {
|
|
39461
|
+
return false;
|
|
39462
|
+
}
|
|
39463
|
+
}
|
|
38475
39464
|
async function main() {
|
|
38476
|
-
const { cmd, positionals, flags } = parseArgs(process.argv.slice(2));
|
|
39465
|
+
const { cmd, positionals, flags, multi } = parseArgs(process.argv.slice(2));
|
|
38477
39466
|
const asJson = flags.json === true;
|
|
38478
39467
|
const die = (msg2) => {
|
|
38479
39468
|
console.error(msg2);
|
|
38480
39469
|
process.exit(1);
|
|
38481
39470
|
};
|
|
38482
39471
|
const unknown = unknownFlags(flags);
|
|
39472
|
+
if (flags.help === true && cmd !== "help") {
|
|
39473
|
+
console.log(CMD_HELP[cmd] ?? HELP);
|
|
39474
|
+
return;
|
|
39475
|
+
}
|
|
38483
39476
|
if (unknown.length) die(`unknown flag: ${unknown.map((f) => `--${f}`).join(", ")}
|
|
38484
39477
|
run \`kt --help\` for the flags each command takes`);
|
|
38485
39478
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
@@ -38490,6 +39483,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38490
39483
|
const configured = resolveBase(flags.base);
|
|
38491
39484
|
if (!configured && !offlineLint && cmd !== "help" && cmd !== "--help" && cmd !== "-h") die(NO_BASE);
|
|
38492
39485
|
const cfg = { base: configured };
|
|
39486
|
+
if (!offlineLint && configured) installBaseFallback(configured);
|
|
38493
39487
|
let issued;
|
|
38494
39488
|
let signedThisRun = false;
|
|
38495
39489
|
const ensureRefreshKeyBound = async () => {
|
|
@@ -38515,8 +39509,11 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38515
39509
|
});
|
|
38516
39510
|
if (outcome !== "failed" && borrowedDevice(cfg.base, kcSlots)?.id === dev.id) settleRefreshBind(cfg.base, kcSlots, dev.id);
|
|
38517
39511
|
};
|
|
39512
|
+
const longCall = cmd === "transfer" || cmd === "sync";
|
|
39513
|
+
const longTimeoutSec = Number(flags.timeout);
|
|
38518
39514
|
const client = new KutrionClient({
|
|
38519
39515
|
baseUrl: cfg.base,
|
|
39516
|
+
...longCall ? { timeoutMs: (Number.isFinite(longTimeoutSec) && longTimeoutSec > 0 ? longTimeoutSec : 6 * 3600) * 1e3 } : {},
|
|
38520
39517
|
getToken: () => getToken(cfg.base),
|
|
38521
39518
|
onToken: (t) => {
|
|
38522
39519
|
issued = t || void 0;
|
|
@@ -38527,7 +39524,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38527
39524
|
// blank User-Agent it never set. Relaxing only, and never required — an older CLI that omits it
|
|
38528
39525
|
// is treated exactly as it is today.
|
|
38529
39526
|
client: "cli",
|
|
38530
|
-
fetchImpl:
|
|
39527
|
+
fetchImpl: sessionWrites.fetch,
|
|
38531
39528
|
// #2984: only a hub borrow leaves a refresh token, in the OS keychain beside the access token.
|
|
38532
39529
|
getRefreshToken: () => {
|
|
38533
39530
|
try {
|
|
@@ -38562,17 +39559,13 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38562
39559
|
}
|
|
38563
39560
|
});
|
|
38564
39561
|
const kc = { get: getToken, set: setToken, clear: clearToken };
|
|
38565
|
-
const hubCall =
|
|
39562
|
+
const hubCall = hubCallFor(cfg.base);
|
|
39563
|
+
const vaultCustody = async (vaultId) => {
|
|
38566
39564
|
try {
|
|
38567
|
-
|
|
38568
|
-
|
|
38569
|
-
|
|
38570
|
-
|
|
38571
|
-
signal: AbortSignal.timeout(15e3)
|
|
38572
|
-
});
|
|
38573
|
-
return { ok: res.ok, status: res.status, data: res.ok ? await res.json().catch(() => null) : null };
|
|
38574
|
-
} catch {
|
|
38575
|
-
return { ok: false, status: null, data: null };
|
|
39565
|
+
return await resolveVaultCustody(client, vaultId, { base: cfg.base, memory: PRIVATE_VAULTS, passphraseStdin: flags[PASSPHRASE_STDIN_FLAG] === true });
|
|
39566
|
+
} catch (e) {
|
|
39567
|
+
if (e instanceof PrivateVaultError) return die(e.message);
|
|
39568
|
+
throw e;
|
|
38576
39569
|
}
|
|
38577
39570
|
};
|
|
38578
39571
|
const connectionContext = async (connId) => {
|
|
@@ -38650,6 +39643,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38650
39643
|
timeoutMs: (intFlag("timeout") ?? 3600) * 1e3,
|
|
38651
39644
|
onTick: (j) => {
|
|
38652
39645
|
if (ticker) process.stderr.write(`\r${j.status} \u2014 ${j.files} file(s), ${fmtBytes(j.bytes)} `);
|
|
39646
|
+
},
|
|
39647
|
+
onRetry: (e) => {
|
|
39648
|
+
if (!asJson) process.stderr.write(`${ticker ? "\n" : ""}could not reach the server (${e instanceof Error ? e.message : String(e)}); still waiting
|
|
39649
|
+
`);
|
|
38653
39650
|
}
|
|
38654
39651
|
});
|
|
38655
39652
|
if (ticker) process.stderr.write("\n");
|
|
@@ -38659,8 +39656,8 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38659
39656
|
process.exit(EXIT_UNKNOWN);
|
|
38660
39657
|
}
|
|
38661
39658
|
show(job);
|
|
38662
|
-
if (job.status === "failed") {
|
|
38663
|
-
if (!asJson) console.error(`job
|
|
39659
|
+
if (job.status === "failed" || job.status === "canceled") {
|
|
39660
|
+
if (!asJson) console.error(`job ${job.status}: ${job.error ?? "no reason given"}`);
|
|
38664
39661
|
process.exit(EXIT_FAIL);
|
|
38665
39662
|
}
|
|
38666
39663
|
process.exit(EXIT_OK);
|
|
@@ -38684,7 +39681,8 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38684
39681
|
console.error("kt login --browser is not available: browser sign-in is switched off. Sign in with the Kutrion app (`kt login`), or use `kt login --email <email> --password-stdin`.");
|
|
38685
39682
|
process.exit(EXIT_ERROR);
|
|
38686
39683
|
}
|
|
38687
|
-
|
|
39684
|
+
const tty = canPromptYes(process.stdin, process.stderr);
|
|
39685
|
+
if (accountRoot && loginTriesHub(flags, accountRoot)) {
|
|
38688
39686
|
const hub = await hubLogin({
|
|
38689
39687
|
base: cfg.base,
|
|
38690
39688
|
location: hubLocation(accountRoot),
|
|
@@ -38692,7 +39690,8 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38692
39690
|
keychainAvailable: osKeychainAvailable,
|
|
38693
39691
|
call: hubCall,
|
|
38694
39692
|
out: (l) => console.error(l),
|
|
38695
|
-
ask: (q) => promptLine(q)
|
|
39693
|
+
ask: (q) => promptLine(q),
|
|
39694
|
+
pairedOnly: !tty
|
|
38696
39695
|
});
|
|
38697
39696
|
if (hub.kind === "store_failed") die(hub.note);
|
|
38698
39697
|
if (hub.kind === "signed_in") {
|
|
@@ -38702,7 +39701,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38702
39701
|
break;
|
|
38703
39702
|
}
|
|
38704
39703
|
if (hub.note) console.error(hub.note);
|
|
38705
|
-
if (hubAbsent(hub.reason)) console.error("Installing the Kutrion app... Run `kt login` again once it is open, or sign in with your email and password below.");
|
|
39704
|
+
if (tty && hubAbsent(hub.reason)) console.error("Installing the Kutrion app... Run `kt login` again once it is open, or sign in with your email and password below.");
|
|
38706
39705
|
}
|
|
38707
39706
|
if (flags.email === void 0 && !fromStdin) {
|
|
38708
39707
|
const hint = otherAppsHint(accountRoot);
|
|
@@ -38744,7 +39743,6 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38744
39743
|
process.exit(1);
|
|
38745
39744
|
}
|
|
38746
39745
|
const staleDevice = dropBorrowed(cfg.base, kc);
|
|
38747
|
-
if (staleDevice) await client.revokeDevice(staleDevice).catch(() => void 0);
|
|
38748
39746
|
const post = fetchPost(cfg.base, refuseBodyRedirects());
|
|
38749
39747
|
const registered = await registerOwnDevice({
|
|
38750
39748
|
base: cfg.base,
|
|
@@ -38759,9 +39757,12 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38759
39757
|
} catch {
|
|
38760
39758
|
return null;
|
|
38761
39759
|
}
|
|
38762
|
-
})()
|
|
39760
|
+
})(),
|
|
39761
|
+
previousDeviceId: staleDevice
|
|
38763
39762
|
}).catch(() => ({ kind: "skipped", note: null }));
|
|
39763
|
+
if (staleDevice) await client.revokeDevice(staleDevice).catch(() => void 0);
|
|
38764
39764
|
recordSignIn(accountRoot, signedIn);
|
|
39765
|
+
recordLastAccountId(cfg.base, kc, signedIn);
|
|
38765
39766
|
console.log(`logged in (token stored in ${backendName()})`);
|
|
38766
39767
|
if (rememberBase(cfg.base)) console.log(`server saved: ${cfg.base} (later commands need no --base)`);
|
|
38767
39768
|
if (registered.kind === "skipped" && registered.note) console.error(registered.note);
|
|
@@ -38790,7 +39791,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38790
39791
|
break;
|
|
38791
39792
|
case "whoami":
|
|
38792
39793
|
requireAuth();
|
|
38793
|
-
|
|
39794
|
+
{
|
|
39795
|
+
const { user } = await client.me();
|
|
39796
|
+
show(asJson ? user : whoamiLines(user));
|
|
39797
|
+
}
|
|
38794
39798
|
break;
|
|
38795
39799
|
case "vaults":
|
|
38796
39800
|
requireAuth();
|
|
@@ -38799,8 +39803,13 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38799
39803
|
case "vault": {
|
|
38800
39804
|
requireAuth();
|
|
38801
39805
|
const sub = positionals[0];
|
|
38802
|
-
if (sub === "create" && positionals[1])
|
|
38803
|
-
|
|
39806
|
+
if (sub === "create" && positionals[1]) {
|
|
39807
|
+
const name = positionals.slice(1).join(" ");
|
|
39808
|
+
const twin = await client.listVaults().then((r) => r.vaults.find((v) => !v.team_id && v.name === name), () => void 0);
|
|
39809
|
+
if (twin) console.error(`warning: a vault named "${name}" already exists (${twin.id}); creating another`);
|
|
39810
|
+
const { vault } = await client.createVault(name);
|
|
39811
|
+
show(asJson ? vault : `created vault "${vault.name}" (${vault.id})`);
|
|
39812
|
+
} else if (sub === "rename" && positionals[1] && positionals[2]) show((await client.renameVault(positionals[1], positionals.slice(2).join(" "))).vault);
|
|
38804
39813
|
else if (sub === "rm" && positionals[1]) {
|
|
38805
39814
|
await client.deleteVault(positionals[1]);
|
|
38806
39815
|
console.log("deleted");
|
|
@@ -38829,12 +39838,15 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38829
39838
|
noReveal = credential.value === null;
|
|
38830
39839
|
} else {
|
|
38831
39840
|
const summary = (await client.listCredentials(positionals[0])).credentials.find((c) => c.id === positionals[1]);
|
|
38832
|
-
if (!summary)
|
|
39841
|
+
if (!summary) {
|
|
39842
|
+
console.error(`no credential ${positionals[1]} in vault ${positionals[0]}`);
|
|
39843
|
+
process.exit(EXIT_NOT_FOUND);
|
|
39844
|
+
}
|
|
38833
39845
|
noReveal = summary.inject_only === true;
|
|
38834
39846
|
if (noReveal) show({ ...summary, value: null });
|
|
38835
39847
|
else {
|
|
38836
39848
|
show({ ...summary, value_hidden: true });
|
|
38837
|
-
console.error("value hidden; pass --reveal to print it");
|
|
39849
|
+
if (!asJson) console.error("value hidden; pass --reveal to print it");
|
|
38838
39850
|
}
|
|
38839
39851
|
}
|
|
38840
39852
|
if (noReveal) {
|
|
@@ -38856,17 +39868,45 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38856
39868
|
if (flags.value !== void 0) refuseSecretFlag("value", "pipe it to --value-stdin, or leave it off to be prompted");
|
|
38857
39869
|
if (flags["value-stdin"] === true && typeof flags.name !== "string") die("--value-stdin needs --name, because stdin carries the value");
|
|
38858
39870
|
const name = String(flags.name ?? await prompt("name: "));
|
|
38859
|
-
const value = flags["value-stdin"] === true ? await readSecret("value: ") : await prompt("value: ", true);
|
|
38860
39871
|
const notes = flags.notes !== void 0 ? String(flags.notes) : void 0;
|
|
38861
|
-
|
|
39872
|
+
const custody = await vaultCustody(vaultId);
|
|
39873
|
+
if (custody.private) {
|
|
39874
|
+
try {
|
|
39875
|
+
const { vault, value: value2 } = await unlockForSealedWrite(client, flags, true);
|
|
39876
|
+
show(withNotesMarker((await createSealedItem(client, vault, vaultId, custody.key, { credential_type: type, name }, { value: value2, notes })).credential, notes));
|
|
39877
|
+
} catch (e) {
|
|
39878
|
+
if (e instanceof PrivateVaultError) return die(e.message);
|
|
39879
|
+
throw e;
|
|
39880
|
+
}
|
|
39881
|
+
break;
|
|
39882
|
+
}
|
|
39883
|
+
const value = flags["value-stdin"] === true ? await readSecret("value: ") : await prompt("value: ", true);
|
|
39884
|
+
if (value === "") die(EMPTY_VALUE_MESSAGE);
|
|
39885
|
+
show(withNotesMarker((await client.storeCredential(vaultId, { credential_type: type, name, value, notes })).credential, notes));
|
|
38862
39886
|
} else if (sub === "set" && vaultId && positionals[2]) {
|
|
38863
39887
|
if (flags.value !== void 0) refuseSecretFlag("value", "pipe it to --value-stdin");
|
|
38864
39888
|
const patch = {};
|
|
38865
39889
|
if (flags.name !== void 0) patch.name = String(flags.name);
|
|
38866
|
-
if (flags["value-stdin"] === true) patch.value = await readSecret("value: ");
|
|
38867
39890
|
if (flags.notes !== void 0) patch.notes = String(flags.notes);
|
|
38868
|
-
if (!Object.keys(patch).length) return die("nothing to update \u2014 pass --name/--value-stdin/--notes");
|
|
38869
|
-
|
|
39891
|
+
if (!Object.keys(patch).length && flags["value-stdin"] !== true) return die("nothing to update \u2014 pass --name/--value-stdin/--notes");
|
|
39892
|
+
const custody = await vaultCustody(vaultId);
|
|
39893
|
+
if (custody.private) {
|
|
39894
|
+
try {
|
|
39895
|
+
const sealsFields = flags["value-stdin"] === true || patch.notes !== void 0;
|
|
39896
|
+
const unlocked = sealsFields ? await unlockForSealedWrite(client, flags, flags["value-stdin"] === true) : null;
|
|
39897
|
+
if (unlocked?.value !== void 0) patch.value = unlocked.value;
|
|
39898
|
+
show(withNotesMarker((await updateSealedItem(client, unlocked?.vault ?? null, vaultId, positionals[2], custody.key, patch)).credential, patch.notes));
|
|
39899
|
+
} catch (e) {
|
|
39900
|
+
if (e instanceof PrivateVaultError) return die(e.message);
|
|
39901
|
+
throw e;
|
|
39902
|
+
}
|
|
39903
|
+
break;
|
|
39904
|
+
}
|
|
39905
|
+
if (flags["value-stdin"] === true) {
|
|
39906
|
+
patch.value = await readSecret("value: ");
|
|
39907
|
+
if (patch.value === "") die(EMPTY_VALUE_MESSAGE);
|
|
39908
|
+
}
|
|
39909
|
+
show(withNotesMarker((await client.updateCredential(vaultId, positionals[2], patch)).credential, patch.notes));
|
|
38870
39910
|
} else if (sub === "rm" && vaultId && positionals[2]) {
|
|
38871
39911
|
await client.deleteCredential(vaultId, positionals[2]);
|
|
38872
39912
|
console.log("deleted");
|
|
@@ -38947,8 +39987,20 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38947
39987
|
}
|
|
38948
39988
|
case "exec": {
|
|
38949
39989
|
requireAuth();
|
|
38950
|
-
if (!positionals[0] || !positionals[1]) return die("usage: kt exec <connId> <command...>");
|
|
38951
|
-
|
|
39990
|
+
if (!positionals[0] || !positionals[1]) return die("usage: kt exec <connId> [--stdin] [--] <command...>");
|
|
39991
|
+
let stdin;
|
|
39992
|
+
if (flags.stdin === true) {
|
|
39993
|
+
const takes = await serverTakesExecStdin(cfg.base).catch((e) => {
|
|
39994
|
+
if (e instanceof ExecStdinCheckTimedOut) return die(e.message);
|
|
39995
|
+
throw e;
|
|
39996
|
+
});
|
|
39997
|
+
if (!takes) return die(STDIN_UNSUPPORTED);
|
|
39998
|
+
stdin = await readExecStdin(process.stdin).catch((e) => {
|
|
39999
|
+
if (e instanceof StdinTooLargeError) return die(`error: ${e.message}`);
|
|
40000
|
+
throw e;
|
|
40001
|
+
});
|
|
40002
|
+
}
|
|
40003
|
+
const r = await client.exec(positionals[0], positionals.slice(1).join(" "), stdin?.toString("base64")).catch(async (e) => {
|
|
38952
40004
|
const k = hostKeyUnconfirmedFromError(e);
|
|
38953
40005
|
const chained = k ? null : hostKeyChainedFromError(e);
|
|
38954
40006
|
if (!k && !chained) throw e;
|
|
@@ -38987,7 +40039,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38987
40039
|
}
|
|
38988
40040
|
let job;
|
|
38989
40041
|
try {
|
|
38990
|
-
job = parseJobFile((0,
|
|
40042
|
+
job = parseJobFile((0, import_fs7.readFileSync)(path, "utf8"));
|
|
38991
40043
|
} catch (e) {
|
|
38992
40044
|
return die(e instanceof JobFileError ? `${path}: ${e.message}` : `${path}: ${e.message}`);
|
|
38993
40045
|
}
|
|
@@ -38996,7 +40048,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38996
40048
|
if (agentToken && !asJson && reporter === "console") {
|
|
38997
40049
|
console.error(`running as a pipeline principal through ${cfg.base.replace(/\/+$/, "")}/mcp`);
|
|
38998
40050
|
}
|
|
38999
|
-
const result = await runJob(job, runnerClient, { readLocal: (p) => (0,
|
|
40051
|
+
const result = await runJob(job, runnerClient, { readLocal: (p) => (0, import_fs7.readFileSync)(p), writeLocal: (p, d) => (0, import_fs7.writeFileSync)(p, d) }, {
|
|
39000
40052
|
dryRun,
|
|
39001
40053
|
onStep: (s) => {
|
|
39002
40054
|
if (asJson || reporter !== "console") return;
|
|
@@ -39022,7 +40074,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39022
40074
|
const path = positionals[2] ?? ".";
|
|
39023
40075
|
const r = await client.sftpList(connId, path);
|
|
39024
40076
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39025
|
-
show(r.entries, ["name", "type", "size", "mode"]);
|
|
40077
|
+
show(r.entries.map((e) => ({ ...e, mode: modeString(e.mode, e.type) })), ["name", "type", "size", "mode"]);
|
|
39026
40078
|
} else if (sub === "mkdir" && connId && positionals[2]) {
|
|
39027
40079
|
await client.sftpMkdir(connId, positionals[2]);
|
|
39028
40080
|
console.log("created");
|
|
@@ -39094,6 +40146,11 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39094
40146
|
to,
|
|
39095
40147
|
throttle_bps: intFlag("throttle"),
|
|
39096
40148
|
...conflict ? { conflict } : {}
|
|
40149
|
+
}).catch((e) => {
|
|
40150
|
+
if (!unreachedApi(e)) throw e;
|
|
40151
|
+
if (asJson) console.log(JSON.stringify({ status: "unknown", error: e instanceof Error ? e.message : String(e) }, null, 2));
|
|
40152
|
+
else console.error(`no answer from the server (${e instanceof Error ? e.message : String(e)}); the transfer may still be running or may have completed. Check the destination before running it again, or use \`kt job new\` for a transfer you can poll.`);
|
|
40153
|
+
return process.exit(EXIT_UNKNOWN);
|
|
39097
40154
|
});
|
|
39098
40155
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39099
40156
|
console.log(`transferred ${r.files} file(s), ${fmtBytes(r.bytes)}${r.skipped ? `, ${r.skipped} skipped` : ""}${r.renamed ? `, ${r.renamed} renamed` : ""}`);
|
|
@@ -39142,8 +40199,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39142
40199
|
return await watchJob(job.id);
|
|
39143
40200
|
} else if (sub === "wait" && positionals[1]) {
|
|
39144
40201
|
return await watchJob(positionals[1]);
|
|
40202
|
+
} else if (sub === "cancel" && positionals[1]) {
|
|
40203
|
+
show((await client.cancelTransferJob(positionals[1])).job);
|
|
39145
40204
|
} else {
|
|
39146
|
-
die("usage: kt job [ls] | kt job get <id> | kt job new <from> <to> [--wait] | kt job wait <id>");
|
|
40205
|
+
die("usage: kt job [ls] | kt job get <id> | kt job new <from> <to> [--wait] | kt job wait <id> | kt job cancel <id>");
|
|
39147
40206
|
}
|
|
39148
40207
|
break;
|
|
39149
40208
|
}
|
|
@@ -39193,6 +40252,13 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39193
40252
|
} else if (sub === "new" && positionals[1] && positionals[2] && positionals[3]) {
|
|
39194
40253
|
const source = sideArg(positionals[2], "source");
|
|
39195
40254
|
const dest = sideArg(positionals[3], "destination");
|
|
40255
|
+
const every = intFlag("every");
|
|
40256
|
+
if (flags.every === true || every !== void 0 && (!Number.isInteger(every) || every < WATCHER_EVERY_MIN || every > WATCHER_EVERY_MAX)) {
|
|
40257
|
+
return die(`--every must be a whole number of seconds from ${WATCHER_EVERY_MIN} to ${WATCHER_EVERY_MAX} (got ${flags.every === true ? "nothing" : flags.every})`);
|
|
40258
|
+
}
|
|
40259
|
+
if (flags.pattern !== void 0 && (flags.pattern === true || !String(flags.pattern).trim() || String(flags.pattern).length > WATCHER_PATTERN_MAX)) {
|
|
40260
|
+
return die(`--pattern needs a filename glob such as '*.csv', at most ${WATCHER_PATTERN_MAX} characters`);
|
|
40261
|
+
}
|
|
39196
40262
|
show((await client.createFolderWatcher({
|
|
39197
40263
|
name: positionals[1],
|
|
39198
40264
|
source,
|
|
@@ -39206,7 +40272,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39206
40272
|
// Stability checks are what stop a watcher picking up a file that is still being written.
|
|
39207
40273
|
// Off is opt-in, never a default.
|
|
39208
40274
|
stability_checks: flags["no-stability"] !== true,
|
|
39209
|
-
interval_seconds:
|
|
40275
|
+
interval_seconds: every
|
|
39210
40276
|
})).watcher);
|
|
39211
40277
|
} else if ((sub === "enable" || sub === "disable") && positionals[1]) {
|
|
39212
40278
|
await client.setFolderWatcherEnabled(positionals[1], sub === "enable");
|
|
@@ -39278,11 +40344,11 @@ ${r.authorize_url}`);
|
|
|
39278
40344
|
const buf = Buffer.from(await blob.arrayBuffer());
|
|
39279
40345
|
if (local === "-") process.stdout.write(buf);
|
|
39280
40346
|
else {
|
|
39281
|
-
(0,
|
|
40347
|
+
(0, import_fs7.writeFileSync)(local, buf);
|
|
39282
40348
|
console.error(`saved ${local} (${fmtBytes(buf.length)})`);
|
|
39283
40349
|
}
|
|
39284
40350
|
} else if (sub === "put" && id && positionals[2] && positionals[3]) {
|
|
39285
|
-
const buf = (0,
|
|
40351
|
+
const buf = (0, import_fs7.readFileSync)(positionals[2]);
|
|
39286
40352
|
await client.cloudUpload(id, positionals[3], new Blob([buf]));
|
|
39287
40353
|
console.error(`uploaded ${fmtBytes(buf.length)}`);
|
|
39288
40354
|
} else if (sub === "mkdir" && id && positionals[2]) {
|
|
@@ -39388,17 +40454,20 @@ ${r.token}`);
|
|
|
39388
40454
|
}
|
|
39389
40455
|
const scopes = flags.scopes ? String(flags.scopes).split(",").map((s) => s.trim()).filter(Boolean) : DEFAULT_SCOPES;
|
|
39390
40456
|
const ttlDays = flags.days !== void 0 ? Number(flags.days) : DEFAULT_TTL_DAYS;
|
|
39391
|
-
const dir = (0,
|
|
40457
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39392
40458
|
const egress = String(flags.egress ?? "cloud");
|
|
39393
40459
|
if (egress !== "lan" && egress !== "cloud") return die("--egress must be lan or cloud");
|
|
40460
|
+
if (flags.vault === true || flags.connection === true) return die("--vault and --connection each take a value (an id or a name)");
|
|
39394
40461
|
try {
|
|
39395
|
-
const
|
|
40462
|
+
const resourceScope = await resolveResourceScope(cfg.base, userToken, multi.vault ?? [], multi.connection ?? []);
|
|
40463
|
+
const r = await installClient({ base: cfg.base, userToken, client: client2, dir, scopes, ttlDays, egressClass: egress, resourceScope });
|
|
39396
40464
|
warnLeftLive(r, client2);
|
|
39397
40465
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39398
40466
|
console.log(`installed ${CLIENTS[client2].label}`);
|
|
39399
40467
|
console.log(` config ${r.configPath}`);
|
|
39400
40468
|
console.log(` agent ${r.principalId} (expires ${r.expiresAt})`);
|
|
39401
40469
|
console.log(` scopes ${r.scopes.join(", ")}`);
|
|
40470
|
+
console.log(` reach ${describeScope(r.resourceScope)}`);
|
|
39402
40471
|
if (r.superseded) console.log(` revoked ${r.superseded} earlier agent(s) for this client`);
|
|
39403
40472
|
console.log(` credential stored in the ${backendName()} \u2014 the config file does not contain it`);
|
|
39404
40473
|
console.log(r.verified ? " verified connection to the broker works" : " warning: could not verify the connection");
|
|
@@ -39417,7 +40486,7 @@ next: ${r.next}`);
|
|
|
39417
40486
|
break;
|
|
39418
40487
|
}
|
|
39419
40488
|
if (sub === "status") {
|
|
39420
|
-
const dir = (0,
|
|
40489
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39421
40490
|
const s = await mcpStatus(cfg.base, dir);
|
|
39422
40491
|
if (asJson) return console.log(JSON.stringify(s, null, 2));
|
|
39423
40492
|
console.log(`credential store: ${s.keychain}`);
|
|
@@ -39437,6 +40506,7 @@ next: ${r.next}`);
|
|
|
39437
40506
|
if (c.agent?.state !== "live") continue;
|
|
39438
40507
|
const rl = c.agent.rate_limit;
|
|
39439
40508
|
console.log(`${c.client}: scopes ${(c.agent.scopes ?? []).join(", ")}` + (rl?.tool_calls ? `; limit ${rl.tool_calls} tool calls / ${Math.round((rl.window_ms ?? 0) / 1e3)}s` : ""));
|
|
40509
|
+
console.log(`${c.client}: reach ${describeScope(c.agent.resource_scope)}`);
|
|
39440
40510
|
}
|
|
39441
40511
|
if (s.legacy) console.log(`
|
|
39442
40512
|
shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.trimEnd());
|
|
@@ -39448,7 +40518,7 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39448
40518
|
if (client2 && !CLIENT_IDS.includes(client2)) {
|
|
39449
40519
|
return die(`unknown --client; one of: ${CLIENT_IDS.join(", ")}`);
|
|
39450
40520
|
}
|
|
39451
|
-
const dir = (0,
|
|
40521
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39452
40522
|
const r = await uninstallClient({ base: cfg.base, userToken, client: client2, dir });
|
|
39453
40523
|
warnLeftLive(r, client2);
|
|
39454
40524
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
@@ -39457,7 +40527,7 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39457
40527
|
break;
|
|
39458
40528
|
}
|
|
39459
40529
|
if (sub && sub !== "serve") {
|
|
39460
|
-
return die("usage: kt mcp [serve] | kt mcp install --client <id> | kt mcp status | kt mcp uninstall [--client <id>]");
|
|
40530
|
+
return die("usage: kt mcp [serve] | kt mcp install --client <id> [--vault <id|name>]... [--connection <id>]... | kt mcp status | kt mcp uninstall [--client <id>]");
|
|
39461
40531
|
}
|
|
39462
40532
|
if (flags.token !== void 0) refuseSecretFlag("token", "export KT_AGENT_TOKEN instead, or run kt mcp install");
|
|
39463
40533
|
let agentToken;
|
|
@@ -39478,6 +40548,136 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39478
40548
|
await runMcpBridge({ base: cfg.base, token: agentToken });
|
|
39479
40549
|
break;
|
|
39480
40550
|
}
|
|
40551
|
+
// #3545: a principal for a server install, which has no keychain and no AI client config to write.
|
|
40552
|
+
case "agent": {
|
|
40553
|
+
if (positionals[0] !== "mint") {
|
|
40554
|
+
return die("usage: kt agent mint [--name <n>] [--scopes a,b] [--days <n>] [--egress lan|cloud] [--scope-vault <id|name>]... [--scope-conn <id|name>]... [--write-env <file>]");
|
|
40555
|
+
}
|
|
40556
|
+
const userToken = requireAuth();
|
|
40557
|
+
if (flags["scope-vault"] === true || flags["scope-conn"] === true) return die("--scope-vault and --scope-conn each take a value (an id or a name)");
|
|
40558
|
+
if (flags["write-env"] === true) return die("--write-env takes a file path");
|
|
40559
|
+
const scopes = flags.scopes ? String(flags.scopes).split(",").map((x) => x.trim()).filter(Boolean) : DEFAULT_SCOPES;
|
|
40560
|
+
const ttlDays = flags.days !== void 0 ? Number(flags.days) : DEFAULT_TTL_DAYS;
|
|
40561
|
+
const egress = String(flags.egress ?? "cloud");
|
|
40562
|
+
if (egress !== "lan" && egress !== "cloud") return die("--egress must be lan or cloud");
|
|
40563
|
+
const envFile = typeof flags["write-env"] === "string" ? (0, import_path7.resolve)(String(flags["write-env"])) : void 0;
|
|
40564
|
+
let created = false;
|
|
40565
|
+
if (envFile) {
|
|
40566
|
+
if ((0, import_fs7.existsSync)(envFile) && flags.yes !== true) return die(`${envFile} already exists; pass --yes to replace it (the principal it names stays live until revoked)`);
|
|
40567
|
+
created = !(0, import_fs7.existsSync)(envFile);
|
|
40568
|
+
try {
|
|
40569
|
+
writeOwnerOnly(envFile, "");
|
|
40570
|
+
} catch (e) {
|
|
40571
|
+
return die(`cannot write ${envFile}: ${e.message}`);
|
|
40572
|
+
}
|
|
40573
|
+
}
|
|
40574
|
+
try {
|
|
40575
|
+
const resourceScope = await resolveResourceScope(cfg.base, userToken, multi["scope-vault"] ?? [], multi["scope-conn"] ?? []);
|
|
40576
|
+
const m = await mintAgent({
|
|
40577
|
+
base: cfg.base,
|
|
40578
|
+
userToken,
|
|
40579
|
+
name: String(flags.name ?? `kt agent on ${(0, import_os5.hostname)()}`).slice(0, 120),
|
|
40580
|
+
purpose: "Server install minted with 'kt agent mint'",
|
|
40581
|
+
scopes,
|
|
40582
|
+
ttlDays,
|
|
40583
|
+
egressClass: egress,
|
|
40584
|
+
resourceScope
|
|
40585
|
+
});
|
|
40586
|
+
if (envFile) writeOwnerOnly(envFile, `KT_AGENT_TOKEN=${m.token}
|
|
40587
|
+
KT_API_BASE=${cfg.base}
|
|
40588
|
+
`);
|
|
40589
|
+
const view = { principal_id: m.id, expires_at: m.expires_at, scopes: m.scopes, resource_scope: resourceScope ?? null, ...envFile ? { env_file: envFile } : { token: m.token } };
|
|
40590
|
+
if (asJson) return console.log(JSON.stringify(view, null, 2));
|
|
40591
|
+
console.log(`minted agent ${m.id} (expires ${m.expires_at})`);
|
|
40592
|
+
console.log(` scopes ${m.scopes.join(", ")}`);
|
|
40593
|
+
console.log(` reach ${describeScope(resourceScope)}`);
|
|
40594
|
+
if (envFile) console.log(` wrote KT_AGENT_TOKEN and KT_API_BASE to ${envFile} (mode 0600); the token is not shown`);
|
|
40595
|
+
else console.log(` token ${m.token}
|
|
40596
|
+
shown once; export it as KT_AGENT_TOKEN`);
|
|
40597
|
+
} catch (e) {
|
|
40598
|
+
if (envFile && created) {
|
|
40599
|
+
try {
|
|
40600
|
+
(0, import_fs7.unlinkSync)(envFile);
|
|
40601
|
+
} catch {
|
|
40602
|
+
}
|
|
40603
|
+
}
|
|
40604
|
+
return die(`mint failed: ${e.message}`);
|
|
40605
|
+
}
|
|
40606
|
+
break;
|
|
40607
|
+
}
|
|
40608
|
+
// #3541: the OIDC trusts `kt ci-login` exchanges against. The routes are owner/admin only and the
|
|
40609
|
+
// server refuses a pattern that matches every project; this only collects the three claims and the
|
|
40610
|
+
// vault scope. --vault has no org-wide default.
|
|
40611
|
+
case "trust": {
|
|
40612
|
+
requireAuth();
|
|
40613
|
+
const sub = positionals[0] ?? "ls";
|
|
40614
|
+
if (sub === "ls") {
|
|
40615
|
+
const trusts = (await client.listAgentOidcTrusts()).trusts.filter((t) => !t.disabled_at);
|
|
40616
|
+
const vaultsOf = (t) => {
|
|
40617
|
+
const ids = t.resource_scope?.vault_ids ?? [];
|
|
40618
|
+
return ids.length ? ids.map((id) => `${t.resource_scope_names?.[id] ?? "vault"} (${id})`).join(", ") : "all vaults the owner can reach";
|
|
40619
|
+
};
|
|
40620
|
+
show(
|
|
40621
|
+
asJson ? trusts : trusts.map((t) => ({ id: t.id, issuer: t.issuer, audience: t.audience, subject: t.subject_pattern, vaults: vaultsOf(t), created: t.created_at })),
|
|
40622
|
+
["id", "issuer", "audience", "subject", "vaults", "created"]
|
|
40623
|
+
);
|
|
40624
|
+
} else if (sub === "add") {
|
|
40625
|
+
const need = (k) => typeof flags[k] === "string" && String(flags[k]).trim() ? String(flags[k]).trim() : die(`--${k} is required; see \`kt trust --help\``);
|
|
40626
|
+
const issuer = need("issuer"), audience = need("audience"), subject = need("subject");
|
|
40627
|
+
const vaultIds = repeatedFlag(process.argv.slice(2), "vault").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
40628
|
+
if (!vaultIds.length) return die("--vault <id> is required (repeat it for more): a trust reaches only the vaults named, never the whole organization");
|
|
40629
|
+
const scopes = (typeof flags.scopes === "string" ? flags.scopes : "vault.list,vault.use").split(",").map((x) => x.trim()).filter(Boolean);
|
|
40630
|
+
const egress = String(flags.egress ?? "lan");
|
|
40631
|
+
if (egress !== "lan" && egress !== "cloud") return die("--egress must be lan or cloud");
|
|
40632
|
+
const trust = await withStepUp(client, () => client.createAgentOidcTrust({
|
|
40633
|
+
name: typeof flags.name === "string" && flags.name.trim() ? flags.name.trim() : `ci ${subject}`.slice(0, 120),
|
|
40634
|
+
issuer,
|
|
40635
|
+
audience,
|
|
40636
|
+
subject_pattern: subject,
|
|
40637
|
+
scopes,
|
|
40638
|
+
...flags.ttl !== void 0 ? { ttl_minutes: Number(flags.ttl) } : {},
|
|
40639
|
+
egress_class: egress,
|
|
40640
|
+
resource_scope: { vault_ids: vaultIds }
|
|
40641
|
+
}));
|
|
40642
|
+
if (asJson) return console.log(JSON.stringify(trust, null, 2));
|
|
40643
|
+
console.log(`trust ${trust.id} created (egress: ${egress})`);
|
|
40644
|
+
console.log(`use it in the pipeline: kt ci-login --trust ${trust.id}`);
|
|
40645
|
+
} else if (sub === "rm" && positionals[1]) {
|
|
40646
|
+
await client.deleteAgentOidcTrust(positionals[1]);
|
|
40647
|
+
console.log(`trust ${positionals[1]} revoked; no new exchange is accepted. Agents already issued under it run out their own lifetime.`);
|
|
40648
|
+
} else {
|
|
40649
|
+
die("usage: kt trust add --issuer <url> --audience <aud> --subject <pattern> --vault <id> | kt trust ls | kt trust rm <id>");
|
|
40650
|
+
}
|
|
40651
|
+
break;
|
|
40652
|
+
}
|
|
40653
|
+
// #3561: only the ordered fallback list lives here; the primary base is set by `kt login`.
|
|
40654
|
+
case "config": {
|
|
40655
|
+
const [sub, key, ...vals] = positionals;
|
|
40656
|
+
if (key !== "base-fallback" || sub !== "set" && sub !== "get" && sub !== "unset") {
|
|
40657
|
+
return die("usage: kt config set base-fallback <url>[,<url>...] [--allow-insecure-lan] | kt config get base-fallback | kt config unset base-fallback");
|
|
40658
|
+
}
|
|
40659
|
+
const saved = storedBase();
|
|
40660
|
+
if (!saved) return die("no saved server to attach a fallback to; run `kt --base <url> login` first");
|
|
40661
|
+
if (sub === "set") {
|
|
40662
|
+
const urls = splitBases(vals);
|
|
40663
|
+
if (!urls.length) return die("give at least one fallback base URL");
|
|
40664
|
+
const entries = urls.map((url) => ({ url, ...flags["allow-insecure-lan"] === true && url.startsWith("http:") ? { insecure: true } : {} }));
|
|
40665
|
+
for (const f of entries) {
|
|
40666
|
+
const why2 = fallbackRefusal(f);
|
|
40667
|
+
if (why2) return die(`error: ${why2}`);
|
|
40668
|
+
if (norm(f.url) === norm(saved)) return die(`error: ${f.url} is the primary server`);
|
|
40669
|
+
}
|
|
40670
|
+
saveCfg(saved, entries);
|
|
40671
|
+
} else if (sub === "unset") {
|
|
40672
|
+
saveCfg(saved, []);
|
|
40673
|
+
}
|
|
40674
|
+
const now = readFallbacks(CONFIG, saved);
|
|
40675
|
+
if (asJson) return console.log(JSON.stringify({ base: saved, fallbacks: now }, null, 2));
|
|
40676
|
+
console.log(`primary ${saved}`);
|
|
40677
|
+
now.forEach((f, i) => console.log(`fallback ${i + 1} ${f.url}${f.insecure ? " (plain http, opted in)" : ""}`));
|
|
40678
|
+
if (!now.length) console.log("no fallback bases");
|
|
40679
|
+
break;
|
|
40680
|
+
}
|
|
39481
40681
|
// #674: exchange this CI job's own OIDC token for a short-lived agent principal. No Kutrion
|
|
39482
40682
|
// secret is stored in the pipeline — the job token is minted per run by the CI provider.
|
|
39483
40683
|
case "ci-login": {
|
|
@@ -39693,7 +40893,15 @@ ${snippetPreview(s)}
|
|
|
39693
40893
|
const connection_id = String(flags.conn ?? ""), credential_id = String(flags.cred ?? "");
|
|
39694
40894
|
if (!connection_id || !credential_id) return die("need --conn <id> --cred <id>");
|
|
39695
40895
|
const input = { connection_id, credential_id, service, target: flags.target ? String(flags.target) : void 0 };
|
|
39696
|
-
|
|
40896
|
+
if (sub === "plan") show(await client.planRotation(input));
|
|
40897
|
+
else {
|
|
40898
|
+
try {
|
|
40899
|
+
show(await client.executeRotation(input));
|
|
40900
|
+
} catch (e) {
|
|
40901
|
+
if (isStepUpRequired(e)) console.error("requires_step_up: true (as `kt rotation plan` reports); run `kt step-up`, then re-run");
|
|
40902
|
+
throw e;
|
|
40903
|
+
}
|
|
40904
|
+
}
|
|
39697
40905
|
} else die("usage: kt rotation services | history <credId> | plan|run --conn <id> --cred <id> --service <svc> [--target ..]");
|
|
39698
40906
|
break;
|
|
39699
40907
|
}
|
|
@@ -39765,8 +40973,9 @@ ${owed}`);
|
|
|
39765
40973
|
case "device":
|
|
39766
40974
|
requireAuth();
|
|
39767
40975
|
if (positionals[0] === "rm" && positionals[1]) {
|
|
39768
|
-
await client.revokeDevice(positionals[1]);
|
|
40976
|
+
const r = await client.revokeDevice(positionals[1]);
|
|
39769
40977
|
console.log("revoked");
|
|
40978
|
+
if (r.agents_ended) console.log(`this ended ${r.agents_ended} agent${r.agents_ended === 1 ? "" : "s"}: ${(r.agents ?? []).map((a) => printable(String(a.name ?? ""))).join(", ")}`);
|
|
39770
40979
|
} else die("usage: kt device rm <deviceId>");
|
|
39771
40980
|
break;
|
|
39772
40981
|
// --- vault share ---
|
|
@@ -39829,7 +41038,7 @@ ${r.url}`);
|
|
|
39829
41038
|
} else if (sub === "import") {
|
|
39830
41039
|
const vaultId = positionals[1], file = positionals[2];
|
|
39831
41040
|
if (!vaultId || !file) return die(CONN_IMPORT_USAGE);
|
|
39832
|
-
const parsed = await client.parseConnImport((0,
|
|
41041
|
+
const parsed = await client.parseConnImport((0, import_fs7.readFileSync)(file, "utf8"), {
|
|
39833
41042
|
provider: flags.provider ? String(flags.provider) : void 0,
|
|
39834
41043
|
filename: file.split("/").pop()
|
|
39835
41044
|
});
|
|
@@ -39959,7 +41168,7 @@ ${ENROLL_USAGE}`);
|
|
|
39959
41168
|
const confirms = parseConfirms(process.argv.slice(2));
|
|
39960
41169
|
if (!(confirms instanceof Map)) return die(confirms.error);
|
|
39961
41170
|
if (flags.key === true || flags["known-hosts"] === true || flags.vault === true) return die(ENROLL_USAGE);
|
|
39962
|
-
const knownHosts = typeof flags["known-hosts"] === "string" ? (0,
|
|
41171
|
+
const knownHosts = typeof flags["known-hosts"] === "string" ? (0, import_fs7.readFileSync)(flags["known-hosts"], "utf8") : void 0;
|
|
39963
41172
|
const fallback = flags.fallback === true;
|
|
39964
41173
|
const vaultId = typeof flags.vault === "string" ? flags.vault : void 0;
|
|
39965
41174
|
const timeoutSec = intFlag("timeout") ?? 1800;
|
|
@@ -39979,7 +41188,7 @@ ${r.label} ${r.host.observedFingerprint}`);
|
|
|
39979
41188
|
if (!chosen.length) return die(ENROLL_COPY.noneConfirmed);
|
|
39980
41189
|
let bootstrap;
|
|
39981
41190
|
if (typeof flags.key === "string") {
|
|
39982
|
-
const privateKey = (0,
|
|
41191
|
+
const privateKey = (0, import_fs7.readFileSync)(flags.key, "utf8");
|
|
39983
41192
|
let passphrase;
|
|
39984
41193
|
if (isEncryptedKey(privateKey)) {
|
|
39985
41194
|
if (flags["password-stdin"] === true) passphrase = await readStdin();
|
|
@@ -40172,14 +41381,15 @@ ${BROKER_CA_USAGE}`);
|
|
|
40172
41381
|
break;
|
|
40173
41382
|
case "import-providers":
|
|
40174
41383
|
requireAuth();
|
|
40175
|
-
show((await client.importProviders()).providers, ["id", "name", "format", "ext"]);
|
|
41384
|
+
show((await client.importProviders()).providers.map((p) => ({ ...p, name: p.label })), ["id", "name", "format", "ext"]);
|
|
40176
41385
|
break;
|
|
40177
41386
|
case "import": {
|
|
40178
41387
|
requireAuth();
|
|
40179
41388
|
const vaultId = positionals[0];
|
|
40180
41389
|
const file = positionals[1];
|
|
40181
41390
|
if (!vaultId || !file) die("usage: kt import <vaultId> <file> [--provider <name>] (see: kt import-providers)");
|
|
40182
|
-
const content = (0,
|
|
41391
|
+
const content = (0, import_fs7.readFileSync)(file, "utf8");
|
|
41392
|
+
if ((await vaultCustody(vaultId)).private) return die(PRIVATE_IMPORT_REFUSED);
|
|
40183
41393
|
const parsed = await client.parseImport(content, { provider: flags.provider ? String(flags.provider) : void 0, filename: file.split("/").pop() });
|
|
40184
41394
|
for (const w of parsed.warnings) console.error("warning:", w);
|
|
40185
41395
|
const items = parsed.items.filter((it) => it.name && it.value).map((it) => ({ ...it, name: it.name.slice(0, 255) }));
|
|
@@ -40258,6 +41468,19 @@ Add this to your authenticator, then run: kt mfa verify --code <code>`);
|
|
|
40258
41468
|
}
|
|
40259
41469
|
break;
|
|
40260
41470
|
}
|
|
41471
|
+
// The adaptive gate's step-up is a per-session proof (server lib/step-up.ts), so one code here clears
|
|
41472
|
+
// the refusal for every command this session runs until it lapses. The code never rides on argv.
|
|
41473
|
+
case "step-up": {
|
|
41474
|
+
requireAuth();
|
|
41475
|
+
if (positionals.length || flags.code !== void 0) die("usage: kt step-up (the authenticator code is asked for, or read from stdin)");
|
|
41476
|
+
const status = await client.mfaStatus();
|
|
41477
|
+
if (status.methods.totp !== true) die("this account has no authenticator app to step up with; enrol one with `kt mfa setup`, then `kt mfa verify --code <code>`, then run `kt step-up`");
|
|
41478
|
+
const code = await prompt("authenticator code: ", true);
|
|
41479
|
+
if (!code) die("no authenticator code given");
|
|
41480
|
+
await client.stepUpTotp(code);
|
|
41481
|
+
console.log("step-up verified; re-run the refused command within a few minutes");
|
|
41482
|
+
break;
|
|
41483
|
+
}
|
|
40261
41484
|
case "passkeys":
|
|
40262
41485
|
requireAuth();
|
|
40263
41486
|
show((await client.listPasskeys()).passkeys, ["id", "label", "kind", "created_at", "last_used"]);
|
|
@@ -40287,12 +41510,36 @@ run \`kt --help\` for the list of commands`);
|
|
|
40287
41510
|
}
|
|
40288
41511
|
}
|
|
40289
41512
|
installFetchProxy();
|
|
40290
|
-
|
|
41513
|
+
var argv = parseArgs(process.argv.slice(2));
|
|
41514
|
+
var hubSlots = { get: getToken, set: setToken, clear: clearToken };
|
|
41515
|
+
var hubSignInAvailable = (e) => e instanceof ApiError && e.status === 401 && appDataDir() !== null && osKeychainAvailable();
|
|
41516
|
+
retryAfterHubReborrow(main, {
|
|
41517
|
+
refusedSession: (e) => refusedSavedSession(e, argv),
|
|
41518
|
+
wrote: sessionWrites.wrote,
|
|
41519
|
+
lastAccount: () => lastAccountId(resolveBase(argv.flags.base), hubSlots),
|
|
41520
|
+
reborrow: async () => {
|
|
41521
|
+
const base = resolveBase(argv.flags.base);
|
|
41522
|
+
const root = appDataDir();
|
|
41523
|
+
if (!root) return { kind: "fallback", note: null, reason: "hub_not_running" };
|
|
41524
|
+
const hub = await reborrowFromHub({
|
|
41525
|
+
base,
|
|
41526
|
+
location: hubLocation(root),
|
|
41527
|
+
kc: hubSlots,
|
|
41528
|
+
keychainAvailable: osKeychainAvailable,
|
|
41529
|
+
call: hubCallFor(base),
|
|
41530
|
+
out: (l) => console.error(l),
|
|
41531
|
+
ask: (q) => promptLine(q)
|
|
41532
|
+
});
|
|
41533
|
+
if (hub.kind === "signed_in") recordSignIn(root, hub.user);
|
|
41534
|
+
return hub.kind === "fallback" && hubAbsent(hub.reason) ? { ...hub, note: null } : hub;
|
|
41535
|
+
},
|
|
41536
|
+
out: (l) => console.error(l)
|
|
41537
|
+
}).catch((e) => {
|
|
40291
41538
|
const api3 = e instanceof ApiError ? e : null;
|
|
40292
41539
|
const code = api3 ? exitForStatus(api3.status) : EXIT_ERROR;
|
|
40293
41540
|
const message = redactProxyUrls(e instanceof Error ? e.message : String(e));
|
|
40294
|
-
const lines = (storeFailure(e) ?? refusalLines(e) ?? [`error: ${message}`]).map(redactProxyUrls);
|
|
40295
|
-
if (
|
|
41541
|
+
const lines = (e instanceof HubAccountSwitchedError ? [e.message] : storeFailure(e) ?? refusalLines(e, " ", { hubSignIn: hubSignInAvailable(e) }) ?? [`error: ${message}`]).map(redactProxyUrls);
|
|
41542
|
+
if (argv.flags.json === true) {
|
|
40296
41543
|
console.log(JSON.stringify({
|
|
40297
41544
|
error: api3 ? refusalCode(api3) : message,
|
|
40298
41545
|
...api3 ? { status: api3.status } : {},
|