codegpt-ai 1.1.0 → 1.2.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.
package/ai_cli/updater.py CHANGED
@@ -148,18 +148,64 @@ def force_update():
148
148
  return
149
149
 
150
150
  asset = exe_assets[0]
151
+
152
+ # Find checksum file in release assets
153
+ sha_assets = [a for a in release.get("assets", []) if a["name"].endswith(".sha256")]
154
+ expected_hash = None
155
+ if sha_assets:
156
+ try:
157
+ sha_resp = requests.get(sha_assets[0]["browser_download_url"], timeout=10)
158
+ # Parse certutil output: second line is the hash
159
+ lines = sha_resp.text.strip().splitlines()
160
+ for line in lines:
161
+ line = line.strip().replace(" ", "")
162
+ if len(line) == 64 and all(c in "0123456789abcdef" for c in line.lower()):
163
+ expected_hash = line.lower()
164
+ break
165
+ except Exception:
166
+ pass
167
+
151
168
  console.print(f" Downloading {asset['name']} ({latest_tag})...")
169
+ if expected_hash:
170
+ console.print(f" Expected SHA256: {expected_hash[:16]}...")
171
+ else:
172
+ console.print("[yellow] WARNING: No checksum file found. Cannot verify integrity.[/]")
152
173
 
153
174
  try:
154
175
  resp = requests.get(asset["browser_download_url"], stream=True, timeout=60)
155
176
  resp.raise_for_status()
156
177
 
157
- # Download to temp file
178
+ # Download to temp file and compute hash
179
+ import hashlib as _hashlib
180
+ sha256 = _hashlib.sha256()
158
181
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".exe")
159
182
  for chunk in resp.iter_content(chunk_size=8192):
160
183
  tmp.write(chunk)
184
+ sha256.update(chunk)
161
185
  tmp.close()
162
186
 
187
+ actual_hash = sha256.hexdigest().lower()
188
+ console.print(f" Actual SHA256: {actual_hash[:16]}...")
189
+
190
+ # Verify checksum if available
191
+ if expected_hash and actual_hash != expected_hash:
192
+ console.print(Panel(
193
+ Text(
194
+ "CHECKSUM MISMATCH — download may be tampered with.\n"
195
+ f"Expected: {expected_hash}\n"
196
+ f"Got: {actual_hash}\n\n"
197
+ "Update aborted for your safety.",
198
+ style="bold red"
199
+ ),
200
+ title="[bold red]SECURITY ALERT[/]",
201
+ border_style="red",
202
+ ))
203
+ os.unlink(tmp.name)
204
+ return
205
+
206
+ if expected_hash:
207
+ console.print("[green] Checksum verified.[/]")
208
+
163
209
  if _is_frozen():
164
210
  # Replace the running exe
165
211
  current_exe = sys.executable
@@ -172,7 +218,7 @@ def force_update():
172
218
  shutil.move(tmp.name, current_exe)
173
219
 
174
220
  console.print(Panel(
175
- Text(f"Updated: v{current} -> {latest_tag}\nRestart to use the new version.", style="green"),
221
+ Text(f"Updated: v{current} -> {latest_tag}\nChecksum: {actual_hash[:16]}...\nRestart to use the new version.", style="green"),
176
222
  border_style="green",
177
223
  ))
178
224
  else:
package/chat.py CHANGED
@@ -589,9 +589,29 @@ def audit_log(action, detail=""):
589
589
  def is_shell_safe(cmd_text):
590
590
  """Check if a shell command is safe to run."""
591
591
  cmd_lower = cmd_text.lower().strip()
592
+
593
+ # Blocklist check
592
594
  for blocked in SHELL_BLOCKLIST:
593
595
  if blocked in cmd_lower:
594
596
  return False, blocked
597
+
598
+ # Block shell injection patterns
599
+ injection_patterns = [
600
+ r'[;&|`]', # Command chaining/injection
601
+ r'\$\(', # Command substitution
602
+ r'>\s*/dev/', # Device writes
603
+ r'\\x[0-9a-f]', # Hex escapes
604
+ r'\\u[0-9a-f]', # Unicode escapes
605
+ r'\brm\b.*-[rR]', # rm with recursive flag (any form)
606
+ ]
607
+ for pattern in injection_patterns:
608
+ if re.search(pattern, cmd_text):
609
+ return False, f"blocked pattern: {pattern}"
610
+
611
+ # Max command length
612
+ if len(cmd_text) > 500:
613
+ return False, "command too long (500 char limit)"
614
+
595
615
  return True, ""
596
616
 
597
617
 
@@ -1978,10 +1998,11 @@ def run_shell(cmd_text):
1978
1998
  print_sys("Usage: /shell <command>\nExample: /shell dir")
1979
1999
  return
1980
2000
 
1981
- # Safety check — use the global blocklist
2001
+ # Safety check
1982
2002
  safe, blocked = is_shell_safe(cmd_text)
1983
2003
  if not safe:
1984
- print_err(f"Blocked: dangerous command detected ({blocked})")
2004
+ print_err(f"Blocked: {blocked}")
2005
+ audit_log("SHELL_BLOCKED", f"{blocked}: {cmd_text[:80]}")
1985
2006
  return
1986
2007
 
1987
2008
  console.print(Panel(
@@ -1991,10 +2012,19 @@ def run_shell(cmd_text):
1991
2012
  ))
1992
2013
 
1993
2014
  try:
1994
- result = subprocess.run(
1995
- cmd_text, shell=True, capture_output=True, text=True, timeout=30,
1996
- cwd=str(Path.home()),
1997
- )
2015
+ # Use shlex.split for safer argument parsing on non-Windows
2016
+ if os.name != "nt":
2017
+ import shlex
2018
+ args = shlex.split(cmd_text)
2019
+ result = subprocess.run(
2020
+ args, capture_output=True, text=True, timeout=30,
2021
+ cwd=str(Path.home()),
2022
+ )
2023
+ else:
2024
+ result = subprocess.run(
2025
+ cmd_text, shell=True, capture_output=True, text=True, timeout=30,
2026
+ cwd=str(Path.home()),
2027
+ )
1998
2028
  output = ""
1999
2029
  if result.stdout:
2000
2030
  output += result.stdout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codegpt-ai",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Local AI Assistant Hub — 80+ commands, 29 tools, 8 agents, training, security",
5
5
  "author": "ArukuX",
6
6
  "license": "MIT",