nanos-lint 2.4.0 → 2.5.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.
@@ -823,6 +823,19 @@ function loadConfigFile(filePath) {
823
823
  return parseJsonc(fs.readFileSync(filePath, "utf-8"));
824
824
  }
825
825
  /**
826
+ * Removes trailing `/` characters from a path-like string.
827
+ *
828
+ * Implemented with a scan instead of a `\/+$` regular expression: on inputs made
829
+ * of many slashes that do not end in a slash, a backtracking engine retries the
830
+ * repetition at every offset, which is quadratic in the input length
831
+ * (CodeQL: js/polynomial-redos).
832
+ */
833
+ function stripTrailingSlashes(value) {
834
+ let end = value.length;
835
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
836
+ return end === value.length ? value : value.slice(0, end);
837
+ }
838
+ /**
826
839
  * Merges a base nanos configuration with a workspace override configuration.
827
840
  * Guarantees that the nanos definitions directory is included in workspace.library,
828
841
  * and standardizes paths for LuaLS.
@@ -869,12 +882,12 @@ function mergeConfigs(base, override = {}, definitionsDir = getDefinitionsDir(),
869
882
  for (const pat of normalizedCliIgnore) {
870
883
  excludePatterns.add(pat);
871
884
  if (!pat.includes("*") && !pat.includes("?") && !pat.endsWith(".lua")) {
872
- const dirPat = pat.replace(/\/+$/, "");
885
+ const dirPat = stripTrailingSlashes(pat);
873
886
  excludePatterns.add(`${dirPat}/**`);
874
887
  }
875
888
  }
876
889
  mergedFilesExclude = Array.from(excludePatterns);
877
- const cliDirs = normalizedCliIgnore.filter((p) => !p.includes("*") && !p.includes("?") && !p.endsWith(".lua")).map((p) => p.replace(/\/+$/, ""));
890
+ const cliDirs = normalizedCliIgnore.filter((p) => !p.includes("*") && !p.includes("?") && !p.endsWith(".lua")).map((p) => stripTrailingSlashes(p));
878
891
  mergedIgnoreDir = Array.from(/* @__PURE__ */ new Set([
879
892
  ...defaultIgnore,
880
893
  ...baseIgnore,
@@ -939,7 +952,7 @@ function resolveWorkspaceConfig(workspacePath, customConfigPath, options) {
939
952
  const hasCliIgnore = Boolean(options?.ignore && options.ignore.length > 0);
940
953
  let cliIgnore = options?.ignore;
941
954
  if (hasCliIgnore && cliIgnore) {
942
- const normWs = workspacePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
955
+ const normWs = stripTrailingSlashes(workspacePath.replace(/\\/g, "/").replace(/^\.\//, ""));
943
956
  const expanded = [];
944
957
  for (const pat of cliIgnore) {
945
958
  expanded.push(pat);
@@ -1002,6 +1015,39 @@ const execFileAsync = promisify(execFile);
1002
1015
  const FALLBACK_LUALS_VERSION = "3.19.1";
1003
1016
  const DEFAULT_LUALS_VERSION = "latest";
1004
1017
  /**
1018
+ * Characters accepted in a LuaLS version/tag. Only ASCII letters, digits, dots,
1019
+ * dashes and underscores are allowed, so a version can never contain a path
1020
+ * separator, a drive letter or a traversal segment.
1021
+ */
1022
+ const SAFE_VERSION_CHARS = new Map([..."0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-"].map((ch) => [ch, ch]));
1023
+ const MAX_VERSION_LENGTH = 64;
1024
+ /**
1025
+ * Validates a LuaLS version/tag and rebuilds it from the allow-list above.
1026
+ *
1027
+ * Version strings originate from untrusted sources: the GitHub releases API
1028
+ * response and user supplied `--luals-version` arguments. They are interpolated
1029
+ * into cache directory paths, download URLs, and the path of the binary that is
1030
+ * eventually executed, so they must be constrained to a single safe path
1031
+ * segment. Rebuilding the value character by character guarantees the returned
1032
+ * string only ever contains allow-listed characters (CodeQL: js/command-line-injection).
1033
+ *
1034
+ * @returns the normalized version (a single leading `v` is dropped), or `null`
1035
+ * when the input cannot be used as a version tag.
1036
+ */
1037
+ function sanitizeLuaLSVersion(raw) {
1038
+ const trimmed = raw.trim();
1039
+ if (trimmed.length === 0 || trimmed.length > MAX_VERSION_LENGTH) return null;
1040
+ let version = "";
1041
+ for (const ch of trimmed) {
1042
+ const allowed = SAFE_VERSION_CHARS.get(ch);
1043
+ if (allowed === void 0) return null;
1044
+ version += allowed;
1045
+ }
1046
+ if (version.charCodeAt(0) === 118) version = version.slice(1);
1047
+ const first = version.charCodeAt(0);
1048
+ return first >= 48 && first <= 57 || first >= 65 && first <= 90 || first >= 97 && first <= 122 ? version : null;
1049
+ }
1050
+ /**
1005
1051
  * Escapes single quotes for safe PowerShell single-quoted string interpolation.
1006
1052
  */
1007
1053
  function escapePowerShellSingleQuote(str) {
@@ -1020,17 +1066,22 @@ async function resolveLatestLuaLSVersion() {
1020
1066
  });
1021
1067
  if (res.ok) {
1022
1068
  const data = await res.json();
1023
- if (data.tag_name) return data.tag_name.replace(/^v/, "");
1069
+ const version = typeof data.tag_name === "string" ? sanitizeLuaLSVersion(data.tag_name) : null;
1070
+ if (version) return version;
1024
1071
  }
1025
1072
  } catch {}
1026
1073
  return FALLBACK_LUALS_VERSION;
1027
1074
  }
1028
1075
  /**
1029
1076
  * Resolves a version string ("latest" -> actual tag).
1077
+ *
1078
+ * @throws when an explicitly requested version is not a valid tag.
1030
1079
  */
1031
1080
  async function resolveLuaLSVersion(version) {
1032
1081
  if (!version || version === "latest") return await resolveLatestLuaLSVersion();
1033
- return version.replace(/^v/, "");
1082
+ const sanitized = sanitizeLuaLSVersion(version);
1083
+ if (!sanitized) throw new Error(`Invalid LuaLS version: "${version}". Expected a release tag such as "3.19.1", or "latest".`);
1084
+ return sanitized;
1034
1085
  }
1035
1086
  function getPlatformInfo(version = FALLBACK_LUALS_VERSION) {
1036
1087
  const platform = process.platform;
@@ -4620,6 +4671,6 @@ if (isDirectExecution()) runCLI().then((code) => {
4620
4671
  process.exit(1);
4621
4672
  });
4622
4673
  //#endregion
4623
- export { mergeConfigs as A, resolveLuaLSVersion as C, getPackageRoot as D, getDefinitionsDir as E, resolveWorkspaceConfig as M, stripJsonComments as N, initWorkspace as O, fileUriToPath as P, resolveLuaLSBinary as S, getDefaultTemplatePath as T, escapePowerShellSingleQuote as _, formatGitHubAnnotations as a, isBinaryValid as b, formatReport as c, pluralize as d, shouldEnableColor as f, downloadAndExtractLuaLS as g, countCheckedFiles as h, runCLI as i, parseJsonc as j, loadConfigFile as k, formatSeverityBadge as l, FALLBACK_LUALS_VERSION as m, createProgram as n, formatPretty as o, DEFAULT_LUALS_VERSION as p, isDirectExecution as r, formatProblemSummary as s, collectIgnorePatterns as t, getColors as u, getCacheDir as v, runLuaLSCheck as w, resolveLatestLuaLSVersion as x, getPlatformInfo as y };
4674
+ export { loadConfigFile as A, resolveLuaLSVersion as C, getDefinitionsDir as D, getDefaultTemplatePath as E, stripTrailingSlashes as F, fileUriToPath as I, parseJsonc as M, resolveWorkspaceConfig as N, getPackageRoot as O, stripJsonComments as P, resolveLuaLSBinary as S, sanitizeLuaLSVersion as T, escapePowerShellSingleQuote as _, formatGitHubAnnotations as a, isBinaryValid as b, formatReport as c, pluralize as d, shouldEnableColor as f, downloadAndExtractLuaLS as g, countCheckedFiles as h, runCLI as i, mergeConfigs as j, initWorkspace as k, formatSeverityBadge as l, FALLBACK_LUALS_VERSION as m, createProgram as n, formatPretty as o, DEFAULT_LUALS_VERSION as p, isDirectExecution as r, formatProblemSummary as s, collectIgnorePatterns as t, getColors as u, getCacheDir as v, runLuaLSCheck as w, resolveLatestLuaLSVersion as x, getPlatformInfo as y };
4624
4675
 
4625
- //# sourceMappingURL=cli-kmHD4epr.js.map
4676
+ //# sourceMappingURL=cli-BVXk8XmH.js.map