@celilo/cli
Celilo — home lab orchestration CLI
Supply chain provenance
Status for the latest visible version.
Without SLSA provenance there is no cryptographic link between this tarball and the public source — the axios compromise (March 2026) relied on exactly this gap.
Maintainers
Keywords
Accepted risks
Findings the reviewer chose to accept rather than block on.
| Source | Rule | Reason | Accepted by | When |
|---|---|---|---|---|
| semgrep | semgrep:env-spread | AI (semgrep): Used to pass process.env to subprocess (ansible-lint); standard pattern, not credential exfiltration. | ai | |
| semgrep | semgrep:etc-passwd-access | AI (semgrep): Fires in a test asserting that /etc/passwd path traversal is rejected — the opposite of credential harvesting. | ai | |
| semgrep | semgrep:hex-decode | AI (semgrep): Decoding a master key from hex in encryption/integration test code; legitimate crypto usage. | ai | |
| semgrep | semgrep:shady-links-raw-ip | AI (semgrep): Fires in a validator unit test accepting LAN IPs (192.168.x.x) for a Proxmox homelab URL — expected usage. | ai | |
| semgrep | semgrep:api-obfuscation-reflect | AI (semgrep): Reflect.get used to read a brand symbol on a hook object in tests; not evasion. | ai | |
| semgrep | semgrep:base64-decode | AI (semgrep): Decoding backup data after decryption in backup-restore.ts; legitimate crypto/storage pattern. | ai | |
| typosquat | typosquat.levenshtein:joi | AI (typosquat): Scoped package @celilo/cli for a homelab CLI; edit-distance match to 'joi' is coincidental. | ai |
v0.3.13
14 findingsSpreading entire process.env into an object — may capture all secrets 103 | // Note: ansible-lint exits with non-zero on warnings AND errors 104 | const args = ['ansible-lint', playbookPath]; > 105 | const env = { ...process.env }; 106 | 107 | // If vault password file provided, set environment variable for ansible-vault
Spreading entire process.env into an object — may capture all secrets 175 | 176 | // Run ansible-playbook --syntax-check > 177 | const env = { ...process.env }; 178 | 179 | // If vault password file provided, set environment variable for ansible-vault
Spreading entire process.env into an object — may capture all secrets 102 | // DigitalOcean) fail immediately with "no value for required 103 | // variable" — a false positive, not real drift. > 104 | env: { ...process.env, ...envVars }, 105 | }, 106 | );
Spreading entire process.env into an object — may capture all secrets 82 | stderr: 'pipe', 83 | stdin: 'ignore', > 84 | env: { ...process.env }, 85 | }); 86 |
Spreading entire process.env into an object — may capture all secrets 8 | beforeEach(() => { 9 | // Save original environment > 10 | originalEnv = { ...process.env }; 11 | _originalPlatform = process.platform; 12 | });
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 95 | test('rejects path that escapes module directory', () => { 96 | expect(() => { > 97 | resolveHookScript('/modules/namecheap', '../../etc/passwd'); 98 | }).toThrow('Hook script path escapes module directory'); 99 | });
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 262 | 263 | const definition: HookDefinition = { > 264 | script: '../../etc/passwd', 265 | }; 266 |
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 392 | script: build.sh 393 | artifacts: > 394 | - ../../../etc/passwd 395 | `; 396 |
Spreading entire process.env into an object — may capture all secrets 246 | // variable expansion would strip shell-only bash variables like 247 | // $STAGE before bash ever sees them. > 248 | const buildEnv = { ...process.env, CELILO_MODULE_SOURCE_DIR: sourceDir }; 249 | try { 250 | if (manifest.build.command) {
Spreading entire process.env into an object — may capture all secrets 35 | const child = spawn(command, args, { 36 | cwd, > 37 | env: { ...process.env, ...env }, 38 | stdio: [stdin ? 'pipe' : 'ignore', 'pipe', 'pipe'], 39 | shell: true,
Spreading entire process.env into an object — may capture all secrets 189 | 190 | this.process = spawn('bun', ['run', this.cliPath], { > 191 | env: { 192 | ...process.env, 193 | ...this.env,
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 284 | 285 | test('accepts path traversal (..)', () => { > 286 | expect(() => validatePath('../../etc/passwd')).not.toThrow(); 287 | }); 288 |
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 120 | * validatePath('/tmp/test') // OK 121 | * validatePath('') // throws: empty path > 122 | * validatePath('../../../etc/passwd') // OK (relative paths allowed) 123 | * ``` 124 | */
Package was published without Sigstore provenance. Only ~12% of npm packages have provenance, so this is common but not ideal.
v0.2.0
14 findingsSpreading entire process.env into an object — may capture all secrets 103 | // Note: ansible-lint exits with non-zero on warnings AND errors 104 | const args = ['ansible-lint', playbookPath]; > 105 | const env = { ...process.env }; 106 | 107 | // If vault password file provided, set environment variable for ansible-vault
Spreading entire process.env into an object — may capture all secrets 175 | 176 | // Run ansible-playbook --syntax-check > 177 | const env = { ...process.env }; 178 | 179 | // If vault password file provided, set environment variable for ansible-vault
Spreading entire process.env into an object — may capture all secrets 102 | // DigitalOcean) fail immediately with "no value for required 103 | // variable" — a false positive, not real drift. > 104 | env: { ...process.env, ...envVars }, 105 | }, 106 | );
Spreading entire process.env into an object — may capture all secrets 82 | stderr: 'pipe', 83 | stdin: 'ignore', > 84 | env: { ...process.env }, 85 | }); 86 |
Spreading entire process.env into an object — may capture all secrets 8 | beforeEach(() => { 9 | // Save original environment > 10 | originalEnv = { ...process.env }; 11 | _originalPlatform = process.platform; 12 | });
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 95 | test('rejects path that escapes module directory', () => { 96 | expect(() => { > 97 | resolveHookScript('/modules/namecheap', '../../etc/passwd'); 98 | }).toThrow('Hook script path escapes module directory'); 99 | });
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 262 | 263 | const definition: HookDefinition = { > 264 | script: '../../etc/passwd', 265 | }; 266 |
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 392 | script: build.sh 393 | artifacts: > 394 | - ../../../etc/passwd 395 | `; 396 |
Spreading entire process.env into an object — may capture all secrets 246 | // variable expansion would strip shell-only bash variables like 247 | // $STAGE before bash ever sees them. > 248 | const buildEnv = { ...process.env, CELILO_MODULE_SOURCE_DIR: sourceDir }; 249 | try { 250 | if (manifest.build.command) {
Spreading entire process.env into an object — may capture all secrets 35 | const child = spawn(command, args, { 36 | cwd, > 37 | env: { ...process.env, ...env }, 38 | stdio: [stdin ? 'pipe' : 'ignore', 'pipe', 'pipe'], 39 | shell: true,
Spreading entire process.env into an object — may capture all secrets 189 | 190 | this.process = spawn('bun', ['run', this.cliPath], { > 191 | env: { 192 | ...process.env, 193 | ...this.env,
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 284 | 285 | test('accepts path traversal (..)', () => { > 286 | expect(() => validatePath('../../etc/passwd')).not.toThrow(); 287 | }); 288 |
Accessing /etc/passwd or /etc/shadow — credential harvesting on Linux 120 | * validatePath('/tmp/test') // OK 121 | * validatePath('') // throws: empty path > 122 | * validatePath('../../../etc/passwd') // OK (relative paths allowed) 123 | * ``` 124 | */
Package was published without Sigstore provenance. Only ~12% of npm packages have provenance, so this is common but not ideal.