@night-slayer18/leetcode-cli 2.0.0 → 2.0.1

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.
Files changed (2) hide show
  1. package/dist/index.js +53 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -454,7 +454,7 @@ var LeetCodeClient = class {
454
454
  if (result.state === "SUCCESS" || result.state === "FAILURE") {
455
455
  return schema.parse(result);
456
456
  }
457
- await new Promise((resolve) => setTimeout(resolve, delay));
457
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
458
458
  }
459
459
  throw new Error("Submission timeout: Result not available after 30 seconds");
460
460
  }
@@ -1732,20 +1732,27 @@ function getLangSlugFromExtension(ext) {
1732
1732
  }
1733
1733
 
1734
1734
  // src/utils/validation.ts
1735
+ import { resolve, sep } from "path";
1735
1736
  function isProblemId(input) {
1736
1737
  return /^\d+$/.test(input);
1737
1738
  }
1738
1739
  function isFileName(input) {
1739
1740
  return !input.includes("/") && !input.includes("\\") && input.includes(".");
1740
1741
  }
1742
+ function isPathInsideWorkDir(filePath, workDir) {
1743
+ const resolvedFilePath = resolve(filePath);
1744
+ const resolvedWorkDir = resolve(workDir);
1745
+ const workDirWithSep = resolvedWorkDir.endsWith(sep) ? resolvedWorkDir : resolvedWorkDir + sep;
1746
+ return resolvedFilePath === resolvedWorkDir || resolvedFilePath.startsWith(workDirWithSep);
1747
+ }
1741
1748
 
1742
1749
  // src/commands/test.ts
