@ciqol/resenv 0.1.0-beta.1 → 0.1.0
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/CONTRIBUTING.md +41 -19
- package/README.md +36 -20
- package/SECURITY.md +20 -20
- package/bin/resenv.cjs +21 -23
- package/docs/build.md +29 -26
- package/docs/dependencies.md +65 -502
- package/docs/doppler.md +69 -60
- package/docs/maintainer-handoff.md +30 -102
- package/docs/release-security.md +64 -134
- package/docs/releasing.md +72 -137
- package/docs/security-exceptions.json +2 -135
- package/doppler/auth.cjs +4 -110
- package/doppler/http.cjs +3 -8
- package/doppler/system.cjs +26 -75
- package/package.json +2 -2
- package/types/src/doppler/auth.d.ts +2 -17
- package/types/src/doppler/http.d.ts +1 -3
- package/types/src/doppler/system.d.ts +3 -8
- package/docs/maturity.md +0 -61
- package/docs/public-readiness.md +0 -138
- package/docs/security-performance-review.md +0 -150
- package/docs/validation.md +0 -700
package/doppler/system.cjs
CHANGED
|
@@ -3,96 +3,47 @@
|
|
|
3
3
|
var node_child_process = require('node:child_process');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
5
|
var node_os = require('node:os');
|
|
6
|
+
var node_path = require('node:path');
|
|
6
7
|
var errors = require('../errors.cjs');
|
|
7
8
|
var assertSafeDiagnostics = require('../assert-safe-diagnostics.cjs');
|
|
8
9
|
var http = require('./http.cjs');
|
|
9
10
|
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function helperEnvironment() {
|
|
16
|
-
const env = { HOME: node_os.userInfo().homedir, PATH: '/usr/bin:/bin', LANG: 'C.UTF-8' };
|
|
17
|
-
for (const key of ['SystemRoot', 'USERPROFILE', 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'DISPLAY', 'WAYLAND_DISPLAY']) {
|
|
11
|
+
// Do not pass application secrets, loader variables or a repository's PATH to
|
|
12
|
+
// the worker or CLI. Keep only the OS session needed by Doppler's own storage.
|
|
13
|
+
function helperEnvironment({ platform = process.platform, home = node_os.userInfo().homedir } = {}) {
|
|
14
|
+
const env = { HOME: home, PATH: '/usr/bin:/bin', LANG: 'C.UTF-8' };
|
|
15
|
+
for (const key of ['SystemRoot', 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'DISPLAY', 'WAYLAND_DISPLAY']) {
|
|
18
16
|
if (process.env[key])
|
|
19
17
|
env[key] = process.env[key];
|
|
20
18
|
}
|
|
19
|
+
if (platform === 'win32') {
|
|
20
|
+
env.USERPROFILE = home;
|
|
21
|
+
env.APPDATA = node_path.win32.join(home, 'AppData', 'Roaming');
|
|
22
|
+
env.LOCALAPPDATA = node_path.win32.join(home, 'AppData', 'Local');
|
|
23
|
+
env.PATH = node_path.win32.join(process.env.SystemRoot || 'C:\\Windows', 'System32');
|
|
24
|
+
}
|
|
21
25
|
return env;
|
|
22
26
|
}
|
|
23
|
-
function createSystem({ platform = process.platform, run = node_child_process.spawnSync, exists = node_fs.existsSync } = {}) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
const mac = platform === 'darwin' && exists('/usr/bin/security');
|
|
32
|
-
const linux = platform === 'linux' && exists('/usr/bin/secret-tool');
|
|
33
|
-
const cli = ['/opt/homebrew/bin/doppler', '/usr/local/bin/doppler', '/usr/bin/doppler'].find(exists);
|
|
34
|
-
const unavailable = () => new errors.ResenvError('DOPPLER_KEYRING_UNAVAILABLE', 'The OS credential store is unavailable. Use the Doppler CLI or supply DOPPLER_TOKEN.');
|
|
35
|
-
function read() {
|
|
36
|
-
if (!mac && !linux)
|
|
37
|
-
return undefined;
|
|
38
|
-
const result = mac
|
|
39
|
-
? invoke('/usr/bin/security', ['find-generic-password', '-s', SERVICE, '-a', ACCOUNT, '-w'])
|
|
40
|
-
: invoke('/usr/bin/secret-tool', ['lookup', 'service', SERVICE, 'account', ACCOUNT]);
|
|
41
|
-
if (!result.error && result.status === 0)
|
|
42
|
-
return http.validateToken(result.stdout.trim());
|
|
43
|
-
if (!result.error && ((mac && result.status === 44) || (linux && result.status === 1 && !result.stderr)))
|
|
44
|
-
return undefined;
|
|
45
|
-
throw unavailable();
|
|
46
|
-
}
|
|
47
|
-
function write(token) {
|
|
48
|
-
http.validateToken(token);
|
|
49
|
-
if (!mac && !linux)
|
|
50
|
-
throw unavailable();
|
|
51
|
-
// security's interactive interpreter receives the token on stdin, never
|
|
52
|
-
// the OS process argument list. The token alphabet excludes separators.
|
|
53
|
-
const result = mac
|
|
54
|
-
? invoke('/usr/bin/security', ['-i'], { input: `add-generic-password -U -s ${SERVICE} -a ${ACCOUNT} -w ${token}\n` })
|
|
55
|
-
: invoke('/usr/bin/secret-tool', ['store', '--label=resenv Doppler', 'service', SERVICE, 'account', ACCOUNT], { input: token });
|
|
56
|
-
if (result.error || result.status !== 0)
|
|
57
|
-
throw unavailable();
|
|
58
|
-
// security -i may report a subcommand failure with a zero process status.
|
|
59
|
-
if (read() !== token)
|
|
60
|
-
throw unavailable();
|
|
61
|
-
}
|
|
62
|
-
function remove() {
|
|
63
|
-
if (!mac && !linux)
|
|
64
|
-
throw unavailable();
|
|
65
|
-
const result = mac
|
|
66
|
-
? invoke('/usr/bin/security', ['delete-generic-password', '-s', SERVICE, '-a', ACCOUNT])
|
|
67
|
-
: invoke('/usr/bin/secret-tool', ['clear', 'service', SERVICE, 'account', ACCOUNT]);
|
|
68
|
-
if (result.error || (result.status !== 0 && !(mac && result.status === 44)))
|
|
69
|
-
throw unavailable();
|
|
70
|
-
}
|
|
27
|
+
function createSystem({ platform = process.platform, home = node_os.userInfo().homedir, run = node_child_process.spawnSync, exists = node_fs.existsSync } = {}) {
|
|
28
|
+
// Use standard installation locations so npm's repository-local PATH cannot
|
|
29
|
+
// substitute a credential helper. Windows locations cover WinGet and Scoop.
|
|
30
|
+
const paths = platform === 'win32'
|
|
31
|
+
? [node_path.win32.join(home, 'AppData', 'Local', 'Microsoft', 'WinGet', 'Links', 'doppler.exe'), node_path.win32.join(home, 'scoop', 'apps', 'doppler', 'current', 'doppler.exe')]
|
|
32
|
+
: ['/opt/homebrew/bin/doppler', '/usr/local/bin/doppler', '/usr/bin/doppler', node_path.join(home, '.local', 'bin', 'doppler'), node_path.join(home, 'bin', 'doppler')];
|
|
71
33
|
function cliToken() {
|
|
34
|
+
assertSafeDiagnostics.assertSafeDiagnostics();
|
|
35
|
+
const cli = paths.find(exists);
|
|
72
36
|
if (!cli)
|
|
73
|
-
|
|
74
|
-
const result =
|
|
37
|
+
throw new errors.ResenvError('DOPPLER_CLI_UNAVAILABLE', 'Install the Doppler CLI in a standard location and run doppler login, or supply DOPPLER_TOKEN. See https://docs.doppler.com/docs/cli.');
|
|
38
|
+
const result = run(cli, ['configure', 'get', 'token', '--plain', '--no-check-version', '--no-read-env'], {
|
|
39
|
+
env: helperEnvironment({ platform, home }), encoding: 'utf8', timeout: 15000,
|
|
40
|
+
maxBuffer: 16384, stdio: ['pipe', 'pipe', 'pipe'], shell: false
|
|
41
|
+
});
|
|
75
42
|
if (result.error || result.status !== 0 || !result.stdout.trim())
|
|
76
43
|
return undefined;
|
|
77
44
|
return http.validateToken(result.stdout.trim());
|
|
78
45
|
}
|
|
79
|
-
|
|
80
|
-
if (!cli)
|
|
81
|
-
throw new errors.ResenvError('DOPPLER_CLI_UNAVAILABLE', 'Doppler CLI fallback is unavailable. Install Doppler or supply DOPPLER_TOKEN.');
|
|
82
|
-
const result = invoke(cli, ['login'], { stdio: 'inherit', timeout: 300000 });
|
|
83
|
-
if (result.error || result.status !== 0)
|
|
84
|
-
throw new errors.ResenvError('DOPPLER_LOGIN_FAILED', 'Doppler CLI login did not complete.');
|
|
85
|
-
if (!cliToken())
|
|
86
|
-
throw new errors.ResenvError('DOPPLER_LOGIN_FAILED', 'Doppler CLI login did not provide a credential.');
|
|
87
|
-
}
|
|
88
|
-
function openBrowser(url) {
|
|
89
|
-
const file = mac ? '/usr/bin/open' : '/usr/bin/xdg-open';
|
|
90
|
-
if (!exists(file))
|
|
91
|
-
return false;
|
|
92
|
-
const result = invoke(file, [url]);
|
|
93
|
-
return !result.error && result.status === 0;
|
|
94
|
-
}
|
|
95
|
-
return { read, write, remove, cliToken, cliLogin, openBrowser, canStore: mac || linux };
|
|
46
|
+
return { cliToken };
|
|
96
47
|
}
|
|
97
48
|
|
|
98
49
|
exports.createSystem = createSystem;
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciqol/resenv",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Replace environment references with their resolved values at application startup",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
8
|
-
"tag": "
|
|
8
|
+
"tag": "latest",
|
|
9
9
|
"registry": "https://registry.npmjs.org/"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
@@ -1,19 +1,4 @@
|
|
|
1
1
|
import type { Environment } from '../types.js';
|
|
2
|
-
import type { Request } from './http.js';
|
|
3
2
|
import type { CredentialSystem } from './system.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly noBrowser?: boolean;
|
|
7
|
-
};
|
|
8
|
-
type LoginDependencies = {
|
|
9
|
-
readonly request?: Request;
|
|
10
|
-
readonly system?: CredentialSystem;
|
|
11
|
-
readonly output?: (text: string) => void;
|
|
12
|
-
readonly interactive?: boolean;
|
|
13
|
-
readonly env?: Readonly<Environment>;
|
|
14
|
-
readonly sleep?: (ms: number) => Promise<unknown>;
|
|
15
|
-
readonly now?: () => number;
|
|
16
|
-
};
|
|
17
|
-
declare function credential(env: Readonly<Environment>, system?: Pick<CredentialSystem, 'read' | 'cliToken'>): Promise<string>;
|
|
18
|
-
declare function login({ cli, noBrowser }?: LoginOptions, { request, system, output, interactive, env, sleep, now }?: LoginDependencies): Promise<'cli' | 'native'>;
|
|
19
|
-
export { credential, login };
|
|
3
|
+
declare function credential(env: Readonly<Environment>, system?: CredentialSystem): Promise<string>;
|
|
4
|
+
export { credential };
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export type RequestOptions = {
|
|
2
2
|
readonly token?: string;
|
|
3
|
-
readonly body?: Readonly<Record<string, string>>;
|
|
4
|
-
readonly pending?: boolean;
|
|
5
3
|
};
|
|
6
|
-
export type Request = (path: string, options?: RequestOptions) => Promise<Record<string, unknown
|
|
4
|
+
export type Request = (path: string, options?: RequestOptions) => Promise<Record<string, unknown>>;
|
|
7
5
|
declare function createClient(fetchImpl?: typeof globalThis.fetch): Request;
|
|
8
6
|
declare function validateToken(token: unknown): string;
|
|
9
7
|
export { createClient, validateToken };
|
|
@@ -7,18 +7,13 @@ type SyncOptions = SpawnSyncOptionsWithStringEncoding & {
|
|
|
7
7
|
export type SyncRunner = (file: string, args: readonly string[], options: SyncOptions) => SpawnSyncReturns<string>;
|
|
8
8
|
type SystemOptions = {
|
|
9
9
|
readonly platform?: NodeJS.Platform;
|
|
10
|
+
readonly home?: string;
|
|
10
11
|
readonly run?: SyncRunner;
|
|
11
12
|
readonly exists?: typeof existsSync;
|
|
12
13
|
};
|
|
13
14
|
export type CredentialSystem = ReturnType<typeof createSystem>;
|
|
14
|
-
declare function helperEnvironment(): Environment;
|
|
15
|
-
declare function createSystem({ platform, run, exists }?: SystemOptions): {
|
|
16
|
-
read: () => string | undefined;
|
|
17
|
-
write: (token: string) => void;
|
|
18
|
-
remove: () => void;
|
|
15
|
+
declare function helperEnvironment({ platform, home }?: Pick<SystemOptions, 'platform' | 'home'>): Environment;
|
|
16
|
+
declare function createSystem({ platform, home, run, exists }?: SystemOptions): {
|
|
19
17
|
cliToken: () => string | undefined;
|
|
20
|
-
cliLogin: () => void;
|
|
21
|
-
openBrowser: (url: string) => boolean;
|
|
22
|
-
canStore: boolean;
|
|
23
18
|
};
|
|
24
19
|
export { createSystem, helperEnvironment };
|
package/docs/maturity.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Maturity evaluation
|
|
2
|
-
|
|
3
|
-
Evaluated on October 8, 2026 with the Tashtit maturity 0.3.1 catalog, using
|
|
4
|
-
static working-tree evidence only. This measures the catalog's repository
|
|
5
|
-
hygiene rules; it does not certify security or readiness to publish. Tests
|
|
6
|
-
and hosted checks are recorded separately in [validation](validation.md).
|
|
7
|
-
|
|
8
|
-
## Maturity: 91% (11/120 weight failing)
|
|
9
|
-
|
|
10
|
-
The maintained source tree scores 100% (0/97 weight failing). The full catalog
|
|
11
|
-
also discovers the generated `dist/package.json`, so its findings are retained.
|
|
12
|
-
|
|
13
|
-
| Scope | Failing weight | Relevant weight |
|
|
14
|
-
| --- | ---: | ---: |
|
|
15
|
-
| Root npm project | 0 | 41 |
|
|
16
|
-
| Generated distribution | 11 | 23 |
|
|
17
|
-
| Repository hygiene | 0 | 24 |
|
|
18
|
-
| GitHub Actions | 0 | 32 |
|
|
19
|
-
| Total | 11 | 120 |
|
|
20
|
-
|
|
21
|
-
## Findings
|
|
22
|
-
|
|
23
|
-
- **High: generated package has no sibling lockfile** (`lockfile-missing`, 8).
|
|
24
|
-
Evidence: `dist/package.json:2`; no `dist/package-lock.json` exists. A maintained
|
|
25
|
-
npm project needs a lockfile. This directory is generated from the root's v3
|
|
26
|
-
lockfile and consumers maintain their own locks; it is not another workspace.
|
|
27
|
-
Reference: [npm lockfiles](https://docs.npmjs.com/cli/configuring-npm/package-lock-json).
|
|
28
|
-
- **Medium: generated package has no sibling runtime-version file**
|
|
29
|
-
(`setup-nvmrc`, 3). Evidence: `dist/package.json:2`; no `dist/.nvmrc` exists.
|
|
30
|
-
Development projects should pin a runtime. The generated package declares
|
|
31
|
-
`engines.node: >=20` and does not need contributor setup files. The root
|
|
32
|
-
`.nvmrc` pins Node 24.19.0. Reference: [nvm version files](https://github.com/nvm-sh/nvm#nvmrc).
|
|
33
|
-
|
|
34
|
-
## Passed checks
|
|
35
|
-
|
|
36
|
-
- Root: one v3 npm lockfile, no competing yarn lockfile, `.npmrc`, `.nvmrc`, and
|
|
37
|
-
registry dependencies with exact direct versions. CI uses `npm ci` and
|
|
38
|
-
normal lifecycle behavior; it does not pass `--ignore-scripts`.
|
|
39
|
-
- Repository: valid CODEOWNERS syntax, README, CONTRIBUTING and `.editorconfig`.
|
|
40
|
-
- CI: a parseable workflow, explicit scoped token permissions, bounded job
|
|
41
|
-
timeouts, no `pull_request_target`, and pinned remote actions. The GitHub-owned
|
|
42
|
-
upload action's exact `v7.0.1` tag is permitted by the catalog.
|
|
43
|
-
|
|
44
|
-
The earlier 88% score included a lifecycle-script finding that is now resolved.
|
|
45
|
-
No dependency installation, build, test, or remote request was used to derive
|
|
46
|
-
this static score.
|
|
47
|
-
|
|
48
|
-
## Not evaluated by this catalog
|
|
49
|
-
|
|
50
|
-
- Docker rules: no candidate Dockerfile exists.
|
|
51
|
-
- Repository protections, private vulnerability reporting and maintainer access.
|
|
52
|
-
- License suitability, secret history, provider login, and runtime behavior.
|
|
53
|
-
|
|
54
|
-
See [public readiness](public-readiness.md) for the separate exposure audit and
|
|
55
|
-
[validation](validation.md) for observed checks.
|
|
56
|
-
|
|
57
|
-
## Fixable findings
|
|
58
|
-
|
|
59
|
-
No automatic fix is appropriate. The generated package's broad runtime range
|
|
60
|
-
does not identify a development version to pin, and adding another lockfile
|
|
61
|
-
would misrepresent it as a maintained workspace.
|
package/docs/public-readiness.md
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# Public repository readiness
|
|
2
|
-
|
|
3
|
-
## Follow-up review — 2026-10-12
|
|
4
|
-
|
|
5
|
-
The repository is now public, with `beta` as the sole remote and default branch.
|
|
6
|
-
CI has staged the first `0.1.0-beta.1` candidate; npm approval and release
|
|
7
|
-
recording are still pending. The accidental public `1.0.0` has been removed,
|
|
8
|
-
and the `0.0.0-stage` placeholder remains. Recheck live state before release work.
|
|
9
|
-
|
|
10
|
-
Private vulnerability reporting returned `enabled: false` during this review.
|
|
11
|
-
The security policy's reporting prerequisite remains outstanding until that
|
|
12
|
-
setting is enabled and verified. See [the session handoff](maintainer-handoff.md)
|
|
13
|
-
and [validation](validation.md) for current behavior and observed checks.
|
|
14
|
-
The dated transition plan below is historical evidence.
|
|
15
|
-
|
|
16
|
-
## Approved transition - 2026-10-10
|
|
17
|
-
|
|
18
|
-
The current release pipeline and launch state are documented in [releasing](releasing.md).
|
|
19
|
-
The release-tool review and expiring advisory policy are in [release security](release-security.md).
|
|
20
|
-
Earlier dated entries below remain historical evidence and are superseded where
|
|
21
|
-
the current release guide describes a different state.
|
|
22
|
-
|
|
23
|
-
The user approved public visibility, default branch `beta`, and deletion of
|
|
24
|
-
the four older ancestor branch references. A fresh settings snapshot, complete
|
|
25
|
-
Git bundle and verified restore are retained outside the repository. Recheck
|
|
26
|
-
remote heads and ancestry immediately before deletion. Other governance changes
|
|
27
|
-
and PR creation remain outside this transition.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Reviewed on October 8, 2026 at `cb6ebe92c3182051a034dd29e192d51a182b5d22`, before
|
|
31
|
-
the accompanying documentation cleanup. Repository: `ciqol/resenv`. Maintainer
|
|
32
|
-
and authenticated GitHub account: `titan-ron`.
|
|
33
|
-
|
|
34
|
-
## Result
|
|
35
|
-
|
|
36
|
-
No real credentials or private machine/employer references were identified in
|
|
37
|
-
the reviewed Git history or hosted Actions outputs. The source can become a
|
|
38
|
-
public experimental repository after the remaining decisions below. This does
|
|
39
|
-
not establish npm release readiness or an independent security audit.
|
|
40
|
-
|
|
41
|
-
The repository remains private. No branch, default-branch, access, protection,
|
|
42
|
-
or visibility setting was changed by this audit.
|
|
43
|
-
|
|
44
|
-
## Exposure review
|
|
45
|
-
|
|
46
|
-
- Gitleaks 8.30.1 scanned all 24 commits reachable from the fetched refs. Its
|
|
47
|
-
nine matches were synthetic Doppler credentials in tests: eight use a dummy
|
|
48
|
-
marker and one uses an alternative fixture marker with sequential digits.
|
|
49
|
-
The tests inject provider and credential helpers instead of authenticating
|
|
50
|
-
those values. No detector exception or history rewrite was added.
|
|
51
|
-
- TruffleHog 3.97.4 scanned the local Git history with network verification and
|
|
52
|
-
update checks disabled. It reported no credentials. Sandbox process-inspection
|
|
53
|
-
restrictions produced temporary-cleanup warnings; the scan completed.
|
|
54
|
-
- Reachable-blob inspection found no committed dotenv/private-key files,
|
|
55
|
-
absolute developer paths, employer references, or private service URLs.
|
|
56
|
-
- All 14 existing Actions log archives and 14 unexpired package artifacts were
|
|
57
|
-
downloaded into a private audit folder and scanned offline. Gitleaks found
|
|
58
|
-
no secrets. Package inspection covered 755 regular files across the archives
|
|
59
|
-
and found no credential files or private developer/employer references.
|
|
60
|
-
- There are no existing issues or pull requests to review for public exposure.
|
|
61
|
-
- The latest pre-cleanup [CI run](https://github.com/ciqol/resenv/actions/runs/37778134741)
|
|
62
|
-
passed on Linux Node 20/22/24 and macOS Node 24.
|
|
63
|
-
|
|
64
|
-
Detection tools cannot prove the absence of every secret. No candidate
|
|
65
|
-
credential was sent to a provider to test whether it works. Git history remains
|
|
66
|
-
visible after deleting an ancestor branch. GitHub exposes existing Actions
|
|
67
|
-
history and logs when visibility becomes public; see
|
|
68
|
-
[GitHub's visibility documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/managing-repository-settings/setting-repository-visibility).
|
|
69
|
-
|
|
70
|
-
## Cleanup prepared
|
|
71
|
-
|
|
72
|
-
- Ignore local `.idea/` settings without deleting the developer's files.
|
|
73
|
-
- Keep installation and contributor text independent of repository visibility.
|
|
74
|
-
- Include the MIT license and ciqol contributors copyright, selected on October 8.
|
|
75
|
-
Registry publication remains disabled.
|
|
76
|
-
- Mark historical validation/dependency entries so old APIs and version plans
|
|
77
|
-
are not mistaken for current usage instructions.
|
|
78
|
-
- Remove unnecessary references to other project names from current docs.
|
|
79
|
-
Historical commits remain unchanged.
|
|
80
|
-
- Direct security questions from the issue chooser to the security policy.
|
|
81
|
-
- Prepare `beta` as the sole normal release branch on npm's `beta` channel. CI
|
|
82
|
-
also checks the current `titan/beta` preparation branch until branch cleanup.
|
|
83
|
-
PR checks and manual dispatch remain; publishing stays disabled.
|
|
84
|
-
- Recalculate maturity: 91% including generated output, 100% for the maintained
|
|
85
|
-
source tree. These scores do not establish release readiness.
|
|
86
|
-
|
|
87
|
-
## Remaining decisions and settings
|
|
88
|
-
|
|
89
|
-
1. **License resolved:** MIT was selected on October 8, 2026, with copyright
|
|
90
|
-
held by ciqol contributors. The root and generated manifests declare MIT;
|
|
91
|
-
the [license](../LICENSE) is included in the package archive.
|
|
92
|
-
2. **Branches:** the selected sole normal release branch is `beta`, with npm
|
|
93
|
-
channel `beta`. A local dry run with only that branch calculated `1.0.0`;
|
|
94
|
-
another stable branch is unnecessary. Default-branch selection and deletion
|
|
95
|
-
of the older branches remain pending. See [release preparation](releasing.md).
|
|
96
|
-
3. **Private reporting:** the endpoint returned HTTP 404 while the repository
|
|
97
|
-
is private. Enable and verify it after the approved visibility transition,
|
|
98
|
-
and verify the reporting option described in SECURITY.md. It is not yet available.
|
|
99
|
-
4. **Protections:** all branch entries report `protected: false`; the ruleset
|
|
100
|
-
API returned HTTP 403 under the current private plan. Re-audit effective
|
|
101
|
-
protections after the transition. No protection policy was applied.
|
|
102
|
-
|
|
103
|
-
## Proposed transition
|
|
104
|
-
|
|
105
|
-
This plan is for review. It does not authorize npm publication, collaborator or
|
|
106
|
-
team changes, merge-policy changes, rulesets, or a pull request.
|
|
107
|
-
|
|
108
|
-
| Branch | Observed head | Action for the beta-only layout |
|
|
109
|
-
| --- | --- | --- |
|
|
110
|
-
| `beta` | Same reviewed source head as the preparation branch | Keep; make default |
|
|
111
|
-
| `titan/beta` | Current preparation branch | Delete after verifying its head is retained by `beta` |
|
|
112
|
-
| `titan/bootstrap-envref` | `b25da7cfe730bfaca0a470d9a31a303cef7d33ad` | Delete branch pointer |
|
|
113
|
-
| `titan/rename-resenv` | `60e70a4e43b39fc9a87379990e4ed44de50726cb` | Delete branch pointer |
|
|
114
|
-
| `titan/prepare-first-release` | `0c313483d7123b8b4afd360b4e8daae58abc4241` | Delete after changing the default |
|
|
115
|
-
|
|
116
|
-
At the original audit, all three older heads were ancestors of `titan/beta`;
|
|
117
|
-
none contained unique commits. Before deletion, re-check every remote head and
|
|
118
|
-
verify all four older branches are ancestors of the retained `beta` head. Stop
|
|
119
|
-
if any contains newer or unique work.
|
|
120
|
-
|
|
121
|
-
After the branch decision, obtain explicit approval for the default
|
|
122
|
-
branch, listed deletions, public visibility, and private vulnerability reporting.
|
|
123
|
-
The CI push filter and release docs are prepared for this layout. Apply
|
|
124
|
-
only approved scopes and verify branches, default, visibility, reporting and
|
|
125
|
-
CI by read-back. Keep npm publishing disabled until its separate prerequisites
|
|
126
|
-
are complete.
|
|
127
|
-
|
|
128
|
-
## Recovery
|
|
129
|
-
|
|
130
|
-
A private settings snapshot and complete Git bundle are retained outside this
|
|
131
|
-
repository. Bundle verification and a restore into a separate local bare
|
|
132
|
-
repository reproduced all four original remote branch heads exactly.
|
|
133
|
-
|
|
134
|
-
Restore deleted branch pointers from their recorded SHAs or bundle before
|
|
135
|
-
restoring the old default branch. Visibility can be set back to private, but
|
|
136
|
-
public forks, downloaded code and copied logs cannot be recalled. A settings
|
|
137
|
-
rollback cannot undo public disclosure. Never overwrite newer branch work or
|
|
138
|
-
weaken protections to force a restore.
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Security and performance review
|
|
2
|
-
|
|
3
|
-
Historical review: the October 12 beta removes dotenvx decryption and its entire
|
|
4
|
-
runtime dependency tree. Earlier cryptography and ciphertext findings below
|
|
5
|
-
apply to the prototype. Current behavior is in [Security](../SECURITY.md).
|
|
6
|
-
|
|
7
|
-
## Release preparation update - 2026-10-10
|
|
8
|
-
|
|
9
|
-
The current release pipeline and launch state are documented in [releasing](releasing.md).
|
|
10
|
-
The release-tool review and expiring advisory policy are in [release security](release-security.md).
|
|
11
|
-
Earlier dated entries below remain historical evidence and are superseded where
|
|
12
|
-
the current release guide describes a different state.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Reviewed on 2026-10-06 against the private 0.1.0 candidate, starting at
|
|
16
|
-
`b50fa9b`. This is a source review with targeted reproductions, regression tests
|
|
17
|
-
and measurements. It is not an independent audit or a security certification.
|
|
18
|
-
|
|
19
|
-
A later change on the same day migrates the implementation and CLI to strict
|
|
20
|
-
TypeScript. The findings below describe the reviewed baseline and fixes; see
|
|
21
|
-
[the current build contract](build.md) and [validation](validation.md) for the
|
|
22
|
-
generated declarations and implementation checks that supersede the earlier
|
|
23
|
-
declaration-only setup.
|
|
24
|
-
|
|
25
|
-
## Findings and changes
|
|
26
|
-
|
|
27
|
-
| Priority | Finding reproduced | Change and regression coverage |
|
|
28
|
-
| --- | --- | --- |
|
|
29
|
-
| P1 | Node's `child_process` debug category printed dummy credentials from subprocess environment values and private worker stdin. | Reject resenv subprocess/helper operations before spawning when that native debug category is enabled. Cover wildcards, case, cached debug state and safe no-work preloads in [diagnostic tests](https://github.com/ciqol/resenv/blob/e57a8fe72ecf956fc99c661d40b1ca707bbcd172/test/diagnostics.test.mjs). |
|
|
30
|
-
| P1 | Combining automatic resolution modes could interpret a fetched secret as a shell expression on the second pass. | Share preload state across CJS/ESM, reject mixed modes and reject CLI resolution after a preload. Verify both preload orders and the CLI combination in [runtime tests](https://github.com/ciqol/resenv/blob/e57a8fe72ecf956fc99c661d40b1ca707bbcd172/test/doppler-runtime.test.mjs). |
|
|
31
|
-
| P2 | Invalid UTF-8 from the provider was silently converted to a different secret value; escaped lone surrogates were also accepted. | Decode JSON with fatal UTF-8 validation and reject malformed Unicode values. Cover both paths in [Doppler tests](https://github.com/ciqol/resenv/blob/e57a8fe72ecf956fc99c661d40b1ca707bbcd172/test/doppler.test.mjs). |
|
|
32
|
-
| P2 | A non-writable second property could fail after the first environment property had already changed. | Preflight all changed descriptors before committing; reject setters and Proxy targets. Preserve writable sealed records. Cover atomicity in [resolver tests](https://github.com/ciqol/resenv/blob/e57a8fe72ecf956fc99c661d40b1ca707bbcd172/test/resolver.test.mjs). |
|
|
33
|
-
| P2 | Unique Doppler lookups ran sequentially, making startup latency grow with each request. | Deduplicate references and allow four requests at a time. Stop scheduling after failure, await active requests and discard partial results. Deterministic tests check overlap, deduplication and failure behavior. |
|
|
34
|
-
| P2 | The generic in-place return type preserved an input's literal reference type after its value had changed. | Return `Environment`; add compile-time regression checks to both installed CJS and ESM consumers. |
|
|
35
|
-
|
|
36
|
-
Unexpected cleanup errors now produce safe errors instead of disappearing in
|
|
37
|
-
empty catch blocks. Known already-exited process groups remain harmless.
|
|
38
|
-
Credential-revocation failures retain a safe message without raw provider
|
|
39
|
-
details. Regression tests cover these cases with dummy values.
|
|
40
|
-
|
|
41
|
-
The debug guard uses Node's actual `debuglog().enabled` state. Clearing
|
|
42
|
-
`NODE_DEBUG` after Node initializes it is insufficient. Use `NODE_DEBUG=ciqol:resenv`
|
|
43
|
-
for variable names and outcome messages without values. See Node's
|
|
44
|
-
[debuglog documentation](https://nodejs.org/api/util.html#utildebuglogsection-callback).
|
|
45
|
-
|
|
46
|
-
## Reviewed boundaries
|
|
47
|
-
|
|
48
|
-
- Only original environment references are processed within a resolution call.
|
|
49
|
-
Fetched and decrypted output remains literal.
|
|
50
|
-
- Doppler requests use a fixed HTTPS origin, reject redirects and request only
|
|
51
|
-
named secrets. An explicit identity is not silently replaced after rejection.
|
|
52
|
-
- The synchronous worker receives references and an optional token over stdin.
|
|
53
|
-
Credential helpers receive a filtered environment and fixed executable paths.
|
|
54
|
-
Application secrets, loader options and a repository's PATH are not forwarded
|
|
55
|
-
to those helpers.
|
|
56
|
-
- Requests have a ten-second deadline and a 1 MiB response limit. A resolution
|
|
57
|
-
allows at most 128 references, with a 64 KiB limit per cloud value. The worker
|
|
58
|
-
has a 60-second deadline and 1 MiB captured-output limit.
|
|
59
|
-
- Shell execution requires explicit opt-in. Startup never starts interactive
|
|
60
|
-
login automatically; login rejects CI and noninteractive use.
|
|
61
|
-
- Plain environments avoid subprocesses, credential discovery and loading the
|
|
62
|
-
cryptography dependency. Runtime dependency versions are unchanged.
|
|
63
|
-
|
|
64
|
-
These boundaries do not isolate secrets from the application, a debugger or
|
|
65
|
-
other code running as the same user. Commands execute with that user's access,
|
|
66
|
-
and their external effects cannot be rolled back. Configure one resolver per
|
|
67
|
-
launch; repeated manual calls or a separately installed child-process preload
|
|
68
|
-
can reinterpret values. See [SECURITY.md](../SECURITY.md).
|
|
69
|
-
|
|
70
|
-
## Performance measurements
|
|
71
|
-
|
|
72
|
-
Run `npm run bench` after building. The benchmark uses dummy values and a mocked
|
|
73
|
-
provider, with no real credentials or network requests. Measurements below use
|
|
74
|
-
Node 24.19.0 on macOS arm64; they are observations, not CI timing thresholds.
|
|
75
|
-
|
|
76
|
-
| Measurement | Before | After |
|
|
77
|
-
| --- | ---: | ---: |
|
|
78
|
-
| 16 references, eight unique requests, 40 ms simulated latency per request; median of five runs | 332.069 ms | 81.705 ms |
|
|
79
|
-
| Peak concurrent provider requests | 1 | 4 |
|
|
80
|
-
| No-work library call with 200 plain variables; median of 5,000 calls | 0.047 ms | 0.045 ms |
|
|
81
|
-
|
|
82
|
-
The mocked lookup case is about 4.1 times faster. Real Doppler latency, rate
|
|
83
|
-
limits and credential-store latency may differ; four is a conservative client
|
|
84
|
-
limit, not a published provider concurrency allowance.
|
|
85
|
-
|
|
86
|
-
The final fresh-process measurements interleave baseline, CJS preload and ESM
|
|
87
|
-
preload runs. Each reports the median of 20 samples after discarding three
|
|
88
|
-
warmup samples; disk caches remain warm.
|
|
89
|
-
|
|
90
|
-
| Final startup measurement | Median |
|
|
91
|
-
| --- | ---: |
|
|
92
|
-
| Node alone | 46.427 ms |
|
|
93
|
-
| Node with `--require @ciqol/resenv/resolve` and no references | 52.177 ms |
|
|
94
|
-
| Node with `--import @ciqol/resenv/resolve` and no references | 50.722 ms |
|
|
95
|
-
|
|
96
|
-
The observed extra startup cost was about 4-6 ms. Scheduling and filesystem
|
|
97
|
-
noise affect these small measurements; no startup speedup is claimed.
|
|
98
|
-
|
|
99
|
-
## Standards applied
|
|
100
|
-
|
|
101
|
-
The requested TypeScript authoring, library-build and style standards were
|
|
102
|
-
applied to the existing `.mjs` project and its public declarations:
|
|
103
|
-
|
|
104
|
-
- Public option objects use type aliases and readonly properties. `Environment`
|
|
105
|
-
remains mutable because in-place resolution writes it.
|
|
106
|
-
- ESLint checks runtime/build JavaScript and declarations. Rules cover undefined
|
|
107
|
-
names, empty handlers, parameter count, catch naming and the selected public
|
|
108
|
-
TypeScript conventions. Existing compact JavaScript formatting is preserved.
|
|
109
|
-
- Declaration checks and installed `.cts`/`.mts` consumers use `strict` and
|
|
110
|
-
`noUncheckedIndexedAccess`. This checks the declaration contract, not the
|
|
111
|
-
`.mjs` implementation with TypeScript.
|
|
112
|
-
- Rollup still emits explicit CJS/ESM extensions with a shared implementation,
|
|
113
|
-
types-first conditional exports and external runtime dependencies. Clean
|
|
114
|
-
consumers verify the packed artifact without source-tree links or lint/build
|
|
115
|
-
tools installed as runtime dependencies.
|
|
116
|
-
- CI runs lint and declaration checks before building one archive for every
|
|
117
|
-
compatibility job. Actions are pinned, permissions are read-only and install
|
|
118
|
-
lifecycle scripts are disabled. Both manifests remain private by request.
|
|
119
|
-
|
|
120
|
-
The new development dependencies and their provenance limits are recorded in
|
|
121
|
-
[the dependency review](dependencies.md). No new runtime dependency was added.
|
|
122
|
-
|
|
123
|
-
## Validation and remaining work
|
|
124
|
-
|
|
125
|
-
- All 70 unit/process tests passed locally on Node 20.20.2, 22.22.3 and 24.19.0.
|
|
126
|
-
- The packed package passed 13 runtime checks and two typed consumers on Node
|
|
127
|
-
24. Lint and strict declaration checks passed.
|
|
128
|
-
- Nx 23.2.1 passed target dotenv loading, local/shell precedence, target
|
|
129
|
-
isolation and strict failures for command and Doppler modes. Tests used a
|
|
130
|
-
temporary workspace, dummy credentials and an existing Nx installation
|
|
131
|
-
read-only; consumer files were not changed.
|
|
132
|
-
- npm reported zero known vulnerabilities, 103 verified registry signatures
|
|
133
|
-
and 37 verified attestations in the installed development tree. Advisory and
|
|
134
|
-
signature checks do not rule out unknown vulnerabilities.
|
|
135
|
-
- [Hosted CI](https://github.com/ciqol/resenv/actions/runs/37462966577) passed for
|
|
136
|
-
`e57a8fe`: one build and four compatibility jobs on Linux (Node 20, 22 and 24)
|
|
137
|
-
and macOS (Node 24). Each job passed all 70 tests, 13 installed runtime checks
|
|
138
|
-
and two typed consumers against the same archive. The Linux build verified
|
|
139
|
-
103 registry signatures and 38 attestations with zero known vulnerabilities.
|
|
140
|
-
Counts apply to each platform's installed tree.
|
|
141
|
-
- Workflow structure and documentation links passed local checks. Existing
|
|
142
|
-
distributed archives retained their original SHA-256 hashes. The final
|
|
143
|
-
documentation update records the tested commit; it does not change runtime code.
|
|
144
|
-
|
|
145
|
-
Before public release, settle the license and whether to retain the older
|
|
146
|
-
dotenvx decryption feature. The fresh native browser-login flow and live Linux
|
|
147
|
-
keyring integration still need verification. Windows native login/storage is
|
|
148
|
-
not implemented. Repository reporting/protection and npm publishing setup are
|
|
149
|
-
also outstanding in [the release plan](releasing.md). The repository remains
|
|
150
|
-
private and unpublished, with PR creation on hold.
|