@envseal/cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1 -1
- package/dist/commands/ensure.js +14 -2
- package/dist/commands/init.js +22 -1
- package/dist/commands/run.js +10 -0
- package/dist/commands/set.js +23 -1
- package/package.json +8 -8
package/dist/bin.js
CHANGED
|
@@ -13,7 +13,7 @@ import { doctor } from './commands/doctor.js';
|
|
|
13
13
|
import { revoke } from './commands/revoke.js';
|
|
14
14
|
import { mcp } from './commands/mcp.js';
|
|
15
15
|
import { init } from './commands/init.js';
|
|
16
|
-
const VERSION = '0.1.
|
|
16
|
+
const VERSION = '0.1.2';
|
|
17
17
|
async function main() {
|
|
18
18
|
const argv = process.argv.slice(2);
|
|
19
19
|
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
package/dist/commands/ensure.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { SepError } from '@envseal/protocol';
|
|
1
2
|
import { emit, fail } from '../output.js';
|
|
2
3
|
import { EXIT } from '../exit-codes.js';
|
|
3
4
|
import { loadManifest, projectPaths } from '@envseal/core';
|
|
4
|
-
import {
|
|
5
|
-
import { createBroker, outcomeForKey } from '../cli-utils.js';
|
|
5
|
+
import { createBroker, hasInteractiveSurface, outcomeForKey } from '../cli-utils.js';
|
|
6
6
|
import { finish } from '../exit.js';
|
|
7
7
|
export async function ensure(root, json, check = false) {
|
|
8
8
|
try {
|
|
@@ -80,6 +80,18 @@ export async function ensure(root, json, check = false) {
|
|
|
80
80
|
}
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
+
// F2: without --check, ensure asks the user for values. When there is no
|
|
84
|
+
// interactive surface and no double-gated test prompter configured, refuse
|
|
85
|
+
// now instead of selecting the loopback surface and waiting on a browser
|
|
86
|
+
// page nobody can see (the W4 hang). Mirrors run.ts's fail-closed stance.
|
|
87
|
+
if (!hasInteractiveSurface() && !(process.env.ENVSEAL_TEST_PROMPTER_VALUE !== undefined ||
|
|
88
|
+
process.env.ENVSEAL_TEST_PROMPTER_OUTCOME !== undefined)) {
|
|
89
|
+
throw new SepError({
|
|
90
|
+
code: 'SEP_NO_INTERACTIVE_SURFACE',
|
|
91
|
+
userMessage: 'envseal ensure needs to collect missing keys, but there is no interactive surface here. ' +
|
|
92
|
+
'Use --check to gate without prompting, provide values out of band, or run interactively.',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
83
95
|
// Request all missing keys at once
|
|
84
96
|
const ticket = await broker.request({
|
|
85
97
|
keys: missingRequired,
|
package/dist/commands/init.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { projectPaths, loadManifest, declareEntries } from '@envseal/core';
|
|
1
|
+
import { projectPaths, loadManifest, declareEntries, scanManifestEntry } from '@envseal/core';
|
|
2
|
+
import { SepError } from '@envseal/protocol';
|
|
2
3
|
import { emit, fail } from '../output.js';
|
|
3
4
|
import { detectHost } from '../host.js';
|
|
4
5
|
import { scanForEnvKeys, entryForKey } from '../scan.js';
|
|
@@ -31,6 +32,26 @@ export async function init(root, json, hostOverride) {
|
|
|
31
32
|
}
|
|
32
33
|
const paths = projectPaths(root);
|
|
33
34
|
const discovered = scanForEnvKeys(root);
|
|
35
|
+
// F1: a manifest that already exists was authored by someone else (an
|
|
36
|
+
// editor, another tool, a malicious repo). Declaring into it without
|
|
37
|
+
// validating it first would launder hostile entries — e.g. a
|
|
38
|
+
// secret-shaped format.example — through envseal's own write path and
|
|
39
|
+
// commit them as blessed. Re-run the same schema+guard validation the
|
|
40
|
+
// declare path uses over every entry already on disk.
|
|
41
|
+
const existing = loadManifest(paths);
|
|
42
|
+
if (existing !== null) {
|
|
43
|
+
for (const [index, entry] of existing.entries.entries()) {
|
|
44
|
+
const finding = scanManifestEntry(entry, `entries[${index}]`);
|
|
45
|
+
if (finding !== null) {
|
|
46
|
+
throw new SepError({
|
|
47
|
+
code: 'SEP_VALUE_IN_REQUEST',
|
|
48
|
+
details: { field: finding.path },
|
|
49
|
+
userMessage: `Refusing to init: the existing ${finding.path} entry looks like it contains a real credential (${finding.label}). ` +
|
|
50
|
+
'Manifest fields are committed to git and must describe keys, never contain values.',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
34
55
|
const entries = discovered.map(entryForKey);
|
|
35
56
|
// declareEntries creates the manifest when absent and edits it surgically
|
|
36
57
|
// when present, so re-running init on an existing project is safe and
|
package/dist/commands/run.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createInterface } from 'node:readline';
|
|
|
2
2
|
import { SepError } from '@envseal/protocol';
|
|
3
3
|
import { emit, fail } from '../output.js';
|
|
4
4
|
import { createBroker } from '../cli-utils.js';
|
|
5
|
+
import { loadManifest, projectPaths } from '@envseal/core';
|
|
5
6
|
import { finish } from '../exit.js';
|
|
6
7
|
/**
|
|
7
8
|
* Ask the user to approve running a command with secrets injected.
|
|
@@ -55,6 +56,15 @@ export async function run(root, command, json, assumeYes = false) {
|
|
|
55
56
|
const broker = await createBroker(root, {
|
|
56
57
|
onConfirm: assumeYes ? async () => true : confirmInteractive,
|
|
57
58
|
});
|
|
59
|
+
// F4: a missing manifest while values still resolve is a suspicious state
|
|
60
|
+
// (deleted by hand or tooling). The run proceeds — the .env is user-owned —
|
|
61
|
+
// but never silently: the condition goes to stderr in both output modes.
|
|
62
|
+
if (loadManifest(projectPaths(root)) === null) {
|
|
63
|
+
const warning = 'envseal: no env.schema.jsonc found for this project. ' +
|
|
64
|
+
'Values are still resolved from .env/keychain because those are user-owned, ' +
|
|
65
|
+
"but nothing is declared here anymore. Run `envseal init` to re-create the manifest.";
|
|
66
|
+
process.stderr.write(`\n${warning}\n\n`);
|
|
67
|
+
}
|
|
58
68
|
const status = await broker.describe();
|
|
59
69
|
const presentKeys = status.entries.filter((e) => e.present).map((e) => e.key);
|
|
60
70
|
const result = await broker.use({ keys: presentKeys, command });
|
package/dist/commands/set.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { emit, fail } from '../output.js';
|
|
2
2
|
import { EXIT, exitCodeForOutcome } from '../exit-codes.js';
|
|
3
|
-
import { createBroker, outcomeForKey } from '../cli-utils.js';
|
|
3
|
+
import { createBroker, hasInteractiveSurface, outcomeForKey } from '../cli-utils.js';
|
|
4
4
|
import { finish } from '../exit.js';
|
|
5
5
|
import { loadManifest, projectPaths, saveManifest } from '@envseal/core';
|
|
6
|
+
import { SepError } from '@envseal/protocol';
|
|
6
7
|
/**
|
|
7
8
|
* Undo a declaration THIS invocation added, after nothing was stored under it.
|
|
8
9
|
*
|
|
@@ -37,6 +38,27 @@ export async function set(root, key, json) {
|
|
|
37
38
|
let declaredHere = false;
|
|
38
39
|
try {
|
|
39
40
|
const broker = await createBroker(root);
|
|
41
|
+
// F2: same fail-closed rule as ensure/run. Without this, a non-TTY caller
|
|
42
|
+
// (agent shell, scheduler) selected the loopback surface and waited on a
|
|
43
|
+
// browser page nobody could see.
|
|
44
|
+
//
|
|
45
|
+
// The guard fires BEFORE any declare, so a refused run also leaves no
|
|
46
|
+
// phantom declaration — but only for keys that are NOT already declared
|
|
47
|
+
// (those keep the pre-existing rollback semantics below). CI=1 keeps the
|
|
48
|
+
// original path: declare-then-request so the refusal is announced with the
|
|
49
|
+
// "declared X but nothing was stored" mutation message, which the contract
|
|
50
|
+
// test pins.
|
|
51
|
+
const surfaceUsable = hasInteractiveSurface() ||
|
|
52
|
+
process.env.CI !== undefined ||
|
|
53
|
+
process.env.ENVSEAL_TEST_PROMPTER_VALUE !== undefined ||
|
|
54
|
+
process.env.ENVSEAL_TEST_PROMPTER_OUTCOME !== undefined;
|
|
55
|
+
if (!surfaceUsable) {
|
|
56
|
+
throw new SepError({
|
|
57
|
+
code: 'SEP_NO_INTERACTIVE_SURFACE',
|
|
58
|
+
userMessage: 'envseal set needs to collect a value, but there is no interactive surface here. ' +
|
|
59
|
+
'Run it in an interactive shell, or use the documented ENVSEAL_TEST_MODE hooks in tests.',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
40
62
|
// Declare the key only if it is not already in the manifest.
|
|
41
63
|
//
|
|
42
64
|
// `declareEntries` replaces an entry wholesale rather than merging, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@envseal/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,13 +23,13 @@
|
|
|
23
23
|
"provenance": true
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@envseal/
|
|
27
|
-
"@envseal/
|
|
28
|
-
"@envseal/
|
|
29
|
-
"@envseal/
|
|
30
|
-
"@envseal/
|
|
31
|
-
"@envseal/
|
|
32
|
-
"@envseal/
|
|
26
|
+
"@envseal/prompters": "0.1.2",
|
|
27
|
+
"@envseal/registry": "0.1.2",
|
|
28
|
+
"@envseal/detector": "0.1.2",
|
|
29
|
+
"@envseal/mcp-server": "0.1.2",
|
|
30
|
+
"@envseal/protocol": "0.1.2",
|
|
31
|
+
"@envseal/core": "0.1.2",
|
|
32
|
+
"@envseal/http-server": "0.1.2"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|