1743
1750
  async function testCommand(fileOrId, options) {
1744
1751
  const { authorized } = await requireAuth();
1745
1752
  if (!authorized) return;
1746
1753
  let filePath = fileOrId;
1754
+ const workDir = config.getWorkDir();
1747
1755
  if (isProblemId(fileOrId)) {
1748
- const workDir = config.getWorkDir();
1749
1756
  const found = await findSolutionFile(workDir, fileOrId);
1750
1757
  if (!found) {
1751
1758
  console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
@@ -1756,7 +1763,6 @@ async function testCommand(fileOrId, options) {
1756
1763
  filePath = found;
1757
1764
  console.log(chalk9.gray(`Found: ${filePath}`));
1758
1765
  } else if (isFileName(fileOrId)) {
1759
- const workDir = config.getWorkDir();
1760
1766
  const found = await findFileByName(workDir, fileOrId);
1761
1767
  if (!found) {
1762
1768
  console.log(chalk9.red(`File not found: ${fileOrId}`));
@@ -1770,6 +1776,14 @@ async function testCommand(fileOrId, options) {
1770
1776
  console.log(chalk9.red(`File not found: ${filePath}`));
1771
1777
  return;
1772
1778
  }
1779
+ if (!isPathInsideWorkDir(filePath, workDir)) {
1780
+ console.log(chalk9.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
1781
+ console.log(chalk9.gray(`File: ${filePath}`));
1782
+ console.log(chalk9.gray(`Workspace: ${workDir}`));
1783
+ console.log(chalk9.yellow("\nFor security reasons, you can only test files from within your workspace."));
1784
+ console.log(chalk9.gray('Use "leetcode config workdir <path>" to change your workspace.'));
1785
+ return;
1786
+ }
1773
1787
  const spinner = ora5({ text: "Reading solution file...", spinner: "dots" }).start();
1774
1788
  try {
1775
1789
  const fileName = basename(filePath);
@@ -1902,8 +1916,8 @@ async function submitCommand(fileOrId) {
1902
1916
  const { authorized } = await requireAuth();
1903
1917
  if (!authorized) return;
1904
1918
  let filePath = fileOrId;
1919
+ const workDir = config.getWorkDir();
1905
1920
  if (isProblemId(fileOrId)) {
1906
- const workDir = config.getWorkDir();
1907
1921
  const found = await findSolutionFile(workDir, fileOrId);
1908
1922
  if (!found) {
1909
1923
  console.log(chalk10.red(`No solution file found for problem ${fileOrId}`));
@@ -1914,7 +1928,6 @@ async function submitCommand(fileOrId) {
1914
1928
  filePath = found;
1915
1929
  console.log(chalk10.gray(`Found: ${filePath}`));
1916
1930
  } else if (isFileName(fileOrId)) {
1917
- const workDir = config.getWorkDir();
1918
1931
  const found = await findFileByName(workDir, fileOrId);
1919
1932
  if (!found) {
1920
1933
  console.log(chalk10.red(`File not found: ${fileOrId}`));
@@ -1928,6 +1941,14 @@ async function submitCommand(fileOrId) {
1928
1941
  console.log(chalk10.red(`File not found: ${filePath}`));
1929
1942
  return;
1930
1943
  }
1944
+ if (!isPathInsideWorkDir(filePath, workDir)) {
1945
+ console.log(chalk10.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
1946
+ console.log(chalk10.gray(`File: ${filePath}`));
1947
+ console.log(chalk10.gray(`Workspace: ${workDir}`));
1948
+ console.log(chalk10.yellow("\nFor security reasons, you can only submit files from within your workspace."));
1949
+ console.log(chalk10.gray('Use "leetcode config workdir <path>" to change your workspace.'));
1950
+ return;
1951
+ }
1931
1952
  const spinner = ora6({ text: "Reading solution file...", spinner: "dots" }).start();
1932
1953
  try {
1933
1954
  const fileName = basename2(filePath);
@@ -2752,6 +2773,17 @@ import { existsSync as existsSync9 } from "fs";
2752
2773
  import chalk20 from "chalk";
2753
2774
  import inquirer3 from "inquirer";
2754
2775
  import ora13 from "ora";
2776
+ function sanitizeRepoName(name) {
2777
+ return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/--+/g, "-");
2778
+ }
2779
+ function isValidGitUrl(url) {
2780
+ const httpsPattern = /^https:\/\/[\w.-]+\/[\w./-]+$/;
2781
+ const sshPattern = /^git@[\w.-]+:[\w./-]+$/;
2782
+ return httpsPattern.test(url) || sshPattern.test(url);
2783
+ }
2784
+ function escapeShellArg(arg) {
2785
+ return `'${arg.replace(/'/g, "'\\''")}'`;
2786
+ }
2755
2787
  function isGitInstalled() {
2756
2788
  try {
2757
2789
  execSync("git --version", { stdio: "ignore" });
@@ -2823,7 +2855,8 @@ async function setupRemote(workDir) {
2823
2855
  if (createGh) {
2824
2856
  spinner.start("Creating GitHub repository...");
2825
2857
  try {
2826
- const repoName = workDir.split("/").pop() || "leetcode-solutions";
2858
+ const rawRepoName = workDir.split("/").pop() || "leetcode-solutions";
2859
+ const repoName = sanitizeRepoName(rawRepoName);
2827
2860
  execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
2828
2861
  spinner.succeed("Created and linked GitHub repository");
2829
2862
  repoUrl = getRemoteUrl(workDir) || "";
@@ -2850,6 +2883,11 @@ async function setupRemote(workDir) {
2850
2883
  repoUrl = url;
2851
2884
  }
2852
2885
  }
2886
+ if (repoUrl && !isValidGitUrl(repoUrl)) {
2887
+ console.log(chalk20.red("Invalid repository URL format."));
2888
+ console.log(chalk20.gray("Expected: https://github.com/user/repo or git@github.com:user/repo"));
2889
+ return "";
2890
+ }
2853
2891
  if (repoUrl) {
2854
2892
  config.setRepo(repoUrl);
2855
2893
  }
@@ -2891,7 +2929,7 @@ async function syncCommand() {
2891
2929
  const count = lines.length;
2892
2930
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
2893
2931
  const message = `Sync: ${count} solutions - ${timestamp}`;
2894
- execSync(`git commit -m "${message}"`, { cwd: workDir });
2932
+ execSync(`git commit -m ${escapeShellArg(message)}`, { cwd: workDir });
2895
2933
  try {
2896
2934
  execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
2897
2935
  } catch {
@@ -3811,6 +3849,13 @@ async function diffCommand(problemId, options) {
3811
3849
  console.log(chalk24.red(`File not found: ${options.file}`));
3812
3850
  return;
3813
3851
  }
3852
+ if (!isPathInsideWorkDir(options.file, workDir)) {
3853
+ console.log(chalk24.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
3854
+ console.log(chalk24.gray(`File: ${options.file}`));
3855
+ console.log(chalk24.gray(`Workspace: ${workDir}`));
3856
+ console.log(chalk24.yellow("\nFor security reasons, you can only diff files from within your workspace."));
3857
+ return;
3858
+ }
3814
3859
  const otherCode = await readFile6(options.file, "utf-8");
3815
3860
  showComparison(currentCode, otherCode, "Your Solution", options.file, options.unified ?? false);
3816
3861
  return;
@@ -3953,7 +3998,7 @@ program.configureHelp({
3953
3998
  optionTerm: (option) => chalk26.yellow(option.flags),
3954
3999
  optionDescription: (option) => chalk26.white(option.description)
3955
4000
  });
3956
- program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.0", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
4001
+ program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.1", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
3957
4002
  ${chalk26.yellow("Examples:")}
3958
4003
  ${chalk26.cyan("$ leetcode login")} Login to LeetCode
3959
4004
  ${chalk26.cyan("$ leetcode list -d easy")} List easy problems
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@night-slayer18/leetcode-cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A modern LeetCode CLI built with TypeScript",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",