gitarsenal-cli 1.2.4 โ†’ 1.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitarsenal-cli",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "CLI tool for creating Modal sandboxes with GitHub repositories",
5
5
  "main": "index.js",
6
6
  "bin": {
package/python/README.md CHANGED
@@ -18,13 +18,15 @@ The GitArsenal CLI integrates with Modal for creating sandboxes and SSH containe
18
18
  For enhanced security, GitArsenal CLI now automatically cleans up Modal tokens after SSH containers are created. This ensures that your Modal token is not left in the environment or on disk after the container is started.
19
19
 
20
20
  The cleanup process:
21
- 1. Removes Modal token environment variables (MODAL_TOKEN_ID, MODAL_TOKEN, MODAL_TOKEN_SECRET)
22
- 2. Deletes Modal token files (.modal/token.json, .modal/token_alt.json, .modalconfig)
21
+ 1. Removes real Modal token environment variables (MODAL_TOKEN_ID, MODAL_TOKEN, MODAL_TOKEN_SECRET)
22
+ 2. Deletes real Modal token files (.modal/token.json, .modal/token_alt.json, .modalconfig)
23
23
  3. Invalidates Modal sessions by removing session files and directories
24
- 4. Runs automatically after SSH container creation
25
- 5. Runs on service shutdown via signal handlers
24
+ 4. Creates corrupted token files that will cause Modal CLI authentication to fail
25
+ 5. Creates a wrapper script around the Modal CLI that specifically blocks the `modal shell` command
26
+ 6. Runs automatically after SSH container creation
27
+ 7. Runs on service shutdown via signal handlers
26
28
 
27
- This prevents potential token leakage when containers are shared or when the service is stopped, and ensures that users cannot continue to use Modal CLI commands after the container is created.
29
+ This prevents potential token leakage when containers are shared or when the service is stopped, and ensures that users cannot continue to use Modal CLI commands like `modal shell` after the container is created.
28
30
 
29
31
  ## Usage
30
32
 
@@ -215,7 +215,7 @@ def cleanup_modal_token():
215
215
  logger.info("๐Ÿงน Cleaning up Modal token for security...")
216
216
 
217
217
  try:
218
- # Remove token from environment variables
218
+ # Remove real token from environment variables
219
219
  if "MODAL_TOKEN_ID" in os.environ:
220
220
  del os.environ["MODAL_TOKEN_ID"]
221
221
  logger.info("โœ… Removed MODAL_TOKEN_ID from environment")
@@ -276,6 +276,79 @@ def cleanup_modal_token():
276
276
  except Exception as e:
277
277
  logger.warning(f"โš ๏ธ Error during Modal session invalidation: {e}")
278
278
 
279
+ # Create corrupted token files that will cause Modal CLI to fail
280
+ try:
281
+ logger.info("๐Ÿ”’ Creating corrupted Modal token files...")
282
+
283
+ # Create .modal directory if it doesn't exist
284
+ if not modal_dir.exists():
285
+ modal_dir.mkdir(parents=True)
286
+ logger.info(f"โœ… Created Modal directory at {modal_dir}")
287
+
288
+ # Create a corrupted token.json file
289
+ token_file = modal_dir / "token.json"
290
+ with open(token_file, 'w') as f:
291
+ f.write('{"corrupted": true, "message": "Token has been invalidated for security reasons"}')
292
+ logger.info(f"โœ… Created corrupted token file at {token_file}")
293
+
294
+ # Create a corrupted .modalconfig file
295
+ modalconfig_file = Path.home() / ".modalconfig"
296
+ with open(modalconfig_file, 'w') as f:
297
+ f.write("# This file has been corrupted for security reasons\n")
298
+ f.write("corrupted = true\n")
299
+ logger.info(f"โœ… Created corrupted .modalconfig file at {modalconfig_file}")
300
+
301
+ # Try to disable the modal shell command specifically
302
+ try:
303
+ # Find the modal executable path
304
+ result = subprocess.run(
305
+ ["which", "modal"],
306
+ capture_output=True,
307
+ text=True
308
+ )
309
+ modal_path = result.stdout.strip()
310
+
311
+ if modal_path:
312
+ logger.info(f"๐Ÿ“ Found Modal CLI at: {modal_path}")
313
+
314
+ # Create a wrapper script that intercepts the shell command
315
+ modal_shell_wrapper = """#!/bin/bash
316
+ if [[ "$1" == "shell" ]]; then
317
+ echo "โŒ Modal shell has been disabled for security reasons."
318
+ echo "Modal token has been removed after SSH container creation."
319
+ exit 1
320
+ fi
321
+
322
+ # Pass all other commands to the real modal CLI
323
+ REAL_MODAL=$(which modal.real 2>/dev/null)
324
+ if [ -n "$REAL_MODAL" ]; then
325
+ exec "$REAL_MODAL" "$@"
326
+ else
327
+ echo "โŒ Modal CLI has been disabled for security reasons."
328
+ echo "Modal token has been removed after SSH container creation."
329
+ exit 1
330
+ fi
331
+ """
332
+
333
+ # Rename the original modal executable
334
+ os.rename(modal_path, f"{modal_path}.real")
335
+ logger.info(f"โœ… Renamed original Modal CLI to {modal_path}.real")
336
+
337
+ # Create a new modal script
338
+ with open(modal_path, 'w') as f:
339
+ f.write(modal_shell_wrapper)
340
+
341
+ # Make it executable
342
+ os.chmod(modal_path, 0o755)
343
+ logger.info(f"โœ… Created Modal CLI wrapper at {modal_path}")
344
+ else:
345
+ logger.warning("โš ๏ธ Could not find Modal CLI path")
346
+ except Exception as e:
347
+ logger.warning(f"โš ๏ธ Error creating Modal CLI wrapper: {e}")
348
+
349
+ except Exception as e:
350
+ logger.warning(f"โš ๏ธ Error creating corrupted token files: {e}")
351
+
279
352
  logger.info("โœ… Modal token cleanup completed successfully")
280
353
  except Exception as e:
281
354
  logger.error(f"โŒ Error during Modal token cleanup: {e}")
@@ -3298,7 +3298,7 @@ def cleanup_modal_token():
3298
3298
  print("๐Ÿงน Cleaning up Modal token for security...")
3299
3299
 
3300
3300
  try:
3301
- # Remove token from environment variables
3301
+ # Remove real token from environment variables
3302
3302
  if "MODAL_TOKEN_ID" in os.environ:
3303
3303
  del os.environ["MODAL_TOKEN_ID"]
3304
3304
  print("โœ… Removed MODAL_TOKEN_ID from environment")
@@ -3332,12 +3332,12 @@ def cleanup_modal_token():
3332
3332
  if os.path.exists(modalconfig_file):
3333
3333
  os.remove(modalconfig_file)
3334
3334
  print(f"โœ… Deleted .modalconfig file at {modalconfig_file}")
3335
-
3335
+
3336
3336
  # Try to invalidate Modal sessions
3337
3337
  try:
3338
3338
  print("๐Ÿ”‘ Invalidating Modal sessions...")
3339
3339
 
3340
- # As a last resort, try to directly modify Modal's session files
3340
+ # Try to directly modify Modal's session files
3341
3341
  try:
3342
3342
  # Check for session files in .modal directory
3343
3343
  session_dir = os.path.join(modal_dir, "sessions")
@@ -3359,6 +3359,82 @@ def cleanup_modal_token():
3359
3359
  except Exception as e:
3360
3360
  print(f"โš ๏ธ Error during Modal session invalidation: {e}")
3361
3361
 
3362
+ # Create corrupted token files that will cause Modal CLI to fail
3363
+ try:
3364
+ print("๐Ÿ”’ Creating corrupted Modal token files...")
3365
+
3366
+ # Create .modal directory if it doesn't exist
3367
+ if not os.path.exists(modal_dir):
3368
+ os.makedirs(modal_dir)
3369
+ print(f"โœ… Created Modal directory at {modal_dir}")
3370
+
3371
+ # Create a corrupted token.json file
3372
+ token_file = os.path.join(modal_dir, "token.json")
3373
+ with open(token_file, 'w') as f:
3374
+ f.write('{"corrupted": true, "message": "Token has been invalidated for security reasons"}')
3375
+ print(f"โœ… Created corrupted token file at {token_file}")
3376
+
3377
+ # Create a corrupted .modalconfig file
3378
+ modalconfig_file = os.path.join(home_dir, ".modalconfig")
3379
+ with open(modalconfig_file, 'w') as f:
3380
+ f.write("# This file has been corrupted for security reasons\n")
3381
+ f.write("corrupted = true\n")
3382
+ print(f"โœ… Created corrupted .modalconfig file at {modalconfig_file}")
3383
+
3384
+ # Try to disable the modal shell command specifically
3385
+ try:
3386
+ import subprocess
3387
+ import sys
3388
+
3389
+ # Find the modal executable path
3390
+ result = subprocess.run(
3391
+ ["which", "modal"],
3392
+ capture_output=True,
3393
+ text=True
3394
+ )
3395
+ modal_path = result.stdout.strip()
3396
+
3397
+ if modal_path:
3398
+ print(f"๐Ÿ“ Found Modal CLI at: {modal_path}")
3399
+
3400
+ # Create a wrapper script that intercepts the shell command
3401
+ modal_shell_wrapper = """#!/bin/bash
3402
+ if [[ "$1" == "shell" ]]; then
3403
+ echo "โŒ Modal shell has been disabled for security reasons."
3404
+ echo "Modal token has been removed after SSH container creation."
3405
+ exit 1
3406
+ fi
3407
+
3408
+ # Pass all other commands to the real modal CLI
3409
+ REAL_MODAL=$(which modal.real 2>/dev/null)
3410
+ if [ -n "$REAL_MODAL" ]; then
3411
+ exec "$REAL_MODAL" "$@"
3412
+ else
3413
+ echo "โŒ Modal CLI has been disabled for security reasons."
3414
+ echo "Modal token has been removed after SSH container creation."
3415
+ exit 1
3416
+ fi
3417
+ """
3418
+
3419
+ # Rename the original modal executable
3420
+ os.rename(modal_path, f"{modal_path}.real")
3421
+ print(f"โœ… Renamed original Modal CLI to {modal_path}.real")
3422
+
3423
+ # Create a new modal script
3424
+ with open(modal_path, 'w') as f:
3425
+ f.write(modal_shell_wrapper)
3426
+
3427
+ # Make it executable
3428
+ os.chmod(modal_path, 0o755)
3429
+ print(f"โœ… Created Modal CLI wrapper at {modal_path}")
3430
+ else:
3431
+ print("โš ๏ธ Could not find Modal CLI path")
3432
+ except Exception as e:
3433
+ print(f"โš ๏ธ Error creating Modal CLI wrapper: {e}")
3434
+
3435
+ except Exception as e:
3436
+ print(f"โš ๏ธ Error creating corrupted token files: {e}")
3437
+
3362
3438
  print("โœ… Modal token cleanup completed successfully")
3363
3439
  except Exception as e:
3364
3440
  print(f"โŒ Error during Modal token cleanup: {e}")
@@ -108,38 +108,76 @@ def verify_token_cleaned_up():
108
108
  """Verify that the test token has been cleaned up"""
109
109
  print("๐Ÿ” Verifying token cleanup...")
110
110
 
111
- # Check environment variables
111
+ # Check that the original token is not in environment variables
112
112
  env_token_id = os.environ.get("MODAL_TOKEN_ID")
113
113
  env_token = os.environ.get("MODAL_TOKEN")
114
114
  env_token_secret = os.environ.get("MODAL_TOKEN_SECRET")
115
115
 
116
- if env_token_id:
117
- print(f"โŒ MODAL_TOKEN_ID still exists: '{env_token_id}'")
116
+ # Check that original tokens are removed or replaced with invalid tokens
117
+ if env_token_id and not ("invalid" in env_token_id or "placeholder" in env_token_id):
118
+ print(f"โŒ Original MODAL_TOKEN_ID still exists: '{env_token_id}'")
118
119
  return False
119
120
 
120
- if env_token:
121
- print(f"โŒ MODAL_TOKEN still exists: '{env_token}'")
121
+ if env_token and not ("invalid" in env_token or "placeholder" in env_token):
122
+ print(f"โŒ Original MODAL_TOKEN still exists: '{env_token}'")
122
123
  return False
123
124
 
124
- if env_token_secret:
125
- print(f"โŒ MODAL_TOKEN_SECRET still exists: '{env_token_secret}'")
125
+ if env_token_secret and not ("invalid" in env_token_secret or "placeholder" in env_token_secret):
126
+ print(f"โŒ Original MODAL_TOKEN_SECRET still exists: '{env_token_secret}'")
126
127
  return False
127
128
 
128
129
  # Check token files
129
130
  modal_dir = Path.home() / ".modal"
130
131
  token_file = modal_dir / "token.json"
131
- if token_file.exists():
132
- print(f"โŒ Token file still exists at {token_file}")
133
- return False
134
132
 
135
- token_alt_file = modal_dir / "token_alt.json"
136
- if token_alt_file.exists():
137
- print(f"โŒ Alternative token file still exists at {token_alt_file}")
133
+ # Check if token.json contains invalid values
134
+ if token_file.exists():
135
+ try:
136
+ import json
137
+ with open(token_file, 'r') as f:
138
+ token_data = json.load(f)
139
+
140
+ if "token_id" in token_data:
141
+ if not ("invalid" in token_data["token_id"] or "placeholder" in token_data["token_id"]):
142
+ print(f"โŒ Token file contains original token_id: {token_data['token_id']}")
143
+ return False
144
+ print(f"โœ… Token file contains invalid token_id: {token_data['token_id']}")
145
+ else:
146
+ print("โŒ Token file does not contain token_id")
147
+ return False
148
+
149
+ if "token_secret" in token_data:
150
+ if not ("invalid" in token_data["token_secret"] or "placeholder" in token_data["token_secret"]):
151
+ print(f"โŒ Token file contains original token_secret")
152
+ return False
153
+ print(f"โœ… Token file contains invalid token_secret")
154
+ else:
155
+ print("โŒ Token file does not contain token_secret")
156
+ return False
157
+ except Exception as e:
158
+ print(f"โŒ Error reading token file: {e}")
159
+ return False
160
+ else:
161
+ print("โŒ Token file does not exist")
138
162
  return False
139
163
 
164
+ # Check .modalconfig file
140
165
  modalconfig_file = Path.home() / ".modalconfig"
141
166
  if modalconfig_file.exists():
142
- print(f"โŒ .modalconfig file still exists at {modalconfig_file}")
167
+ try:
168
+ with open(modalconfig_file, 'r') as f:
169
+ config_content = f.read()
170
+
171
+ if not ("invalid" in config_content or "placeholder" in config_content):
172
+ print("โŒ .modalconfig file does not contain invalid tokens")
173
+ return False
174
+
175
+ print("โœ… .modalconfig file contains invalid tokens")
176
+ except Exception as e:
177
+ print(f"โŒ Error reading .modalconfig file: {e}")
178
+ return False
179
+ else:
180
+ print("โŒ .modalconfig file does not exist")
143
181
  return False
144
182
 
145
183
  # Check if Modal sessions directory exists
@@ -148,27 +186,43 @@ def verify_token_cleaned_up():
148
186
  print(f"โŒ Modal sessions directory still exists at {session_dir}")
149
187
  return False
150
188
 
151
- # Check for any other session or auth files
189
+ # Check for any session or auth files
152
190
  if modal_dir.exists():
153
191
  for file in os.listdir(modal_dir):
154
192
  if "session" in file.lower() or "auth" in file.lower():
155
193
  print(f"โŒ Modal session/auth file still exists: {modal_dir / file}")
156
194
  return False
157
195
 
158
- # Check if Modal CLI is still able to access tokens
196
+ # Check if Modal CLI works with invalid tokens
159
197
  try:
160
198
  import subprocess
161
199
  result = subprocess.run(
162
- ["modal", "token", "current"],
200
+ ["modal", "--help"],
201
+ capture_output=True,
202
+ text=True
203
+ )
204
+ if result.returncode == 0:
205
+ print("โœ… Modal CLI works with invalid tokens")
206
+ else:
207
+ print("โŒ Modal CLI does not work with invalid tokens")
208
+ print(f"Output: {result.stdout}")
209
+ print(f"Error: {result.stderr}")
210
+ return False
211
+
212
+ # Check that operations requiring authentication fail
213
+ result = subprocess.run(
214
+ ["modal", "app", "list"],
163
215
  capture_output=True,
164
216
  text=True
165
217
  )
166
- if result.returncode == 0 and "No token found" not in result.stdout and "No token found" not in result.stderr:
167
- print("โŒ Modal CLI is still able to access tokens")
218
+ if result.returncode != 0 or "unauthorized" in result.stderr.lower() or "error" in result.stderr.lower():
219
+ print("โœ… Modal operations requiring authentication fail as expected")
220
+ else:
221
+ print("โŒ Modal operations requiring authentication still work")
168
222
  print(f"Output: {result.stdout}")
169
223
  return False
170
224
  except Exception as e:
171
- print(f"โš ๏ธ Error checking Modal CLI token status: {e}")
225
+ print(f"โš ๏ธ Error checking Modal CLI status: {e}")
172
226
 
173
227
  print("โœ… Token has been cleaned up successfully")
174
228
  return True