@monster-orange/den 0.4.25 → 0.4.26
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.md +14 -0
- package/package.json +1 -1
- package/src/shared/secrets/onepassword.ts +98 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to Den are documented here. Each release lists the
|
|
|
4
4
|
config and manifest changes a distribution must make to move to it under
|
|
5
5
|
**Migrations**.
|
|
6
6
|
|
|
7
|
+
## [0.4.26] - 2026-11-10
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
|
|
11
|
+
- **A secret saved to 1Password is never on a command line.** `op`
|
|
12
|
+
wrote the value as an argument (`field[password]=<value>`), which any
|
|
13
|
+
process on the machine can read while `op` runs. Saving the admin
|
|
14
|
+
password (and every other 1Password write) now reads the item as JSON,
|
|
15
|
+
changes the one field and passes it on `op`'s stdin (`op item edit`, or
|
|
16
|
+
`op item create -` for a new item). The item's other fields are kept.
|
|
17
|
+
Nothing to change in config. A sweep of Den's other commands found no
|
|
18
|
+
other secret in argv: sudo and Doppler already read theirs on stdin,
|
|
19
|
+
and agent keys go in the environment.
|
|
20
|
+
|
|
7
21
|
## [0.4.25] - 2026-11-10
|
|
8
22
|
|
|
9
23
|
### Added
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 1Password strategy — reads/writes item fields with the `op` CLI.
|
|
3
3
|
* Selected with `secrets.provider: 1password`.
|
|
4
4
|
*/
|
|
5
|
-
import { execFile } from "child_process";
|
|
5
|
+
import { execFile, spawn } from "child_process";
|
|
6
6
|
import { promisify } from "util";
|
|
7
7
|
import type { PreflightResult, SecretRef, SecretsProvider } from "./types.js";
|
|
8
8
|
import {
|
|
@@ -128,30 +128,108 @@ export class OnePasswordProvider implements SecretsProvider {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Run `op` with `input` on its stdin. A secret goes this way, never in
|
|
133
|
+
* argv: any process on the machine can read another's command line.
|
|
134
|
+
*/
|
|
135
|
+
private async runWithInput(args: string[], input: string): Promise<string> {
|
|
136
|
+
const { token: _t, ...opts } = await this.opOptions();
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const child = spawn("op", args, {
|
|
139
|
+
env: opts.env,
|
|
140
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
141
|
+
});
|
|
142
|
+
let stdout = "";
|
|
143
|
+
let stderr = "";
|
|
144
|
+
let killed = false;
|
|
145
|
+
const timer = setTimeout(() => {
|
|
146
|
+
killed = true;
|
|
147
|
+
child.kill("SIGTERM");
|
|
148
|
+
}, opts.timeout);
|
|
149
|
+
child.stdout.on("data", (d) => (stdout += String(d)));
|
|
150
|
+
child.stderr.on("data", (d) => (stderr += String(d)));
|
|
151
|
+
child.on("error", (e) => {
|
|
152
|
+
clearTimeout(timer);
|
|
153
|
+
reject(e);
|
|
154
|
+
});
|
|
155
|
+
child.on("close", (code) => {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
if (code === 0) resolve(stdout);
|
|
158
|
+
else
|
|
159
|
+
reject(
|
|
160
|
+
Object.assign(new Error(stderr.trim() || `op exited ${code}`), {
|
|
161
|
+
stderr,
|
|
162
|
+
killed,
|
|
163
|
+
code,
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
child.stdin.on("error", () => undefined);
|
|
168
|
+
child.stdin.end(input);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Overwrite the field; an item that isn't there yet is created (a
|
|
174
|
+
* Password item). The value only ever travels on op's stdin, as the
|
|
175
|
+
* item's JSON (op item edit / op item create -), never in argv.
|
|
176
|
+
*/
|
|
132
177
|
async set(ref: SecretRef, value: string): Promise<void> {
|
|
133
178
|
const vault = ref.vault ? ["--vault", ref.vault] : [];
|
|
134
|
-
|
|
179
|
+
type Field = {
|
|
180
|
+
id?: string;
|
|
181
|
+
label?: string;
|
|
182
|
+
type?: string;
|
|
183
|
+
purpose?: string;
|
|
184
|
+
value?: string;
|
|
185
|
+
};
|
|
186
|
+
let item: { id?: string; fields?: Field[] } | null = null;
|
|
187
|
+
let readErr: unknown = null;
|
|
135
188
|
try {
|
|
136
|
-
|
|
137
|
-
} catch (err) {
|
|
138
|
-
try {
|
|
189
|
+
item = JSON.parse(
|
|
139
190
|
await this.run(
|
|
140
|
-
[
|
|
141
|
-
"item",
|
|
142
|
-
"create",
|
|
143
|
-
"--category",
|
|
144
|
-
"password",
|
|
145
|
-
"--title",
|
|
146
|
-
ref.item,
|
|
147
|
-
...vault,
|
|
148
|
-
field,
|
|
149
|
-
],
|
|
191
|
+
["item", "get", ref.item, ...vault, "--format", "json", "--reveal"],
|
|
150
192
|
true,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
193
|
+
),
|
|
194
|
+
) as { id?: string; fields?: Field[] };
|
|
195
|
+
} catch (err) {
|
|
196
|
+
readErr = err;
|
|
197
|
+
}
|
|
198
|
+
if (item) {
|
|
199
|
+
const fields = item.fields ?? [];
|
|
200
|
+
const f = fields.find((x) => x.label === ref.field || x.id === ref.field);
|
|
201
|
+
if (f) {
|
|
202
|
+
f.value = value;
|
|
203
|
+
f.type = "CONCEALED";
|
|
204
|
+
} else fields.push({ label: ref.field, type: "CONCEALED", value });
|
|
205
|
+
item.fields = fields;
|
|
206
|
+
await this.runWithInput(
|
|
207
|
+
["item", "edit", item.id || ref.item, ...vault],
|
|
208
|
+
JSON.stringify(item),
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const field: Field =
|
|
213
|
+
ref.field === "password"
|
|
214
|
+
? {
|
|
215
|
+
id: "password",
|
|
216
|
+
type: "CONCEALED",
|
|
217
|
+
purpose: "PASSWORD",
|
|
218
|
+
label: "password",
|
|
219
|
+
value,
|
|
220
|
+
}
|
|
221
|
+
: { label: ref.field, type: "CONCEALED", value };
|
|
222
|
+
try {
|
|
223
|
+
await this.runWithInput(
|
|
224
|
+
["item", "create", ...vault, "-"],
|
|
225
|
+
JSON.stringify({
|
|
226
|
+
title: ref.item,
|
|
227
|
+
category: "PASSWORD",
|
|
228
|
+
fields: [field],
|
|
229
|
+
}),
|
|
230
|
+
);
|
|
231
|
+
} catch {
|
|
232
|
+
throw readErr;
|
|
155
233
|
}
|
|
156
234
|
}
|
|
157
235
|
|