Compare commits

..
Author SHA1 Message Date
dependabot[bot] eb2e261c62 Bump esbuild from 0.21.4 to 0.25.0
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.21.4 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.21.4...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 15:15:40 +00:00
15 changed files with 2747 additions and 3021 deletions

No files matched your search

-21
View File
@@ -1,26 +1,5 @@
# Change Log # Change Log
## 1.2.16 (2026-03-29)
- Fix regression in v1.2.15 that broke the device search component in the UI by
pinning Mithril dependency to v2.2.2 due to multiple unresolved regressions in
later versions.
## 1.2.15 (2026-03-26)
- Fix critical RCE vulnerability in /api/ping endpoint (follow-up to
CVE-2021-46704). Credit: JuHwiSang.
- Fix device IDs containing special characters (e.g., %) not being handled
correctly in the UI due to a regression in Mithril 2.3.8.
## 1.2.14 (2026-03-12)
- Prevent UI crash when a malformed URL is sent to the server.
- Fix potential edge-case bugs in expression evaluation and session
serialization.
## 1.2.13 (2024-06-06) ## 1.2.13 (2024-06-06)
- Increase connection timeout for UI and NBI from 30 to 120 seconds to avoid - Increase connection timeout for UI and NBI from 30 to 120 seconds to avoid
+4 -4
View File
@@ -2,13 +2,13 @@ import { PermissionSet, Expression } from "../types.ts";
import { evaluate, or } from "./expression/util.ts"; import { evaluate, or } from "./expression/util.ts";
export default class Authorizer { export default class Authorizer {
declare private permissionSets: PermissionSet[]; private declare permissionSets: PermissionSet[];
declare private validatorCache: WeakMap< private declare validatorCache: WeakMap<
any, any,
(mutationType, mutation, any) => boolean (mutationType, mutation, any) => boolean
>; >;
declare private hasAccessCache: Map<string, boolean>; private declare hasAccessCache: Map<string, boolean>;
declare private getFilterCache: Map<string, Expression>; private declare getFilterCache: Map<string, Expression>;
public constructor(permissionSets: PermissionSet[]) { public constructor(permissionSets: PermissionSet[]) {
this.permissionSets = permissionSets; this.permissionSets = permissionSets;
+3 -3
View File
@@ -9,8 +9,8 @@ const TWO = BI.BigInt(2);
const NEGATIVE_ONE = BI.BigInt(-1); const NEGATIVE_ONE = BI.BigInt(-1);
class Indeterminates { class Indeterminates {
declare public map: Map<string, number>; public declare map: Map<string, number>;
declare public sortedKeys: string[]; public declare sortedKeys: string[];
public constructor(str?: string) { public constructor(str?: string) {
this.map = new Map(); this.map = new Map();
@@ -98,7 +98,7 @@ function findGcd(a: BI.bigint, b: BI.bigint): BI.bigint {
} }
class Polynomial { class Polynomial {
declare public terms: Term[]; public declare terms: Term[];
public constructor(terms: Term[]) { public constructor(terms: Term[]) {
this.terms = terms; this.terms = terms;
+1 -1
View File
@@ -955,7 +955,7 @@ export function covers(expr1: Expression, expr2: Expression): boolean {
expr2 = normalize(expr2); expr2 = normalize(expr2);
if (!expr2) return true; if (!expr2) return true;
expr1 = normalize(expr1); expr1 = normalize(expr1);
if (!Array.isArray(expr1) && expr1) return true; if (!Array.isArray(expr1)) return !!expr1;
const synt1 = Clause.fromExpression(expr1); const synt1 = Clause.fromExpression(expr1);
const synt2 = Clause.fromExpression(expr2); const synt2 = Clause.fromExpression(expr2);
+3 -3
View File
@@ -1,9 +1,9 @@
import Path from "./path.ts"; import Path from "./path.ts";
export default class PathSet { export default class PathSet {
declare private lengthIndex: Set<Path>[]; private declare lengthIndex: Set<Path>[];
declare private fragmentIndex: Map<string, Set<Path>>[]; private declare fragmentIndex: Map<string, Set<Path>>[];
declare private stringIndex: Map<string, Path>; private declare stringIndex: Map<string, Path>;
public constructor() { public constructor() {
this.lengthIndex = []; this.lengthIndex = [];
+5 -5
View File
@@ -30,11 +30,11 @@ function legalChar(c: number): boolean {
} }
export default class Path { export default class Path {
declare public readonly segments: Segments; public declare readonly segments: Segments;
declare public readonly wildcard: number; public declare readonly wildcard: number;
declare public readonly alias: number; public declare readonly alias: number;
declare protected _string: string; protected declare _string: string;
declare protected _stringIndex: number[]; protected declare _stringIndex: number[];
protected static parseAliasValue( protected static parseAliasValue(
pattern: string, pattern: string,
+1 -1
View File
@@ -3,7 +3,7 @@ interface Instance {
} }
export default class InstanceSet { export default class InstanceSet {
declare private set: Set<Instance>; private declare set: Set<Instance>;
public constructor() { public constructor() {
this.set = new Set(); this.set = new Set();
+7 -16
View File
@@ -1,6 +1,6 @@
import { platform } from "node:os"; import { platform } from "node:os";
import { execFile } from "node:child_process"; import { exec } from "node:child_process";
import { domainToASCII, domainToUnicode } from "node:url"; import { domainToASCII } from "node:url";
export interface PingResult { export interface PingResult {
packetsTransmitted: number; packetsTransmitted: number;
@@ -16,16 +16,9 @@ function isValidHost(host: string): boolean {
// Valid chars in IPv4, IPv6, domain names // Valid chars in IPv4, IPv6, domain names
if (/^[a-zA-Z0-9\-.:[\]-]+$/.test(host)) return true; if (/^[a-zA-Z0-9\-.:[\]-]+$/.test(host)) return true;
const lowercase = host.toLowerCase();
const ascii = domainToASCII(lowercase);
// Check if input is an IDN convert to Punycode // Check if input is an IDN convert to Punycode
// Can't merge with above because domainToASCII doesn't accept IP addresses // Can't merge with above because domainToASCII doesn't accept IP addresses
if (!/^[a-zA-Z0-9\-.:[\]-]+$/.test(ascii)) return false; return /^[a-zA-Z0-9\-.:[\]-]+$/.test(domainToASCII(host));
// Round-trip to ensure the original host matches its normalized form.
// This prevents bypasses where domainToASCII strips paths (e.g. "example.com/;cmd").
return lowercase === domainToUnicode(ascii);
} }
export function parsePing(osPlatform: string, stdout: string): PingResult { export function parsePing(osPlatform: string, stdout: string): PingResult {
@@ -80,26 +73,24 @@ export function ping(
): void { ): void {
// Validate input to prevent possible remote code execution // Validate input to prevent possible remote code execution
// Credit to Alex Hordijk for reporting this vulnerability // Credit to Alex Hordijk for reporting this vulnerability
// Credit to JuHwiSang for reporting a follow-up vulnerability
if (!isValidHost(host)) return callback(new Error("Invalid host")); if (!isValidHost(host)) return callback(new Error("Invalid host"));
host = host.replace("[", "").replace("]", ""); host = host.replace("[", "").replace("]", "");
let args: string[]; let cmd: string;
switch (platform()) { switch (platform()) {
case "linux": case "linux":
args = ["-w", "1", "-i", "0.2", "-c", "3", "--", host]; cmd = `ping -w 1 -i 0.2 -c 3 ${host}`;
break; break;
case "freebsd": case "freebsd":
// Send a single packet because on FreeBSD only superuser can send // Send a single packet because on FreeBSD only superuser can send
// packets that are only 200 ms apart. // packets that are only 200 ms apart.
args = ["-t", "1", "-c", "3", "--", host]; cmd = `ping -t 1 -c 3 ${host}`;
break; break;
default: default:
return callback(new Error("Platform not supported")); return callback(new Error("Platform not supported"));
} }
// Use execFile instead of exec to avoid shell interpretation of the host exec(cmd, (err, stdout) => {
execFile("ping", args, (err, stdout) => {
if (err) return callback(err); if (err) return callback(err);
const parsed: PingResult = parsePing(platform(), stdout); const parsed: PingResult = parsePing(platform(), stdout);
return callback(err, parsed, stdout); return callback(err, parsed, stdout);
+5 -9
View File
@@ -7,10 +7,10 @@ interface Revisions<V> {
} }
export default class VersionedMap<K, V> { export default class VersionedMap<K, V> {
declare private _sizeDiff: number[]; private declare _sizeDiff: number[];
declare private _revision: number; private declare _revision: number;
declare private map: Map<K, (V | symbol)[]>; private declare map: Map<K, (V | symbol)[]>;
declare public dirty: number; public declare dirty: number;
public constructor() { public constructor() {
this._sizeDiff = [0]; this._sizeDiff = [0];
@@ -112,8 +112,6 @@ export default class VersionedMap<K, V> {
prev = v; prev = v;
} }
if (prev === NONEXISTENT && !res.delete) return null;
return res; return res;
} }
@@ -124,8 +122,6 @@ export default class VersionedMap<K, V> {
del, del,
); );
if (!mutations) return;
const revisions = []; const revisions = [];
let prev: V | symbol = NONEXISTENT; let prev: V | symbol = NONEXISTENT;
@@ -153,7 +149,7 @@ export default class VersionedMap<K, V> {
public *diff(): IterableIterator<[K, V, V]> { public *diff(): IterableIterator<[K, V, V]> {
for (const [key, revisions] of this.map) { for (const [key, revisions] of this.map) {
if (revisions.length <= 1) continue; if (revisions.length === 1) continue;
let first = revisions[0]; let first = revisions[0];
let last = revisions[revisions.length - 1]; let last = revisions[revisions.length - 1];
if (first === NONEXISTENT && last === NONEXISTENT) continue; if (first === NONEXISTENT && last === NONEXISTENT) continue;
+2685 -2890
View File
File diff suppressed because it is too large. Load diff
+23 -23
View File
@@ -1,6 +1,6 @@
{ {
"name": "genieacs", "name": "genieacs",
"version": "1.2.16", "version": "1.2.13",
"description": "A TR-069 Auto Configuration Server (ACS)", "description": "A TR-069 Auto Configuration Server (ACS)",
"repository": { "repository": {
"type": "git", "type": "git",
@@ -26,15 +26,15 @@
}, },
"dependencies": { "dependencies": {
"@breejs/later": "^4.2.0", "@breejs/later": "^4.2.0",
"@koa/bodyparser": "^5.1.2", "@koa/bodyparser": "^5.1.1",
"@koa/router": "^13.1.1", "@koa/router": "^13.1.0",
"bson": "^4.7.2", "bson": "^4.7.2",
"espresso-iisojs": "^1.0.8", "espresso-iisojs": "^1.0.8",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"ipaddr.js": "^2.3.0", "ipaddr.js": "^2.2.0",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.2",
"koa": "^2.16.4", "koa": "^2.15.3",
"koa-compress": "^5.2.1", "koa-compress": "^5.1.0",
"koa-jwt": "^4.0.3", "koa-jwt": "^4.0.3",
"koa-send": "^5.0.1", "koa-send": "^5.0.1",
"mongodb": "^4.16.0", "mongodb": "^4.16.0",
@@ -42,26 +42,26 @@
"seedrandom": "^3.0.5" "seedrandom": "^3.0.5"
}, },
"devDependencies": { "devDependencies": {
"@types/codemirror": "^5.60.17", "@types/codemirror": "^5.60.15",
"@types/jsonwebtoken": "^9.0.10", "@types/jsonwebtoken": "^9.0.6",
"@types/koa": "^2.15.0", "@types/koa": "^2.15.0",
"@types/koa-compress": "^4.0.7", "@types/koa-compress": "^4.0.6",
"@types/mithril": "^2.2.6", "@types/mithril": "^2.2.6",
"@types/node": "^20.19.37", "@types/node": "^20.14.2",
"@types/parsimmon": "^1.10.9", "@types/parsimmon": "^1.10.9",
"@types/seedrandom": "^3.0.8", "@types/seedrandom": "^3.0.8",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.12.0",
"codemirror": "^5.65.21", "codemirror": "^5.65.16",
"esbuild": "^0.27.4", "esbuild": "^0.25.0",
"eslint": "^8.57.1", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.2", "eslint-config-prettier": "^9.1.0",
"mithril": "2.2.2", "mithril": "^2.2.2",
"prettier": "^3.8.1", "prettier": "^3.3.1",
"sql.js": "^1.14.1", "sql.js": "^1.10.3",
"svgo": "^3.3.3", "svgo": "^3.3.2",
"typescript": "^5.9.3", "typescript": "^5.4.5",
"yaml": "^1.10.3" "yaml": "^1.10.2"
}, },
"engines": { "engines": {
"node": ">=12.13.0" "node": ">=12.13.0"
+1 -35
View File
@@ -1,7 +1,7 @@
import test from "node:test"; import test from "node:test";
import assert from "node:assert"; import assert from "node:assert";
import initSqlJs from "sql.js/dist/sql-asm.js"; import initSqlJs from "sql.js/dist/sql-asm.js";
import { covers, minimize, unionDiff } from "../lib/common/expression/synth.ts"; import { minimize, unionDiff } from "../lib/common/expression/synth.ts";
import { parse, stringify } from "../lib/common/expression/parser.ts"; import { parse, stringify } from "../lib/common/expression/parser.ts";
const STRING_VALUES = [null, "", "a", "ab", "ab10", "ab-10"]; const STRING_VALUES = [null, "", "a", "ab", "ab10", "ab-10"];
@@ -115,37 +115,3 @@ void test("unionDiff", async () => {
assert.strictEqual(setsEqual(res4, diffSet), true); assert.strictEqual(setsEqual(res4, diffSet), true);
} }
}); });
void test("covers", async () => {
assert.strictEqual(covers(false, false), true);
assert.strictEqual(covers(false, parse("decimal > 5 AND decimal < 3")), true);
assert.strictEqual(covers(parse("true"), parse("decimal > 0")), true);
assert.strictEqual(covers(parse("true"), parse("false")), true);
assert.strictEqual(covers(parse("false"), parse("false")), true);
assert.strictEqual(covers(parse("false"), parse("decimal > 0")), false);
assert.strictEqual(covers(parse("decimal >= 0"), parse("decimal > 0")), true);
assert.strictEqual(
covers(parse("decimal > 0"), parse("decimal >= 0")),
false,
);
const cases = [
["decimal >= 0", "decimal > 0"],
["decimal > 0", "decimal > 5"],
["string IS NOT NULL", "string = 'a'"],
["true", "decimal > 0"],
];
for (const [c1, c2] of cases) {
const res1 = await query(c1);
const res2 = await query(c2);
const coversResult = covers(parse(c1), parse(c2));
const actuallyCovers = Array.from(res2).every((r) => res1.has(r));
assert.strictEqual(
coversResult,
actuallyCovers,
`covers(${c1}, ${c2}) should match actual coverage`,
);
}
});
+1 -2
View File
@@ -11,8 +11,7 @@
"strictFunctionTypes": true, "strictFunctionTypes": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true
"skipLibCheck": true
}, },
"include": [ "include": [
"./bin/*.ts", "./bin/*.ts",
+7 -7
View File
@@ -4,13 +4,13 @@ type AutocompleteCallback = (
) => void; ) => void;
export default class Autocomplete { export default class Autocomplete {
declare private callback: AutocompleteCallback; private declare callback: AutocompleteCallback;
declare private element: HTMLInputElement; private declare element: HTMLInputElement;
declare private hideTimeout: NodeJS.Timeout; private declare hideTimeout: NodeJS.Timeout;
declare private visible: boolean; private declare visible: boolean;
declare private default: string; private declare default: string;
declare private selection: number; private declare selection: number;
declare private container: HTMLElement; private declare container: HTMLElement;
public constructor(className: string, callback: AutocompleteCallback) { public constructor(className: string, callback: AutocompleteCallback) {
this.callback = callback; this.callback = callback;
+1 -1
View File
@@ -79,7 +79,7 @@ export function structureConfig(config: Config[]): any {
if (idx >= 0 && idx < MAX_ARRAY_SIZE && String(idx) === key) { if (idx >= 0 && idx < MAX_ARRAY_SIZE && String(idx) === key) {
const pos = Math.floor(idx / MAX_BITS); const pos = Math.floor(idx / MAX_BITS);
if (!indexes[pos]) indexes[pos] = 0; if (!indexes[pos]) indexes[pos] = 0;
indexes[pos] |= 1 << (idx % MAX_BITS); indexes[pos] |= 1 << idx % MAX_BITS;
} else { } else {
indexes = []; indexes = [];
break; break;