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.
@@ -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
+ ];
@@ -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
- const env = { ...process.env, ...deployCreds() }; // inject infra creds; never a file
210
- delete env.FLEET_TOKEN; // the deploy command has no business reading it; keep it out of a command that might echo its env
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.0",
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": {