Paul Hammant's Blog:
Building a Secure Container Sandbox on ChromeOS for Testing Untrusted Code
The Problem: Running Random GitHub Code Safely
As developers, we frequently encounter interesting GitHub repositories, development tools, or scripts that we want to test. However, running` untrusted code directly on our development machines poses significant security risks:
- Supply chain attacks: Malicious code that modifies system binaries or installs backdoors (like the 2025 Chalk npm package compromise that affected millions of downloads)
- Data theft: Scripts that scan for SSH keys (passphrase protected or not), API tokens, or sensitive files
- System compromise: Privilege escalation attacks that gain persistent access
- Resource abuse: Cryptocurrency miners or botnet participation
Traditional solutions like virtual machines are heavyweight and slow to reset. Docker provides isolation but shares the kernel and can be escaped. ChromeOS’s unique architecture offers a compelling alternative through its layered container system.
ChromeOS Container Architecture: Defense in Depth
ChromeOS provides multiple layers of isolation that make it ideal for secure sandboxing:
┌────────────────────────────────────────-─────┐
│ ChromeOS (Host) │
│ ┌─────────────────────────────────────────┐ │
│ │ Termina VM │ │
│ │ ┌─────────────┐ ┌─────────────────────┤ │
│ │ │ Penguin │ │ OSS │ │
│ │ │ (Trusted) │ │ (Untrusted) │ │
│ │ │ │ │ │ │
│ │ │ - Your work │ │ - Random GitHub │ │
│ │ │ - SSH keys │ │ repositories │ │
│ │ │ - Configs │ │ - Untested tools │ │
│ │ │ │ │ - No sensitive │ │
│ │ │ │ │ data │ │
│ │ └─────────────┘ └─────────────────────┤ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────-────┘
Or a second ascii-art way of outlining the same situation, with more detail ()
ChromeOS (host)
└─ crosvm (VM with KVM acceleration if hardware supports it)
└─ Termina (tiny VM OS, runs LXD daemon and socket)
│ └- uses LXD API to query kernel level cgroups/namespaces
│ └- avoids trusting oss's /bin/ps /bin/find etc
│
├─ oss (Debian container, untrusted)
│ └─ [supply-chain attacker could replace ps/find/ls]
│
└─ penguin (main Debian container, trusted code)
Complete Setup Process
Prerequisites
This setup must be run in ChromeOS’s Termina VM, not inside a container. You can access this by pressing ctrl-alt-t and the resulting terminal should say “Welcome to crosh, the ChromeOS developer shell.” You should see a prompt like crosh>
Quick Start Script
There’s no vi, emacs or nano in chrosh. You’ll prepare scripts elsewhere and email them to yourself. You’ll use ChromeOS’ regular mail app to read those, as you logged in with your google account after all. The ChromeOS text editor is a bit weak for my liking, and I like to keep a copy of things that may get refined and are not canonically in source control.
cat > /tmp/filename_as_instructed.sh << 'SETUP_SCRIPT_EOF' | bash
The bash code below
SETUP_SCRIPT_EOF
Save this as /tmp/setup-secure-containers.sh and run it with bash /tmp/setup-secure-containers.sh as you can’t make it executable:
#!/bin/bash
# ChromeOS Secure Container Setup
# This creates two containers:
# - penguin (default/trusted) - your main work environment
# - oss (untrusted) - for running cloned repos and untrusted code
# IMPORTANT: This script must be run in Termina (the ChromeOS VM)
# because lxc commands don't work from inside containers
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo "================================================="
echo "ChromeOS Secure Container Setup"
echo "================================================="
echo ""
echo "This script will create a two-container security setup:"
echo "- penguin: Your trusted container (already exists)"
echo "- oss: Untrusted container for running random code"
echo ""
# Verify we're in Termina
if ! command -v lxc &> /dev/null; then
echo "ERROR: lxc command not found. This script must be run in Termina"
echo "Open the Terminal app in ChromeOS and run this script there."
exit 1
fi
echo "Environment check passed - lxc command found"
# Step 1: Show current containers
echo ""
echo "Step 1: Current container status"
echo "================================"
lxc list
# Step 2: Get available images
echo ""
echo "Step 2: Finding Debian image..."
echo "==============================="
IMAGE_FINGERPRINT=$(lxc image list --format csv | grep -i debian | cut -d',' -f2 | head -1)
if [ -z "$IMAGE_FINGERPRINT" ]; then
echo "No Debian image found. Available images:"
lxc image list
exit 1
fi
# Step 3: Create the oss container
echo ""
echo "Step 3: Creating oss container..."
echo "=================================="
# Check if oss container already exists
if lxc info oss &>/dev/null; then
echo "Container 'oss' already exists, skipping..."
else
echo "Creating 'oss' container..."
lxc launch $IMAGE_FINGERPRINT oss
echo "Waiting for container to start..."
sleep 5
fi
# Show updated container list
echo ""
lxc list
echo ""
echo "Step 4: Setting resource limits for oss container..."
echo "===================================================="
# Set resource limits and security restrictions
lxc config set oss limits.cpu 2
lxc config set oss limits.memory 2GB
lxc config set oss security.nesting false
lxc config set oss security.privileged false
echo "Resource limits and security restrictions applied to oss container"
echo ""
echo "Step 5: Setting up oss container for untrusted code..."
echo "====================================================="
# Install packages in oss container
lxc exec oss -- apt-get update
lxc exec oss -- apt-get install -y git build-essential python3 python3-pip curl wget nodejs npm
lxc exec oss -- mkdir -p /workspace
lxc exec oss -- bash -c "echo 'Untrusted code workspace - Only run random GitHub repos here!' > /workspace/README.txt"
echo "OSS container setup complete"
# Step 6: Set up baseline in trusted container (penguin)
echo ""
echo "Step 6: Creating baseline in penguin (trusted container)..."
echo "=========================================================="
# Create baseline hashes for binary integrity monitoring
lxc exec penguin -- bash -c "find /bin /usr/bin -type f -executable -exec sha256sum {} \; > /root/baseline_hashes.txt"
lxc exec penguin -- bash -c "echo 'Baseline created at $(date)' >> /root/baseline_hashes.txt"
lxc exec penguin -- bash -c "wc -l < /root/baseline_hashes.txt | xargs -I {} echo 'Baseline hash count: {}'"
# Ensure penguin has essential tools
lxc exec penguin -- apt-get update
lxc exec penguin -- apt-get install -y vim git
echo "Baseline created in penguin container"
# Step 7: Create monitoring script in Termina
echo ""
echo "Step 7: Creating Termina-based monitoring script..."
echo "================================================"
cat > /tmp/monitor-containers.sh << 'MONITOR_EOF'
#!/bin/bash
# Container Security Monitor - Runs in Termina
# Monitors the oss (untrusted) and penguin (trusted) containers for supply chain attacks
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
function setup_baseline() {
local baseline_file="/tmp/.container_baseline"
if [ ! -f "$baseline_file" ]; then
echo "Creating baseline for binary integrity monitoring..."
echo "# Container Security Baseline - Created $(date)" > "$baseline_file"
# Create baseline for both containers
for container in oss penguin; do
if lxc info "$container" &>/dev/null; then
echo "# $container container baseline" >> "$baseline_file"
for binary in /bin/bash /bin/sh /usr/bin/python3 /usr/bin/node /usr/bin/git; do
hash=$(lxc exec $container -- sha256sum $binary 2>/dev/null | awk '{print $1}')
if [ -n "$hash" ]; then
echo "$container:$binary:$hash" >> "$baseline_file"
fi
done
fi
done
echo "Baseline created at $baseline_file"
fi
}
function check_binary_integrity() {
local container=$1
local found_issues=0
echo -e "${YELLOW}Binary Integrity Check - $container${NC}"
# Check key binaries against baseline
while IFS=: read -r base_container base_binary base_hash; do
if [[ "$base_container" == "$container" ]] && [[ "$base_binary" =~ ^/.*$ ]]; then
current_hash=$(lxc exec $container -- sha256sum $base_binary 2>/dev/null | awk '{print $1}')
if [ -n "$current_hash" ] && [ "$current_hash" != "$base_hash" ]; then
echo -e " ${RED}!!! Modified: $base_binary${NC}"
echo " Expected: $base_hash"
echo " Current: $current_hash"
found_issues=1
fi
fi
done < "/tmp/.container_baseline" 2>/dev/null
if [ $found_issues -eq 0 ]; then
echo " All monitored binaries match baseline"
fi
return $found_issues
}
function check_processes() {
local container=$1
echo -e "${YELLOW}Process Check - $container${NC}"
echo " Top processes:"
# Show top CPU-consuming processes
lxc exec $container -- ps aux --sort=-%cpu 2>/dev/null | head -6 | while IFS= read -r line; do
echo " $line"
done
}
function check_network() {
local container=$1
echo -e "${YELLOW}Network Connections - $container${NC}"
# Count listening ports and established connections
listening=$(lxc exec $container -- ss -tlnp 2>/dev/null | grep LISTEN | wc -l)
connections=$(lxc exec $container -- ss -tnp 2>/dev/null | grep ESTAB | wc -l)
echo " Listening ports: $listening"
echo " Active connections: $connections"
# Alert on any external connections from untrusted container
if [ "$container" = "oss" ] && [ "$connections" -gt 0 ]; then
echo -e " ${RED}!!! External connections detected in untrusted container:${NC}"
lxc exec $container -- ss -tnp 2>/dev/null | grep ESTAB | while IFS= read -r line; do
echo " $line"
done
fi
}
function check_recent_files() {
local container=$1
echo -e "${YELLOW}Recently Modified Files - $container${NC}"
echo " Files modified in last 10 minutes:"
# Focus on system directories for supply chain attacks
lxc exec $container -- find /bin /usr/bin /lib -xdev -type f -mmin -10 2>/dev/null | while IFS= read -r line; do
echo " $line"
done
# Also check workspace for oss
if [ "$container" = "oss" ]; then
echo " Workspace files:"
lxc exec $container -- find /workspace -xdev -type f -mmin -10 2>/dev/null | head -5 | while IFS= read -r line; do
echo " $line"
done
fi
}
function check_suspicious_files() {
local container=$1
echo -e "${YELLOW}Suspicious Files Check - $container${NC}"
# Look for hidden files in tmp directories
suspicious_count=$(lxc exec $container -- find /tmp /var/tmp /dev/shm -name ".*" -type f 2>/dev/null | wc -l)
if [ "$suspicious_count" -gt 0 ]; then
echo -e " ${RED}!!! Found $suspicious_count hidden files in temp directories${NC}"
else
echo " No suspicious hidden files found"
fi
}
function check_suid_changes() {
local container=$1
# Check for new SUID binaries
lxc exec $container -- find / -xdev -perm -4000 -type f 2>/dev/null | while read suid_file; do
echo " SUID: $suid_file"
done
}
function setup_suid_baseline() {
for container in oss penguin; do
if lxc info "$container" &>/dev/null; then
if [ ! -f "/tmp/.known_suid_$container" ]; then
lxc exec $container -- find / -xdev -perm -4000 -type f 2>/dev/null > "/tmp/.known_suid_$container"
fi
fi
done
}
# Main monitoring loop
echo "================================================="
echo "Container Security Monitor"
echo "================================================="
echo "Monitoring for supply chain attacks in:"
echo " - oss (untrusted) - where you run random code"
echo " - penguin (trusted) - your main environment"
echo ""
# Setup baselines on first run
setup_baseline
setup_suid_baseline
while true; do
clear
echo "Container Security Monitor - $(date)"
echo "====================================="
echo ""
# Monitor the untrusted container more closely
echo -e "${RED}=== OSS Container (UNTRUSTED) ===${NC}"
if ! check_binary_integrity "oss"; then
echo -e "\n${RED}!!! ALERT: Binary modification detected in OSS container!${NC}"
echo -e "${RED}This could indicate a supply chain attack from recently run code${NC}\n"
fi
echo ""
check_processes "oss"
echo ""
check_network "oss"
echo ""
check_recent_files "oss"
echo ""
check_suspicious_files "oss"
echo ""
echo -e "${GREEN}=== Penguin Container (TRUSTED) ===${NC}"
if ! check_binary_integrity "penguin"; then
echo -e "\n${RED}!!! CRITICAL: Binary modification in TRUSTED container!${NC}\n"
fi
echo ""
check_network "penguin"
echo ""
echo "Next scan in 30 seconds... (Press Ctrl+C to exit)"
sleep 30
done
MONITOR_EOF
chmod +x /tmp/monitor-containers.sh
echo "Monitoring script created at /tmp/monitor-containers.sh"
# Step 8: Create helper scripts
echo ""
echo "Step 8: Creating helper scripts..."
echo "=================================="
# Create a script to enter each container
cat > /tmp/enter-oss.sh << 'ENTER_OSS_EOF'
#!/bin/bash
echo "========================================="
echo "Entering OSS (UNTRUSTED) Container"
echo "========================================="
echo "!!! SECURITY WARNING:"
echo "- Only run untrusted code here"
echo "- No sensitive files or keys"
echo "- Monitor with: bash /tmp/monitor-containers.sh"
echo "========================================="
lxc exec oss -- bash
ENTER_OSS_EOF
chmod +x /tmp/enter-oss.sh
cat > /tmp/enter-penguin.sh << 'ENTER_PENGUIN_EOF'
#!/bin/bash
echo "========================================="
echo "Entering Penguin (TRUSTED) Container"
echo "========================================="
echo "This is your trusted development environment"
echo "========================================="
lxc exec penguin -- bash
ENTER_PENGUIN_EOF
chmod +x /tmp/enter-penguin.sh
# Create status script
cat > /tmp/status.sh << 'STATUS_EOF'
#!/bin/bash
echo "Container Security Setup Status"
echo "==============================="
lxc list
echo ""
echo "OSS Container Security Settings:"
echo " CPU limit: $(lxc config get oss limits.cpu)"
echo " Memory limit: $(lxc config get oss limits.memory)"
echo " Privileged: $(lxc config get oss security.privileged)"
echo " Nesting: $(lxc config get oss security.nesting)"
echo ""
echo "Quick security check:"
for container in oss penguin; do
echo -n " $container binary integrity: "
ps_hash=$(lxc exec $container -- sha256sum /bin/ps 2>/dev/null | awk '{print substr($1,1,8)}')
echo "$ps_hash"
done
STATUS_EOF
chmod +x /tmp/status.sh
# Create a reset script for the oss container
cat > /tmp/reset-oss.sh << 'RESET_EOF'
#!/bin/bash
echo "This will destroy and recreate the OSS container"
echo "Any data in the OSS container will be lost!"
read -p "Are you sure? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Resetting OSS container..."
lxc stop oss
lxc delete oss
# Recreate with same settings
IMAGE_FP=$(lxc image list --format csv | grep -i debian | cut -d',' -f2 | head -1)
lxc launch $IMAGE_FP oss
# Reapply security settings
lxc config set oss limits.cpu 2
lxc config set oss limits.memory 2GB
lxc config set oss security.nesting false
lxc config set oss security.privileged false
# Reinstall packages
lxc exec oss -- apt-get update
lxc exec oss -- apt-get install -y git build-essential python3 python3-pip curl wget nodejs npm
lxc exec oss -- mkdir -p /workspace
echo "OSS container reset complete"
# Update baseline
rm -f /tmp/.container_baseline
echo "Run: bash /tmp/monitor-containers.sh to recreate baseline"
fi
RESET_EOF
chmod +x /tmp/reset-oss.sh
# Step 9: Optional SSH Setup for OSS Container
echo ""
echo "Step 9: Setting up SSH access to OSS (optional)..."
echo "=================================================="
# Install and configure SSH
lxc exec oss -- apt-get install -y openssh-server
lxc exec oss -- rm -f /etc/ssh/sshd_not_to_be_run
lxc exec oss -- ssh-keygen -A
lxc exec oss -- systemctl restart ssh
lxc exec oss -- systemctl enable ssh
# Create non-root user
lxc exec oss -- useradd -m -s /bin/bash dev 2>/dev/null || true
lxc exec oss -- bash -c "echo 'dev:changeme' | chpasswd"
lxc exec oss -- usermod -aG sudo dev
# Get IP address
OSS_IP=$(lxc list oss -f csv -c 4 | cut -d' ' -f1)
echo ""
echo "SSH access configured:"
echo " ssh dev@$OSS_IP"
echo " Default password: changeme (change immediately!)"
echo ""
echo "!!! WARNING: Never use SSH agent forwarding (-A) with untrusted containers!"
# Final summary
echo ""
echo "Setup Complete!"
echo "==============="
lxc list
echo ""
echo "Available commands:"
echo " bash /tmp/monitor-containers.sh - Start security monitoring"
echo " bash /tmp/enter-oss.sh - Enter untrusted container"
echo " bash /tmp/enter-penguin.sh - Enter trusted container"
echo " bash /tmp/status.sh - Check security status"
echo " bash /tmp/reset-oss.sh - Reset OSS container (if compromised)"
echo ""
echo "Usage pattern:"
echo "1. Run random code ONLY in oss container (bash /tmp/enter-oss.sh)"
echo "2. Keep monitoring running in another terminal (bash /tmp/monitor-containers.sh)"
echo "3. If monitor detects modified binaries, consider using reset script"
echo ""
echo "The monitor will detect:"
echo "- Modified system binaries (supply chain attacks)"
echo "- Suspicious processes and network connections"
echo "- Recently modified files in system directories"
echo "- Hidden files in temporary directories"
echo ""
echo "🔒 Your trusted work remains safe in the penguin container!"
Manual Setup Steps
If you prefer to understand each step, here’s the manual process:
1. Create the Untrusted Container
# Get available image
IMAGE_FP=$(lxc image list --format csv | grep -i debian | cut -d',' -f2 | head -1)
# Create OSS container
lxc launch $IMAGE_FP oss
# Set security restrictions
lxc config set oss limits.cpu 2
lxc config set oss limits.memory 2GB
lxc config set oss security.nesting false
lxc config set oss security.privileged false
2. Install Development Tools
# Update and install common development tools
lxc exec oss -- apt-get update
lxc exec oss -- apt-get install -y git build-essential python3 python3-pip curl wget nodejs npm
# Create workspace directory
lxc exec oss -- mkdir -p /workspace
3. Create Monitoring Script
The monitoring script runs from Termina and watches both containers for signs of compromise:
# Create the monitoring script
cat > /tmp/monitor-containers.sh << 'EOF'
#!/bin/bash
# [Full monitoring script from above]
EOF
chmod +x /tmp/monitor-containers.sh
Troubleshooting Common Issues
Termina Filesystem Issues
Issue: Scripts fail with “Read-only file system” error. Termina’s home directory (~/) is read-only:
Solution: Use /tmp for all scripts which is not read only.
Issue: no editors
Solution pipe to file trick as mentioned.
# Correct - use /tmp
cat > /tmp/script.sh << 'EOF'
...
EOF
bash /tmp/script.sh
SSH Setup
SSH service needs manual setup in containers:
You get into the container using lxc exec container_name -- bash from Termina (crosh):
# In the target container (run via lxc exec)
apt-get install -y openssh-server
rm -f /etc/ssh/sshd_not_to_be_run # Remove startup blocker
ssh-keygen -A # Generate host keys
systemctl restart ssh
systemctl enable ssh
echo 'root:changeme' | chpasswd # Set password
LXC Command Not Found
Do NOT run from inside penguin or any other container - you need to be in crosh: ctrl-alt-t
Secure Git Access Patterns
You don’t want you SSH private key on the container that may be taken over by ‘chalk’ style actions. At least you don’t want it without a lengthy passphrase, but there are traditional solutions:
The SSH Agent Forwarding Security Risk
Critical Warning: SSH agent forwarding allows untrusted code to use your keys!
While your SSH session with -A flag is active:
- Malicious code can push to ANY repo you have write access to
- Can clone ANY private repo you have access to
- Cannot steal your key, but can USE it
Safer Alternatives for Git Access
Option 1: Read-Only Deploy Keys (RECOMMENDED)
# Create separate key for untrusted work
ssh-keygen -t ed25519 -f ~/.ssh/oss_readonly_key -N ""
# Add to GitHub as deploy key (READ-ONLY) for specific repos
# Copy ONLY this key to oss container
lxc exec penguin -- cat /home/USER/.ssh/oss_readonly_key | \
lxc exec oss -- bash -c "cat > /home/dev/.ssh/id_ed25519 && chmod 600 /home/dev/.ssh/id_ed25519"
Option 2: Fine-Grained Personal Access Tokens
# Create token with minimal permissions (public_repo only)
# Use in oss container:
git clone https://TOKEN@github.com/user/repo.git
Option 3: Time-Limited Agent Forwarding (USE SPARINGLY)
# Only when absolutely necessary for push operations
ssh -A dev@[oss-ip]
# Do your git operation
# EXIT IMMEDIATELY
exit
# Or use confirmation-required keys
ssh-add -c ~/.ssh/id_ed25519 # Requires confirmation for each use
Security Best Practices
What to NEVER Do
- Don’t store sensitive data in the OSS container:
# NEVER DO THIS cp ~/.ssh/id_rsa /path/to/oss/container - Don’t run trusted code in OSS container:
# NEVER DO THIS lxc exec oss -- git clone git@github.com:yourcompany/private-repo.git - Don’t disable monitoring:
# NEVER DO THIS - Always keep monitoring running pkill monitor-containers
What to ALWAYS Do
- Reset compromised containers:
# If monitor detects issues bash /tmp/reset-oss.shDon’t attempt to repair them. Heck, maybe reset with some regularity anyway.
- Regularly update baselines:
# After legitimate updates rm /tmp/.container_baseline bash /tmp/monitor-containers.sh # Recreates baseline
Container Isolation Rules
| Container | Purpose | SSH Keys | Git Configs | Sensitive Data |
|---|---|---|---|---|
| penguin | Trusted development | Safe | Safe | Safe |
| oss | Untrusted testing | Never | Never | Never |
Git Security Matrix
| Operation | Penguin (Trusted) | OSS (Untrusted) | Method |
|---|---|---|---|
| Clone public repos | y | y | HTTPS |
| Clone private repos | y | ! | Deploy keys only |
| Push to repos | y | N | Never from OSS |
| Store SSH keys | y | N | Never |
| Store PATs | y | ! | Limited scope only |
| Agent forwarding | N/A | !️ | Brief sessions only |
Container Access Patterns
Daily workflow
- Terminal App -> penguin # Normal development
- Ctrl+Alt+T -> vsh termina -> bash /tmp/enter-oss.sh # Testing
- Or ssh from penguin to oss
Security monitoring
Ctrl+Alt+T -> vsh termina -> bash /tmp/monitor-containers.sh
- Never create shortcuts that bypass security
- Don’t alias direct access to OSS in penguin
- Don’t auto-start monitoring (review alerts manually)
Lessons from Production Use
Key Insight: The separation between Termina (VM host) and containers is crucial. Many security solutions try to work entirely within containers, but the real power comes from leveraging the host-level view.
What we learned:
- Container isolation is only as good as your monitoring for breaches
- Host-level monitoring provides better security visibility
- ChromeOS’s architecture is designed for this type of security model
Understanding the Monitoring System
The monitoring system watches for several types of compromise:
1. Binary Integrity Monitoring
I grant you this is underdeveloped at the point of this blog entry.
# Creates baseline hashes of critical binaries
for binary in /bin/bash /bin/sh /usr/bin/python3 /usr/bin/node /usr/bin/git; do
hash=$(lxc exec $container -- sha256sum $binary 2>/dev/null | awk '{print $1}')
echo "$container:$binary:$hash" >> ~/.container_baseline
done
What it detects: Supply chain attacks that modify system binaries
Real-world example: The 2024 Chalk npm package compromise replaced legitimate packages with malicious versions that could modify Node.js binaries or install backdoors. Our monitoring would detect such changes immediately.
Example alert:
!!! Modified: /usr/bin/python3
Expected: a1b2c3d4e5f6...
Current: x9y8z7w6v5u4...
2. Process Monitoring
# Shows CPU-intensive processes
lxc exec $container -- ps aux --sort=-%cpu | head -6
What it detects: Cryptocurrency miners, botnet activity, unexpected daemons
3. Network Connection Analysis
# Counts active connections
connections=$(lxc exec $container -- ss -tnp | grep ESTAB | wc -l)
What it detects: Data exfiltration, command & control communication, unexpected servers
4. File System Changes
# Finds recently modified system files
lxc exec $container -- find /bin /usr/bin /lib -xdev -type f -mmin -10
What it detects: System file tampering, backdoor installation
5. Hidden File Detection
# Searches for hidden files in temp directories
lxc exec $container -- find /tmp /var/tmp /dev/shm -name ".*" -type f
What it detects: Malware staging areas, credential harvesting tools
Advanced Usage Patterns
Testing Suspicious GitHub Repositories
# 1. Start monitoring (separate terminal)
bash /tmp/monitor-containers.sh
# 2. Enter untrusted container
bash /tmp/enter-oss.sh
# 3. In OSS container, test the repo
cd /workspace
git clone https://github.com/suspicious/repo.git
cd repo
./setup.sh # This runs in isolation
# 4. Monitor terminal will alert on any suspicious changes
# 5. If compromised, reset the container
exit # Leave OSS container
bash /tmp/reset-oss.sh
Development Tool Testing
# Test a new development tool safely
bash /tmp/enter-oss.sh
# In OSS container
curl -fsSL https://some-tool.com/install.sh | bash
some-new-tool --help
# Monitor for:
# - Modified binaries
# - Network connections
# - Hidden files
# - Unexpected processes
Multi-Container Workflow
# Terminal 1: Monitoring
bash /tmp/monitor-containers.sh
# Terminal 2: Trusted work
bash /tmp/enter-penguin.sh
# Do your normal development here
# Terminal 3: Untrusted testing
bash /tmp/enter-oss.sh
# Test random GitHub projects here
# Terminal 4: Status checking
bash /tmp/status.sh
Performance Considerations
Resource Limits
The OSS container is intentionally limited to prevent resource abuse:
# Current limits (modify as needed)
lxc config set oss limits.cpu 2 # 2 CPU cores max
lxc config set oss limits.memory 2GB # 2GB RAM max
Monitoring Overhead
- Monitoring script uses minimal resources
- Scans every 30 seconds (configurable)
- Focuses on security-critical changes only
- Can run continuously without impact
Container Reset Speed
# Full OSS container reset takes ~2-3 minutes
time bash /tmp/reset-oss.sh
# Includes: stop, delete, recreate, configure, install packages
Integration with Development Workflow
IDE Integration
You can configure your IDE to work with the container setup:
# VS Code with Remote-Containers
# Point to penguin container for trusted development
code --folder-uri vscode-remote://attached-container+penguin/path/to/project
# For untrusted code testing, always use terminal access
bash /tmp/enter-oss.sh
Git Configuration
# In penguin (trusted) - normal git config
git config --global user.name "Your Name"
git config --global user.email "your.email@domain.com"
# In OSS (untrusted) - minimal or fake config only
git config --global user.name "Test User"
git config --global user.email "test@example.com"
# Never configure real credentials
File Sharing Between Containers
# Share files from trusted to untrusted (one-way only)
# Copy from penguin to OSS for testing
lxc file push /path/in/penguin/container/file.txt oss/workspace/
# NEVER copy from OSS to penguin without verification
# Instead, manually recreate verified files in penguin
Extending the Security Model
Custom Monitoring Rules
Add your own detection rules to the monitoring script:
# Example: Monitor for specific file types
function check_crypto_miners() {
local container=$1
miners=$(lxc exec $container -- find /tmp -name "*mine*" -o -name "*crypto*" 2>/dev/null | wc -l)
if [ "$miners" -gt 0 ]; then
echo -e "${RED}!!! Potential cryptocurrency miner detected${NC}"
fi
}
Log Integration
# Enhanced logging in monitoring script
LOG_FILE="$HOME/container-security.log"
function log_alert() {
echo "$(date '+%Y-%m-%d %H:%M:%S') ALERT: $1" | tee -a "$LOG_FILE"
}
Conclusion
ChromeOS’s layered container architecture provides an excellent foundation for safely testing untrusted code. This setup gives you:
- True isolation: Each container is properly sandboxed
- Real-time monitoring: Immediate detection of compromise attempts
- Easy recovery: Quick container reset when needed
- Maintained productivity: Your trusted environment stays clean
The key insight is using ChromeOS’s existing security model rather than fighting it. By running the monitoring from Termina and isolating untrusted code in its own container, you get enterprise-grade security with developer-friendly workflows.
All that said, I’m unlikely to use it without the Terminal integration (see below). I’d like to open Terminal then click oss a line below penguin. Instead, I’m probably use a container in podman inside penguin - that seems hardened as a solution and workable today.
The Terminal App Limitation
CromeOS’ Terminal app’s inability to add custom container entries is a significant UX gap. The fact that only penguin appears as a clickable row means:
- No visual distinction between trusted/untrusted environments
- Can’t theme the OSS terminal differently (red background would be perfect!)
- Extra friction to access the untrusted container
- No way to know at a glance which container you’re in
The SSH workaround (penguin -> ssh dev@oss-ip) adds complexity to what should be a single click.
Security Implications
These UX limitations create real security risks:
- Command confusion: Without visual distinction, you might accidentally run trusted commands (like
git pushwith your real credentials) in the untrusted container - Increased attack surface: The SSH workaround opens network ports and adds authentication complexity that could be exploited
- Remote access temptation: Lack of remote access means you might be tempted to run untrusted code directly on your primary machine when traveling, defeating the entire security model
I would really want to be able to make a second container from within the Terminal app. The UX hints that it should be possible but the feature is not there. A huge shame given how incredibly strong the dev experience on ChromeBooks (that have enough RAM and SSD).
The Chrome Remote Desktop Problem
This is even more frustrating. A ChromeOS Flex machine effectively becomes an island because, only Windows and Mac are first-class choices for destination for Chrome Remote Desktop. HOST-OS Linux is a second class choice because one seems to need PhD-level understanding of X11/Wayland/DISPLAY. Multi-user possibilities might be one of complexities, and systemd could be on the “complicating” mix too.
Chrome Remote Desktop TO ChromeOS/Linux (the very platform Google controls!) is not supported at all.
Yet, Chrome Remote Desktop FROM ChromeOS to Windows or Mac is supported.
This means you can’t remotely access your secure container (in ChromeOS Flex on a spare PC) from your Chromebook when traveling, defeating much of the purpose of having a dedicated security testing environment. Potential Workarounds (all imperfect). Or from your Mac or Windows laptop which support Chrome Remoting as an origin just fine.
For Terminal access I could create a web-based terminal (ttyd or similar) in each container, accessible via different ports, but I’d rather be in my terminal of choice: Terminal
I have other VNC server in penguin or Guacamole, too, but I wish this were a mainstream feature of ChromeOS once you’ve enabled developer features.
The irony is that Google has all the pieces (Crostini, Chrome Remote Desktop, Terminal app) but hasn’t connected them properly. A simple “Add Container to Terminal” button and proper Chrome Remote Desktop support would solve everything. It’s particularly galling because ChromeOS is supposed to be the “simple, secure” option, yet these limitations push us toward complex workarounds that probably decrease security.
Googlers
And if any Googlers have got this far: can you have an explicit “Disable trackpad while typing” setting as macOS Sierra had. It was removed after sierra. Chromebooks have plastic chassis and mild weight adjacent to the trackpad while typing can cause a click at current pointer position.