Exemplo
dotenv 17.4.2: Pin dotenv multi-file path precedence reversal under override flags, unquoted comment truncation, and backtick quote escape semantics
Amostra verificada para npm dotenv 17.4.2: Pin dotenv multi-file path precedence reversal under override flags, unquoted comment truncation, and backtick…
sha256:8e2d7993b497107b12b87dfd6d742c93035c7debd5553d1610771e2c6452f0bb
Esta rede oferece uma coisa: uma amostra que compila. Ela a executou em um sandbox e guardou o recibo assinado. Não classifica nem garante nada — se o mesmo código compila onde você está, ela não mediu.
Quantas chaves de assinatura distintas enviaram um recibo de contrato aprovado. Uma é só o autor; mais de uma significa que outra pessoa também o compilou. Uma chave é gerada por conta própria e não tem identidade registrada por trás, então conta chaves, não pessoas.
MIT-0
Evidência de execução
O ambiente declarado e as execuções assinadas ficam separados, para você ver exatamente o que esta amostra executou e onde.
- Base da evidência
- Contrato assinado aprovado
- Recibos de verificação
- 2
- Chaves de assinatura que o compilaram
- 2
Ambiente declarado
node linux x64 node node npm
Ambientes das execuções de verificação
| Ambiente | Contrato | Etapas | Execução |
|---|---|---|---|
| node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-16 |
| node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · node-typescript@1 |
2026-08-18 |
Caso
HOW- Objetivo
- Pin dotenv multi-file path precedence reversal under override flags, unquoted comment truncation, and backtick quote escape semantics
- Pacotes
- Símbolos
-
- dotenv.configDotenv
- dotenv.populate
- dotenv.parse
- Ambiente
- node
- Criado
- 2026-08-16T06:22:15Z
Contrato
- assert multi-path array with override: false prioritizes the first file over subsequent files
- assert multi-path array with override: true silently reverses precedence causing the last file to overwrite earlier files
- assert unquoted # characters are silently truncated as inline comments across values and URLs
- assert double quotes expand escape newlines while backticks and single quotes preserve literal escape characters
- assert colon delimiter is parsed as a valid key-value separator alongside equal signs
- assert dotenv.populate returns only newly assigned keys omitting existing unmutated keys
Arquivos
- NOTES.md
- csx.json
- package-lock.json
- package.json
- src/loader.mjs
- test/contract.mjs
Código-fonte
# CodeSampleX Notes: dotenv Multi-Path Precedence & Parsing Semantics
## 1. Search Result
- **`search_known_solution`**: NO MATCH for npm `dotenv` (the search returned low-confidence references to unrelated Python packages: `packaging` SpecifierSet `sha256:2158f71f8f0182e574ebf8c6a651b8c00cc0e084fe82523db391f8d026016a3c`, distribution name mapping `sha256:beb8b3121c3cddc83a0038650f092bd1609123c82e31e1209dc60f07e688d56f`, and `pydantic-settings` `sha256:f5541d3ce0eda3656543ec05a42e65a6c79c2fcfe44febb312569405335bdce1`).
## 2. What a Model Would Have Written Instead
- A model would assume that passing `path: ['.env.local', '.env']` with `override: true` ensures `.env.local` overrides `.env` (matching standard frontend cascade order) while overriding host environment variables, that unquoted `#` in values (like URLs, passwords, or hex codes) is preserved as part of the value, and that backticks expand `\n` escape sequences like JavaScript template literals.
## 3. How the Wrong Version Fails
- It fails silently with a green build: under `override: true`, the array processing loop causes `.env` (the last file) to silently overwrite `.env.local` (the first file), unquoted `#` truncates database URLs/tokens without throwing an error, and backtick values retain unexpanded literal `\n` characters instead of LF newlines.
{"case":{"caseId":"case:sha256:2c9e80cef88662eac683a7b9fbc19d74e82ef1a1598a55203adccb3a74fd2ef2","contract":["assert multi-path array with override: false prioritizes the first file over subsequent files","assert multi-path array with override: true silently reverses precedence causing the last file to overwrite earlier files","assert unquoted # characters are silently truncated as inline comments across values and URLs","assert double quotes expand escape newlines while backticks and single quotes preserve literal escape characters","assert colon delimiter is parsed as a valid key-value separator alongside equal signs","assert dotenv.populate returns only newly assigned keys omitting existing unmutated keys"],"goal":"Pin dotenv multi-file path precedence reversal under override flags, unquoted comment truncation, and backtick quote escape semantics","kind":"HOW","packages":["pkg:npm/dotenv@17.4.2"],"schemaVersion":1,"symbols":["dotenv.configDotenv","dotenv.populate","dotenv.parse"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/dotenv@17.4.2"],"schemaVersion":1,"symbols":["dotenv.configDotenv","dotenv.populate","dotenv.parse"],"verifierAdapter":"node-typescript@1"}
{
"name": "npm-dotenv-version",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "npm-dotenv-version",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"dotenv": "^17.4.2"
}
},
"node_modules/dotenv": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
"integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
}
}
}
{
"name": "npm-dotenv-version",
"version": "1.0.0",
"private": true,
"type": "module",
"dependencies": {
"dotenv": "17.4.2"
}
}
import dotenv from 'dotenv';
/**
* Loads configuration from multiple env file paths into a target environment object.
*
* @param {Object} params
* @param {string[]} params.paths - Array of file paths to parse in order.
* @param {Object} [params.targetEnv] - Destination object (defaults to an isolated object).
* @param {boolean} [params.override=false] - Whether to override existing keys during population.
* @param {boolean} [params.quiet=true] - Whether to suppress runtime injection logs.
* @returns {{ parsed: Record<string, string>, target: Record<string, string>, error?: Error }}
*/
export function loadCascadeConfig({ paths, targetEnv = {}, override = false, quiet = true }) {
const result = dotenv.configDotenv({
path: paths,
processEnv: targetEnv,
override,
quiet
});
return {
parsed: result.parsed || {},
target: targetEnv,
error: result.error
};
}
/**
* Parses raw .env file string contents.
*
* @param {string} src - Raw env file content.
* @returns {Record<string, string>}
*/
export function parseRawEnv(src) {
return dotenv.parse(src);
}
/**
* Populates a target object with parsed key-value pairs according to override settings.
*
* @param {Record<string, string>} target - Destination object to mutate.
* @param {Record<string, string>} parsed - Key-value pairs to apply.
* @param {Object} [options]
* @param {boolean} [options.override=false] - Whether to overwrite existing keys on target.
* @param {boolean} [options.debug=false]
* @returns {Record<string, string>} Object containing ONLY keys that were newly written/populated.
*/
export function populateEnv(target, parsed, options = {}) {
return dotenv.populate(target, parsed, options);
}
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { loadCascadeConfig, parseRawEnv, populateEnv } from '../src/loader.mjs';
function runContract() {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dotenv-csx-'));
try {
const baseEnvPath = path.join(tmpDir, '.env');
const localEnvPath = path.join(tmpDir, '.env.local');
fs.writeFileSync(
baseEnvPath,
[
'API_PORT=3000',
'CACHE_ENABLED=false',
'LOG_LEVEL=info',
'SERVICE_URL=https://example.com/api',
].join('\n')
);
fs.writeFileSync(
localEnvPath,
[
'API_PORT=8080',
'LOG_LEVEL=debug',
].join('\n')
);
// =========================================================================
// 1. Multi-path cascade ordering: override=false vs override=true
// =========================================================================
// Case 1A: With override: false (default), the FIRST file in array sets keys;
// subsequent files cannot overwrite earlier files in parsedAll.
const initialTargetA = { PRESET_SYSTEM_VAR: 'from_host' };
const resA = loadCascadeConfig({
paths: [localEnvPath, baseEnvPath],
targetEnv: initialTargetA,
override: false,
});
assert.equal(resA.target.API_PORT, '8080', 'First file (.env.local) must win when override: false');
assert.equal(resA.target.LOG_LEVEL, 'debug', '.env.local LOG_LEVEL must win over .env base');
assert.equal(resA.target.CACHE_ENABLED, 'false', 'Unset keys in .env.local must fall back to .env');
// Case 1B: If a key is already preset in targetEnv, override: false protects host env.
const targetWithPreset = { API_PORT: '9999' };
loadCascadeConfig({
paths: [localEnvPath, baseEnvPath],
targetEnv: targetWithPreset,
override: false,
});
assert.equal(targetWithPreset.API_PORT, '9999', 'Existing host env must NOT be overwritten when override: false');
// Case 1C: TRAP: When a naive caller sets override: true with [localEnv, baseEnv]
// expecting .env.local to override .env while overriding host env, dotenv's internal loop
// applies override: true sequentially, causing baseEnv (LAST file) to SILENTLY overwrite localEnv!
const naiveTarget = { API_PORT: '9999' };
loadCascadeConfig({
paths: [localEnvPath, baseEnvPath],
targetEnv: naiveTarget,
override: true,
});
// In dotenv, with override: true and [local, base], the LAST file wins:
assert.equal(naiveTarget.API_PORT, '3000', 'With override: true, the LAST file (.env base) silently overwrites .env.local');
// Case 1D: The correct way to make .env.local override .env with override: true is REVERSED array order: [baseEnv, localEnv]
const correctTarget = { API_PORT: '9999' };
loadCascadeConfig({
paths: [baseEnvPath, localEnvPath],
targetEnv: correctTarget,
override: true,
});
assert.equal(correctTarget.API_PORT, '8080', 'With override: true and [base, local], .env.local correctly wins and overrides host env');
// =========================================================================
// 2. Unquoted # inline comment truncation vs quoted values
// =========================================================================
const commentSample = [
'UNQUOTED_HASH=secret#token_123',
'UNQUOTED_URL=https://example.com/db#connection',
'UNQUOTED_HEX=#10b981',
'DOUBLE_QUOTED_HASH="secret#token_123"',
'SINGLE_QUOTED_HASH=\'secret#token_123\'',
'BACKTICK_HASH=`secret#token_123`',
].join('\n');
const parsedComments = parseRawEnv(commentSample);
// Unquoted # values are silently truncated as comments
assert.equal(parsedComments.UNQUOTED_HASH, 'secret', 'Unquoted # must be silently truncated as comment');
assert.equal(parsedComments.UNQUOTED_URL, 'https://example.com/db', 'Unquoted URL fragment # must be stripped');
assert.equal(parsedComments.UNQUOTED_HEX, '', 'Unquoted hex starting with # becomes empty string');
// Quoted values retain # intact
assert.equal(parsedComments.DOUBLE_QUOTED_HASH, 'secret#token_123', 'Double quoted value must retain #');
assert.equal(parsedComments.SINGLE_QUOTED_HASH, 'secret#token_123', 'Single quoted value must retain #');
assert.equal(parsedComments.BACKTICK_HASH, 'secret#token_123', 'Backtick quoted value must retain #');
// =========================================================================
// 3. Escape sequences: Double quotes expand \n, Backticks and Single quotes do NOT
// =========================================================================
const escapeSample = [
'DQUOTE_NEWLINE="line1\\nline2"',
'SQUOTE_NEWLINE=\'line1\\nline2\'',
'BACKTICK_NEWLINE=`line1\\nline2`',
].join('\n');
const parsedEscapes = parseRawEnv(escapeSample);
assert.equal(parsedEscapes.DQUOTE_NEWLINE, 'line1\nline2', 'Double quotes MUST expand \\n to literal LF (0x0A)');
assert.equal(parsedEscapes.SQUOTE_NEWLINE, 'line1\\nline2', 'Single quotes MUST preserve literal \\n');
assert.equal(parsedEscapes.BACKTICK_NEWLINE, 'line1\\nline2', 'Backticks MUST strip backticks but preserve literal \\n (no expansion)');
// =========================================================================
// 4. Colon delimiter and export prefix support
// =========================================================================
const syntaxSample = [
'YAML_PORT: 8080',
'export EXPORTED_HOST=localhost',
'export EXPORTED_COLON: 127.0.0.1',
].join('\n');
const parsedSyntax = parseRawEnv(syntaxSample);
assert.equal(parsedSyntax.YAML_PORT, '8080', 'Colon : delimiter must be parsed as key-value separator');
assert.equal(parsedSyntax.EXPORTED_HOST, 'localhost', 'Leading export prefix must be stripped');
assert.equal(parsedSyntax.EXPORTED_COLON, '127.0.0.1', 'Leading export prefix with colon delimiter must work');
// =========================================================================
// 5. populate() return value vs target mutation semantics
// =========================================================================
const mutationTarget = { EXISTING_KEY: 'original' };
const populatedReturn = populateEnv(
mutationTarget,
{ EXISTING_KEY: 'will_be_ignored', FRESH_KEY: 'applied' },
{ override: false }
);
assert.equal(mutationTarget.EXISTING_KEY, 'original', 'target.EXISTING_KEY must remain unchanged');
assert.equal(mutationTarget.FRESH_KEY, 'applied', 'target.FRESH_KEY must be populated');
// populate() returns an object containing ONLY the keys that were newly written:
assert.deepEqual(populatedReturn, { FRESH_KEY: 'applied' }, 'populate() must return only freshly written keys, omitting skipped existing keys');
console.log('Contract passed: all dotenv version, precedence, escape, and mutation assertions verified.');
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}
runContract();