gitarsenal-cli 1.2.3 โ†’ 1.2.5

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.3",
3
+ "version": "1.2.5",
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,12 +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)
23
- 3. Runs automatically after SSH container creation
24
- 4. Runs on service shutdown via signal handlers
25
-
26
- This prevents potential token leakage when containers are shared or when the service is stopped.
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
+ 3. Invalidates Modal sessions by removing session files and directories
24
+ 4. Sets invalid Modal tokens using the `modal token set` command with a dedicated profile
25
+ 5. Falls back to manually setting placeholder tokens if the command fails
26
+ 6. Runs automatically after SSH container creation
27
+ 7. Runs on service shutdown via signal handlers
28
+
29
+ This prevents potential token leakage when containers are shared or when the service is stopped, while still allowing users to run basic Modal CLI commands. Any operations requiring authentication will fail, effectively logging the user out from Modal.
27
30
 
28
31
  ## Usage
29
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")
@@ -250,6 +250,92 @@ def cleanup_modal_token():
250
250
  modalconfig_file.unlink()
251
251
  logger.info(f"โœ… Deleted .modalconfig file at {modalconfig_file}")
252
252
 
253
+ # Try to invalidate Modal sessions
254
+ try:
255
+ logger.info("๐Ÿ”‘ Invalidating Modal sessions...")
256
+
257
+ # Try to directly modify Modal's session files
258
+ try:
259
+ # Check for session files in .modal directory
260
+ session_dir = modal_dir / "sessions"
261
+ if session_dir.exists():
262
+ import shutil
263
+ shutil.rmtree(session_dir)
264
+ logger.info(f"โœ… Removed Modal sessions directory at {session_dir}")
265
+
266
+ # Also check for any other potential session files
267
+ for file in os.listdir(modal_dir) if modal_dir.exists() else []:
268
+ if "session" in file.lower() or "auth" in file.lower():
269
+ file_path = modal_dir / file
270
+ if file_path.is_file():
271
+ file_path.unlink()
272
+ logger.info(f"โœ… Removed Modal session file: {file_path}")
273
+ except Exception as e:
274
+ logger.warning(f"โš ๏ธ Error removing Modal sessions: {e}")
275
+
276
+ except Exception as e:
277
+ logger.warning(f"โš ๏ธ Error during Modal session invalidation: {e}")
278
+
279
+ # Set invalid Modal tokens using the Modal CLI
280
+ try:
281
+ logger.info("๐Ÿ”„ Setting invalid Modal tokens...")
282
+
283
+ # Use modal token set command with invalid credentials
284
+ invalid_token_id = "ak-invalid-token-not-valid"
285
+ invalid_token_secret = "as-invalid-secret-not-valid"
286
+ profile = "gitarsenal"
287
+
288
+ # Run the modal token set command
289
+ result = subprocess.run(
290
+ ["modal", "token", "set",
291
+ "--token-id", invalid_token_id,
292
+ "--token-secret", invalid_token_secret,
293
+ f"--profile={profile}"],
294
+ capture_output=True,
295
+ text=True
296
+ )
297
+
298
+ if result.returncode == 0:
299
+ logger.info(f"โœ… Successfully set invalid Modal tokens with profile '{profile}'")
300
+ else:
301
+ logger.warning(f"โš ๏ธ Error setting invalid Modal tokens: {result.stderr}")
302
+
303
+ # Fall back to manually setting placeholder tokens
304
+ logger.info("๐Ÿ”„ Falling back to manually setting placeholder tokens...")
305
+
306
+ # Create .modal directory if it doesn't exist
307
+ if not modal_dir.exists():
308
+ modal_dir.mkdir(parents=True)
309
+ logger.info(f"โœ… Created Modal directory at {modal_dir}")
310
+
311
+ # Set placeholder tokens in token.json
312
+ placeholder_token_id = "ak-placeholder-token-not-valid"
313
+ placeholder_token_secret = "as-placeholder-secret-not-valid"
314
+
315
+ # Create token.json with placeholder values
316
+ token_file = modal_dir / "token.json"
317
+ with open(token_file, 'w') as f:
318
+ import json
319
+ json.dump({
320
+ "token_id": placeholder_token_id,
321
+ "token_secret": placeholder_token_secret
322
+ }, f)
323
+ logger.info(f"โœ… Created placeholder token file at {token_file}")
324
+
325
+ # Create .modalconfig file with placeholder values
326
+ with open(modalconfig_file, 'w') as f:
327
+ f.write(f"token_id = {placeholder_token_id}\n")
328
+ f.write(f"token_secret = {placeholder_token_secret}\n")
329
+ logger.info(f"โœ… Created placeholder .modalconfig file at {modalconfig_file}")
330
+
331
+ # Set placeholder values in environment variables
332
+ os.environ["MODAL_TOKEN_ID"] = placeholder_token_id
333
+ os.environ["MODAL_TOKEN_SECRET"] = placeholder_token_secret
334
+ logger.info("โœ… Set placeholder tokens in environment variables")
335
+
336
+ except Exception as e:
337
+ logger.warning(f"โš ๏ธ Error setting invalid Modal tokens: {e}")
338
+
253
339
  logger.info("โœ… Modal token cleanup completed successfully")
254
340
  except Exception as e:
255
341
  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")
@@ -3333,6 +3333,94 @@ def cleanup_modal_token():
3333
3333
  os.remove(modalconfig_file)
3334
3334
  print(f"โœ… Deleted .modalconfig file at {modalconfig_file}")
3335
3335
 
