Rithwik Ravi commited on
Commit
7d84930
·
1 Parent(s): de07414

Security Update

Browse files
Dockerfile CHANGED
@@ -1,8 +1,9 @@
1
  FROM python:3.10-slim
2
 
3
- # Creates a non-root user with an explicit UID required by Hugging Face Spaces
4
- RUN useradd -m -u 1000 user
5
- USER user
 
6
 
7
  # Set environment variables
8
  ENV HOME=/home/user \
 
1
  FROM python:3.10-slim
2
 
3
+ # Creates a non-root user with an explicit UID and GID required by Hugging Face Spaces
4
+ RUN groupadd -g 1000 user && \
5
+ useradd -m -u 1000 -g 1000 user
6
+ USER 1000:1000
7
 
8
  # Set environment variables
9
  ENV HOME=/home/user \
openenv.yaml CHANGED
@@ -18,3 +18,10 @@ port: 7860
18
  dockerfile: Dockerfile
19
  environment_vars:
20
  - HF_TOKEN
 
 
 
 
 
 
 
 
18
  dockerfile: Dockerfile
19
  environment_vars:
20
  - HF_TOKEN
21
+ - AGENT_API_KEY
22
+
23
+ isolation:
24
+ network: none
25
+ resource_limits:
26
+ cpu_cores: 1.0
27
+ memory_gb: 1.0
server/__pycache__/app.cpython-314.pyc CHANGED
Binary files a/server/__pycache__/app.cpython-314.pyc and b/server/__pycache__/app.cpython-314.pyc differ
 
server/app.py CHANGED
@@ -1,14 +1,81 @@
1
  import sqlite3
2
  import os
 
 
 
 
3
  from typing import Dict, Any, Optional
4
- from fastapi import FastAPI, HTTPException
 
 
5
  from pydantic import BaseModel
6
  import uvicorn
7
 
8
  from .models import Action, Observation, Reward, StepResult, ResetResult
