PenguinCode implements a comprehensive security model for local and remote execution scenarios. This document covers authentication, authorization, encryption, and best practices for safe code generation and deployment.
- Security Levels
- JWT Authentication
- API Key Management
- TLS Configuration
- Local Tool Execution Model
- Code Generation Safety
- OWASP Top 10 Compliance
- Production Deployment
PenguinCode supports three security levels configured in config.yaml:
| Level | Name | Behavior | Use Case |
|---|---|---|---|
| 1 | Always Prompt | Prompts for confirmation on ALL operations | Development/Testing |
| 2 | Destructive Prompt | Prompts only for destructive operations (delete, modify, execute) | Default Production |
| 3 | No Prompts | Automatic approval of all operations | High-Trust, Automated |
security:
level: 2 # Recommended defaultDestructive Operations include: file deletion, code execution, database modifications, configuration changes, and external network calls.
PenguinCode uses JWT (JSON Web Tokens) for client-server authentication when running in remote or standalone modes.
- Client authenticates with API key → Server validates against whitelist
- Server generates access token (1 hour expiry) + refresh token (24 hours)
- Client stores tokens securely at
~/.penguincode/token(mode 0600) - Interceptor validates JWT on each request via
JWTValidationInterceptor - Automatic refresh when access token expires
Access Token (HS256 signed):
{
"sub": "client_id",
"iat": 1704067200,
"exp": 1704070800,
"scopes": ["chat", "tools"],
"type": "access"
}Refresh Token: Cryptographically random 32-byte URL-safe string, single-use with server-side tracking.
auth:
enabled: false # Set to true for remote mode
jwt_secret: "${PENGUINCODE_JWT_SECRET}"
token_expiry: 3600 # 1 hour
refresh_expiry: 86400 # 24 hours
api_keys:
- "${PENGUINCODE_API_KEY}"
client:
server_url: "grpc://server:50051"
token_path: "~/.penguincode/token"Environment Variables:
PENGUINCODE_JWT_SECRET: Min 32 bytes (useopenssl rand -hex 32)PENGUINCODE_API_KEY: Complex string, min 32 characters
API keys provide initial authentication to obtain JWT tokens.
- Generate Strong Keys: Min 32 chars, alphanumeric + special
- Environment Variables: Store in
.envor secret manager, never in code - Rotation: Change quarterly or after exposure
- Validation: Server-side whitelist comparison (timing-safe)
- Scope Limiting: Future feature for granular permissions
Generation:
openssl rand -base64 32 # API key
openssl rand -hex 32 # JWT secretTLS protects gRPC traffic from eavesdropping and MITM attacks.
server:
tls_enabled: true
tls_cert_path: "/etc/penguincode/certs/server.crt"
tls_key_path: "/etc/penguincode/certs/server.key"
client:
server_url: "grpcs://server:50051" # grpcs for TLSopenssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crtProduction: Use CA-signed certificates, TLS 1.3, enable OCSP stapling, rotate every 90 days.
PenguinCode uses security by architecture: sensitive tools execute locally on the client.
Local Tools (config.yaml):
read: File reading (no risk)write: File writing (user controls target)edit: File editing (granular, auditable)bash: Shell execution (direct system access)grep/glob: File search (info gathering only)
Security Benefits:
- User Control: Operations directly affect user's filesystem
- Data Privacy: Server cannot read private files
- Malicious Server Protection: Even if compromised, cannot execute arbitrary code
- Audit Trail: All commands in shell history
- Type Checking: mypy (Python), vet (Go)
- Linting: flake8, ESLint, golangci-lint
- Security Scanning: Trivy, CodeQL, bandit
- Manual Review: Always review AI-generated code
- Containerization: Isolated Docker execution
# ✅ DO: Validate inputs
def process_user_input(data: str) -> dict:
if not isinstance(data, str) or len(data) > 10000:
raise ValueError("Invalid input")
return parse_input(data)
# ✅ DO: Use parameterized queries
result = db.query("SELECT * FROM users WHERE id = ?", [user_id])
# ✅ DO: Explicit error handling
try:
result = risky_operation()
except SpecificException as e:
logger.error(f"Operation failed: {e}")
raiseThe Executor agent follows OWASP Top 10 (2021) guidelines when generating code:
What we do:
- Generate proper authorization checks before sensitive operations
- Implement deny-by-default access patterns
- Use indirect object references (UUIDs) instead of sequential IDs
- Server-side permission validation
Example - FastAPI:
from fastapi import Depends, HTTPException, status
from uuid import UUID
async def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
user = await verify_token(token)
if not user:
raise HTTPException(status_code=401)
return user
@app.get("/users/{user_id}")
async def get_user(
user_id: UUID, # UUID, not int
current_user: User = Depends(get_current_user)
):
if not current_user.can_view_user(user_id):
raise HTTPException(status_code=403)
return await get_user_by_id(user_id)What we do:
- Never hardcode secrets in generated code
- Use environment variables or secret managers
- Modern encryption algorithms (AES-256, bcrypt/argon2)
- Proper password hashing with salts
Example - Password hashing:
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)What we do:
- Parameterized queries for all database operations
- Input validation and sanitization
- Context-aware output escaping
- No shell command construction with user input
Example - SQL (SQLAlchemy):
# GOOD: Parameterized query
user = session.execute(
select(User).where(User.email == email)
).scalar_one_or_none()
# BAD: String concatenation (never generated)
# query = f"SELECT * FROM users WHERE email = '{email}'"What we do:
- Rate limiting on authentication endpoints
- CSRF protection for state-changing operations
- Secure session management
- Principle of least privilege
What we do:
- No debug info in production responses
- Secure HTTP headers by default
- Minimal service exposure
Example - FastAPI security headers:
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.sessions import SessionMiddleware
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["*.example.com"])
app.add_middleware(SessionMiddleware, secret_key=os.environ["SESSION_SECRET"])
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
return responseWhat we do:
- Recommend well-maintained libraries
- Suggest version pinning
- Warn about known vulnerabilities
What we do:
- Strong password policies
- Account lockout mechanisms
- Secure session tokens
- Proper token expiration
What we do:
- Validate all external data
- Suggest integrity checks
What we do:
- Log security events
- Never log sensitive data
- Structured logging with context
Example:
import structlog
logger = structlog.get_logger()
async def login(email: str, password: str):
user = await get_user(email)
if not user or not verify_password(password, user.password_hash):
logger.warning("login_failed", email=email) # Log email, not password
raise HTTPException(status_code=401)
logger.info("login_success", user_id=str(user.id))
return create_token(user)What we do:
- URL validation before requests
- Domain allowlists
- Block internal IP ranges
Future releases will include:
- Input sanitization (special tokens/delimiters)
- Instruction separation (system/user boundary)
- Template validation (prompt structure)
- Pattern detection (manipulation attempts)
Current Mitigation: Run code in isolated containers, review before execution.
Planned comprehensive audit trail:
- Request logging (user, method, parameters)
- Authorization events (role changes, permissions)
- Code execution (generated code, results)
- Authentication events (logins, token refresh)
- Data access (file I/O, database queries)
- System changes (configuration, secrets)
{
"timestamp": "2024-01-15T10:30:45Z",
"user_id": "client_abc123",
"event": "code_execution",
"action": "bash",
"parameters": {"command": "ls -la"},
"result": "success"
}- Environment variables set (JWT secret, API keys)
- TLS certificates installed and tested
- JWT secret: min 32 bytes from cryptographic RNG
- API keys: complex, unique per client
- Security level: 2 recommended
- Firewall: only expose gRPC port
- Network: isolated on private network
- Scanning: dependencies/containers (Trivy)
- Code review: all custom code reviewed
- Monitoring: Prometheus metrics enabled
- Alerting: failed auth, security events
# Firewall rules (Linux iptables)
iptables -A INPUT -p tcp --dport 50051 -j ACCEPT # gRPC
# Source IP whitelist
iptables -A INPUT -p tcp --dport 50051 -s 192.168.1.0/24 -j ACCEPT# Generate secrets
export PENGUINCODE_JWT_SECRET=$(openssl rand -hex 32)
export PENGUINCODE_API_KEY=$(openssl rand -base64 32)
# Use Kubernetes Secrets
kubectl create secret generic penguincode \
--from-literal=jwt-secret=<value> \
--from-literal=api-key=<value>- Detect: Monitor auth failures, unusual patterns
- Isolate: Disconnect compromised client/server
- Investigate: Check audit logs, review access
- Revoke: Invalidate tokens, rotate secrets
- Patch: Update vulnerabilities
- Notify: Alert users/admins
- Review: Post-incident analysis
# JWT secret rotation (invalidates all tokens immediately)
NEW_SECRET=$(openssl rand -hex 32)
# Update server config, restart with new secret
# API key rotation (grace period)
# 1. Add new key to whitelist
# 2. Update clients
# 3. Remove old key after grace period| Risk | Mitigation |
|---|---|
| Insecure random | Use secrets module, not random |
| Command injection | Use subprocess with shell=False and list args |
| Code injection | Avoid eval(), exec(), pickle.loads() |
| XSS | Use html.escape() for HTML output |
| Risk | Mitigation |
|---|---|
| XSS | Use textContent instead of innerHTML |
| Input validation | Use Zod, Yup, or similar |
| URL injection | Use encodeURIComponent() |
| Cookie theft | Set httpOnly and secure flags |
| Risk | Mitigation |
|---|---|
| SQL injection | Use database/sql with ? placeholders |
| Command injection | Use exec.Command() with separate args |
| Path traversal | Use filepath.Clean() and validate paths |
| Risk | Mitigation |
|---|---|
| Memory safety | Leverage ownership system (automatic) |
| SQL injection | Use sqlx or diesel with parameters |
| Untrusted input | Use serde with validation |
Secrets Management:
# GOOD: Use variables with sensitive flag
variable "db_password" {
type = string
sensitive = true
}
resource "aws_db_instance" "main" {
password = var.db_password
}
# BAD: Hardcoded (never generated)
# password = "supersecret123"State Security:
# Use encrypted backend
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}Resource Security:
# Enable encryption by default
resource "aws_s3_bucket" "data" {
bucket = "my-secure-bucket"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Use private subnets
resource "aws_instance" "app" {
subnet_id = aws_subnet.private.id
associate_public_ip_address = false
}Vault Usage:
# Create encrypted variables
# ansible-vault create secrets.yml
# Reference in playbook
- name: Deploy application
hosts: webservers
vars_files:
- secrets.yml
tasks:
- name: Configure database
template:
src: db.conf.j2
dest: /etc/app/db.conf
no_log: true # Don't log secretsPrivilege Escalation:
# Use become only when necessary
- name: Install packages
become: true
ansible.builtin.apt:
name: nginx
state: present
- name: Copy config (no root needed)
become: false
ansible.builtin.copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf# config.yaml
security:
enabled: true
scan_on_write: true # Scan generated code
block_insecure: false # Warn but don't block
rules:
- no_hardcoded_secrets
- no_sql_injection
- no_command_injection
- use_parameterized_queries
- use_secure_randomsecurity:
custom_rules:
- pattern: "password\\s*=\\s*['\"][^'\"]+['\"]"
message: "Hardcoded password detected"
severity: high
- pattern: "eval\\(.*\\$"
message: "Eval with variable input"
severity: criticalIf you discover a security vulnerability in PenguinCode:
- Do not open a public issue
- Email security@penguintech.io
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We will respond within 48 hours and work with you on responsible disclosure.
PenguinCode follows:
- OWASP Top 10 2021: Mitigations for all A1-A10 risks
- NIST Cybersecurity Framework: Identify, Protect, Detect, Respond, Recover
- CWE Top 25: Common weakness enumeration prevention
- OWASP API Security: API-first design, authentication/authorization
- OWASP Top 10 2021
- JWT Best Practices (RFC 8725)
- gRPC Security
- NIST Cybersecurity Framework
- CWE/SANS Top 25
If you discover a security vulnerability in PenguinCode:
- Do not open a public issue
- Email security@penguintech.io
- Include: description, reproduction steps, impact, suggested fix
We will respond within 48 hours and work on responsible disclosure.
Last Updated: 2025-12-28 See Also: AGENTS.md, STANDARDS.md, LICENSE_SERVER_INTEGRATION.md