@ouro.bot/cli 0.1.0-alpha.405 → 0.1.0-alpha.407
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/changelog.json
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.407",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`credential_store` now uses the same Bitwarden-derived error scrubber as provider auth, so agent-saved secret flows redact raw `bw ...` command lines, encoded payload blobs, and hidden prompt echoes instead of free-styling their own failure cleanup.",
|
|
8
|
+
"The shared sanitizer now collapses fully scrubbed failures back to `command failed`, so redaction does not leave behind useless `[redacted]` noise when the only surviving text was secret-shaped.",
|
|
9
|
+
"Added regression coverage for the shared sanitizer itself plus the `credential_store` path that agents hit after signups and manual secret saves.",
|
|
10
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the shared credential error sanitization release."
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "0.1.0-alpha.406",
|
|
15
|
+
"changes": [
|
|
16
|
+
"`ouro auth --agent <agent> --provider <provider>` now keeps narrating the post-login vault save path with `opening ... vault session`, `storing ... credentials`, and `refreshing local provider snapshot`, so a successful browser login no longer drops into a silent cursor while secrets are being persisted.",
|
|
17
|
+
"Bitwarden-backed provider saves now classify timeouts and empty command failures by operation and redact raw `bw create item ...` command text, encoded payloads, and prompt echoes from auth output.",
|
|
18
|
+
"Auth, repair CLI, and Bitwarden regression coverage now encodes the reported post-login save failure shapes so the same leak-prone path stays guarded.",
|
|
19
|
+
"`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the post-login vault save hardening release."
|
|
20
|
+
]
|
|
21
|
+
},
|
|
4
22
|
{
|
|
5
23
|
"version": "0.1.0-alpha.405",
|
|
6
24
|
"changes": [
|
|
@@ -148,6 +148,7 @@ async function storeProviderCredentials(agentName, provider, credentials, deps =
|
|
|
148
148
|
config: split.config,
|
|
149
149
|
provenance: { source: "auth-flow" },
|
|
150
150
|
now: deps.now,
|
|
151
|
+
onProgress: deps.onProgress,
|
|
151
152
|
});
|
|
152
153
|
return { credentialPath: (0, provider_credentials_1.providerCredentialItemName)(provider) };
|
|
153
154
|
}
|
|
@@ -391,11 +392,14 @@ async function runRuntimeAuthFlow(input, deps = {}) {
|
|
|
391
392
|
throw new Error(`${vault.error}\n${(0, vault_unlock_1.vaultUnlockReplaceRecoverFix)(input.agentName, `Then retry 'ouro auth --agent ${input.agentName} --provider ${input.provider}'.`)}`);
|
|
392
393
|
}
|
|
393
394
|
const credentials = await collectRuntimeAuthCredentials(input, deps);
|
|
394
|
-
writeAuthProgress(input, `${input.provider} credentials collected; storing in ${input.agentName}'s vault...`);
|
|
395
395
|
let credentialPath;
|
|
396
396
|
try {
|
|
397
397
|
;
|
|
398
|
-
({
|
|
398
|
+
({
|
|
399
|
+
credentialPath,
|
|
400
|
+
} = await storeProviderCredentials(input.agentName, input.provider, credentials, {
|
|
401
|
+
onProgress: (message) => writeAuthProgress(input, message),
|
|
402
|
+
}));
|
|
399
403
|
}
|
|
400
404
|
catch (error) {
|
|
401
405
|
throw formatVaultStoreError(input.agentName, input.provider, error);
|
|
@@ -328,12 +328,15 @@ async function upsertProviderCredential(input) {
|
|
|
328
328
|
},
|
|
329
329
|
};
|
|
330
330
|
const record = recordFromPayload(payload);
|
|
331
|
+
input.onProgress?.(`opening ${input.agentName}'s vault session...`);
|
|
331
332
|
const store = (0, credential_access_1.getCredentialStore)(input.agentName);
|
|
333
|
+
input.onProgress?.(`storing ${input.provider} credentials in ${input.agentName}'s vault...`);
|
|
332
334
|
await store.store(providerCredentialItemName(input.provider), {
|
|
333
335
|
username: input.provider,
|
|
334
336
|
password: JSON.stringify(payload),
|
|
335
337
|
notes: "Ouro provider credentials. The vault item password is a versioned JSON payload.",
|
|
336
338
|
});
|
|
339
|
+
input.onProgress?.(`refreshing local provider snapshot from ${input.agentName}'s vault...`);
|
|
337
340
|
const refreshResult = await refreshProviderCredentialPool(input.agentName);
|
|
338
341
|
if (!refreshResult.ok) {
|
|
339
342
|
throw new Error(`credential stored in vault, but the local provider snapshot could not be refreshed: ${refreshResult.error}. ` +
|
|
@@ -42,10 +42,39 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
})();
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
44
|
exports.BitwardenCredentialStore = void 0;
|
|
45
|
+
exports.sanitizeCredentialErrorDetail = sanitizeCredentialErrorDetail;
|
|
45
46
|
const node_child_process_1 = require("node:child_process");
|
|
46
47
|
const fs = __importStar(require("node:fs"));
|
|
47
48
|
const runtime_1 = require("../nerves/runtime");
|
|
48
49
|
const bw_installer_1 = require("./bw-installer");
|
|
50
|
+
const MAX_ERROR_DETAIL_LENGTH = 500;
|
|
51
|
+
const LONG_ENCODED_TOKEN_PATTERN = /[A-Za-z0-9+/=]{32,}/g;
|
|
52
|
+
function uniqueSecrets(secrets) {
|
|
53
|
+
return [...new Set(secrets.filter((value) => typeof value === "string" && value.length >= 4))].sort((left, right) => right.length - left.length);
|
|
54
|
+
}
|
|
55
|
+
function sanitizeCredentialErrorDetail(message, options = {}) {
|
|
56
|
+
const filtered = message
|
|
57
|
+
.split(/\r?\n/)
|
|
58
|
+
.filter((line) => {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (trimmed.startsWith("Command failed:"))
|
|
61
|
+
return false;
|
|
62
|
+
if (trimmed.includes("[input is hidden]"))
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
})
|
|
66
|
+
.join("\n")
|
|
67
|
+
.trim();
|
|
68
|
+
let sanitized = filtered || "command failed";
|
|
69
|
+
for (const secret of uniqueSecrets(options.secrets ?? [])) {
|
|
70
|
+
sanitized = sanitized.split(secret).join("[redacted]");
|
|
71
|
+
}
|
|
72
|
+
sanitized = sanitized.replace(LONG_ENCODED_TOKEN_PATTERN, "[redacted]");
|
|
73
|
+
if (sanitized.replace(/\[redacted\]/g, "").trim().length === 0) {
|
|
74
|
+
return "command failed";
|
|
75
|
+
}
|
|
76
|
+
return sanitized.slice(0, MAX_ERROR_DETAIL_LENGTH);
|
|
77
|
+
}
|
|
49
78
|
// ---------------------------------------------------------------------------
|
|
50
79
|
// bw CLI wrapper
|
|
51
80
|
// ---------------------------------------------------------------------------
|
|
@@ -59,6 +88,21 @@ function isBwSessionUnavailableMessage(message) {
|
|
|
59
88
|
function isBwInvalidUnlockSecretMessage(message) {
|
|
60
89
|
return /invalid master password/i.test(message) || /saved vault unlock secret/i.test(message);
|
|
61
90
|
}
|
|
91
|
+
function isBwTimeoutError(err) {
|
|
92
|
+
const timeoutErr = err;
|
|
93
|
+
const message = err.message.toLowerCase();
|
|
94
|
+
return (timeoutErr.code === "ETIMEDOUT" ||
|
|
95
|
+
timeoutErr.killed === true ||
|
|
96
|
+
timeoutErr.signal === "SIGTERM" ||
|
|
97
|
+
message.includes("timed out"));
|
|
98
|
+
}
|
|
99
|
+
function formatBwOperation(args) {
|
|
100
|
+
const [command, target] = args;
|
|
101
|
+
/* v8 ignore next -- defensive: all execBw call sites pass a concrete bw subcommand @preserve */
|
|
102
|
+
if (!command)
|
|
103
|
+
return "bw command";
|
|
104
|
+
return [command, target].filter(Boolean).join(" ");
|
|
105
|
+
}
|
|
62
106
|
function sanitizeBwErrorDetail(message) {
|
|
63
107
|
if (isBwInvalidUnlockSecretMessage(message)) {
|
|
64
108
|
return "bw CLI rejected the saved vault unlock secret for this machine";
|
|
@@ -66,17 +110,17 @@ function sanitizeBwErrorDetail(message) {
|
|
|
66
110
|
if (isBwSessionUnavailableMessage(message)) {
|
|
67
111
|
return "bw CLI could not use the local Bitwarden session because it is locked, missing, or expired";
|
|
68
112
|
}
|
|
69
|
-
|
|
70
|
-
.split(/\r?\n/)
|
|
71
|
-
.filter((line) => !line.trim().startsWith("Command failed:"))
|
|
72
|
-
.join("\n")
|
|
73
|
-
.trim();
|
|
74
|
-
return (withoutCommandLine || "command failed")
|
|
75
|
-
.replace(/[A-Za-z0-9+/=]{80,}/g, "[redacted]")
|
|
76
|
-
.slice(0, 500);
|
|
113
|
+
return sanitizeCredentialErrorDetail(message);
|
|
77
114
|
}
|
|
78
|
-
function formatBwCliError(err, stderr = "") {
|
|
115
|
+
function formatBwCliError(err, stderr = "", args = []) {
|
|
116
|
+
const operation = formatBwOperation(args);
|
|
117
|
+
if (isBwTimeoutError(err)) {
|
|
118
|
+
return new Error(`bw CLI error: ${operation} timed out while waiting for a vault response`);
|
|
119
|
+
}
|
|
79
120
|
const detail = sanitizeBwErrorDetail(stderr.trim() || err.message);
|
|
121
|
+
if (detail === "command failed") {
|
|
122
|
+
return new Error(`bw CLI error: ${operation} failed without error detail`);
|
|
123
|
+
}
|
|
80
124
|
return new Error(`bw CLI error: ${detail}`);
|
|
81
125
|
}
|
|
82
126
|
function isBwSessionAuthError(err) {
|
|
@@ -99,7 +143,7 @@ function execBw(args, sessionToken, appDataDir, stdin) {
|
|
|
99
143
|
reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
|
|
100
144
|
return;
|
|
101
145
|
}
|
|
102
|
-
reject(formatBwCliError(err, stderr));
|
|
146
|
+
reject(formatBwCliError(err, stderr, args));
|
|
103
147
|
return;
|
|
104
148
|
}
|
|
105
149
|
resolve(stdout);
|
|
@@ -36,11 +36,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.credentialToolDefinitions = void 0;
|
|
37
37
|
const crypto = __importStar(require("node:crypto"));
|
|
38
38
|
const credential_access_1 = require("./credential-access");
|
|
39
|
+
const bitwarden_store_1 = require("./bitwarden-store");
|
|
39
40
|
const runtime_1 = require("../nerves/runtime");
|
|
40
41
|
const DEFAULT_PASSWORD_LENGTH = 24;
|
|
41
42
|
const MIN_PASSWORD_LENGTH = 12;
|
|
42
43
|
const MAX_PASSWORD_LENGTH = 128;
|
|
43
|
-
const MAX_ERROR_DETAIL_LENGTH = 500;
|
|
44
44
|
const PASSWORD_CHARSETS = {
|
|
45
45
|
lower: "abcdefghijkmnopqrstuvwxyz",
|
|
46
46
|
upper: "ABCDEFGHJKLMNPQRSTUVWXYZ",
|
|
@@ -49,20 +49,7 @@ const PASSWORD_CHARSETS = {
|
|
|
49
49
|
};
|
|
50
50
|
function sanitizeCredentialToolError(err, secrets = []) {
|
|
51
51
|
const raw = err instanceof Error ? err.message : String(err);
|
|
52
|
-
|
|
53
|
-
.split(/\r?\n/)
|
|
54
|
-
.filter((line) => !line.trim().startsWith("Command failed:"))
|
|
55
|
-
.join("\n")
|
|
56
|
-
.trim();
|
|
57
|
-
let sanitized = scrubbed || "command failed";
|
|
58
|
-
const uniqueSecrets = [...new Set(secrets.filter((value) => typeof value === "string" && value.length >= 4))]
|
|
59
|
-
.sort((left, right) => right.length - left.length);
|
|
60
|
-
for (const secret of uniqueSecrets) {
|
|
61
|
-
sanitized = sanitized.split(secret).join("[redacted]");
|
|
62
|
-
}
|
|
63
|
-
return sanitized
|
|
64
|
-
.replace(/[A-Za-z0-9+/=]{80,}/g, "[redacted]")
|
|
65
|
-
.slice(0, MAX_ERROR_DETAIL_LENGTH);
|
|
52
|
+
return (0, bitwarden_store_1.sanitizeCredentialErrorDetail)(raw, { secrets });
|
|
66
53
|
}
|
|
67
54
|
function requireTrimmedText(value, fieldName) {
|
|
68
55
|
if (typeof value !== "string" || value.trim().length === 0) {
|