9
  from .tasks import TASKS
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  app = FastAPI(title="OpenEnv SQL Data Engineer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  class SQLEnvironment:
14
  def __init__(self):
@@ -50,6 +117,24 @@ class SQLEnvironment:
50
  self.step_count = 0
51
  self.current_score = 0.0
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # Initialize standard SQLite settings
54
  self.conn.execute("PRAGMA foreign_keys = ON")
55
 
@@ -82,10 +167,17 @@ class SQLEnvironment:
82
  try:
83
  c = self.conn.cursor()
84
  query = action.action_str.strip()
85
- # Basic mitigation of forbidden queries just in case (though we're in mock)
 
 
 
 
 
 
86
  if query.upper().startswith("DROP TABLE sqlite_"):
87
  raise Exception("Cannot modify system tables.")
88
 
 
89
  c.execute(query)
90
 
91
  if query.upper().startswith("SELECT") or query.upper().startswith("PRAGMA"):
@@ -154,22 +246,33 @@ class ResetRequest(BaseModel):
154
  task_id: int = 1
155
 
156
  @app.post("/reset", response_model=ResetResult)
157
- def reset(req: ResetRequest):
158
- try:
159
- return env_instance.reset(task_id=req.task_id)
160
- except Exception as e:
161
- raise HTTPException(status_code=400, detail=str(e))
 
 
 
162
 
163
  @app.post("/step", response_model=StepResult)
164
- def step(action: Action):
165
- try:
166
- return env_instance.step(action)
167
- except Exception as e:
168
- raise HTTPException(status_code=400, detail=str(e))
 
 
 
 
 
 
 
169
 
170
  @app.get("/state")
171
- def state():
172
- return env_instance.state()
 
173
 
174
  def main():
175
  uvicorn.run("server.app:app", host="0.0.0.0", port=7860, reload=False)
 
1
  import sqlite3
2
  import os
3
+ import logging
4
+ import re
5
+ import time
6
+ import asyncio
7
  from typing import Dict, Any, Optional
8
+ from fastapi import FastAPI, HTTPException, Request, Depends
9
+ from fastapi.responses import JSONResponse
10
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
11
  from pydantic import BaseModel
12
  import uvicorn
13
 
14
  from .models import Action, Observation, Reward, StepResult, ResetResult
15
  from .tasks import TASKS
16
 
17
+ # -- Security & Telemetry Setup --
18
+ class SecretMaskingFormatter(logging.Formatter):
19
+ def format(self, record):
20
+ msg = super().format(record)
21
+ msg = re.sub(r'Bearer\s+[A-Za-z0-9_\-]+', 'Bearer ***', msg)
22
+ hf_token = os.environ.get("HF_TOKEN")
23
+ if hf_token and hf_token in msg:
24
+ msg = msg.replace(hf_token, "***")
25
+ return msg
26
+
27
+ logger = logging.getLogger("agent_audit")
28
+ logger.setLevel(logging.INFO)
29
+ ch = logging.StreamHandler()
30
+ ch.setFormatter(SecretMaskingFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
31
+ logger.addHandler(ch)
32
+
33
+ security = HTTPBearer()
34
+ AGENT_API_KEY = os.environ.get("AGENT_API_KEY", "test-agent-key")
35
+
36
+ class TokenBucket:
37
+ def __init__(self, capacity: int, fill_rate: float):
38
+ self.capacity = capacity
39
+ self.fill_rate = fill_rate
40
+ self.tokens = capacity
41
+ self.last_fill = time.time()
42
+
43
+ def consume(self, tokens: int = 1) -> bool:
44
+ now = time.time()
45
+ self.tokens = min(self.capacity, self.tokens + (now - self.last_fill) * self.fill_rate)
46
+ self.last_fill = now
47
+ if self.tokens >= tokens:
48
+ self.tokens -= tokens
49
+ return True
50
+ return False
51
+
52
+ rate_limiter = TokenBucket(capacity=50, fill_rate=50.0/60.0)
53
+
54
+ async def verify_auth_and_rate_limit(credentials: HTTPAuthorizationCredentials = Depends(security)):
55
+ if credentials.credentials != AGENT_API_KEY:
56
+ raise HTTPException(status_code=401, detail="Invalid API Key")
57
+ if not rate_limiter.consume(1):
58
+ raise HTTPException(status_code=429, detail="Rate limit exceeded")
59
+ return credentials.credentials
60
+
61
  app = FastAPI(title="OpenEnv SQL Data Engineer")
62
+ db_semaphore = asyncio.Semaphore(5)
63
+
64
+ @app.middleware("http")
65
+ async def security_middleware(request: Request, call_next):
66
+ # Max payload limit ~ 1MB
67
+ content_length = request.headers.get("content-length")
68
+ if content_length and int(content_length) > 1048576:
69
+ return JSONResponse(status_code=413, content={"error": "Payload Too Large"})
70
+
71
+ try:
72
+ response = await asyncio.wait_for(call_next(request), timeout=10.0)
73
+ return response
74
+ except asyncio.TimeoutError:
75
+ return JSONResponse(status_code=504, content={"error": "Gateway Timeout"})
76
+ except Exception as e:
77
+ logger.error(f"Unhandled exception: {str(e)}")
78
+ return JSONResponse(status_code=500, content={"error": "Internal Server Error"})
79
 
80
  class SQLEnvironment:
81
  def __init__(self):
 
117
  self.step_count = 0
118
  self.current_score = 0.0
119
 
120
+ # Security: 5 second timeout on queries
121
+ self.query_start_time = 0
122
+ def progress_handler():
123
+ if time.time() - self.query_start_time > 5.0:
124
+ return 1 # Abort query
125
+ return 0
126
+ self.conn.set_progress_handler(progress_handler, 1000)
127
+
128
+ # Security: Simple Authorizer to block DROP TABLE and explicit destructive system mods
129
+ # We allow standard DDL since the agent gets asked to CREATE VIEW, but restrict DROP
130
+ def authorizer(action_code, arg1, arg2, dbname, source):
131
+ # 11 = SQLITE_DROP_TABLE, 16 = SQLITE_DROP_VIEW
132
+ # 17 = SQLITE_ATTACH (blocks ATTACH DATABASE)
133
+ if action_code in (11, 16, 17):
134
+ return sqlite3.SQLITE_DENY
135
+ return sqlite3.SQLITE_OK
136
+ self.conn.set_authorizer(authorizer)
137
+
138
  # Initialize standard SQLite settings
139
  self.conn.execute("PRAGMA foreign_keys = ON")
140
 
 
167
  try:
168
  c = self.conn.cursor()
169
  query = action.action_str.strip()
170
+
171
+ # Injection Mitigations at parser level
172
+ blocked_patterns = [r"(?i)DROP\s+DATABASE", r"(?i)pg_sleep", r"(?i)randomblob", r"(?i)ATTACH\s+DATABASE"]
173
+ for p in blocked_patterns:
174
+ if re.search(p, query):
175
+ raise Exception(f"Blocked destructive command pattern detected: {p}")
176
+
177
  if query.upper().startswith("DROP TABLE sqlite_"):
178
  raise Exception("Cannot modify system tables.")
179
 
180
+ self.query_start_time = time.time()
181
  c.execute(query)
182
 
183
  if query.upper().startswith("SELECT") or query.upper().startswith("PRAGMA"):
 
246
  task_id: int = 1
247
 
248
  @app.post("/reset", response_model=ResetResult)
249
+ async def reset(req: ResetRequest, token: str = Depends(verify_auth_and_rate_limit)):
250
+ async with db_semaphore:
251
+ try:
252
+ logger.info(f"Resetting environment for task_id: {req.task_id}")
253
+ return env_instance.reset(task_id=req.task_id)
254
+ except Exception as e:
255
+ logger.error(f"Reset Error: {str(e)}")
256
+ raise HTTPException(status_code=400, detail="Error during reset")
257
 
258
  @app.post("/step", response_model=StepResult)
259
+ async def step(action: Action, token: str = Depends(verify_auth_and_rate_limit)):
260
+ async with db_semaphore:
261
+ try:
262
+ start_t = time.time()
263
+ logger.info(f"Attempting query: {action.action_str}")
264
+ res = env_instance.step(action)
265
+ duration = time.time() - start_t
266
+ logger.info(f"Query completed in {duration:.3f}s. Error state: {res.observation.last_action_error}")
267
+ return res
268
+ except Exception as e:
269
+ logger.error(f"Step Error: {str(e)}")
270
+ raise HTTPException(status_code=400, detail="Error executing SQL step")
271
 
272
  @app.get("/state")
273
+ async def state(token: str = Depends(verify_auth_and_rate_limit)):
274
+ async with db_semaphore:
275
+ return env_instance.state()
276
 
277
  def main():
278
  uvicorn.run("server.app:app", host="0.0.0.0", port=7860, reload=False)