@kutrion/cli 1.2.14 → 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 +12 -1
- package/dist/index.js +1294 -279
- 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
|
|
|
@@ -24056,45 +24056,148 @@ function auditResultLabel(raw) {
|
|
|
24056
24056
|
|
|
24057
24057
|
// ../packages/shared/src/refresh-refusal-copy.ts
|
|
24058
24058
|
init_define_KT_HUB_FILES();
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
},
|
|
24067
|
-
|
|
24068
|
-
|
|
24069
|
-
|
|
24070
|
-
|
|
24071
|
-
},
|
|
24072
|
-
|
|
24073
|
-
|
|
24074
|
-
|
|
24075
|
-
|
|
24076
|
-
|
|
24077
|
-
|
|
24078
|
-
|
|
24079
|
-
|
|
24080
|
-
|
|
24081
|
-
|
|
24082
|
-
|
|
24083
|
-
|
|
24084
|
-
|
|
24085
|
-
|
|
24086
|
-
|
|
24087
|
-
|
|
24088
|
-
|
|
24089
|
-
|
|
24090
|
-
|
|
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
|
+
}
|
|
24091
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}`;
|
|
24092
24168
|
};
|
|
24093
|
-
|
|
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) {
|
|
24094
24178
|
if (typeof code !== "string" || !Object.prototype.hasOwnProperty.call(REFRESH_REFUSAL_COPY, code)) return null;
|
|
24095
|
-
const
|
|
24096
|
-
const
|
|
24097
|
-
|
|
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 });
|
|
24098
24201
|
}
|
|
24099
24202
|
|
|
24100
24203
|
// ../packages/shared/src/client.ts
|
|
@@ -27849,9 +27952,13 @@ async function withTimeout(inner, ms, input, init) {
|
|
|
27849
27952
|
var REFRESH_BODY_MS = 3e3;
|
|
27850
27953
|
var REFRESH_STORE_ATTEMPTS = 3;
|
|
27851
27954
|
var REFRESH_STORE_TIMEOUT_MS = 1e4;
|
|
27955
|
+
var SIGNATURE_REQUIRED_COOLDOWN_MS = 3e4;
|
|
27852
27956
|
function isRefreshSignatureRefusal(body) {
|
|
27853
27957
|
const error = body?.error;
|
|
27854
|
-
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";
|
|
27855
27962
|
}
|
|
27856
27963
|
async function readBodyWithin(res, ms) {
|
|
27857
27964
|
let timer;
|
|
@@ -28010,6 +28117,8 @@ var KutrionClient = class {
|
|
|
28010
28117
|
// `next` is presented in its place: `spent` is already rotated, and presenting it reads as reuse.
|
|
28011
28118
|
heldRefresh = null;
|
|
28012
28119
|
refreshing = null;
|
|
28120
|
+
// performance.now() time, so a wall-clock step cannot lengthen or cut the cooldown.
|
|
28121
|
+
signatureRequiredUntil = 0;
|
|
28013
28122
|
// VT-405 (#153): per-session key for decrypting sealed response payloads. Agreed lazily over ECDH
|
|
28014
28123
|
// and held in memory only (re-agreed on demand after a reload / worker restart).
|
|
28015
28124
|
sessionKey = null;
|
|
@@ -28089,6 +28198,7 @@ var KutrionClient = class {
|
|
|
28089
28198
|
async tryRefresh() {
|
|
28090
28199
|
const rt = this.presentableRefreshToken();
|
|
28091
28200
|
if (!rt) return { outcome: "no_token" };
|
|
28201
|
+
if (!this.refreshing && performance.now() < this.signatureRequiredUntil) return { outcome: "indeterminate", status: 401 };
|
|
28092
28202
|
if (!this.refreshing) {
|
|
28093
28203
|
this.refreshing = (async () => {
|
|
28094
28204
|
try {
|
|
@@ -28110,7 +28220,8 @@ var KutrionClient = class {
|
|
|
28110
28220
|
}
|
|
28111
28221
|
if (res.status === 401) {
|
|
28112
28222
|
if (refusalBody === void 0) refusalBody = await readBodyWithin(res, REFRESH_BODY_MS);
|
|
28113
|
-
if (signerThrew && !signed.signature && isRefreshSignatureRefusal(refusalBody)) {
|
|
28223
|
+
if (signerThrew && !signed.signature && isRefreshSignatureRefusal(refusalBody) || isSignatureRequired(refusalBody)) {
|
|
28224
|
+
if (!signerThrew && isSignatureRequired(refusalBody)) this.signatureRequiredUntil = performance.now() + SIGNATURE_REQUIRED_COOLDOWN_MS;
|
|
28114
28225
|
return { outcome: "indeterminate", status: 401 };
|
|
28115
28226
|
}
|
|
28116
28227
|
const code = refusalBody?.error;
|
|
@@ -28499,7 +28610,10 @@ var KutrionClient = class {
|
|
|
28499
28610
|
// losing its keys; the server revokes that row if it is the caller's and ignores it otherwise.
|
|
28500
28611
|
async registerDevice(input) {
|
|
28501
28612
|
const reg = await this.req("POST", "/auth/devices/register", input);
|
|
28502
|
-
if (reg.refresh_key_bound)
|
|
28613
|
+
if (reg.refresh_key_bound) {
|
|
28614
|
+
this.refreshSigningRefused = false;
|
|
28615
|
+
this.signatureRequiredUntil = 0;
|
|
28616
|
+
}
|
|
28503
28617
|
return reg;
|
|
28504
28618
|
}
|
|
28505
28619
|
// #301: mint a second refresh-token family for an existing device, owned by a companion process
|
|
@@ -28523,8 +28637,11 @@ var KutrionClient = class {
|
|
|
28523
28637
|
try {
|
|
28524
28638
|
return await this.req("POST", "/auth/refresh", body, false);
|
|
28525
28639
|
} catch (e) {
|
|
28526
|
-
if (
|
|
28527
|
-
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
|
+
}
|
|
28528
28645
|
}
|
|
28529
28646
|
throw e;
|
|
28530
28647
|
}
|
|
@@ -28544,7 +28661,10 @@ var KutrionClient = class {
|
|
|
28544
28661
|
refreshKey ? { device_id: deviceId, signature, refresh_key: refreshKey } : { device_id: deviceId, signature },
|
|
28545
28662
|
false
|
|
28546
28663
|
);
|
|
28547
|
-
if (tokens.refresh_key_bound)
|
|
28664
|
+
if (tokens.refresh_key_bound) {
|
|
28665
|
+
this.refreshSigningRefused = false;
|
|
28666
|
+
this.signatureRequiredUntil = 0;
|
|
28667
|
+
}
|
|
28548
28668
|
return tokens;
|
|
28549
28669
|
}
|
|
28550
28670
|
// --- local vault cache: keys (#709, epic #708) ---
|
|
@@ -28900,16 +29020,25 @@ var KutrionClient = class {
|
|
|
28900
29020
|
// `port` omitted means this protocol's default, resolved and stored server-side — NOT a wildcard, and
|
|
28901
29021
|
// not something to fill in here: a client-supplied default would come back as a chosen port.
|
|
28902
29022
|
//
|
|
28903
|
-
//
|
|
28904
|
-
//
|
|
28905
|
-
// the
|
|
28906
|
-
//
|
|
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`.
|
|
28907
29032
|
//
|
|
28908
29033
|
// #1170: `domain` is RDP-only and omitting it means the credential is presented with NO domain — not
|
|
28909
29034
|
// "any domain". It is the third member of the RDP destination tuple because it selects which
|
|
28910
29035
|
// directory authenticates the credential, so a binding that omits it does not authorise one that
|
|
28911
29036
|
// names it. Sent on any other protocol it answers 400 `domain_not_supported`; unexpressible values
|
|
28912
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.
|
|
28913
29042
|
addCredentialDestination(vaultId, cid, input) {
|
|
28914
29043
|
return this.req("POST", `/vault/${encodeURIComponent(vaultId)}/credentials/${encodeURIComponent(cid)}/destinations`, input);
|
|
28915
29044
|
}
|
|
@@ -29567,6 +29696,12 @@ var KutrionClient = class {
|
|
|
29567
29696
|
listAgentOidcTrusts() {
|
|
29568
29697
|
return this.req("GET", "/agents/oidc/trusts");
|
|
29569
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
|
+
}
|
|
29570
29705
|
setAgentOidcTrustResourceScope(id, scope, opts2 = {}) {
|
|
29571
29706
|
return this.req("PUT", `/agents/oidc/trusts/${encodeURIComponent(id)}/resource-scope`, { ...scope, ...opts2 });
|
|
29572
29707
|
}
|
|
@@ -29693,8 +29828,9 @@ var KutrionClient = class {
|
|
|
29693
29828
|
}
|
|
29694
29829
|
// Pre-flight risk guardrail (#141): a high-risk command 403s with { error: "step_up_required" }
|
|
29695
29830
|
// until the session completes WebAuthn step-up; retry after stepping up.
|
|
29696
|
-
|
|
29697
|
-
|
|
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 });
|
|
29698
29834
|
}
|
|
29699
29835
|
validateCommand(command) {
|
|
29700
29836
|
return this.req("POST", "/ssh/validate", { command });
|
|
@@ -29711,7 +29847,7 @@ var KutrionClient = class {
|
|
|
29711
29847
|
*
|
|
29712
29848
|
* `rdp.tunnel` is deliberately not mintable here — the RDP broker returns its own on the
|
|
29713
29849
|
* session-open response (`tunnel.ws_ticket`), and this route refuses the scope so a caller without
|
|
29714
|
-
* 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).
|
|
29715
29851
|
*/
|
|
29716
29852
|
mintWsTicket(scope, resourceId) {
|
|
29717
29853
|
return this.req(
|
|
@@ -30113,9 +30249,20 @@ var KutrionClient = class {
|
|
|
30113
30249
|
listFileConnections() {
|
|
30114
30250
|
return this.req("GET", "/files/connections");
|
|
30115
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
|
+
}
|
|
30116
30262
|
createFileConnection(input) {
|
|
30117
30263
|
return this.req("POST", "/files/connections", input);
|
|
30118
30264
|
}
|
|
30265
|
+
// #3456: `username: null` switches the connection to inherit the credential's username.
|
|
30119
30266
|
updateFileConnection(id, input) {
|
|
30120
30267
|
return this.req("PATCH", `/files/connections/${encodeURIComponent(id)}`, input);
|
|
30121
30268
|
}
|
|
@@ -30332,6 +30479,10 @@ var KutrionClient = class {
|
|
|
30332
30479
|
patchTransferJob(id, input) {
|
|
30333
30480
|
return this.req("PATCH", `/files/jobs/${encodeURIComponent(id)}`, input);
|
|
30334
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`, {});
|
|
30485
|
+
}
|
|
30335
30486
|
// #563: recurring transfer schedules (interval or daily UTC time).
|
|
30336
30487
|
createSchedule(input) {
|
|
30337
30488
|
return this.req("POST", "/files/schedules", input);
|
|
@@ -30958,50 +31109,65 @@ function parseDestinationRefusal(body) {
|
|
|
30958
31109
|
reason: DESTINATION_REFUSAL_REASONS.includes(b.reason) ? b.reason : null
|
|
30959
31110
|
};
|
|
30960
31111
|
}
|
|
30961
|
-
var DESTINATION_REFUSAL_TITLE = "
|
|
30962
|
-
var
|
|
30963
|
-
function destinationRefusalSummary(r) {
|
|
30964
|
-
const
|
|
30965
|
-
|
|
30966
|
-
}
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
if (r.
|
|
30970
|
-
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
var
|
|
30974
|
-
var
|
|
30975
|
-
var
|
|
30976
|
-
var
|
|
30977
|
-
var
|
|
30978
|
-
var
|
|
30979
|
-
|
|
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) {
|
|
30980
31133
|
if (r.reason === "host_not_normalizable" || r.reason === "domain_not_normalizable") {
|
|
30981
|
-
return
|
|
31134
|
+
return securityCopy("destinationRefusal.title.connection", t);
|
|
30982
31135
|
}
|
|
30983
|
-
return destinationRefusalFault(r) === "kutrion" ?
|
|
31136
|
+
return securityCopy(destinationRefusalFault(r) === "kutrion" ? "destinationRefusal.title.fault" : "destinationRefusal.title", t);
|
|
30984
31137
|
}
|
|
30985
|
-
function destinationRefusalAction(r) {
|
|
30986
|
-
if (r
|
|
30987
|
-
if (r.reason === "
|
|
30988
|
-
|
|
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);
|
|
30989
31143
|
}
|
|
30990
|
-
function destinationRefusalLines(r, indent = " ") {
|
|
31144
|
+
function destinationRefusalLines(r, indent = " ", t) {
|
|
30991
31145
|
return [
|
|
30992
|
-
destinationRefusalTitle(r),
|
|
31146
|
+
destinationRefusalTitle(r, t),
|
|
30993
31147
|
"",
|
|
30994
|
-
destinationRefusalSummary(r),
|
|
31148
|
+
destinationRefusalSummary(r, t),
|
|
30995
31149
|
"",
|
|
30996
|
-
destinationRefusalAllowedLabel(r),
|
|
31150
|
+
destinationRefusalAllowedLabel(r, t),
|
|
30997
31151
|
...r.allowedDestinations.map((d) => `${indent}${d}`),
|
|
30998
31152
|
"",
|
|
30999
|
-
destinationRefusalAction(r),
|
|
31153
|
+
destinationRefusalAction(r, void 0, t),
|
|
31000
31154
|
// Only true of a refusal that turned on where the credential may go. On a fault of ours the secret
|
|
31001
31155
|
// was never the subject, and the line reads as one more thing the user got wrong.
|
|
31002
|
-
...destinationRefusalFault(r) === "kutrion" ? [] : [
|
|
31156
|
+
...destinationRefusalFault(r) === "kutrion" ? [] : [securityCopy("destinationRefusal.notARemedy", t)]
|
|
31003
31157
|
];
|
|
31004
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"];
|
|
31005
31171
|
|
|
31006
31172
|
// ../packages/shared/src/credential-size-refusal.ts
|
|
31007
31173
|
init_define_KT_HUB_FILES();
|
|
@@ -31163,6 +31329,16 @@ function platformFlagRefusalText(code) {
|
|
|
31163
31329
|
return code && Object.prototype.hasOwnProperty.call(PLATFORM_FLAG_REFUSAL_COPY, code) ? PLATFORM_FLAG_REFUSAL_COPY[code] : null;
|
|
31164
31330
|
}
|
|
31165
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
|
+
|
|
31166
31342
|
// ../packages/shared/src/ssh-refusal.ts
|
|
31167
31343
|
init_define_KT_HUB_FILES();
|
|
31168
31344
|
var SSH_REFUSAL_CLASSIFICATION = {
|
|
@@ -31408,12 +31584,12 @@ function parseJumpRefusal(body) {
|
|
|
31408
31584
|
const code = str4(b.code) ?? str4(b.error);
|
|
31409
31585
|
if (!isJumpRefusalCode(code)) return null;
|
|
31410
31586
|
const nested = b.detail && typeof b.detail === "object" ? b.detail : {};
|
|
31411
|
-
const
|
|
31412
|
-
const idx =
|
|
31413
|
-
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");
|
|
31414
31590
|
return {
|
|
31415
31591
|
code,
|
|
31416
|
-
hopConnectionId: str4(
|
|
31592
|
+
hopConnectionId: str4(pick3("hop_connection_id")),
|
|
31417
31593
|
hopIndex: typeof idx === "number" && Number.isFinite(idx) ? idx : null,
|
|
31418
31594
|
detail: typeof b.detail === "string" ? str4(b.detail) : str4(nested.detail) ?? str4(b.error_description),
|
|
31419
31595
|
allowedDestinations: Array.isArray(allowed) ? allowed.filter((x) => typeof x === "string" && x.trim() !== "") : null
|
|
@@ -31472,12 +31648,12 @@ function parseJumpHostInUse(body) {
|
|
|
31472
31648
|
var JUMP_HOST_IN_USE_TITLE = "Other connections are reached through this one";
|
|
31473
31649
|
function jumpHostInUseSummary(r, deletingName) {
|
|
31474
31650
|
const name = str4(deletingName);
|
|
31475
|
-
const
|
|
31651
|
+
const subject = name ? `\u201C${name}\u201D` : "this connection";
|
|
31476
31652
|
const n = r.usedBy.length;
|
|
31477
31653
|
if (!n) {
|
|
31478
|
-
return `${
|
|
31654
|
+
return `${subject} is used as a jump host by other connections, so deleting it would leave them with no route.`;
|
|
31479
31655
|
}
|
|
31480
|
-
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.";
|
|
31481
31657
|
}
|
|
31482
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.";
|
|
31483
31659
|
|
|
@@ -32567,6 +32743,12 @@ function subtle() {
|
|
|
32567
32743
|
if (!s || typeof globalThis.crypto.getRandomValues !== "function") throw new VaultKeyError("crypto_unavailable");
|
|
32568
32744
|
return s;
|
|
32569
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
|
+
}
|
|
32570
32752
|
function fromB64(b64) {
|
|
32571
32753
|
const s = atob(b64);
|
|
32572
32754
|
const out = new Uint8Array(s.length);
|
|
@@ -32612,6 +32794,10 @@ async function derivePassphraseKek(passphrase, kdf, onProgress) {
|
|
|
32612
32794
|
out.fill(0);
|
|
32613
32795
|
}
|
|
32614
32796
|
}
|
|
32797
|
+
function generateVaultKey() {
|
|
32798
|
+
subtle();
|
|
32799
|
+
return random(VAULT_KEY_BYTES);
|
|
32800
|
+
}
|
|
32615
32801
|
async function unwrapVaultKey(wrap, kek, purpose) {
|
|
32616
32802
|
let bytes;
|
|
32617
32803
|
try {
|
|
@@ -32647,6 +32833,15 @@ async function unlockVaultKey(record, passphrase, onProgress) {
|
|
|
32647
32833
|
}
|
|
32648
32834
|
var dataKeyAad = (vaultId) => enc.encode(`kutrion-vault-key-v1:data-key:${vaultId.toLowerCase()}`);
|
|
32649
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
|
+
}
|
|
32650
32845
|
async function unwrapVaultDataKey(wrap, vaultKey, vaultId) {
|
|
32651
32846
|
let bytes;
|
|
32652
32847
|
try {
|
|
@@ -32670,6 +32865,14 @@ async function unwrapVaultDataKey(wrap, vaultKey, vaultId) {
|
|
|
32670
32865
|
}
|
|
32671
32866
|
}
|
|
32672
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
|
+
}
|
|
32673
32876
|
async function openItemField(dataKey, iv, blob, vaultId, itemId, purpose) {
|
|
32674
32877
|
let ivBytes, b;
|
|
32675
32878
|
try {
|
|
@@ -32728,6 +32931,40 @@ async function openPrivateItem(item, sealed, vaultKey) {
|
|
|
32728
32931
|
]);
|
|
32729
32932
|
return { value: value ?? "", notes, custom_fields: fields ? JSON.parse(fields) : [], passphrase, totp };
|
|
32730
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
|
+
}
|
|
32731
32968
|
|
|
32732
32969
|
// ../packages/shared/src/private-vault-copy.ts
|
|
32733
32970
|
init_define_KT_HUB_FILES();
|
|
@@ -32812,6 +33049,7 @@ var PRIVATE_VAULT_COPY = {
|
|
|
32812
33049
|
|
|
32813
33050
|
// src/args.ts
|
|
32814
33051
|
init_define_KT_HUB_FILES();
|
|
33052
|
+
var REPEATABLE = /* @__PURE__ */ new Set(["vault", "connection", "scope-vault", "scope-conn"]);
|
|
32815
33053
|
function parseForwardSpec(spec) {
|
|
32816
33054
|
const m = spec.match(/^(\d{1,5}):(.+):(\d{1,5})$/);
|
|
32817
33055
|
if (!m) return null;
|
|
@@ -32823,10 +33061,12 @@ function parseForwardSpec(spec) {
|
|
|
32823
33061
|
return { localPort, dstHost, dstPort };
|
|
32824
33062
|
}
|
|
32825
33063
|
var GLOBAL_FLAGS = { base: "value", json: "boolean" };
|
|
32826
|
-
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"]);
|
|
32827
33065
|
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
32828
33066
|
"action",
|
|
32829
33067
|
"agent-forward",
|
|
33068
|
+
"allow-insecure-lan",
|
|
33069
|
+
"audience",
|
|
32830
33070
|
"base",
|
|
32831
33071
|
"browser",
|
|
32832
33072
|
"client",
|
|
@@ -32838,6 +33078,7 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32838
33078
|
"conflict",
|
|
32839
33079
|
"conn",
|
|
32840
33080
|
"conns",
|
|
33081
|
+
"connection",
|
|
32841
33082
|
"cred",
|
|
32842
33083
|
"current",
|
|
32843
33084
|
"daily-at",
|
|
@@ -32853,7 +33094,9 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32853
33094
|
"fingerprint",
|
|
32854
33095
|
"format",
|
|
32855
33096
|
"from",
|
|
33097
|
+
"help",
|
|
32856
33098
|
"host",
|
|
33099
|
+
"issuer",
|
|
32857
33100
|
"insert",
|
|
32858
33101
|
"intent",
|
|
32859
33102
|
"json",
|
|
@@ -32890,12 +33133,16 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32890
33133
|
"reveal",
|
|
32891
33134
|
"role",
|
|
32892
33135
|
"scope",
|
|
33136
|
+
"scope-conn",
|
|
33137
|
+
"scope-vault",
|
|
32893
33138
|
"scopes",
|
|
32894
33139
|
"secret",
|
|
32895
33140
|
"service",
|
|
32896
33141
|
"session",
|
|
32897
33142
|
"since",
|
|
32898
33143
|
"status",
|
|
33144
|
+
"stdin",
|
|
33145
|
+
"subject",
|
|
32899
33146
|
"target",
|
|
32900
33147
|
"throttle",
|
|
32901
33148
|
"timeout",
|
|
@@ -32913,13 +33160,22 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
32913
33160
|
"verify",
|
|
32914
33161
|
"views",
|
|
32915
33162
|
"wait",
|
|
33163
|
+
"write-env",
|
|
32916
33164
|
"yes"
|
|
32917
33165
|
]);
|
|
32918
33166
|
function unknownFlags(flags) {
|
|
32919
33167
|
return Object.keys(flags).filter((k) => !KNOWN_FLAGS.has(k));
|
|
32920
33168
|
}
|
|
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
|
+
}
|
|
32921
33176
|
function parseArgs(argv2) {
|
|
32922
33177
|
const flags = {};
|
|
33178
|
+
const multi = {};
|
|
32923
33179
|
let at = 0;
|
|
32924
33180
|
while (at < argv2.length && argv2[at].startsWith("--")) {
|
|
32925
33181
|
const key = argv2[at].slice(2);
|
|
@@ -32937,13 +33193,19 @@ function parseArgs(argv2) {
|
|
|
32937
33193
|
const cmd = argv2[at];
|
|
32938
33194
|
const rest = argv2.slice(at + 1);
|
|
32939
33195
|
const positionals = [];
|
|
33196
|
+
if (cmd === "exec") return { cmd, positionals: parseExecArgs(rest, flags), flags, multi };
|
|
32940
33197
|
for (let i = 0; i < rest.length; i++) {
|
|
32941
33198
|
const a = rest[i];
|
|
33199
|
+
if (a === "--") {
|
|
33200
|
+
positionals.push(...rest.slice(i + 1));
|
|
33201
|
+
break;
|
|
33202
|
+
}
|
|
32942
33203
|
if (a.startsWith("--")) {
|
|
32943
33204
|
const key = a.slice(2);
|
|
32944
33205
|
const next = rest[i + 1];
|
|
32945
33206
|
if (!SWITCHES.has(key) && next !== void 0 && !next.startsWith("--")) {
|
|
32946
33207
|
flags[key] = next;
|
|
33208
|
+
if (REPEATABLE.has(key)) (multi[key] ??= []).push(next);
|
|
32947
33209
|
i++;
|
|
32948
33210
|
} else {
|
|
32949
33211
|
flags[key] = true;
|
|
@@ -32952,7 +33214,134 @@ function parseArgs(argv2) {
|
|
|
32952
33214
|
positionals.push(a);
|
|
32953
33215
|
}
|
|
32954
33216
|
}
|
|
32955
|
-
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
|
+
}
|
|
32956
33345
|
}
|
|
32957
33346
|
|
|
32958
33347
|
// src/history.ts
|
|
@@ -33005,7 +33394,7 @@ function exitForStatus(status) {
|
|
|
33005
33394
|
init_define_KT_HUB_FILES();
|
|
33006
33395
|
var import_child_process2 = require("child_process");
|
|
33007
33396
|
var import_crypto = require("crypto");
|
|
33008
|
-
var
|
|
33397
|
+
var import_fs3 = require("fs");
|
|
33009
33398
|
var import_os = require("os");
|
|
33010
33399
|
var import_path2 = require("path");
|
|
33011
33400
|
|
|
@@ -33103,7 +33492,7 @@ function run(bin, args, store, verb, input) {
|
|
|
33103
33492
|
var SECRET_TOOL_DIRS = ["/usr/bin", "/bin", "/usr/local/bin", "/run/current-system/sw/bin"];
|
|
33104
33493
|
var isExecutable = (p) => {
|
|
33105
33494
|
try {
|
|
33106
|
-
(0,
|
|
33495
|
+
(0, import_fs3.accessSync)(p, import_fs3.constants.X_OK);
|
|
33107
33496
|
return true;
|
|
33108
33497
|
} catch {
|
|
33109
33498
|
return false;
|
|
@@ -33222,7 +33611,7 @@ function why(r, ...secrets) {
|
|
|
33222
33611
|
function fileStore(file = FILE) {
|
|
33223
33612
|
let raw;
|
|
33224
33613
|
try {
|
|
33225
|
-
raw = (0,
|
|
33614
|
+
raw = (0, import_fs3.readFileSync)(file, "utf8");
|
|
33226
33615
|
} catch (e) {
|
|
33227
33616
|
if (e.code === "ENOENT") return {};
|
|
33228
33617
|
throw new Error(`cannot read the token file ${file}: ${e.message}`);
|
|
@@ -33239,9 +33628,9 @@ function fileStore(file = FILE) {
|
|
|
33239
33628
|
var dirLocked = false;
|
|
33240
33629
|
function ensureFileDir() {
|
|
33241
33630
|
const dir = (0, import_path2.dirname)(FILE);
|
|
33242
|
-
(0,
|
|
33631
|
+
(0, import_fs3.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
33243
33632
|
if ((0, import_os.platform)() !== "win32") try {
|
|
33244
|
-
(0,
|
|
33633
|
+
(0, import_fs3.chmodSync)(dir, 448);
|
|
33245
33634
|
} catch {
|
|
33246
33635
|
}
|
|
33247
33636
|
else if (!dirLocked) {
|
|
@@ -33270,32 +33659,32 @@ function withLockFile(lock, fn, afterJudge) {
|
|
|
33270
33659
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
33271
33660
|
for (; ; ) {
|
|
33272
33661
|
try {
|
|
33273
|
-
const fd = (0,
|
|
33662
|
+
const fd = (0, import_fs3.openSync)(lock, "wx", 384);
|
|
33274
33663
|
try {
|
|
33275
|
-
(0,
|
|
33664
|
+
(0, import_fs3.writeSync)(fd, owner);
|
|
33276
33665
|
} finally {
|
|
33277
|
-
(0,
|
|
33666
|
+
(0, import_fs3.closeSync)(fd);
|
|
33278
33667
|
}
|
|
33279
33668
|
break;
|
|
33280
33669
|
} catch (e) {
|
|
33281
33670
|
const code = e.code;
|
|
33282
33671
|
if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") throw e;
|
|
33283
|
-
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}`);
|
|
33284
33673
|
try {
|
|
33285
|
-
if (Date.now() - (0,
|
|
33286
|
-
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");
|
|
33287
33676
|
afterJudge?.();
|
|
33288
33677
|
const gone = `${lock}.${process.pid}.${(0, import_crypto.randomBytes)(4).toString("hex")}`;
|
|
33289
|
-
(0,
|
|
33290
|
-
if ((0,
|
|
33291
|
-
(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 });
|
|
33292
33681
|
continue;
|
|
33293
33682
|
}
|
|
33294
33683
|
try {
|
|
33295
|
-
(0,
|
|
33684
|
+
(0, import_fs3.linkSync)(gone, lock);
|
|
33296
33685
|
} catch {
|
|
33297
33686
|
}
|
|
33298
|
-
(0,
|
|
33687
|
+
(0, import_fs3.rmSync)(gone, { force: true });
|
|
33299
33688
|
}
|
|
33300
33689
|
} catch {
|
|
33301
33690
|
}
|
|
@@ -33307,7 +33696,7 @@ function withLockFile(lock, fn, afterJudge) {
|
|
|
33307
33696
|
return fn();
|
|
33308
33697
|
} finally {
|
|
33309
33698
|
try {
|
|
33310
|
-
if ((0,
|
|
33699
|
+
if ((0, import_fs3.readFileSync)(lock, "utf8") === owner) (0, import_fs3.rmSync)(lock, { force: true });
|
|
33311
33700
|
} catch {
|
|
33312
33701
|
}
|
|
33313
33702
|
}
|
|
@@ -33316,10 +33705,10 @@ var withFileLock = (fn) => withLockFile(`${FILE}.lock`, fn);
|
|
|
33316
33705
|
function sweepStaleTemps(file) {
|
|
33317
33706
|
const dir = (0, import_path2.dirname)(file), prefix = `${(0, import_path2.basename)(file)}.`;
|
|
33318
33707
|
try {
|
|
33319
|
-
for (const n of (0,
|
|
33708
|
+
for (const n of (0, import_fs3.readdirSync)(dir)) {
|
|
33320
33709
|
if (!n.startsWith(prefix) || !n.endsWith(".tmp")) continue;
|
|
33321
33710
|
try {
|
|
33322
|
-
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 });
|
|
33323
33712
|
} catch {
|
|
33324
33713
|
}
|
|
33325
33714
|
}
|
|
@@ -33329,16 +33718,16 @@ function sweepStaleTemps(file) {
|
|
|
33329
33718
|
function writeFileStore(store) {
|
|
33330
33719
|
const tmp = `${FILE}.${process.pid}.${(0, import_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
33331
33720
|
try {
|
|
33332
|
-
(0,
|
|
33721
|
+
(0, import_fs3.closeSync)((0, import_fs3.openSync)(tmp, "wx", 384));
|
|
33333
33722
|
if ((0, import_os.platform)() === "win32") lockDownWin(tmp, "file");
|
|
33334
33723
|
else try {
|
|
33335
|
-
(0,
|
|
33724
|
+
(0, import_fs3.chmodSync)(tmp, 384);
|
|
33336
33725
|
} catch {
|
|
33337
33726
|
}
|
|
33338
|
-
(0,
|
|
33727
|
+
(0, import_fs3.writeFileSync)(tmp, JSON.stringify(store));
|
|
33339
33728
|
for (let i = 0; ; i++) {
|
|
33340
33729
|
try {
|
|
33341
|
-
(0,
|
|
33730
|
+
(0, import_fs3.renameSync)(tmp, FILE);
|
|
33342
33731
|
break;
|
|
33343
33732
|
} catch (e) {
|
|
33344
33733
|
const code = e.code;
|
|
@@ -33347,7 +33736,7 @@ function writeFileStore(store) {
|
|
|
33347
33736
|
}
|
|
33348
33737
|
}
|
|
33349
33738
|
} catch (e) {
|
|
33350
|
-
(0,
|
|
33739
|
+
(0, import_fs3.rmSync)(tmp, { force: true });
|
|
33351
33740
|
throw e;
|
|
33352
33741
|
}
|
|
33353
33742
|
}
|
|
@@ -33358,7 +33747,7 @@ function updateFileStore(mutate) {
|
|
|
33358
33747
|
const store = fileStore();
|
|
33359
33748
|
if (!mutate(store)) return;
|
|
33360
33749
|
if (Object.keys(store).length) writeFileStore(store);
|
|
33361
|
-
else (0,
|
|
33750
|
+
else (0, import_fs3.rmSync)(FILE, { force: true });
|
|
33362
33751
|
});
|
|
33363
33752
|
}
|
|
33364
33753
|
function dropFileEntry(base) {
|
|
@@ -34980,9 +35369,9 @@ function installLocationFromRegistry(regOutput) {
|
|
|
34980
35369
|
}
|
|
34981
35370
|
function underInstallRoot(dir, roots) {
|
|
34982
35371
|
if (!import_node_path3.win32.isAbsolute(dir)) return false;
|
|
34983
|
-
const
|
|
34984
|
-
const d =
|
|
34985
|
-
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) + "\\"));
|
|
34986
35375
|
}
|
|
34987
35376
|
function hubLauncher(env = {}) {
|
|
34988
35377
|
const platform3 = env.platform ?? process.platform;
|
|
@@ -35134,13 +35523,14 @@ var host = () => (0, import_os3.hostname)().trim().replace(/\.local$/, "") || "t
|
|
|
35134
35523
|
var ownDeviceName = () => `Kutrion CLI on ${host()}`.slice(0, 255);
|
|
35135
35524
|
async function registerOwnDevice(deps) {
|
|
35136
35525
|
const tty = deps.tty ?? Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
35137
|
-
|
|
35526
|
+
const previous = deps.previousDeviceId ?? null;
|
|
35527
|
+
if (!previous && seedSkipReason(deps.env ?? process.env, deps.platform ?? process.platform, tty)) return { kind: "skipped", note: null };
|
|
35138
35528
|
if (!deps.bearer || !deps.keychainAvailable()) return { kind: "skipped", note: null };
|
|
35139
35529
|
const { store, failedWrite } = cliHubStores(deps.base, deps.kc, deps.keychainAvailable);
|
|
35140
35530
|
const key = resolveDeviceKey(store);
|
|
35141
35531
|
const res = await deps.post(
|
|
35142
35532
|
"/auth/devices/register",
|
|
35143
|
-
{ 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 } : {} },
|
|
35144
35534
|
deps.bearer
|
|
35145
35535
|
);
|
|
35146
35536
|
if (!res.ok) {
|
|
@@ -35310,16 +35700,16 @@ function redactProxyUrls(text2) {
|
|
|
35310
35700
|
function bypassesProxy(host2, port, no) {
|
|
35311
35701
|
if (!no) return false;
|
|
35312
35702
|
for (const raw of no.split(/[\s,]+/)) {
|
|
35313
|
-
let
|
|
35314
|
-
if (!
|
|
35315
|
-
if (
|
|
35316
|
-
const colon =
|
|
35703
|
+
let entry2 = raw.trim().toLowerCase();
|
|
35704
|
+
if (!entry2) continue;
|
|
35705
|
+
if (entry2 === "*") return true;
|
|
35706
|
+
const colon = entry2.match(/^(.*):(\d+)$/);
|
|
35317
35707
|
if (colon) {
|
|
35318
35708
|
if (colon[2] !== port) continue;
|
|
35319
|
-
|
|
35709
|
+
entry2 = colon[1];
|
|
35320
35710
|
}
|
|
35321
|
-
|
|
35322
|
-
if (host2 ===
|
|
35711
|
+
entry2 = entry2.replace(/^\*?\./, "");
|
|
35712
|
+
if (host2 === entry2 || host2.endsWith(`.${entry2}`)) return true;
|
|
35323
35713
|
}
|
|
35324
35714
|
return false;
|
|
35325
35715
|
}
|
|
@@ -35473,8 +35863,8 @@ function firstBindingWarning(name, group, host2, port) {
|
|
|
35473
35863
|
...group.protocols.length > 1 ? ["", "Authorizing SSH also authorizes SFTP. They are one destination."] : []
|
|
35474
35864
|
];
|
|
35475
35865
|
}
|
|
35476
|
-
function removalWarning(name, group,
|
|
35477
|
-
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."] : [];
|
|
35478
35868
|
if (remainingAfter === 0) {
|
|
35479
35869
|
return [
|
|
35480
35870
|
`This is the only destination authorized on "${name}".`,
|
|
@@ -35493,7 +35883,7 @@ function removalWarning(name, group, entry, remainingAfter, sameGroupRemaining)
|
|
|
35493
35883
|
];
|
|
35494
35884
|
}
|
|
35495
35885
|
return [
|
|
35496
|
-
`"${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 ")}.`,
|
|
35497
35887
|
"",
|
|
35498
35888
|
`Its other ${group.label} destinations are unchanged.`,
|
|
35499
35889
|
...pairNote
|
|
@@ -35664,16 +36054,22 @@ var REFUSALS = {
|
|
|
35664
36054
|
why: "A jump host must be one of your own SSH connections, and no connection of yours has that id.",
|
|
35665
36055
|
action: "Run `kt conns` and pass an id from the first column to --jump."
|
|
35666
36056
|
},
|
|
35667
|
-
//
|
|
35668
|
-
//
|
|
35669
|
-
|
|
35670
|
-
|
|
35671
|
-
|
|
35672
|
-
|
|
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).
|
|
35673
36069
|
step_up_required: {
|
|
35674
36070
|
title: "Step-up verification required",
|
|
35675
36071
|
why: "This action needs a recent passkey or app-MFA check, and this session does not have one.",
|
|
35676
|
-
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."
|
|
35677
36073
|
},
|
|
35678
36074
|
// #2225 AC5, and the one shape this table exists for: the server owns the REASON, this client adds
|
|
35679
36075
|
// the ROUTE. The server's hint names what ended the session (server/src/index.ts for a credential
|
|
@@ -35709,7 +36105,7 @@ var REFUSALS = {
|
|
|
35709
36105
|
},
|
|
35710
36106
|
refresh_hub_revoked: {
|
|
35711
36107
|
title: "This session was signed out",
|
|
35712
|
-
why: refreshWhy("refresh_hub_revoked"),
|
|
36108
|
+
why: refreshWhy("refresh_hub_revoked", "app"),
|
|
35713
36109
|
action: "Run `kt login` to sign in again."
|
|
35714
36110
|
},
|
|
35715
36111
|
device_revoked: {
|
|
@@ -35818,7 +36214,7 @@ var REFUSALS = {
|
|
|
35818
36214
|
why: "Promoting now would leave those hosts refusing every session, because they do not yet accept a certificate from the next CA.",
|
|
35819
36215
|
action: "Install or verify each host listed by `kt broker-ca status`, or revoke its enrollment, then promote again."
|
|
35820
36216
|
},
|
|
35821
|
-
// authz.ts:69, connection-import.ts:
|
|
36217
|
+
// authz.ts:69, connection-import.ts:164. Overage freeze after a downgrade — read stays, write stops.
|
|
35822
36218
|
vault_frozen: {
|
|
35823
36219
|
title: "Vault is read-only",
|
|
35824
36220
|
why: "This vault is frozen for writes because the plan's vault limit was exceeded after a downgrade.",
|
|
@@ -35844,7 +36240,7 @@ var REFUSALS = {
|
|
|
35844
36240
|
why: "This organization keeps AI on its own network, so only agents declared `lan` may act for it.",
|
|
35845
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".'
|
|
35846
36242
|
},
|
|
35847
|
-
// files.ts:
|
|
36243
|
+
// server/src/modules/files.ts:353.
|
|
35848
36244
|
tls_downgrade_refused: {
|
|
35849
36245
|
title: "Transport security cannot be weakened",
|
|
35850
36246
|
why: "The change would turn TLS off, or allow an unverified certificate, on a connection that already has it on.",
|
|
@@ -35900,7 +36296,7 @@ var REFUSALS = {
|
|
|
35900
36296
|
why: "The code was valid for a limited time and that time has passed.",
|
|
35901
36297
|
action: "Run `kt login` again; a new code is emailed when the password is accepted."
|
|
35902
36298
|
},
|
|
35903
|
-
// 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
|
|
35904
36300
|
// `inject_only`; the name the product uses is "No-reveal", and the second half is stated positively
|
|
35905
36301
|
// so the credential does not read as broken.
|
|
35906
36302
|
inject_only: {
|
|
@@ -35908,12 +36304,42 @@ var REFUSALS = {
|
|
|
35908
36304
|
why: "Kutrion does not display, copy, export or share this credential's value. It is still used to authenticate connections and integrations.",
|
|
35909
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."
|
|
35910
36306
|
},
|
|
35911
|
-
// modules/ssh.ts:
|
|
36307
|
+
// modules/ssh.ts:478, modules/rdp.ts:151.
|
|
35912
36308
|
inject_only_requires_write: {
|
|
35913
36309
|
title: "Write access required for a No-reveal credential",
|
|
35914
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.",
|
|
35915
36311
|
action: "Ask an owner or editor of that vault to make the change, or to grant you write access to it."
|
|
35916
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
|
+
},
|
|
35917
36343
|
// access-policy.ts:264, reached from a root preHandler over the file family; reason values from AccessDecision.reason. The server sends
|
|
35918
36344
|
// a bare code here and no hint, so translating `reason` is the whole value this entry adds.
|
|
35919
36345
|
blocked_by_access_policy: {
|
|
@@ -35921,7 +36347,7 @@ var REFUSALS = {
|
|
|
35921
36347
|
why: "This organization restricts where its file connections may be reached from, and this request came from outside that.",
|
|
35922
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."
|
|
35923
36349
|
},
|
|
35924
|
-
// files.ts:
|
|
36350
|
+
// server/src/modules/files.ts:56/1054, file-requests.ts:280. `path` and `rule` name the file and the rule.
|
|
35925
36351
|
blocked_by_dlp_policy: {
|
|
35926
36352
|
title: "Blocked by a data-loss-prevention rule",
|
|
35927
36353
|
why: "The content or the path matched a rule this organization applies to file transfers.",
|
|
@@ -36091,8 +36517,14 @@ function contextLines(body, indent) {
|
|
|
36091
36517
|
const text2 = Array.isArray(v) ? v.filter((x) => x !== null && typeof x !== "object").map((x) => String(x)).join(", ") : String(v);
|
|
36092
36518
|
if (text2.trim()) out.push(`${indent}${f}: ${text2}`);
|
|
36093
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
|
+
}
|
|
36094
36525
|
return out;
|
|
36095
36526
|
}
|
|
36527
|
+
var scrubInput = (m) => m.replace(/,?\s*(?:received|got)\s+(?:'[^']*'|"[^"]*"|\S+)/gi, "").replace(/\s{2,}/g, " ").replace(/[\s,]+$/, "").trim();
|
|
36096
36528
|
var STATUS_ACTION = {
|
|
36097
36529
|
400: "The server could not use the request as sent. Check the command's arguments against `kt --help`.",
|
|
36098
36530
|
401: "This machine's session was not accepted. Run `kt login` to sign in again.",
|
|
@@ -36109,7 +36541,7 @@ var HUB_SIGN_IN_ACTION = {
|
|
|
36109
36541
|
status: "This machine's session was not accepted. Run `kt login`, it signs in through Kutrion Hub.",
|
|
36110
36542
|
signed_out: "Run `kt login`, it signs in through Kutrion Hub."
|
|
36111
36543
|
};
|
|
36112
|
-
var SERVER_REFUSALS = /* @__PURE__ */ new Set(["payment_unconfirmed"]);
|
|
36544
|
+
var SERVER_REFUSALS = /* @__PURE__ */ new Set(["payment_unconfirmed", "tls_certificate_untrusted"]);
|
|
36113
36545
|
var sentenceKey = (t) => t.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
36114
36546
|
var sameSentence = (a, b) => b !== void 0 && sentenceKey(a) === sentenceKey(b);
|
|
36115
36547
|
var SIGNIN_CODE_REFUSALS = /* @__PURE__ */ new Set(["email_code_required", "signin_code_cap"]);
|
|
@@ -36140,6 +36572,10 @@ function refusalLines(e, indent = " ", opts2 = {}) {
|
|
|
36140
36572
|
const code = fromRefresh ? e.refreshCode : refusalCode(e);
|
|
36141
36573
|
const platformFlag = platformFlagRefusalText(code);
|
|
36142
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
|
+
}
|
|
36143
36579
|
const copy = Object.hasOwn(REFUSALS, code) ? REFUSALS[code] : void 0;
|
|
36144
36580
|
const server = fromRefresh || SIGNIN_CODE_REFUSALS.has(code) ? null : serverText(e.body);
|
|
36145
36581
|
const context = contextLines(e.body, indent);
|
|
@@ -36214,7 +36650,9 @@ function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
|
36214
36650
|
const jump = jumpRefusalReport(frame, code, head, jumpCtx, indent);
|
|
36215
36651
|
if (jump) return jump;
|
|
36216
36652
|
const lines = [code && !ourFault ? `${head} (${code})` : head, ""];
|
|
36217
|
-
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
|
+
);
|
|
36218
36656
|
if (host2) lines.push("", `${indent}endpoint: ${port === null ? host2 : `${host2}:${port}`}`);
|
|
36219
36657
|
if (allowed.length) {
|
|
36220
36658
|
lines.push("", destinationRefusalAllowedLabel(dest));
|
|
@@ -36227,6 +36665,17 @@ function wsRefusalLines(frame, indent = " ", jumpCtx = {}) {
|
|
|
36227
36665
|
}
|
|
36228
36666
|
|
|
36229
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
|
+
}
|
|
36230
36679
|
function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onChainedHostKey) {
|
|
36231
36680
|
const wsUrl = shellUrl(base, connId);
|
|
36232
36681
|
const ws = new wrapper_default(wsUrl, { headers: { Authorization: `Bearer ${token}` }, agent: wsAgentFor(wsUrl) });
|
|
@@ -36235,12 +36684,12 @@ function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onCha
|
|
|
36235
36684
|
const isTty = Boolean(stdin.isTTY && stdout.isTTY);
|
|
36236
36685
|
const sendResize = () => {
|
|
36237
36686
|
if (ws.readyState !== wrapper_default.OPEN) return;
|
|
36238
|
-
const cols = stdout.columns
|
|
36239
|
-
const rows = stdout.rows ?? 24;
|
|
36687
|
+
const [cols, rows] = ttySize(stdout.columns, stdout.rows);
|
|
36240
36688
|
ws.send(resizeFrame(cols, rows));
|
|
36241
36689
|
};
|
|
36242
36690
|
let exiting = false;
|
|
36243
36691
|
let recordedSessionId = null;
|
|
36692
|
+
let sessionInfo = null;
|
|
36244
36693
|
const printReceipt = async () => {
|
|
36245
36694
|
const id = recordedSessionId;
|
|
36246
36695
|
if (!id) return;
|
|
@@ -36262,6 +36711,9 @@ function runShell(base, token, connId, onExit, jumpCtx = {}, afterSession, onCha
|
|
|
36262
36711
|
ws.close();
|
|
36263
36712
|
} catch {
|
|
36264
36713
|
}
|
|
36714
|
+
if (!ended && sessionInfo) process.stderr.write(`\r
|
|
36715
|
+
${detachNotice(sessionInfo)}\r
|
|
36716
|
+
`);
|
|
36265
36717
|
void (ended ? printReceipt() : Promise.resolve()).catch(() => {
|
|
36266
36718
|
}).then(() => onExit?.()).catch(() => {
|
|
36267
36719
|
}).then(() => process.exit(code));
|
|
@@ -36302,6 +36754,7 @@ ${wsRefusalLines(frame, " ", jumpCtx).join("\r\n")}\r
|
|
|
36302
36754
|
const sessionFrame = sawSession ? null : parseSessionFrame(text2);
|
|
36303
36755
|
if (sessionFrame) {
|
|
36304
36756
|
sawSession = true;
|
|
36757
|
+
sessionInfo = sessionFrame;
|
|
36305
36758
|
const state = recordingStateLabel(sessionFrame);
|
|
36306
36759
|
if (state) process.stderr.write(`\r
|
|
36307
36760
|
[Kutrion] ${state}\r
|
|
@@ -36379,7 +36832,7 @@ var import_readline = require("readline");
|
|
|
36379
36832
|
|
|
36380
36833
|
// src/mcp-install.ts
|
|
36381
36834
|
init_define_KT_HUB_FILES();
|
|
36382
|
-
var
|
|
36835
|
+
var import_fs4 = require("fs");
|
|
36383
36836
|
var import_os4 = require("os");
|
|
36384
36837
|
var import_path4 = require("path");
|
|
36385
36838
|
var import_child_process3 = require("child_process");
|
|
@@ -36474,35 +36927,35 @@ function bundleCandidates(moduleDir) {
|
|
|
36474
36927
|
function resolveLaunch(base, client, moduleDir = __dirname) {
|
|
36475
36928
|
const which = (0, import_child_process3.spawnSync)("command -v kt", { encoding: "utf8", shell: true });
|
|
36476
36929
|
const onPath = which.status === 0 ? (which.stdout ?? "").trim().split("\n")[0] : "";
|
|
36477
|
-
let
|
|
36930
|
+
let entry2;
|
|
36478
36931
|
if ((0, import_path4.isAbsolute)(onPath)) {
|
|
36479
|
-
|
|
36932
|
+
entry2 = { command: onPath, args: ["mcp"] };
|
|
36480
36933
|
} else {
|
|
36481
|
-
const bundle = bundleCandidates(moduleDir).find((p) => (0,
|
|
36934
|
+
const bundle = bundleCandidates(moduleDir).find((p) => (0, import_fs4.existsSync)(p));
|
|
36482
36935
|
if (!bundle) {
|
|
36483
36936
|
throw new Error(
|
|
36484
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."
|
|
36485
36938
|
);
|
|
36486
36939
|
}
|
|
36487
|
-
|
|
36940
|
+
entry2 = { command: process.execPath, args: [bundle, "mcp"] };
|
|
36488
36941
|
}
|
|
36489
36942
|
const fileStore2 = (ktEnv("TOKEN_STORE") ?? "").trim().toLowerCase() === "file";
|
|
36490
|
-
|
|
36943
|
+
entry2.env = {
|
|
36491
36944
|
KT_API_BASE: base,
|
|
36492
36945
|
...client ? { KT_MCP_CLIENT: client } : {},
|
|
36493
36946
|
...fileStore2 ? { KT_TOKEN_STORE: "file" } : {}
|
|
36494
36947
|
};
|
|
36495
|
-
return
|
|
36948
|
+
return entry2;
|
|
36496
36949
|
}
|
|
36497
36950
|
var STORE_ENV = /* @__PURE__ */ new Set(["TOKEN_STORE", "TOKEN_STORE_TIMEOUT_MS"]);
|
|
36498
|
-
async function verifyLaunch(
|
|
36951
|
+
async function verifyLaunch(entry2, timeoutMs = 15e3) {
|
|
36499
36952
|
const probe = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} });
|
|
36500
36953
|
const inherited = Object.fromEntries(
|
|
36501
36954
|
Object.entries(process.env).filter(([k]) => !/^(KT|VT)_/.test(k) || STORE_ENV.has(k.slice(3)))
|
|
36502
36955
|
);
|
|
36503
|
-
const res = (0, import_child_process3.spawnSync)(
|
|
36956
|
+
const res = (0, import_child_process3.spawnSync)(entry2.command, entry2.args, {
|
|
36504
36957
|
input: probe + "\n",
|
|
36505
|
-
env: { ...inherited, ...
|
|
36958
|
+
env: { ...inherited, ...entry2.env ?? {} },
|
|
36506
36959
|
cwd: (0, import_os4.tmpdir)(),
|
|
36507
36960
|
encoding: "utf8",
|
|
36508
36961
|
timeout: timeoutMs
|
|
@@ -36521,10 +36974,10 @@ async function verifyLaunch(entry, timeoutMs = 15e3) {
|
|
|
36521
36974
|
return { ok: false, error: "the bridge produced output that is not JSON-RPC" };
|
|
36522
36975
|
}
|
|
36523
36976
|
}
|
|
36524
|
-
function mergeConfig(existing, key,
|
|
36977
|
+
function mergeConfig(existing, key, entry2) {
|
|
36525
36978
|
const cfg = existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : {};
|
|
36526
36979
|
const servers = cfg[key] && typeof cfg[key] === "object" && !Array.isArray(cfg[key]) ? { ...cfg[key] } : {};
|
|
36527
|
-
servers[SERVER_NAME] =
|
|
36980
|
+
servers[SERVER_NAME] = entry2;
|
|
36528
36981
|
cfg[key] = servers;
|
|
36529
36982
|
return cfg;
|
|
36530
36983
|
}
|
|
@@ -36540,14 +36993,14 @@ function removeFromConfig(existing, key) {
|
|
|
36540
36993
|
}
|
|
36541
36994
|
function readJson(path) {
|
|
36542
36995
|
try {
|
|
36543
|
-
return JSON.parse((0,
|
|
36996
|
+
return JSON.parse((0, import_fs4.readFileSync)(path, "utf8"));
|
|
36544
36997
|
} catch {
|
|
36545
36998
|
return void 0;
|
|
36546
36999
|
}
|
|
36547
37000
|
}
|
|
36548
37001
|
function writeJson(path, value) {
|
|
36549
|
-
(0,
|
|
36550
|
-
(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");
|
|
36551
37004
|
}
|
|
36552
37005
|
var agentAccount = (base, client) => client ? `agent:${base}:${client}` : `agent:${base}`;
|
|
36553
37006
|
function getAgentToken(base, client) {
|
|
@@ -36592,7 +37045,7 @@ async function installClient(o) {
|
|
|
36592
37045
|
if (!configPath) {
|
|
36593
37046
|
throw new Error(`${target.label} is not available on this platform (${(0, import_os4.platform)()}).`);
|
|
36594
37047
|
}
|
|
36595
|
-
const
|
|
37048
|
+
const entry2 = resolveLaunch(o.base, o.client);
|
|
36596
37049
|
const mint = () => api2(o.base, "/agents", o.userToken, {
|
|
36597
37050
|
method: "POST",
|
|
36598
37051
|
body: JSON.stringify({
|
|
@@ -36601,7 +37054,8 @@ async function installClient(o) {
|
|
|
36601
37054
|
client: o.client,
|
|
36602
37055
|
scopes: o.scopes,
|
|
36603
37056
|
ttl_minutes: o.ttlDays * 24 * 60,
|
|
36604
|
-
egress_class: o.egressClass ?? "cloud"
|
|
37057
|
+
egress_class: o.egressClass ?? "cloud",
|
|
37058
|
+
...o.resourceScope ? { resource_scope: o.resourceScope } : {}
|
|
36605
37059
|
})
|
|
36606
37060
|
});
|
|
36607
37061
|
let superseded = 0;
|
|
@@ -36642,16 +37096,17 @@ async function installClient(o) {
|
|
|
36642
37096
|
superseded += old.revoked;
|
|
36643
37097
|
revokeFailed.push(...old.revokeFailed);
|
|
36644
37098
|
agentListStatus ??= old.agentListStatus;
|
|
36645
|
-
writeJson(configPath, mergeConfig(readJson(configPath), target.key,
|
|
37099
|
+
writeJson(configPath, mergeConfig(readJson(configPath), target.key, entry2));
|
|
36646
37100
|
const who = await api2(o.base, "/mcp/whoami", agent.token);
|
|
36647
37101
|
return {
|
|
37102
|
+
...o.resourceScope ? { resourceScope: o.resourceScope } : {},
|
|
36648
37103
|
configPath,
|
|
36649
37104
|
principalId: agent.id,
|
|
36650
37105
|
expiresAt: agent.expires_at,
|
|
36651
37106
|
scopes: agent.scopes,
|
|
36652
37107
|
next: target.next,
|
|
36653
37108
|
verified: who.status === 200 && who.body?.principal_id === agent.id,
|
|
36654
|
-
launch: await verifyLaunch(
|
|
37109
|
+
launch: await verifyLaunch(entry2),
|
|
36655
37110
|
superseded,
|
|
36656
37111
|
revokeFailed,
|
|
36657
37112
|
...agentListStatus !== void 0 ? { agentListStatus } : {}
|
|
@@ -36669,7 +37124,7 @@ async function uninstallClient(o) {
|
|
|
36669
37124
|
if (o.client) {
|
|
36670
37125
|
const target = CLIENTS[o.client];
|
|
36671
37126
|
configPath = target.path(o.dir);
|
|
36672
|
-
if (configPath && (0,
|
|
37127
|
+
if (configPath && (0, import_fs4.existsSync)(configPath)) {
|
|
36673
37128
|
writeJson(configPath, removeFromConfig(readJson(configPath), target.key));
|
|
36674
37129
|
}
|
|
36675
37130
|
}
|
|
@@ -36691,6 +37146,7 @@ async function agentStatus(base, token) {
|
|
|
36691
37146
|
principal_id: who.body?.principal_id,
|
|
36692
37147
|
expires_at: who.body?.expires_at,
|
|
36693
37148
|
scopes: who.body?.scopes,
|
|
37149
|
+
resource_scope: who.body?.resource_scope,
|
|
36694
37150
|
rate_limit: who.body?.rate_limit
|
|
36695
37151
|
};
|
|
36696
37152
|
}
|
|
@@ -36706,17 +37162,66 @@ async function mcpStatus(base, dir) {
|
|
|
36706
37162
|
const p = t.path(dir);
|
|
36707
37163
|
let installed = false;
|
|
36708
37164
|
let configuredBase = null;
|
|
36709
|
-
if (p && (0,
|
|
37165
|
+
if (p && (0, import_fs4.existsSync)(p)) {
|
|
36710
37166
|
const cfg = readJson(p);
|
|
36711
|
-
const
|
|
36712
|
-
installed = !!
|
|
36713
|
-
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;
|
|
36714
37170
|
}
|
|
36715
37171
|
return { client: id, label: t.label, path: p, installed, configuredBase, agent: await agentStatus(base, getToken(agentAccount(base, id))) };
|
|
36716
37172
|
}));
|
|
36717
37173
|
const legacy = await agentStatus(base, getToken(agentAccount(base)));
|
|
36718
37174
|
return { keychain: backendName(), hasToken: !!legacy || configs.some((c) => c.agent), configs, legacy };
|
|
36719
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
|
+
}
|
|
36720
37225
|
|
|
36721
37226
|
// src/mcp.ts
|
|
36722
37227
|
var MODERN = "2026-07-28";
|
|
@@ -37474,9 +37979,23 @@ function recoveryText(rec, s) {
|
|
|
37474
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."
|
|
37475
37980
|
].join("\n");
|
|
37476
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
|
+
}
|
|
37477
37994
|
|
|
37478
37995
|
// src/private-vault.ts
|
|
37479
37996
|
init_define_KT_HUB_FILES();
|
|
37997
|
+
var import_fs5 = require("fs");
|
|
37998
|
+
var import_path5 = require("path");
|
|
37480
37999
|
var PASSPHRASE_STDIN_FLAG = "vault-passphrase-stdin";
|
|
37481
38000
|
var PrivateVaultError = class extends Error {
|
|
37482
38001
|
};
|
|
@@ -37494,6 +38013,9 @@ There is no flag that takes the passphrase itself: a flag lands in shell history
|
|
|
37494
38013
|
return io.ask("vault passphrase: ");
|
|
37495
38014
|
}
|
|
37496
38015
|
async function unlockForCommand(client, io) {
|
|
38016
|
+
return (await unlockVault(client, io)).key;
|
|
38017
|
+
}
|
|
38018
|
+
async function unlockVault(client, io) {
|
|
37497
38019
|
const passphrase = await readVaultPassphrase(io);
|
|
37498
38020
|
if (!passphrase) throw new PrivateVaultError("no vault passphrase was given");
|
|
37499
38021
|
let record;
|
|
@@ -37506,7 +38028,7 @@ async function unlockForCommand(client, io) {
|
|
|
37506
38028
|
throw e;
|
|
37507
38029
|
}
|
|
37508
38030
|
try {
|
|
37509
|
-
return await unlockVaultKey(record, passphrase);
|
|
38031
|
+
return { key: await unlockVaultKey(record, passphrase), enrolledAt: record.created_at };
|
|
37510
38032
|
} catch (e) {
|
|
37511
38033
|
if (e instanceof VaultKeyError && e.code === "wrong_secret") throw new PrivateVaultError(PRIVATE_VAULT_COPY.wrongPassphrase);
|
|
37512
38034
|
throw e;
|
|
@@ -37517,12 +38039,115 @@ async function openSealedItem(client, key, vaultId, credId) {
|
|
|
37517
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.");
|
|
37518
38040
|
try {
|
|
37519
38041
|
const opened = await openPrivateItem({ id: credential.id, vault_id: credential.vault_id }, sealed, key);
|
|
37520
|
-
return { value: opened.value };
|
|
38042
|
+
return { value: opened.value, notes: opened.notes };
|
|
37521
38043
|
} catch (e) {
|
|
37522
38044
|
if (e instanceof VaultKeyError) throw new PrivateVaultError("this item could not be opened with your vault key.");
|
|
37523
38045
|
throw e;
|
|
37524
38046
|
}
|
|
37525
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`.";
|
|
37526
38151
|
|
|
37527
38152
|
// src/enroll.ts
|
|
37528
38153
|
init_define_KT_HUB_FILES();
|
|
@@ -37732,10 +38357,47 @@ function parseHostsUnconfirmed(body) {
|
|
|
37732
38357
|
return { count: typeof b.unconfirmedCount === "number" ? b.unconfirmedCount : hosts.length, hosts };
|
|
37733
38358
|
}
|
|
37734
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
|
+
|
|
37735
38397
|
// src/transfer.ts
|
|
37736
38398
|
init_define_KT_HUB_FILES();
|
|
37737
|
-
var
|
|
37738
|
-
var
|
|
38399
|
+
var import_fs6 = require("fs");
|
|
38400
|
+
var import_path6 = require("path");
|
|
37739
38401
|
var import_promises = require("stream/promises");
|
|
37740
38402
|
var EXIT_OK = 0;
|
|
37741
38403
|
var EXIT_FAIL = 1;
|
|
@@ -37762,7 +38424,7 @@ function isSideError(s) {
|
|
|
37762
38424
|
var listOn = (c, k, id, p) => k === "sftp" ? c.sftpList(id, p) : c.fileList(id, p);
|
|
37763
38425
|
var mkdirOn = (c, k, id, p) => k === "sftp" ? c.sftpMkdir(id, p) : c.fileMkdir(id, p);
|
|
37764
38426
|
var downloadOn = (c, k, id, p, o) => k === "sftp" ? c.sftpDownloadStream(id, p, o) : c.fileDownloadStream(id, p, o);
|
|
37765
|
-
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);
|
|
37766
38428
|
var abortOnHostKey = (e) => {
|
|
37767
38429
|
if (hostKeyUnconfirmedFromError(e)) throw e;
|
|
37768
38430
|
};
|
|
@@ -37808,6 +38470,8 @@ function exitFor(o) {
|
|
|
37808
38470
|
return o.ok > 0 ? EXIT_PARTIAL : EXIT_FAIL;
|
|
37809
38471
|
}
|
|
37810
38472
|
var msg = (e) => e instanceof Error ? e.message : String(e);
|
|
38473
|
+
async function* emptyChunks() {
|
|
38474
|
+
}
|
|
37811
38475
|
async function* counted(chunks, p, tally) {
|
|
37812
38476
|
for await (const c of chunks) {
|
|
37813
38477
|
tally.bytes += c.byteLength;
|
|
@@ -37821,15 +38485,15 @@ async function saveTo(s, local, append, p) {
|
|
|
37821
38485
|
await (0, import_promises.pipeline)(counted(s.chunks, p, tally), process.stdout, { end: false });
|
|
37822
38486
|
return tally.bytes;
|
|
37823
38487
|
}
|
|
37824
|
-
const dir = (0,
|
|
37825
|
-
if (dir && dir !== "." && !(0,
|
|
37826
|
-
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" } : {}));
|
|
37827
38491
|
return tally.bytes;
|
|
37828
38492
|
}
|
|
37829
38493
|
async function getOne(c, kind, id, remote, local, opts2) {
|
|
37830
38494
|
let start = 0;
|
|
37831
|
-
if (opts2.resume && local !== "-" && (0,
|
|
37832
|
-
const st = (0,
|
|
38495
|
+
if (opts2.resume && local !== "-" && (0, import_fs6.existsSync)(local)) {
|
|
38496
|
+
const st = (0, import_fs6.statSync)(local);
|
|
37833
38497
|
if (st.isFile()) start = st.size;
|
|
37834
38498
|
}
|
|
37835
38499
|
let s;
|
|
@@ -37852,7 +38516,7 @@ async function getOne(c, kind, id, remote, local, opts2) {
|
|
|
37852
38516
|
opts2.note?.(`${remote}: server ignored the resume range, downloading from the start`);
|
|
37853
38517
|
start = 0;
|
|
37854
38518
|
}
|
|
37855
|
-
const p = opts2.quiet ? NO_PROGRESS : progressBar((0,
|
|
38519
|
+
const p = opts2.quiet ? NO_PROGRESS : progressBar((0, import_path6.basename)(remote) || remote, s.total);
|
|
37856
38520
|
const bytes = await saveTo(s, local, start > 0, p);
|
|
37857
38521
|
p.finish();
|
|
37858
38522
|
return { bytes };
|
|
@@ -37904,13 +38568,13 @@ async function getPath(c, kind, id, remote, local, opts2 = {}) {
|
|
|
37904
38568
|
return out;
|
|
37905
38569
|
}
|
|
37906
38570
|
for await (const item of walkRemote(c, kind, id, remote)) {
|
|
37907
|
-
const dest = (0,
|
|
38571
|
+
const dest = (0, import_path6.join)(local, item.rel);
|
|
37908
38572
|
if (item.link) {
|
|
37909
38573
|
opts2.note?.(`skipped symlink ${item.path}`);
|
|
37910
38574
|
continue;
|
|
37911
38575
|
}
|
|
37912
38576
|
if (item.dir) {
|
|
37913
|
-
(0,
|
|
38577
|
+
(0, import_fs6.mkdirSync)(dest, { recursive: true });
|
|
37914
38578
|
continue;
|
|
37915
38579
|
}
|
|
37916
38580
|
try {
|
|
@@ -37926,8 +38590,8 @@ async function getPath(c, kind, id, remote, local, opts2 = {}) {
|
|
|
37926
38590
|
return out;
|
|
37927
38591
|
}
|
|
37928
38592
|
function* walkLocal(root, rel = "") {
|
|
37929
|
-
for (const e of (0,
|
|
37930
|
-
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);
|
|
37931
38595
|
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
37932
38596
|
if (e.isSymbolicLink()) continue;
|
|
37933
38597
|
if (e.isDirectory()) {
|
|
@@ -37939,18 +38603,21 @@ function* walkLocal(root, rel = "") {
|
|
|
37939
38603
|
}
|
|
37940
38604
|
}
|
|
37941
38605
|
async function putOne(c, kind, id, local, remote, opts2) {
|
|
37942
|
-
const total = (0,
|
|
37943
|
-
|
|
37944
|
-
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);
|
|
37945
38608
|
const tally = { bytes: 0 };
|
|
37946
|
-
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);
|
|
37947
38614
|
p.finish();
|
|
37948
38615
|
if (!r.streamed) opts2.note?.(`${local}: this runtime could not stream the request body, the file was buffered`);
|
|
37949
38616
|
return r.size ?? tally.bytes;
|
|
37950
38617
|
}
|
|
37951
38618
|
async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
37952
38619
|
const out = empty();
|
|
37953
|
-
const st = (0,
|
|
38620
|
+
const st = (0, import_fs6.existsSync)(local) ? (0, import_fs6.statSync)(local) : null;
|
|
37954
38621
|
if (!st) {
|
|
37955
38622
|
out.failed++;
|
|
37956
38623
|
out.errors.push(`${local}: no such file`);
|
|
@@ -37983,7 +38650,7 @@ async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
|
37983
38650
|
}
|
|
37984
38651
|
continue;
|
|
37985
38652
|
}
|
|
37986
|
-
const parent = (0,
|
|
38653
|
+
const parent = (0, import_path6.dirname)(dest);
|
|
37987
38654
|
if (!made.has(parent)) {
|
|
37988
38655
|
try {
|
|
37989
38656
|
await mkdirOn(c, kind, id, parent);
|
|
@@ -38002,13 +38669,32 @@ async function putPath(c, kind, id, local, remote, opts2 = {}) {
|
|
|
38002
38669
|
}
|
|
38003
38670
|
return out;
|
|
38004
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
|
+
}
|
|
38005
38683
|
async function waitForJob(get, id, opts2) {
|
|
38006
38684
|
const interval = opts2.intervalMs ?? 2e3;
|
|
38007
38685
|
const deadline = Date.now() + opts2.timeoutMs;
|
|
38008
38686
|
for (; ; ) {
|
|
38009
|
-
|
|
38010
|
-
|
|
38011
|
-
|
|
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
|
+
}
|
|
38012
38698
|
if (Date.now() + interval > deadline) return null;
|
|
38013
38699
|
await new Promise((r) => setTimeout(r, interval));
|
|
38014
38700
|
}
|
|
@@ -38022,11 +38708,12 @@ function parseDailyAt(v) {
|
|
|
38022
38708
|
}
|
|
38023
38709
|
|
|
38024
38710
|
// src/index.ts
|
|
38025
|
-
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"));
|
|
38026
38713
|
function storedBase() {
|
|
38027
38714
|
let file;
|
|
38028
38715
|
try {
|
|
38029
|
-
file = JSON.parse((0,
|
|
38716
|
+
file = JSON.parse((0, import_fs7.readFileSync)(CONFIG, "utf8"));
|
|
38030
38717
|
} catch {
|
|
38031
38718
|
return "";
|
|
38032
38719
|
}
|
|
@@ -38044,9 +38731,17 @@ function storedBase() {
|
|
|
38044
38731
|
}
|
|
38045
38732
|
return base;
|
|
38046
38733
|
}
|
|
38047
|
-
function saveCfg(base) {
|
|
38048
|
-
(0,
|
|
38049
|
-
(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);
|
|
38050
38745
|
}
|
|
38051
38746
|
function resolveBase(flag) {
|
|
38052
38747
|
const explicit = typeof flag === "string" ? flag.trim() : "";
|
|
@@ -38193,16 +38888,38 @@ async function readStdin() {
|
|
|
38193
38888
|
function readSecret(q) {
|
|
38194
38889
|
return process.stdin.isTTY ? prompt(q, true) : readStdin();
|
|
38195
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.";
|
|
38196
38892
|
async function openPrivateItem2(client, fromStdin, vaultId, credId) {
|
|
38197
38893
|
try {
|
|
38198
38894
|
const key = await unlockForCommand(client, { tty: !!process.stdin.isTTY, fromStdin, ask: promptSecret, readStdin });
|
|
38199
|
-
|
|
38895
|
+
const item = await openSealedItem(client, key, vaultId, credId);
|
|
38896
|
+
console.log(item.value);
|
|
38897
|
+
if (item.notes) console.error(`notes: ${item.notes}`);
|
|
38200
38898
|
return null;
|
|
38201
38899
|
} catch (e) {
|
|
38202
38900
|
if (e instanceof PrivateVaultError) return e.message;
|
|
38203
38901
|
throw e;
|
|
38204
38902
|
}
|
|
38205
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 };
|
|
38206
38923
|
function refuseSecretFlag(flag, instead) {
|
|
38207
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.`);
|
|
38208
38925
|
console.error(` ${instead}`);
|
|
@@ -38277,12 +38994,12 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38277
38994
|
const secret = await promptSecret(`current value for "${name}": `);
|
|
38278
38995
|
let added;
|
|
38279
38996
|
try {
|
|
38280
|
-
added = await client.addCredentialDestination(vaultId, cid, {
|
|
38997
|
+
added = await withStepUp(client, () => client.addCredentialDestination(vaultId, cid, {
|
|
38281
38998
|
protocol: group.key,
|
|
38282
38999
|
host: host2,
|
|
38283
39000
|
...portGiven ? { port } : {},
|
|
38284
39001
|
secret
|
|
38285
|
-
});
|
|
39002
|
+
}));
|
|
38286
39003
|
} catch (e) {
|
|
38287
39004
|
const text2 = destRefusalText(e);
|
|
38288
39005
|
if (text2) return die(text2);
|
|
@@ -38305,24 +39022,24 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38305
39022
|
const group = groupFor(row.protocol);
|
|
38306
39023
|
if (!group) return die(`this destination is recorded on protocol ${row.protocol}, which this version of kt cannot withdraw`);
|
|
38307
39024
|
const inGroup = entriesFor(group, before.destinations);
|
|
38308
|
-
const
|
|
38309
|
-
if (!
|
|
39025
|
+
const entry2 = inGroup.find((x) => x.rows.some((r) => r.id === destId));
|
|
39026
|
+
if (!entry2) return die(`no destination ${destId} on "${name}"`);
|
|
38310
39027
|
const sameGroupRows = inGroup.reduce((n, x) => n + x.rows.length, 0);
|
|
38311
|
-
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)) {
|
|
38312
39029
|
console.error(line);
|
|
38313
39030
|
}
|
|
38314
39031
|
console.error("");
|
|
38315
39032
|
if (!await confirmTyped("Type yes to withdraw it: ")) return die("nothing was changed");
|
|
38316
39033
|
const secret = await promptSecret(`current value for "${name}": `);
|
|
38317
39034
|
const gone = [];
|
|
38318
|
-
for (const r of
|
|
39035
|
+
for (const r of entry2.rows) {
|
|
38319
39036
|
try {
|
|
38320
|
-
await client.removeCredentialDestination(vaultId, cid, r.id, secret);
|
|
39037
|
+
await withStepUp(client, () => client.removeCredentialDestination(vaultId, cid, r.id, secret));
|
|
38321
39038
|
gone.push(r.protocol);
|
|
38322
39039
|
} catch (e) {
|
|
38323
39040
|
if (gone.length) {
|
|
38324
|
-
const left =
|
|
38325
|
-
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.`);
|
|
38326
39043
|
}
|
|
38327
39044
|
const text2 = destRefusalText(e);
|
|
38328
39045
|
if (text2) return die(text2);
|
|
@@ -38331,7 +39048,7 @@ async function credDest(client, args, flags, asJson, die) {
|
|
|
38331
39048
|
}
|
|
38332
39049
|
const after = await list();
|
|
38333
39050
|
if (!asJson) {
|
|
38334
|
-
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}`);
|
|
38335
39052
|
console.log("");
|
|
38336
39053
|
}
|
|
38337
39054
|
return void await printList();
|
|
@@ -38343,7 +39060,33 @@ var ROLES = ["OWNER", "ADMIN", "EDITOR", "VIEWER"];
|
|
|
38343
39060
|
var TOKEN_SCOPES = ["scim", "events"];
|
|
38344
39061
|
var ORG_ROLES = ["owner", "admin", "member"];
|
|
38345
39062
|
var ROTATION_SERVICES = ["linux_password", "postgres_password", "mysql_password"];
|
|
38346
|
-
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
|
+
};
|
|
38347
39090
|
var HELP = `kt \u2014 Kutrion CLI
|
|
38348
39091
|
kt login [--email <e>] [--password-stdin] [--code <2fa>] prompts for the password, or reads it from stdin;
|
|
38349
39092
|
with no --email or --password-stdin, first asks the Kutrion desktop app
|
|
@@ -38361,8 +39104,10 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38361
39104
|
|
|
38362
39105
|
kt creds <vaultId> list credentials in a vault
|
|
38363
39106
|
kt get <vaultId> <credId> [--reveal] [--vault-passphrase-stdin] show a credential (the value only with --reveal); a Private vault asks for its passphrase
|
|
38364
|
-
kt cred add <vaultId> --name <n> [--value-stdin] [--type PASSWORD] [--notes ..] prompts for the value, or reads it from stdin
|
|
38365
|
-
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
|
|
38366
39111
|
kt cred rm <vaultId> <credId> delete a credential
|
|
38367
39112
|
kt cred dest ls <vaultId> <credId> where this credential may be presented
|
|
38368
39113
|
kt cred dest add <vaultId> <credId> --host <h> [--protocol SSH] [--port N]
|
|
@@ -38372,10 +39117,30 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38372
39117
|
kt export <vaultId> [file] export a vault's credentials (plaintext, written mode 0600)
|
|
38373
39118
|
|
|
38374
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
|
|
38375
39125
|
kt mcp status report each client's agent and which clients are wired
|
|
38376
39126
|
kt mcp uninstall [--client <id>] revoke the agent and remove the client config entry
|
|
38377
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
|
|
38378
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)
|
|
38379
39144
|
|
|
38380
39145
|
kt conns list SSH connections
|
|
38381
39146
|
kt ssh <connId> interactive SSH shell (Ctrl-] to detach)
|
|
@@ -38387,7 +39152,10 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38387
39152
|
--insert places it at the prompt without pressing Enter
|
|
38388
39153
|
kt demo [start] brokered sandbox session, then the audit trail + AI summary
|
|
38389
39154
|
kt demo status | reset demo availability / remove everything it provisioned
|
|
38390
|
-
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
|
|
38391
39159
|
kt forward <connId> <localPort>:<dstHost>:<dstPort> (ssh -L style tunnel)
|
|
38392
39160
|
|
|
38393
39161
|
kt sftp ls <connId> [path] list a remote directory
|
|
@@ -38414,7 +39182,8 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38414
39182
|
A "side" below is written <sftp|file|cloud>:<connId>:<path> \u2014 one argument for one endpoint.
|
|
38415
39183
|
|
|
38416
39184
|
kt transfer <from> <to> [--dir] remote-to-remote, brokered by the server
|
|
38417
|
-
[--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
|
|
38418
39187
|
kt sync <from> <to> [--newer-only] directory sync
|
|
38419
39188
|
[--mirror] [--yes] [--dry-run] --mirror DELETES extra files at the destination
|
|
38420
39189
|
|
|
@@ -38423,6 +39192,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38423
39192
|
kt job new <from> <to> [--wait] queue a job (--wait polls to completion)
|
|
38424
39193
|
[--dir] [--verify] [--throttle N] [--newer-only] [--mirror --yes] [--timeout <sec>]
|
|
38425
39194
|
kt job wait <id> [--timeout <sec>] poll an existing job
|
|
39195
|
+
kt job cancel <id> cancel a queued or running job
|
|
38426
39196
|
|
|
38427
39197
|
kt schedule [ls] recurring transfers
|
|
38428
39198
|
kt schedule new <name> <from> <to> --every <sec> or --daily-at HH:MM (UTC)
|
|
@@ -38431,6 +39201,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38431
39201
|
|
|
38432
39202
|
kt watcher [ls] folder watchers (fire on new/changed files)
|
|
38433
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
|
|
38434
39205
|
[--min-size N] [--max-size N] [--min-age <sec>] [--no-stability]
|
|
38435
39206
|
kt watcher enable|disable|rm <id> pause / resume / delete a watcher
|
|
38436
39207
|
|
|
@@ -38545,7 +39316,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38545
39316
|
[--reporter console|junit|json] [--out <file>] [--dry-run]
|
|
38546
39317
|
with KT_AGENT_TOKEN set, runs as that pipeline principal over /mcp
|
|
38547
39318
|
|
|
38548
|
-
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
|
|
38549
39320
|
[--session <id>] [--host <connection id>] [--since <iso>] [--until <iso>]
|
|
38550
39321
|
kt news security news feed
|
|
38551
39322
|
|
|
@@ -38565,6 +39336,7 @@ var HELP = `kt \u2014 Kutrion CLI
|
|
|
38565
39336
|
kt mfa verify --code <code> finish TOTP enrollment
|
|
38566
39337
|
kt mfa disable turn off TOTP
|
|
38567
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
|
|
38568
39340
|
kt passkeys registered passkeys / security keys
|
|
38569
39341
|
kt passkey rm <id> remove a passkey
|
|
38570
39342
|
|
|
@@ -38690,13 +39462,17 @@ function refusedSavedSession(e, argv2) {
|
|
|
38690
39462
|
}
|
|
38691
39463
|
}
|
|
38692
39464
|
async function main() {
|
|
38693
|
-
const { cmd, positionals, flags } = parseArgs(process.argv.slice(2));
|
|
39465
|
+
const { cmd, positionals, flags, multi } = parseArgs(process.argv.slice(2));
|
|
38694
39466
|
const asJson = flags.json === true;
|
|
38695
39467
|
const die = (msg2) => {
|
|
38696
39468
|
console.error(msg2);
|
|
38697
39469
|
process.exit(1);
|
|
38698
39470
|
};
|
|
38699
39471
|
const unknown = unknownFlags(flags);
|
|
39472
|
+
if (flags.help === true && cmd !== "help") {
|
|
39473
|
+
console.log(CMD_HELP[cmd] ?? HELP);
|
|
39474
|
+
return;
|
|
39475
|
+
}
|
|
38700
39476
|
if (unknown.length) die(`unknown flag: ${unknown.map((f) => `--${f}`).join(", ")}
|
|
38701
39477
|
run \`kt --help\` for the flags each command takes`);
|
|
38702
39478
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
@@ -38707,6 +39483,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38707
39483
|
const configured = resolveBase(flags.base);
|
|
38708
39484
|
if (!configured && !offlineLint && cmd !== "help" && cmd !== "--help" && cmd !== "-h") die(NO_BASE);
|
|
38709
39485
|
const cfg = { base: configured };
|
|
39486
|
+
if (!offlineLint && configured) installBaseFallback(configured);
|
|
38710
39487
|
let issued;
|
|
38711
39488
|
let signedThisRun = false;
|
|
38712
39489
|
const ensureRefreshKeyBound = async () => {
|
|
@@ -38732,8 +39509,11 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38732
39509
|
});
|
|
38733
39510
|
if (outcome !== "failed" && borrowedDevice(cfg.base, kcSlots)?.id === dev.id) settleRefreshBind(cfg.base, kcSlots, dev.id);
|
|
38734
39511
|
};
|
|
39512
|
+
const longCall = cmd === "transfer" || cmd === "sync";
|
|
39513
|
+
const longTimeoutSec = Number(flags.timeout);
|
|
38735
39514
|
const client = new KutrionClient({
|
|
38736
39515
|
baseUrl: cfg.base,
|
|
39516
|
+
...longCall ? { timeoutMs: (Number.isFinite(longTimeoutSec) && longTimeoutSec > 0 ? longTimeoutSec : 6 * 3600) * 1e3 } : {},
|
|
38737
39517
|
getToken: () => getToken(cfg.base),
|
|
38738
39518
|
onToken: (t) => {
|
|
38739
39519
|
issued = t || void 0;
|
|
@@ -38780,6 +39560,14 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38780
39560
|
});
|
|
38781
39561
|
const kc = { get: getToken, set: setToken, clear: clearToken };
|
|
38782
39562
|
const hubCall = hubCallFor(cfg.base);
|
|
39563
|
+
const vaultCustody = async (vaultId) => {
|
|
39564
|
+
try {
|
|
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;
|
|
39569
|
+
}
|
|
39570
|
+
};
|
|
38783
39571
|
const connectionContext = async (connId) => {
|
|
38784
39572
|
try {
|
|
38785
39573
|
const rows = (await client.listConnections()).connections;
|
|
@@ -38855,6 +39643,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38855
39643
|
timeoutMs: (intFlag("timeout") ?? 3600) * 1e3,
|
|
38856
39644
|
onTick: (j) => {
|
|
38857
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
|
+
`);
|
|
38858
39650
|
}
|
|
38859
39651
|
});
|
|
38860
39652
|
if (ticker) process.stderr.write("\n");
|
|
@@ -38864,8 +39656,8 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38864
39656
|
process.exit(EXIT_UNKNOWN);
|
|
38865
39657
|
}
|
|
38866
39658
|
show(job);
|
|
38867
|
-
if (job.status === "failed") {
|
|
38868
|
-
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"}`);
|
|
38869
39661
|
process.exit(EXIT_FAIL);
|
|
38870
39662
|
}
|
|
38871
39663
|
process.exit(EXIT_OK);
|
|
@@ -38951,7 +39743,6 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38951
39743
|
process.exit(1);
|
|
38952
39744
|
}
|
|
38953
39745
|
const staleDevice = dropBorrowed(cfg.base, kc);
|
|
38954
|
-
if (staleDevice) await client.revokeDevice(staleDevice).catch(() => void 0);
|
|
38955
39746
|
const post = fetchPost(cfg.base, refuseBodyRedirects());
|
|
38956
39747
|
const registered = await registerOwnDevice({
|
|
38957
39748
|
base: cfg.base,
|
|
@@ -38966,8 +39757,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38966
39757
|
} catch {
|
|
38967
39758
|
return null;
|
|
38968
39759
|
}
|
|
38969
|
-
})()
|
|
39760
|
+
})(),
|
|
39761
|
+
previousDeviceId: staleDevice
|
|
38970
39762
|
}).catch(() => ({ kind: "skipped", note: null }));
|
|
39763
|
+
if (staleDevice) await client.revokeDevice(staleDevice).catch(() => void 0);
|
|
38971
39764
|
recordSignIn(accountRoot, signedIn);
|
|
38972
39765
|
recordLastAccountId(cfg.base, kc, signedIn);
|
|
38973
39766
|
console.log(`logged in (token stored in ${backendName()})`);
|
|
@@ -38998,7 +39791,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
38998
39791
|
break;
|
|
38999
39792
|
case "whoami":
|
|
39000
39793
|
requireAuth();
|
|
39001
|
-
|
|
39794
|
+
{
|
|
39795
|
+
const { user } = await client.me();
|
|
39796
|
+
show(asJson ? user : whoamiLines(user));
|
|
39797
|
+
}
|
|
39002
39798
|
break;
|
|
39003
39799
|
case "vaults":
|
|
39004
39800
|
requireAuth();
|
|
@@ -39007,8 +39803,13 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39007
39803
|
case "vault": {
|
|
39008
39804
|
requireAuth();
|
|
39009
39805
|
const sub = positionals[0];
|
|
39010
|
-
if (sub === "create" && positionals[1])
|
|
39011
|
-
|
|
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);
|
|
39012
39813
|
else if (sub === "rm" && positionals[1]) {
|
|
39013
39814
|
await client.deleteVault(positionals[1]);
|
|
39014
39815
|
console.log("deleted");
|
|
@@ -39037,12 +39838,15 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39037
39838
|
noReveal = credential.value === null;
|
|
39038
39839
|
} else {
|
|
39039
39840
|
const summary = (await client.listCredentials(positionals[0])).credentials.find((c) => c.id === positionals[1]);
|
|
39040
|
-
if (!summary)
|
|
39841
|
+
if (!summary) {
|
|
39842
|
+
console.error(`no credential ${positionals[1]} in vault ${positionals[0]}`);
|
|
39843
|
+
process.exit(EXIT_NOT_FOUND);
|
|
39844
|
+
}
|
|
39041
39845
|
noReveal = summary.inject_only === true;
|
|
39042
39846
|
if (noReveal) show({ ...summary, value: null });
|
|
39043
39847
|
else {
|
|
39044
39848
|
show({ ...summary, value_hidden: true });
|
|
39045
|
-
console.error("value hidden; pass --reveal to print it");
|
|
39849
|
+
if (!asJson) console.error("value hidden; pass --reveal to print it");
|
|
39046
39850
|
}
|
|
39047
39851
|
}
|
|
39048
39852
|
if (noReveal) {
|
|
@@ -39064,17 +39868,45 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39064
39868
|
if (flags.value !== void 0) refuseSecretFlag("value", "pipe it to --value-stdin, or leave it off to be prompted");
|
|
39065
39869
|
if (flags["value-stdin"] === true && typeof flags.name !== "string") die("--value-stdin needs --name, because stdin carries the value");
|
|
39066
39870
|
const name = String(flags.name ?? await prompt("name: "));
|
|
39067
|
-
const value = flags["value-stdin"] === true ? await readSecret("value: ") : await prompt("value: ", true);
|
|
39068
39871
|
const notes = flags.notes !== void 0 ? String(flags.notes) : void 0;
|
|
39069
|
-
|
|
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));
|
|
39070
39886
|
} else if (sub === "set" && vaultId && positionals[2]) {
|
|
39071
39887
|
if (flags.value !== void 0) refuseSecretFlag("value", "pipe it to --value-stdin");
|
|
39072
39888
|
const patch = {};
|
|
39073
39889
|
if (flags.name !== void 0) patch.name = String(flags.name);
|
|
39074
|
-
if (flags["value-stdin"] === true) patch.value = await readSecret("value: ");
|
|
39075
39890
|
if (flags.notes !== void 0) patch.notes = String(flags.notes);
|
|
39076
|
-
if (!Object.keys(patch).length) return die("nothing to update \u2014 pass --name/--value-stdin/--notes");
|
|
39077
|
-
|
|
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));
|
|
39078
39910
|
} else if (sub === "rm" && vaultId && positionals[2]) {
|
|
39079
39911
|
await client.deleteCredential(vaultId, positionals[2]);
|
|
39080
39912
|
console.log("deleted");
|
|
@@ -39155,8 +39987,20 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39155
39987
|
}
|
|
39156
39988
|
case "exec": {
|
|
39157
39989
|
requireAuth();
|
|
39158
|
-
if (!positionals[0] || !positionals[1]) return die("usage: kt exec <connId> <command...>");
|
|
39159
|
-
|
|
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) => {
|
|
39160
40004
|
const k = hostKeyUnconfirmedFromError(e);
|
|
39161
40005
|
const chained = k ? null : hostKeyChainedFromError(e);
|
|
39162
40006
|
if (!k && !chained) throw e;
|
|
@@ -39195,7 +40039,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39195
40039
|
}
|
|
39196
40040
|
let job;
|
|
39197
40041
|
try {
|
|
39198
|
-
job = parseJobFile((0,
|
|
40042
|
+
job = parseJobFile((0, import_fs7.readFileSync)(path, "utf8"));
|
|
39199
40043
|
} catch (e) {
|
|
39200
40044
|
return die(e instanceof JobFileError ? `${path}: ${e.message}` : `${path}: ${e.message}`);
|
|
39201
40045
|
}
|
|
@@ -39204,7 +40048,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39204
40048
|
if (agentToken && !asJson && reporter === "console") {
|
|
39205
40049
|
console.error(`running as a pipeline principal through ${cfg.base.replace(/\/+$/, "")}/mcp`);
|
|
39206
40050
|
}
|
|
39207
|
-
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) }, {
|
|
39208
40052
|
dryRun,
|
|
39209
40053
|
onStep: (s) => {
|
|
39210
40054
|
if (asJson || reporter !== "console") return;
|
|
@@ -39230,7 +40074,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39230
40074
|
const path = positionals[2] ?? ".";
|
|
39231
40075
|
const r = await client.sftpList(connId, path);
|
|
39232
40076
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39233
|
-
show(r.entries, ["name", "type", "size", "mode"]);
|
|
40077
|
+
show(r.entries.map((e) => ({ ...e, mode: modeString(e.mode, e.type) })), ["name", "type", "size", "mode"]);
|
|
39234
40078
|
} else if (sub === "mkdir" && connId && positionals[2]) {
|
|
39235
40079
|
await client.sftpMkdir(connId, positionals[2]);
|
|
39236
40080
|
console.log("created");
|
|
@@ -39302,6 +40146,11 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39302
40146
|
to,
|
|
39303
40147
|
throttle_bps: intFlag("throttle"),
|
|
39304
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);
|
|
39305
40154
|
});
|
|
39306
40155
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39307
40156
|
console.log(`transferred ${r.files} file(s), ${fmtBytes(r.bytes)}${r.skipped ? `, ${r.skipped} skipped` : ""}${r.renamed ? `, ${r.renamed} renamed` : ""}`);
|
|
@@ -39350,8 +40199,10 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39350
40199
|
return await watchJob(job.id);
|
|
39351
40200
|
} else if (sub === "wait" && positionals[1]) {
|
|
39352
40201
|
return await watchJob(positionals[1]);
|
|
40202
|
+
} else if (sub === "cancel" && positionals[1]) {
|
|
40203
|
+
show((await client.cancelTransferJob(positionals[1])).job);
|
|
39353
40204
|
} else {
|
|
39354
|
-
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>");
|
|
39355
40206
|
}
|
|
39356
40207
|
break;
|
|
39357
40208
|
}
|
|
@@ -39401,6 +40252,13 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39401
40252
|
} else if (sub === "new" && positionals[1] && positionals[2] && positionals[3]) {
|
|
39402
40253
|
const source = sideArg(positionals[2], "source");
|
|
39403
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
|
+
}
|
|
39404
40262
|
show((await client.createFolderWatcher({
|
|
39405
40263
|
name: positionals[1],
|
|
39406
40264
|
source,
|
|
@@ -39414,7 +40272,7 @@ run \`kt --help\` for the flags each command takes`);
|
|
|
39414
40272
|
// Stability checks are what stop a watcher picking up a file that is still being written.
|
|
39415
40273
|
// Off is opt-in, never a default.
|
|
39416
40274
|
stability_checks: flags["no-stability"] !== true,
|
|
39417
|
-
interval_seconds:
|
|
40275
|
+
interval_seconds: every
|
|
39418
40276
|
})).watcher);
|
|
39419
40277
|
} else if ((sub === "enable" || sub === "disable") && positionals[1]) {
|
|
39420
40278
|
await client.setFolderWatcherEnabled(positionals[1], sub === "enable");
|
|
@@ -39486,11 +40344,11 @@ ${r.authorize_url}`);
|
|
|
39486
40344
|
const buf = Buffer.from(await blob.arrayBuffer());
|
|
39487
40345
|
if (local === "-") process.stdout.write(buf);
|
|
39488
40346
|
else {
|
|
39489
|
-
(0,
|
|
40347
|
+
(0, import_fs7.writeFileSync)(local, buf);
|
|
39490
40348
|
console.error(`saved ${local} (${fmtBytes(buf.length)})`);
|
|
39491
40349
|
}
|
|
39492
40350
|
} else if (sub === "put" && id && positionals[2] && positionals[3]) {
|
|
39493
|
-
const buf = (0,
|
|
40351
|
+
const buf = (0, import_fs7.readFileSync)(positionals[2]);
|
|
39494
40352
|
await client.cloudUpload(id, positionals[3], new Blob([buf]));
|
|
39495
40353
|
console.error(`uploaded ${fmtBytes(buf.length)}`);
|
|
39496
40354
|
} else if (sub === "mkdir" && id && positionals[2]) {
|
|
@@ -39596,17 +40454,20 @@ ${r.token}`);
|
|
|
39596
40454
|
}
|
|
39597
40455
|
const scopes = flags.scopes ? String(flags.scopes).split(",").map((s) => s.trim()).filter(Boolean) : DEFAULT_SCOPES;
|
|
39598
40456
|
const ttlDays = flags.days !== void 0 ? Number(flags.days) : DEFAULT_TTL_DAYS;
|
|
39599
|
-
const dir = (0,
|
|
40457
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39600
40458
|
const egress = String(flags.egress ?? "cloud");
|
|
39601
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)");
|
|
39602
40461
|
try {
|
|
39603
|
-
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 });
|
|
39604
40464
|
warnLeftLive(r, client2);
|
|
39605
40465
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
39606
40466
|
console.log(`installed ${CLIENTS[client2].label}`);
|
|
39607
40467
|
console.log(` config ${r.configPath}`);
|
|
39608
40468
|
console.log(` agent ${r.principalId} (expires ${r.expiresAt})`);
|
|
39609
40469
|
console.log(` scopes ${r.scopes.join(", ")}`);
|
|
40470
|
+
console.log(` reach ${describeScope(r.resourceScope)}`);
|
|
39610
40471
|
if (r.superseded) console.log(` revoked ${r.superseded} earlier agent(s) for this client`);
|
|
39611
40472
|
console.log(` credential stored in the ${backendName()} \u2014 the config file does not contain it`);
|
|
39612
40473
|
console.log(r.verified ? " verified connection to the broker works" : " warning: could not verify the connection");
|
|
@@ -39625,7 +40486,7 @@ next: ${r.next}`);
|
|
|
39625
40486
|
break;
|
|
39626
40487
|
}
|
|
39627
40488
|
if (sub === "status") {
|
|
39628
|
-
const dir = (0,
|
|
40489
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39629
40490
|
const s = await mcpStatus(cfg.base, dir);
|
|
39630
40491
|
if (asJson) return console.log(JSON.stringify(s, null, 2));
|
|
39631
40492
|
console.log(`credential store: ${s.keychain}`);
|
|
@@ -39645,6 +40506,7 @@ next: ${r.next}`);
|
|
|
39645
40506
|
if (c.agent?.state !== "live") continue;
|
|
39646
40507
|
const rl = c.agent.rate_limit;
|
|
39647
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)}`);
|
|
39648
40510
|
}
|
|
39649
40511
|
if (s.legacy) console.log(`
|
|
39650
40512
|
shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.trimEnd());
|
|
@@ -39656,7 +40518,7 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39656
40518
|
if (client2 && !CLIENT_IDS.includes(client2)) {
|
|
39657
40519
|
return die(`unknown --client; one of: ${CLIENT_IDS.join(", ")}`);
|
|
39658
40520
|
}
|
|
39659
|
-
const dir = (0,
|
|
40521
|
+
const dir = (0, import_path7.resolve)(String(flags.dir ?? process.cwd()));
|
|
39660
40522
|
const r = await uninstallClient({ base: cfg.base, userToken, client: client2, dir });
|
|
39661
40523
|
warnLeftLive(r, client2);
|
|
39662
40524
|
if (asJson) return console.log(JSON.stringify(r, null, 2));
|
|
@@ -39665,7 +40527,7 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39665
40527
|
break;
|
|
39666
40528
|
}
|
|
39667
40529
|
if (sub && sub !== "serve") {
|
|
39668
|
-
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>]");
|
|
39669
40531
|
}
|
|
39670
40532
|
if (flags.token !== void 0) refuseSecretFlag("token", "export KT_AGENT_TOKEN instead, or run kt mcp install");
|
|
39671
40533
|
let agentToken;
|
|
@@ -39686,6 +40548,136 @@ shared pre-split credential: ${s.legacy.principal_id ?? ""} ${state(s.legacy)}`.
|
|
|
39686
40548
|
await runMcpBridge({ base: cfg.base, token: agentToken });
|
|
39687
40549
|
break;
|
|
39688
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
|
+
}
|
|
39689
40681
|
// #674: exchange this CI job's own OIDC token for a short-lived agent principal. No Kutrion
|
|
39690
40682
|
// secret is stored in the pipeline — the job token is minted per run by the CI provider.
|
|
39691
40683
|
case "ci-login": {
|
|
@@ -39901,7 +40893,15 @@ ${snippetPreview(s)}
|
|
|
39901
40893
|
const connection_id = String(flags.conn ?? ""), credential_id = String(flags.cred ?? "");
|
|
39902
40894
|
if (!connection_id || !credential_id) return die("need --conn <id> --cred <id>");
|
|
39903
40895
|
const input = { connection_id, credential_id, service, target: flags.target ? String(flags.target) : void 0 };
|
|
39904
|
-
|
|
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
|
+
}
|
|
39905
40905
|
} else die("usage: kt rotation services | history <credId> | plan|run --conn <id> --cred <id> --service <svc> [--target ..]");
|
|
39906
40906
|
break;
|
|
39907
40907
|
}
|
|
@@ -39973,8 +40973,9 @@ ${owed}`);
|
|
|
39973
40973
|
case "device":
|
|
39974
40974
|
requireAuth();
|
|
39975
40975
|
if (positionals[0] === "rm" && positionals[1]) {
|
|
39976
|
-
await client.revokeDevice(positionals[1]);
|
|
40976
|
+
const r = await client.revokeDevice(positionals[1]);
|
|
39977
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(", ")}`);
|
|
39978
40979
|
} else die("usage: kt device rm <deviceId>");
|
|
39979
40980
|
break;
|
|
39980
40981
|
// --- vault share ---
|
|
@@ -40037,7 +41038,7 @@ ${r.url}`);
|
|
|
40037
41038
|
} else if (sub === "import") {
|
|
40038
41039
|
const vaultId = positionals[1], file = positionals[2];
|
|
40039
41040
|
if (!vaultId || !file) return die(CONN_IMPORT_USAGE);
|
|
40040
|
-
const parsed = await client.parseConnImport((0,
|
|
41041
|
+
const parsed = await client.parseConnImport((0, import_fs7.readFileSync)(file, "utf8"), {
|
|
40041
41042
|
provider: flags.provider ? String(flags.provider) : void 0,
|
|
40042
41043
|
filename: file.split("/").pop()
|
|
40043
41044
|
});
|
|
@@ -40167,7 +41168,7 @@ ${ENROLL_USAGE}`);
|
|
|
40167
41168
|
const confirms = parseConfirms(process.argv.slice(2));
|
|
40168
41169
|
if (!(confirms instanceof Map)) return die(confirms.error);
|
|
40169
41170
|
if (flags.key === true || flags["known-hosts"] === true || flags.vault === true) return die(ENROLL_USAGE);
|
|
40170
|
-
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;
|
|
40171
41172
|
const fallback = flags.fallback === true;
|
|
40172
41173
|
const vaultId = typeof flags.vault === "string" ? flags.vault : void 0;
|
|
40173
41174
|
const timeoutSec = intFlag("timeout") ?? 1800;
|
|
@@ -40187,7 +41188,7 @@ ${r.label} ${r.host.observedFingerprint}`);
|
|
|
40187
41188
|
if (!chosen.length) return die(ENROLL_COPY.noneConfirmed);
|
|
40188
41189
|
let bootstrap;
|
|
40189
41190
|
if (typeof flags.key === "string") {
|
|
40190
|
-
const privateKey = (0,
|
|
41191
|
+
const privateKey = (0, import_fs7.readFileSync)(flags.key, "utf8");
|
|
40191
41192
|
let passphrase;
|
|
40192
41193
|
if (isEncryptedKey(privateKey)) {
|
|
40193
41194
|
if (flags["password-stdin"] === true) passphrase = await readStdin();
|
|
@@ -40380,14 +41381,15 @@ ${BROKER_CA_USAGE}`);
|
|
|
40380
41381
|
break;
|
|
40381
41382
|
case "import-providers":
|
|
40382
41383
|
requireAuth();
|
|
40383
|
-
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"]);
|
|
40384
41385
|
break;
|
|
40385
41386
|
case "import": {
|
|
40386
41387
|
requireAuth();
|
|
40387
41388
|
const vaultId = positionals[0];
|
|
40388
41389
|
const file = positionals[1];
|
|
40389
41390
|
if (!vaultId || !file) die("usage: kt import <vaultId> <file> [--provider <name>] (see: kt import-providers)");
|
|
40390
|
-
const content = (0,
|
|
41391
|
+
const content = (0, import_fs7.readFileSync)(file, "utf8");
|
|
41392
|
+
if ((await vaultCustody(vaultId)).private) return die(PRIVATE_IMPORT_REFUSED);
|
|
40391
41393
|
const parsed = await client.parseImport(content, { provider: flags.provider ? String(flags.provider) : void 0, filename: file.split("/").pop() });
|
|
40392
41394
|
for (const w of parsed.warnings) console.error("warning:", w);
|
|
40393
41395
|
const items = parsed.items.filter((it) => it.name && it.value).map((it) => ({ ...it, name: it.name.slice(0, 255) }));
|
|
@@ -40466,6 +41468,19 @@ Add this to your authenticator, then run: kt mfa verify --code <code>`);
|
|
|
40466
41468
|
}
|
|
40467
41469
|
break;
|
|
40468
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
|
+
}
|
|
40469
41484
|
case "passkeys":
|
|
40470
41485
|
requireAuth();
|
|
40471
41486
|
show((await client.listPasskeys()).passkeys, ["id", "label", "kind", "created_at", "last_used"]);
|