3336
+ # Try to invalidate Modal sessions
3337
+ try:
3338
+ print("๐Ÿ”‘ Invalidating Modal sessions...")
3339
+
3340
+ # Try to directly modify Modal's session files
3341
+ try:
3342
+ # Check for session files in .modal directory
3343
+ session_dir = os.path.join(modal_dir, "sessions")
3344
+ if os.path.exists(session_dir):
3345
+ import shutil
3346
+ shutil.rmtree(session_dir)
3347
+ print(f"โœ… Removed Modal sessions directory at {session_dir}")
3348
+
3349
+ # Also check for any other potential session files
3350
+ for file in os.listdir(modal_dir) if os.path.exists(modal_dir) else []:
3351
+ if "session" in file.lower() or "auth" in file.lower():
3352
+ file_path = os.path.join(modal_dir, file)
3353
+ if os.path.isfile(file_path):
3354
+ os.remove(file_path)
3355
+ print(f"โœ… Removed Modal session file: {file_path}")
3356
+ except Exception as e:
3357
+ print(f"โš ๏ธ Error removing Modal sessions: {e}")
3358
+
3359
+ except Exception as e:
3360
+ print(f"โš ๏ธ Error during Modal session invalidation: {e}")
3361
+
3362
+ # Set invalid Modal tokens using the Modal CLI
3363
+ try:
3364
+ print("๐Ÿ”„ Setting invalid Modal tokens...")
3365
+ import subprocess
3366
+
3367
+ # Use modal token set command with invalid credentials
3368
+ invalid_token_id = "ak-invalid-token-not-valid"
3369
+ invalid_token_secret = "as-invalid-secret-not-valid"
3370
+ profile = "gitarsenal"
3371
+
3372
+ # Run the modal token set command
3373
+ result = subprocess.run(
3374
+ ["modal", "token", "set",
3375
+ "--token-id", invalid_token_id,
3376
+ "--token-secret", invalid_token_secret,
3377
+ f"--profile={profile}"],
3378
+ capture_output=True,
3379
+ text=True
3380
+ )
3381
+
3382
+ if result.returncode == 0:
3383
+ print(f"โœ… Successfully set invalid Modal tokens with profile '{profile}'")
3384
+ else:
3385
+ print(f"โš ๏ธ Error setting invalid Modal tokens: {result.stderr}")
3386
+
3387
+ # Fall back to manually setting placeholder tokens
3388
+ print("๐Ÿ”„ Falling back to manually setting placeholder tokens...")
3389
+
3390
+ # Create .modal directory if it doesn't exist
3391
+ if not os.path.exists(modal_dir):
3392
+ os.makedirs(modal_dir)
3393
+ print(f"โœ… Created Modal directory at {modal_dir}")
3394
+
3395
+ # Set placeholder tokens in token.json
3396
+ placeholder_token_id = "ak-placeholder-token-not-valid"
3397
+ placeholder_token_secret = "as-placeholder-secret-not-valid"
3398
+
3399
+ # Create token.json with placeholder values
3400
+ token_file = os.path.join(modal_dir, "token.json")
3401
+ with open(token_file, 'w') as f:
3402
+ import json
3403
+ json.dump({
3404
+ "token_id": placeholder_token_id,
3405
+ "token_secret": placeholder_token_secret
3406
+ }, f)
3407
+ print(f"โœ… Created placeholder token file at {token_file}")
3408
+
3409
+ # Create .modalconfig file with placeholder values
3410
+ modalconfig_file = os.path.join(home_dir, ".modalconfig")
3411
+ with open(modalconfig_file, 'w') as f:
3412
+ f.write(f"token_id = {placeholder_token_id}\n")
3413
+ f.write(f"token_secret = {placeholder_token_secret}\n")
3414
+ print(f"โœ… Created placeholder .modalconfig file at {modalconfig_file}")
3415
+
3416
+ # Set placeholder values in environment variables
3417
+ os.environ["MODAL_TOKEN_ID"] = placeholder_token_id
3418
+ os.environ["MODAL_TOKEN_SECRET"] = placeholder_token_secret
3419
+ print("โœ… Set placeholder tokens in environment variables")
3420
+
3421
+ except Exception as e:
3422
+ print(f"โš ๏ธ Error setting invalid Modal tokens: {e}")
3423
+
3336
3424
  print("โœ… Modal token cleanup completed successfully")
3337
3425
  except Exception as e:
3338
3426
  print(f"โŒ Error during Modal token cleanup: {e}")
@@ -108,40 +108,122 @@ 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
 
183
+ # Check if Modal sessions directory exists
184
+ session_dir = modal_dir / "sessions"
185
+ if session_dir.exists():
186
+ print(f"โŒ Modal sessions directory still exists at {session_dir}")
187
+ return False
188
+
189
+ # Check for any session or auth files
190
+ if modal_dir.exists():
191
+ for file in os.listdir(modal_dir):
192
+ if "session" in file.lower() or "auth" in file.lower():
193
+ print(f"โŒ Modal session/auth file still exists: {modal_dir / file}")
194
+ return False
195
+
196
+ # Check if Modal CLI works with invalid tokens
197
+ try:
198
+ import subprocess
199
+ result = subprocess.run(
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"],
215
+ capture_output=True,
216
+ text=True
217
+ )
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")
222
+ print(f"Output: {result.stdout}")
223
+ return False
224
+ except Exception as e:
225
+ print(f"โš ๏ธ Error checking Modal CLI status: {e}")
226
+
145
227
  print("โœ… Token has been cleaned up successfully")
146
228
  return True
147
229