- Posted on
- • Artificial Intelligence
Local Artificial Intelligence Security Best Practices
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Local Artificial Intelligence Security Best Practices on Linux (with Bash you can trust)
You grabbed a local LLM, pointed it at your documents, and magic happened. But is it safe? Local doesn’t automatically mean secure. Model weights are just files that can be tampered with, binaries change fast, and “offline” tools can still phone home. This post gives you a practical, Linux-first checklist to harden your local AI stack—so you can ship faster without shipping your data.
What you’ll get:
Why local AI security matters even when you never touch the cloud
3–5 concrete, Bash-centric steps you can apply today
Copy-pasteable install and command snippets for apt, dnf, and zypper
A self-contained example that runs a local model in a locked-down environment
Why this matters (even when you’re fully local)
Supply chain risk: Models and runtimes arrive as large blobs from the internet. If you don’t verify them, you don’t control them.
Data exfiltration: “Offline” tools sometimes fetch models, telemetry, or updates unless you block network egress.
Least privilege is not optional: AI apps tend to demand write access everywhere. Don’t let them.
Compliance-by-default: Prompt logs, training data, and outputs often include sensitive content. Secure-by-default prevents accidental leakage.
Install the tools we’ll use (apt, dnf, zypper)
Note: You don’t need every tool below for every step. Install only what you plan to use.
- Core utilities for verification and fetching:
# Debian/Ubuntu
sudo apt update
sudo apt install -y curl gnupg coreutils wget
# Fedora/RHEL
sudo dnf install -y curl gnupg2 coreutils wget
# openSUSE
sudo zypper install -y curl gpg2 coreutils wget
- Build tools (for compiling local runtimes like llama.cpp):
# Debian/Ubuntu
sudo apt update
sudo apt install -y build-essential cmake git
# Fedora/RHEL
sudo dnf install -y gcc gcc-c++ make cmake git
# openSUSE
sudo zypper install -y gcc gcc-c++ make cmake git
- Sandboxing and isolation:
# Debian/Ubuntu
sudo apt update
sudo apt install -y firejail bubblewrap
# Fedora/RHEL
sudo dnf install -y firejail bubblewrap
# openSUSE
sudo zypper install -y firejail bubblewrap
- Firewalls and egress control:
# Debian/Ubuntu
sudo apt update
sudo apt install -y nftables firewalld ufw
# Fedora/RHEL
sudo dnf install -y nftables firewalld ufw
# openSUSE
sudo zypper install -y nftables firewalld ufw
- Optional container runtime (for strong least-privilege sandboxes):
# Debian/Ubuntu
sudo apt update
sudo apt install -y podman
# Fedora/RHEL
sudo dnf install -y podman
# openSUSE
sudo zypper install -y podman
- Auditing/logging and secrets storage:
# Debian/Ubuntu
sudo apt update
sudo apt install -y auditd pass
# Fedora/RHEL
sudo dnf install -y audit pass
# openSUSE
sudo zypper install -y audit password-store
Tip: On RPM-based distros, the audit package is “audit” (service is auditd). On Debian/Ubuntu it’s “auditd”.
1) Verify what you run: provenance and integrity checks
Don’t ever run unverified binaries or load unverified model files.
- Verify checksums:
# If the publisher gives you a SHA256 file and the artifact:
sha256sum -c model.gguf.sha256
# or compare directly
sha256sum model.gguf
# Expect output to match the official publisher’s checksum.
- Verify GPG signatures when provided:
# Import publisher key (replace KEYID and URL as appropriate)
gpg --keyserver hkps://keys.openpgp.org --recv-keys KEYID
# or from a key file you trust:
curl -fsSLo pubkey.asc https://example.com/KEYID.asc
gpg --import pubkey.asc
# Verify the signature against the artifact:
gpg --verify artifact.tar.gz.sig artifact.tar.gz
- Prefer reproducible sources:
- Build from source when practical and check tagged releases.
- For model files, use official mirrors, and cross-check hashes from multiple announcements (release notes, website, Git).
Why it helps: This breaks supply-chain attacks where a malicious binary or model is swapped in transit or by a compromised mirror.
2) Run with least privilege and lock down the filesystem
Give your AI process its own unprivileged user, restrict writable paths, and avoid running as your desktop user.
- Create a dedicated system user and secure directories:
sudo useradd --system --create-home --shell /usr/sbin/nologin ai
sudo install -d -m 0750 -o ai -g ai /opt/ai-models
sudo install -d -m 0750 -o ai -g ai /var/log/ai
- Make model directories read-only:
sudo chown -R ai:ai /opt/ai-models
sudo chmod -R a-w /opt/ai-models
- Optional: run the workload in a container with no new privileges and a read-only root:
# Example with podman (adjust image and command to your runtime)
sudo -u ai podman run --rm \
--read-only --cap-drop ALL --security-opt=no-new-privileges \
-v /opt/ai-models:/models:ro -v /var/log/ai:/logs:rw \
localhost/your-llm-image:latest \
/app/your-llm-binary -m /models/model.gguf > /logs/run.log 2>&1
Why it helps: Even if an LLM runtime is compromised, it can’t write to your home, escalate easily, or tamper with model files.
3) Contain the network: default-deny egress for local AI
Local models shouldn’t leak. If you truly run offline, cut the cord. If you need a local HTTP API, restrict to loopback only.
- Zero-network runtime with Firejail:
# Run a binary with no network at all
sudo -u ai firejail --quiet --net=none /path/to/llm-binary -m /opt/ai-models/model.gguf
- Allow only localhost communications (nftables per-user egress block):
# Enable and start nftables if not already
sudo systemctl enable --now nftables
# Find the ai user’s UID
AI_UID=$(id -u ai)
# Create a dedicated table and output chain if not present
sudo nft add table inet ai_out 2>/dev/null || true
sudo nft add chain inet ai_out output '{ type filter hook output priority 0 ; }' 2>/dev/null || true
# Drop all non-loopback egress for ai user
sudo nft add rule inet ai_out output skuid $AI_UID oifname != "lo" drop
# Verify rules
sudo nft list ruleset
- If you need to expose an API only on localhost, bind explicitly:
# Example local server binding only to 127.0.0.1
/path/to/llm-server --host 127.0.0.1 --port 11434
- Optional firewall services:
- Firewalld (mostly inbound control):
sudo systemctl enable --now firewalld - UFW (simple inbound control on Debian/Ubuntu):
sudo ufw enable
- Firewalld (mostly inbound control):
Why it helps: Many “offline” tools auto-fetch models, send telemetry, or expose ports. Default-deny outbound guarantees they can’t.
4) Secrets and sensitive data hygiene
Treat prompts, embeddings, and artifacts as sensitive. Avoid hardcoding secrets and reduce exposure.
- Keep API keys out of source and shell history:
# Use a .env file with strict permissions
install -m 0600 /dev/null .env
echo 'OPENAI_API_KEY=sk-...redacted...' >> .env
- Prefer password-store (pass) for secrets:
# Initialize pass (requires GPG key)
gpg --full-generate-key
pass init "Your Name <you@example.com>"
pass insert ai/openai_api_key
# Use it at runtime (avoid exporting globally if possible)
OPENAI_API_KEY="$(pass ai/openai_api_key)" your-ai-command ...
- Minimize data sprawl:
- Store working sets under one secured directory (e.g., /opt/ai-work).
- Log to a controlled location (e.g., /var/log/ai) with limited read access.
- Consider disabling core dumps for sensitive processes:
ulimit -c 0
Why it helps: Secrets and sensitive data are easy to leak via logs, dumps, and shell history. Default secure locations and tight perms help a lot.
5) Audit, monitor, and update
You need a trail for what changed and when—especially model files and binaries.
- Enable auditing and watch critical paths:
# Start auditd (or audit on RPM-based)
sudo systemctl enable --now auditd || sudo systemctl enable --now auditd.service
# Watch model directory writes and attribute changes
sudo auditctl -w /opt/ai-models -p wa -k ai_models
# Watch execution of your AI binary
sudo auditctl -w /usr/local/bin/llm-binary -p x -k ai_exec
- Periodic updates:
- Rebuild from trusted source periodically.
- Re-verify checksums/signatures when updating models.
- Keep your sandboxing tools current (firejail, bubblewrap, podman).
Why it helps: Detects tampering, supports incident response, and makes maintenance intentional rather than accidental.
Real-world walk-through: offline llama.cpp, least privilege, and logs
Below is a compact example to build llama.cpp, verify a model, and run it offline as a dedicated user with logging.
1) Build llama.cpp:
# Install build deps (see section above), then:
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
mkdir build && cd build
cmake ..
cmake --build . -j
# Assume the binary is build/bin/llama-cli or build/bin/main (adjust as needed)
2) Create user and directories:
sudo useradd --system --create-home --shell /usr/sbin/nologin ai
sudo install -d -m 0750 -o ai -g ai /opt/ai-models /var/log/ai
3) Fetch and verify a model (example with SHA256):
cd /tmp
curl -fSLO https://example.com/models/MyModel.gguf
curl -fSLO https://example.com/models/MyModel.gguf.sha256
sha256sum -c MyModel.gguf.sha256
sudo mv MyModel.gguf /opt/ai-models/
sudo chown ai:ai /opt/ai-models/MyModel.gguf
sudo chmod a-w /opt/ai-models/MyModel.gguf
4) Run completely offline with Firejail, as ai, with logs:
cd /path/to/llama.cpp/build/bin
sudo -u ai firejail --quiet --net=none \
./llama-cli -m /opt/ai-models/MyModel.gguf 2>&1 | sudo tee -a /var/log/ai/llama.log
5) Optional: block all non-loopback egress at the kernel level for ai:
sudo systemctl enable --now nftables
AI_UID=$(id -u ai)
sudo nft add table inet ai_out 2>/dev/null || true
sudo nft add chain inet ai_out output '{ type filter hook output priority 0 ; }' 2>/dev/null || true
sudo nft add rule inet ai_out output skuid $AI_UID oifname != "lo" drop
Now you’ve got a local model: verified, running as an unprivileged user, with no network, and logs captured centrally.
Quick checklist you can adapt
Do I verify every model and binary (checksum/signature)?
Is the runtime unprivileged, with read-only model dirs?
Is outbound network blocked by default for AI processes?
Are secrets kept out of code/history and restricted by file perms?
Are model directories and AI binaries under audit/watch?
Conclusion and next steps
Local AI is powerful—but only if you keep control. Start by verifying artifacts, isolating execution, denying egress, and auditing key paths. Pick one area to improve today:
If you haven’t: create the ai user and move models into /opt/ai-models with read-only perms.
If you’re online by default: add the nftables rule or run with firejail --net=none.
If you lack visibility: enable auditd and add two watch rules.
When your local stack is trustworthy, you can iterate faster with less risk. If you want a ready-to-run hardening script or a systemd unit template for your favorite runtime, ask and I’ll share one tailored to your distro and model.