Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2339,7 +2339,8 @@ INSPECTOR_INTERNAL_INGEST_TOKEN=
# unset): path to a file whose trimmed content is the token.
# OMNIROUTE_INTERNAL_SERVICE_TOKEN_FILE=
# Quota Sharing (Group B — planos 16+22)
QUOTA_STORE_DRIVER=sqlite # sqlite | redis
# sqlite | redis
QUOTA_STORE_DRIVER=sqlite
# QUOTA_STORE_REDIS_URL= # ex.: redis://localhost:6379 (apenas quando driver=redis)
# QUOTA_SATURATION_THRESHOLD=0.5 # 0..1; >= threshold ativa modo strict (sem empréstimo)
# QUOTA_SOFT_DEPRIORITIZE_FACTOR=0.7 # 0..1; multiplicador do score quando soft policy ativa
Expand Down
25 changes: 23 additions & 2 deletions bin/omniroute.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,28 @@ function migrateElectronServerEnv(dataDir) {
}
}

/**
* Parse a `.env` value with dotenv-compatible comment handling.
*
* Without this, `KEY=value # note` stored the comment text as part of the
* value. The shipped .env ships exactly such a line for QUOTA_STORE_DRIVER, and
* consumers compare it with `===`, so annotating a variable inline silently
* disabled it (#10100).
*
* Quoted values are returned verbatim — a `#` inside quotes is data. For
* unquoted values a `#` *preceded by whitespace* starts a comment, so
* `pass#word` is preserved.
*/
function parseEnvValue(raw) {
const value = String(raw).trim();

const quoted = value.match(/^(['"])([\s\S]*)\1\s*(?:#.*)?$/);
if (quoted) return quoted[2];

const commentIdx = value.search(/\s#/);
return (commentIdx === -1 ? value : value.slice(0, commentIdx)).trim();
}

function loadEnvFile() {
const envPaths = [];
const loadedEnvPaths = [];
Expand Down Expand Up @@ -128,9 +150,8 @@ function loadEnvFile() {
const eqIdx = trimmed.indexOf("=");
if (eqIdx > 0) {
const key = trimmed.slice(0, eqIdx).trim();
const value = trimmed.slice(eqIdx + 1).trim();
if (process.env[key] === undefined) {
process.env[key] = value.replace(/^["']|["']$/g, "");
process.env[key] = parseEnvValue(trimmed.slice(eqIdx + 1));
}
}
}
Expand Down
81 changes: 81 additions & 0 deletions tests/unit/cli-env-inline-comment-10100.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";

// #10100 — the .env loader kept inline comments inside values, so the shipped
// `QUOTA_STORE_DRIVER=sqlite # sqlite | redis` line produced the literal value
// "sqlite # sqlite | redis". Consumers compare with `===`, so a
// user annotating `QUOTA_STORE_DRIVER=redis # ...` silently got SQLite with no
// warning (the existing warning lives inside the `redis` branch).

const LOADER = path.resolve("bin/omniroute.mjs");

/**
* The loader is a CLI entrypoint with side effects on import, so exercise the
* pure helper by extracting it from source rather than importing the module.
*/
function loadParseEnvValue(): (raw: string) => string {
const source = fs.readFileSync(LOADER, "utf8");
const start = source.indexOf("function parseEnvValue(");
assert.ok(start > -1, "parseEnvValue should exist in bin/omniroute.mjs");
// Walk to the end of the function body.
let depth = 0;
let end = start;
for (let i = source.indexOf("{", start); i < source.length; i++) {
if (source[i] === "{") depth++;
else if (source[i] === "}") {
depth--;
if (depth === 0) {
end = i + 1;
break;
}
}
}
return new Function(`${source.slice(start, end)}; return parseEnvValue;`)() as (
raw: string
) => string;
}

const parseEnvValue = loadParseEnvValue();

test("an unquoted inline comment is stripped", () => {
assert.equal(parseEnvValue("sqlite # sqlite | redis"), "sqlite");
assert.equal(parseEnvValue("redis # sqlite | redis"), "redis");
assert.equal(parseEnvValue("value\t# tab-separated comment"), "value");
});

test("a '#' with no preceding whitespace is part of the value", () => {
// dotenv semantics — passwords and fragments must survive.
assert.equal(parseEnvValue("pass#word"), "pass#word");
assert.equal(
parseEnvValue("https://example.com/page#section"),
"https://example.com/page#section"
);
});

test("quoted values are returned verbatim, including '#'", () => {
assert.equal(parseEnvValue('"sqlite # not a comment"'), "sqlite # not a comment");
assert.equal(parseEnvValue("'a # b'"), "a # b");
// A comment may still follow a closing quote.
assert.equal(parseEnvValue('"sqlite" # sqlite | redis'), "sqlite");
});

test("plain values are unchanged", () => {
assert.equal(parseEnvValue("sqlite"), "sqlite");
assert.equal(parseEnvValue(" spaced "), "spaced");
assert.equal(parseEnvValue(""), "");
});

test(".env.example no longer annotates QUOTA_STORE_DRIVER inline", () => {
const example = fs.readFileSync(path.resolve(".env.example"), "utf8");
const line = example.split("\n").find((l) => l.startsWith("QUOTA_STORE_DRIVER="));
assert.ok(line, "QUOTA_STORE_DRIVER should still be documented");
assert.equal(line, "QUOTA_STORE_DRIVER=sqlite");
// Guard the whole file against reintroducing the pattern on unquoted values.
const offenders = example
.split("\n")
.filter((l) => /^[A-Z0-9_]+=[^"'#\n]*\s#/.test(l))
.slice(0, 5);
assert.deepEqual(offenders, [], `unquoted inline comments would land in the value: ${offenders}`);
});