instabox 0.6.0 → 0.6.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.
@@ -3,6 +3,7 @@
3
3
  // The one rule every function here keeps: a credential's value is never
4
4
  // returned to the agent, stored in a file or printed. Code that looks at a
5
5
  // secret returns names, places and sizes only.
6
+ import { isIP } from 'node:net';
6
7
  import { spawnSync } from 'node:child_process';
7
8
  import { lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
8
9
  import { fileURLToPath } from 'node:url';
@@ -82,9 +83,23 @@ export const WELL_KNOWN = new Set([
82
83
  'DIGITALOCEAN_ACCESS_TOKEN',
83
84
  ]);
84
85
 
85
- // Names the Vault refuses for env items (docs/vault.md).
86
- const RESERVED = new Set(['HOME', 'PATH', 'USER', 'LOGNAME', 'SHELL', 'TERM', 'XDG_RUNTIME_DIR', 'DBUS_SESSION_BUS_ADDRESS']);
87
- export function vaultEnvNameOK(name) {
86
+ // Reserved Secret variable names (internal/secrets/validate.go).
87
+ const RESERVED = new Set([
88
+ 'HOME',
89
+ 'PATH',
90
+ 'USER',
91
+ 'LOGNAME',
92
+ 'SHELL',
93
+ 'TERM',
94
+ 'XDG_RUNTIME_DIR',
95
+ 'DBUS_SESSION_BUS_ADDRESS',
96
+ 'SSL_CERT_FILE',
97
+ 'SSL_CERT_DIR',
98
+ 'REQUESTS_CA_BUNDLE',
99
+ 'CURL_CA_BUNDLE',
100
+ 'NODE_EXTRA_CA_CERTS',
101
+ ]);
102
+ export function secretNameOK(name) {
88
103
  return (
89
104
  /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) &&
90
105
  name.length <= 128 &&
@@ -100,12 +115,13 @@ export const isSecretName = (name) => SECRET_NAME.test(name);
100
115
  export const MACOS_ONLY =
101
116
  /\/opt\/homebrew|\/usr\/local\/Cellar|\/usr\/local\/Homebrew|\/Applications\/|\/Library\/|\bosxkeychain\b|\bUseKeychain\b|\bpbcopy\b|\bpbpaste\b|\bosascript\b|\breattach-to-user-namespace\b|\bterminal-notifier\b|\bafplay\b|\/System\/|\.orbstack\/|1password.*agent\.sock|\bsecurity (?:find|add)-[a-z-]*password\b/i;
102
117
 
103
- // The Vault's limits (docs/vault.md, pkg/instaboxapi MaxVaultValueBytes); the server's own
104
- // come from `instabox usage --json` (.limits.vault) when it answers.
105
- export const VAULT_MAX_VALUE = 64 * 1024;
106
- export const VAULT_MAX_TOTAL = 256 * 1024;
107
- export const VAULT_MAX_ITEMS = 100;
118
+ // Org Secret limits; the server may override these in .limits.vault (legacy wire name).
119
+ export const SECRET_MAX_VALUE = 64 * 1024;
120
+ export const SECRET_MAX_TOTAL = 256 * 1024;
121
+ export const SECRET_MAX_ITEMS = 100;
108
122
 
123
+ export const FILE_MAX_VALUE = 64 * 1024;
124
+ export const FILE_MAX_TOTAL = 512 * 1024;
109
125
  export const BOX_HOME = '/data/home';
110
126
 
111
127
  // Runs a command without a shell; never throws. stdout is returned to the caller only:
@@ -184,43 +200,49 @@ export function human(bytes) {
184
200
  return `${v >= 10 ? Math.round(v) : v.toFixed(1)} ${u[i]}`;
185
201
  }
186
202
 
187
- // The Vault item name for a file item: its path with / as -, as `instabox vault set-file` does.
188
- export function vaultFileName(path) {
189
- return path
190
- .replace(/^\./, '')
191
- .replaceAll('/', '-')
192
- .replace(/[^A-Za-z0-9._-]/g, '-')
193
- .replace(/^-+|-+$/g, '')
194
- .slice(0, 128);
195
- }
196
-
197
- // A Vault file path must be relative, clean, without .. components (docs/vault.md).
198
- export function vaultPathOK(p) {
199
- if (!p || p.startsWith('/') || p.length > 255 || /[\0\\\n]/.test(p)) return false;
203
+ // Paths are relative to the guest home. Config copying never follows symlinks.
204
+ export function configPathOK(p) {
205
+ if (typeof p !== 'string' || !p || p.startsWith('/') || p.length > 255 || /[\x00-\x1f\x7f\\]/.test(p)) return false;
200
206
  return p.split('/').every((c) => c !== '' && c !== '.' && !c.startsWith('..'));
201
207
  }
202
208
 
203
- // A selector as the plan writes it: `*` (every box) or `key=value,key=value`.
204
- export function parseSelector(s) {
205
- s = s.trim();
206
- if (s === '*' || s === '') return {};
207
- const out = {};
208
- for (const part of s.split(',')) {
209
- const m = part.trim().match(/^([A-Za-z0-9_.-]{1,63})=([^,]{0,512})$/);
210
- if (!m) throw new Error(`selector ${JSON.stringify(s)}: write * or key=value,key=value`);
211
- out[m[1]] = m[2];
212
- }
213
- if (Object.keys(out).length > 32) throw new Error('selector: 32 labels at most');
214
- return out;
209
+ export function deliveryPolicy(delivery, hosts = []) {
210
+ if (!['env', 'proxy'].includes(delivery) || !Array.isArray(hosts)) throw new Error('delivery must be env or proxy');
211
+ if (delivery === 'env' && hosts.length) throw new Error('env delivery cannot enforce hosts; use -');
212
+ if (delivery === 'proxy' && (!hosts.length || hosts.length > 32))
213
+ throw new Error('proxy delivery requires 1-32 exact hosts');
214
+ const normalized = hosts.map((name) => {
215
+ if (typeof name !== 'string') throw new Error('hosts must be exact public DNS names');
216
+ const h = name.toLowerCase().replace(/\.$/, '');
217
+ if (
218
+ h.length > 253 ||
219
+ !h.includes('.') ||
220
+ isIP(h) ||
221
+ /(?:^|\.)(?:local|localhost)$/.test(h) ||
222
+ h.split('.').some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label))
223
+ ) {
224
+ throw new Error('hosts must be exact public DNS names without a scheme, port, path, IP address or wildcard');
225
+ }
226
+ return h;
227
+ });
228
+ return { delivery, hosts: [...new Set(normalized)].sort() };
215
229
  }
216
230
 
217
- export function selectorString(sel) {
218
- const e = Object.entries(sel || {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
219
- return e.length ? e.map(([k, v]) => `${k}=${v}`).join(',') : '*';
231
+ export function validID(s) {
232
+ return typeof s === 'string' && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(s);
220
233
  }
221
-
222
- export function sameSelector(a, b) {
223
- return selectorString(a) === selectorString(b);
234
+ export function secretIdentity(s) {
235
+ return {
236
+ id: s.id,
237
+ name: s.name,
238
+ orgId: s.orgId,
239
+ version: s.version,
240
+ delivery: s.delivery,
241
+ hosts: [...(s.hosts || [])].sort(),
242
+ };
243
+ }
244
+ export function sameSecret(a, b) {
245
+ return !!a && !!b && JSON.stringify(secretIdentity(a)) === JSON.stringify(secretIdentity(b));
224
246
  }
225
247
 
226
248
  // True when the module is the script node was started with (not imported by a test).