Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions AI_ASSISTANT_MODULE_GUIDE.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,73 @@ Q: "wt.loss ~ age + sex + ph.ecog 선형모델에서 VIF로 다중공선성 확
## 보안 고려사항

### 코드 실행 보안
- **샌드박스 실행**: 제한된 환경에서 코드 실행
- **파일 접근 불가**: 시스템의 파일을 읽거나 쓸 수 없음
- **네트워크 접근 불가**: 외부 API 호출 불가 (AI 제공자 API 제외)

#### 환경 인식 실행 (개발 vs 프로덕션)

AI Assistant 모듈은 보안과 사용성의 균형을 위해 **환경 인식 코드 실행**을 구현합니다:

**개발 모드** (기본값):
- 표준 `eval()` 함수로 코드 실행
- 디버깅과 개발이 용이
- 모든 콘솔 출력 표시
- 로컬, 신뢰할 수 있는 환경에 적합

**프로덕션 모드**:
- `RAppArmor::eval.secure()`로 샌드박스 실행 (Linux 전용)
- 향상된 보안 및 리소스 제한:
- 1GB RAM 제한
- 1MB 파일 크기 제한
- 10초 타임아웃
- 새 프로세스 생성 금지
- 시스템 명령 실행 방지
- 공개 배포 시 필수

**환경 감지**:
모듈은 다음을 통해 프로덕션 환경을 자동 감지합니다:
1. `DEPLOYMENT_ENV` 환경 변수 (`production` 또는 `development`)
2. shinyapps.io 배포 감지
3. RStudio Connect 감지
4. `.production` 마커 파일

**배포 모드 설정**:

로컬 개발용 (기본값):
```r
# 별도 설정 불필요 - 기본값이 개발 모드
# 또는 .Renviron에 명시적으로 설정:
DEPLOYMENT_ENV=development
```

프로덕션 배포용:
```r
# .Renviron 파일에 추가:
DEPLOYMENT_ENV=production
```

또는 마커 파일 생성:
```bash
# 앱 디렉토리에서
touch .production
```

**Linux 서버 설정** (RAppArmor용):
```bash
# AppArmor 설치
sudo apt-get install apparmor apparmor-utils libapparmor-dev

# R 패키지 설치
R -e "install.packages('RAppArmor')"
```

**플랫폼 지원**:
- ✅ **Linux**: 완전한 RAppArmor 샌드박싱 사용 가능
- ⚠️ **macOS/Windows**: 프로덕션 모드에서 경고와 함께 표준 eval로 폴백
- 권장사항: 최대 보안을 위해 Linux 서버에 배포

#### 기본 보안 기능
- **패키지 화이트리스트**: 승인된 패키지만 허용
- **실행 전 검토**: 실행 전 코드 편집 가능
- **오류 처리**: 시스템 정보 없는 안전한 오류 메시지

### API 키 보안

Expand Down
69 changes: 66 additions & 3 deletions AI_ASSISTANT_MODULE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,73 @@ Q: "Check VIF for multicollinearity in the linear model with wt.loss ~ age + sex
## Security Considerations

### Code Execution Security
- **Sandboxed Execution**: Code runs in restricted environment
- **No File Access**: Cannot read/write files on system
- **No Network Access**: Cannot make external API calls (except AI provider APIs)

#### Environment-Aware Execution (Development vs Production)

The AI Assistant module implements **environment-aware code execution** to balance security and usability:

**Development Mode** (Default):
- Uses standard `eval()` for code execution
- Easier debugging and development
- All console output visible
- Suitable for local, trusted environments

**Production Mode**:
- Uses `RAppArmor::eval.secure()` for sandboxed execution (Linux only)
- Enhanced security with resource limits:
- 1GB RAM limit
- 1MB file size limit
- 10 second timeout
- No new process creation
- Prevents system command execution
- Required for public deployments

**Environment Detection**:
The module automatically detects production environments using:
1. `DEPLOYMENT_ENV` environment variable (`production` or `development`)
2. shinyapps.io deployment detection
3. RStudio Connect detection
4. `.production` marker file

**Setting Deployment Mode**:

For local development (default):
```r
# No setup needed - defaults to development mode
# Or explicitly set in .Renviron:
DEPLOYMENT_ENV=development
```

For production deployment:
```r
# Add to .Renviron file:
DEPLOYMENT_ENV=production
```

Or create a marker file:
```bash
# In your app directory
touch .production
```

**Linux Server Setup** (for RAppArmor):
```bash
# Install AppArmor
sudo apt-get install apparmor apparmor-utils libapparmor-dev

# Install R package
R -e "install.packages('RAppArmor')"
```

**Platform Support**:
- ✅ **Linux**: Full RAppArmor sandboxing available
- ⚠️ **macOS/Windows**: Falls back to standard eval with warning in production mode
- Recommendation: Deploy on Linux servers for maximum security

#### Basic Security Features
- **Package Whitelist**: Only approved packages allowed
- **Pre-execution Review**: Code can be edited before execution
- **Error Handling**: Safe error messages without system information

### API Key Security

