flowviant 0.56.0 → 0.56.1
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/bin/lib/childEnv.mjs +113 -0
- package/bin/lib/deploy.mjs +13 -2
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE ENVIRONMENT A SPAWNED COMMAND GETS — and, more to the point, what it does
|
|
3
|
+
* not get.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS: `deploy.mjs` carried
|
|
6
|
+
*
|
|
7
|
+
* const env = { ...process.env, ...deployCreds() };
|
|
8
|
+
* delete env.FLEET_TOKEN; // ... keep it out of a command that might echo its env
|
|
9
|
+
*
|
|
10
|
+
* and that `delete` was a LIVE NO-OP. `FLEET_TOKEN` is a JS module constant in
|
|
11
|
+
* `config.mjs`; the environment variable is `FLOWVIANT_FLEET`. So the machine
|
|
12
|
+
* credential sat in the environment of every deploy command — including
|
|
13
|
+
* `target.build`, which is a string the REPO controls — under a confident
|
|
14
|
+
* comment saying it did not. That is the whole argument for an allowlist: a
|
|
15
|
+
* denylist is a claim about a set you cannot see, and it rots into a lie the
|
|
16
|
+
* moment one identifier is wrong or one new secret is added upstream.
|
|
17
|
+
*
|
|
18
|
+
* SO: BUILT FROM `{}`, NEVER FROM `{...process.env}` MINUS NAMES. Anything not
|
|
19
|
+
* named below is absent by construction, and a secret added to the daemon's
|
|
20
|
+
* environment next year is absent without anybody remembering this file.
|
|
21
|
+
*
|
|
22
|
+
* WHAT THIS IS NOT. A spawned command runs as the SAME UID as the daemon.
|
|
23
|
+
* `~/.flowviant/credentials.json` and `~/.flowviant/env-keypair.json` are 0600
|
|
24
|
+
* and readable by it. This is a control against ACCIDENT AND INHERITANCE — a
|
|
25
|
+
* crash reporter, a framework error page that dumps `process.env`, a build log,
|
|
26
|
+
* a process that echoes its own environment — and it is NOT confinement. Real
|
|
27
|
+
* confinement is a separate uid or a namespace and is not in this product. No
|
|
28
|
+
* surface may describe anything here as "sandboxed" or "isolated".
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The complete kept set. Two groups, and both are here for a reason that bit
|
|
33
|
+
* somebody:
|
|
34
|
+
*
|
|
35
|
+
* - the basics a process needs to exist at all;
|
|
36
|
+
* - the TOOLCHAIN SHIMS. An operator on nvm, asdf, volta, pnpm or bun has a
|
|
37
|
+
* PATH that points into a version-manager directory, and without these the
|
|
38
|
+
* PATH we hand over resolves to nothing — the command fails with ENOENT and
|
|
39
|
+
* the failure looks like a bad command rather than a stripped environment.
|
|
40
|
+
*/
|
|
41
|
+
const KEEP = [
|
|
42
|
+
'PATH',
|
|
43
|
+
'HOME',
|
|
44
|
+
'USER',
|
|
45
|
+
'LOGNAME',
|
|
46
|
+
'SHELL',
|
|
47
|
+
'TZ',
|
|
48
|
+
'LANG',
|
|
49
|
+
'LC_ALL',
|
|
50
|
+
'TMPDIR',
|
|
51
|
+
// Toolchain shims.
|
|
52
|
+
'NVM_DIR',
|
|
53
|
+
'NVM_BIN',
|
|
54
|
+
'ASDF_DIR',
|
|
55
|
+
'ASDF_DATA_DIR',
|
|
56
|
+
'VOLTA_HOME',
|
|
57
|
+
'PNPM_HOME',
|
|
58
|
+
'BUN_INSTALL',
|
|
59
|
+
'N_PREFIX',
|
|
60
|
+
'XDG_CACHE_HOME',
|
|
61
|
+
'XDG_DATA_HOME',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A child environment for a command Flowviant runs on the operator's behalf.
|
|
66
|
+
*
|
|
67
|
+
* `extra` is layered LAST and is the caller's own material — deploy
|
|
68
|
+
* credentials, or nothing. It is never repo-supplied: the deleted preview
|
|
69
|
+
* feature let `.flowviant/preview.json` contribute an `env` map that was
|
|
70
|
+
* layered last and therefore won every collision, which is how a branch got to
|
|
71
|
+
* set `PATH`.
|
|
72
|
+
*/
|
|
73
|
+
export function childEnv({ cwd, extra } = {}) {
|
|
74
|
+
const env = {};
|
|
75
|
+
for (const k of KEEP) {
|
|
76
|
+
if (typeof process.env[k] === 'string') env[k] = process.env[k];
|
|
77
|
+
}
|
|
78
|
+
// Set by us rather than inherited.
|
|
79
|
+
//
|
|
80
|
+
// TERM=dumb: a process that believes it owns a TTY draws progress bars and
|
|
81
|
+
// spinners into a pipe forever, which is unreadable in a log tail and pins a
|
|
82
|
+
// CPU on some tools.
|
|
83
|
+
env.TERM = 'dumb';
|
|
84
|
+
// BROWSER=none: nothing should try to open a browser on a headless box. This
|
|
85
|
+
// is the one survivor of the deleted feature's env extras, and it is
|
|
86
|
+
// anti-annoyance rather than security.
|
|
87
|
+
env.BROWSER = 'none';
|
|
88
|
+
if (cwd) env.PWD = cwd;
|
|
89
|
+
// Deliberately NOT set: NODE_ENV (asserting 'development' would be Flowviant
|
|
90
|
+
// choosing what the framework should decide) and PORT (we never hint a port —
|
|
91
|
+
// the port is DISCOVERED by cwd attribution, and a hinted one has no
|
|
92
|
+
// attribution behind it).
|
|
93
|
+
return extra ? { ...env, ...extra } : env;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The names this deliberately drops, for the test to assert against. NOT the
|
|
97
|
+
* mechanism — the mechanism is the allowlist above, and this list is only ever
|
|
98
|
+
* a sample of what it excludes. Adding a name here changes nothing. */
|
|
99
|
+
export const DROPPED_SAMPLE = [
|
|
100
|
+
'FLOWVIANT_FLEET',
|
|
101
|
+
'FLOWVIANT_FLEET_URL',
|
|
102
|
+
'FLOWVIANT_API_URL',
|
|
103
|
+
'FLOWVIANT_MCP_URL',
|
|
104
|
+
'FLOWVIANT_MODEL',
|
|
105
|
+
'ANTHROPIC_API_KEY',
|
|
106
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
107
|
+
'GH_TOKEN',
|
|
108
|
+
'GITHUB_TOKEN',
|
|
109
|
+
'SSH_AUTH_SOCK',
|
|
110
|
+
'CLOUDFLARE_API_TOKEN',
|
|
111
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
112
|
+
'npm_config__authToken',
|
|
113
|
+
];
|
package/bin/lib/deploy.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { join } from 'node:path';
|
|
|
16
16
|
import { FLEET_URL, FLEET_TOKEN, USER_AGENT, DAEMON_INSTANCE } from './config.mjs';
|
|
17
17
|
import { c, note, ok, warn } from './ui.mjs';
|
|
18
18
|
import { deployCreds, appSecretsFor, scrub, myPubB64 } from './env.mjs';
|
|
19
|
+
import { childEnv } from './childEnv.mjs';
|
|
19
20
|
|
|
20
21
|
const deployUrl = (tail) => FLEET_URL.replace(/\/agents\/?$/, `/${tail}`);
|
|
21
22
|
|
|
@@ -206,8 +207,18 @@ export function processDeployJobs(jobs, ctx) {
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
async function runDeploy(job, target, ctx) {
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
// AN ALLOWLIST, not `{...process.env}` minus names. What stood here was
|
|
211
|
+
//
|
|
212
|
+
// const env = { ...process.env, ...deployCreds() };
|
|
213
|
+
// delete env.FLEET_TOKEN;
|
|
214
|
+
//
|
|
215
|
+
// and that delete was a NO-OP: `FLEET_TOKEN` is a module constant in
|
|
216
|
+
// config.mjs, while the environment variable is `FLOWVIANT_FLEET`. The
|
|
217
|
+
// machine credential was therefore in the environment of every deploy
|
|
218
|
+
// command — and of `target.build`, which is a string the REPO controls —
|
|
219
|
+
// under a comment asserting the opposite. A denylist is a claim about a set
|
|
220
|
+
// you cannot see; this is built from {} instead.
|
|
221
|
+
const env = childEnv({ cwd: ctx.repoRoot, extra: deployCreds() }); // infra creds; never a file
|
|
211
222
|
const logs = [];
|
|
212
223
|
// Rollback is a single wrangler command; deploy is build → secrets → deploy.
|
|
213
224
|
if (job.kind === 'rollback') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowviant",
|
|
3
|
-
"version": "0.56.
|
|
3
|
+
"version": "0.56.1",
|
|
4
4
|
"description": "Run your own coding CLIs as build agents for Flowviant \u2014 Claude Code, Codex or Antigravity, on your own credentials. Holds your sessions, keeps a worktree per tab, and ships branches on your word.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|