Expand Down
5 changes: 3 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ Imports:
gridExtra
URL: https://jinseob2kim.github.io/jsmodule/, https://github.com/jinseob2kim/jsmodule
BugReports: https://github.com/jinseob2kim/jsmodule/issues
Suggests:
Suggests:
testthat,
shinytest,
knitr,
rmarkdown
rmarkdown,
RAppArmor
VignetteBuilder: knitr
106 changes: 103 additions & 3 deletions R/aiAssistant.R
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,86 @@ aiAssistantUI <- function(id, show_api_config = TRUE) {
}


# Helper Functions for Environment Detection and Safe Evaluation ----

#' Detect if running in production/deployment environment
#' @return Logical. TRUE if in production, FALSE if local development
#' @details Default is FALSE (development mode) when DEPLOYMENT_ENV is not set
is_production_environment <- function() {
# Method 1: Check for custom environment variable (recommended)
# Default to "development" if not set
deployment_env <- Sys.getenv("DEPLOYMENT_ENV", unset = "development")
if (deployment_env == "production") {
return(TRUE)
}

# Method 2: Detect shinyapps.io
if (nzchar(Sys.getenv("SHINY_PORT")) &&
Sys.getenv("R_CONFIG_ACTIVE") == "shinyapps") {
return(TRUE)
}

# Method 3: Detect RStudio Connect
if (Sys.getenv("RSTUDIO_PRODUCT") == "CONNECT") {
return(TRUE)
}

# Method 4: Check for .production marker file
if (file.exists(".production")) {
return(TRUE)
}

# Method 5: Check if running on specific server hostname
hostname <- Sys.info()["nodename"]
if (grepl("shinyapps\\.io|rstudio\\.cloud", hostname, ignore.case = TRUE)) {
return(TRUE)
}

# Default: local development mode
return(FALSE)
}

#' Safe evaluation wrapper with environment-aware security
#' @param expr Expression to evaluate
#' @param envir Environment for evaluation
#' @param timeout Timeout in seconds (default: 10)
#' @return Evaluation result
#' @details In production mode, uses RAppArmor::eval.secure if available.
#' In development mode, uses standard eval for easier debugging.
safe_eval_expr <- function(expr, envir, timeout = 10) {
# Production environment: Use RAppArmor if available
if (is_production_environment()) {
if (.Platform$OS.type == "unix" &&
requireNamespace("RAppArmor", quietly = TRUE)) {

# Use RAppArmor for sandboxed execution
return(RAppArmor::eval.secure(
expr,
envir = envir,
timeout = timeout,
RLIMIT_AS = 1e9, # 1GB RAM limit
RLIMIT_FSIZE = 1e6, # 1MB file size limit
RLIMIT_CPU = timeout, # CPU time limit
RLIMIT_NPROC = 0, # No new processes allowed
profile = "r-base" # AppArmor profile
))

} else {
# Production mode but RAppArmor not available
warning(
"Production environment detected but RAppArmor not available. ",
"Using standard eval. For security, install RAppArmor on Linux systems. ",
"Run: install.packages('RAppArmor')"
)
return(eval(expr, envir = envir))
}
}

# Development environment: Use standard eval for easier debugging
return(eval(expr, envir = envir))
}


#' @title aiAssistant: AI Assistant module server
#' @description AI-powered statistical analysis assistant module server
#' @param input input
Expand Down Expand Up @@ -1586,7 +1666,11 @@ aiAssistant <- function(input, output, session, data, data_label,

# Build context from data
data_context <- reactive({
req(data())
# Check if data is available
if (is.null(data()) || nrow(data()) == 0) {
stop("Please upload data first using the 'Data' tab before using AI Assistant.")
}

d <- data()
dl <- if (!is.null(data_label)) data_label() else NULL
vs <- data_varStruct()
Expand Down Expand Up @@ -1673,10 +1757,26 @@ aiAssistant <- function(input, output, session, data, data_label,
context_section <- build_analysis_context(context_info)

# Build system prompt from template + context
# Check if data is available first
data_ctx <- tryCatch({
data_context()
}, error = function(e) {
# Return error if data validation fails
return(list(
success = FALSE,
message = "Please upload data first using the 'Data' tab before using AI Assistant."
))
})

# If data_context returned an error structure, return it immediately
if (is.list(data_ctx) && !is.null(data_ctx$success) && !data_ctx$success) {
return(data_ctx)
}

system_prompt <- paste0(
system_prompt_text(), "\n\n",
"## Current Project Context\n",
data_context(),
data_ctx,
context_section
)

Expand Down Expand Up @@ -2384,7 +2484,7 @@ Please fix the code to ensure it returns a proper result that can be displayed a
res <- NULL
line_num <- 1
for (expr in parsed) {
res <- eval(expr, envir = env)
res <- safe_eval_expr(expr, envir = env, timeout = 10)
line_num <- line_num + length(attr(expr, "srcref")[[1]])
}
# Check for result variable
Expand Down
17 changes: 17 additions & 0 deletions man/is_production_environment.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions man/safe_eval_expr.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading