Skip to content

Commit 38213ea

Browse files
SecAI-Hubclaude
andcommitted
Epic 4/5: Secure-by-default profiles + minimize attack surface
Profile system with three named profiles controlling service activation, network stance, and agent mode: - offline_private (default): no network, core services only - research: Tor-routed web search, airlock enabled - full_lab: all services including diffusion path unit Design: - Profile definitions baked in appliance.yaml (immutable) - Active selection stored in /var/lib/secure-ai/state/profile.json (root:root 0644, atomic writes, corrupted → falls back to offline_private) - Operator override at /etc/secure-ai/local.d/profile.yaml is a hard lock that disables UI profile changes (403) - Privilege boundary: UI writes request file, systemd path unit triggers privileged apply-profile.sh oneshot (same pattern as diffusion activation) - Transactional: apply → validate → rollback-on-failure - Audit entries for all profile changes Attack surface minimization (Epic 5): - Moved secure-ai-enable-diffusion.path from enabled to disabled in recipe.yml (only activated by full_lab profile via apply-profile.sh) - Airlock, tor, searxng, search-mediator already disabled by default UI changes: - Profile badge in topbar (green/yellow/accent by profile) - Profile selection step in setup wizard (Step 1) - Profile API: GET /api/profile, POST /api/profile/preview, POST /api/profile/select, GET /api/profile/status Tests: 32 new tests in test_profile_system.py covering definitions, service-unit mapping, agent safety in offline_private, apply-profile.sh structure, systemd units, API endpoints, and recipe integration. Also fixes: CI image-ref-consistency job self-exclusion for string literals. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8b5d05f commit 38213ea

11 files changed

Lines changed: 1045 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,16 @@ jobs:
183183
184184
ERRORS=0
185185
for pattern in "${WRONG_PATTERNS[@]}"; do
186+
# Exclude files that legitimately contain wrong patterns as string
187+
# literals for detection purposes (this CI job + the consistency test)
186188
MATCHES=$(grep -rn \
187189
--include='*.sh' --include='*.py' --include='*.yaml' \
188190
--include='*.yml' --include='*.md' --include='*.json' \
189-
"$pattern" . 2>/dev/null | grep -v '.git/' | grep -v 'node_modules/' || true)
191+
--exclude='ci.yml' \
192+
--exclude='test_image_ref_consistency.py' \
193+
"$pattern" . 2>/dev/null \
194+
| grep -v '.git/' | grep -v 'node_modules/' \
195+
|| true)
190196
if [ -n "$MATCHES" ]; then
191197
echo "::error::Found wrong image reference '$pattern':"
192198
echo "$MATCHES"

files/system/etc/secure-ai/config/appliance.yaml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,130 @@ logging:
209209
store_raw_prompts: false
210210
store_raw_responses: false
211211
audit_tool_calls: true
212+
213+
# ---------------------------------------------------------------------------
214+
# Runtime profiles — control which optional services are active and the
215+
# network/agent posture. Profile DEFINITIONS are baked into the image
216+
# (immutable). The active selection lives in the writable runtime state
217+
# at /var/lib/secure-ai/state/profile.json (root:root 0644, atomic writes).
218+
#
219+
# An operator can hard-lock the profile by placing a file at
220+
# /etc/secure-ai/local.d/profile.yaml — when present it overrides the
221+
# runtime state and disables UI profile changes (403).
222+
#
223+
# On missing/malformed/invalid profile.json the system falls back to
224+
# offline_private (safest default) and emits an audit alert.
225+
# ---------------------------------------------------------------------------
226+
profile:
227+
# Default profile for new installs (used when no profile.json exists)
228+
default: "offline_private"
229+
230+
definitions:
231+
offline_private:
232+
description: "Maximum isolation — no network, core services only"
233+
mode: "local-only"
234+
session_mode: "offline-only"
235+
agent_mode: "offline_only"
236+
rationale: >
237+
Agent remains active in offline_only mode because it provides useful
238+
local automation (file management, workspace tasks) with no network
239+
access, hard budget limits, and loopback-only IPC. The agent service
240+
unit uses PrivateNetwork=yes and the nftables rules block all egress.
241+
policy.yaml network.runtime_egress=deny cannot be overridden by agent
242+
mode. This is verified by test_profile_system.py.
243+
services_enabled:
244+
# Tier 1: Security infrastructure (always on)
245+
- secure-ai-runtime-attestor.service
246+
- secure-ai-integrity-monitor.service
247+
- secure-ai-incident-recorder.service
248+
- secure-ai-policy-engine.service
249+
- secure-ai-boot-verify.service
250+
- secure-ai-vault-watchdog.service
251+
- secure-ai-health-check.service
252+
- secure-ai-gpu-integrity-watch.service
253+
- secure-ai-mcp-firewall.service
254+
# Tier 2: Core function
255+
- secure-ai-ui.service
256+
- secure-ai-registry.service
257+
- secure-ai-tool-firewall.service
258+
- secure-ai-agent.service
259+
- secure-ai-quarantine-watcher.service
260+
- secure-ai-inference.service
261+
services_disabled:
262+
- secure-ai-diffusion.service
263+
- secure-ai-enable-diffusion.path
264+
- secure-ai-airlock.service
265+
- secure-ai-tor.service
266+
- secure-ai-searxng.service
267+
- secure-ai-search-mediator.service
268+
269+
research:
270+
description: "Privacy-preserving research — Tor-routed web search"
271+
mode: "online-augmented"
272+
session_mode: "normal"
273+
agent_mode: "online_assisted"
274+
rationale: >
275+
Enables the search stack (SearXNG + Tor + search-mediator) and the
276+
airlock for filtered outbound access. All web traffic is Tor-routed
277+
with PII stripping and differential privacy (decoy queries). Agent
278+
can use search-assisted tools but only through the airlock.
279+
services_enabled:
280+
# Everything from offline_private plus search/airlock stack
281+
- secure-ai-runtime-attestor.service
282+
- secure-ai-integrity-monitor.service
283+
- secure-ai-incident-recorder.service
284+
- secure-ai-policy-engine.service
285+
- secure-ai-boot-verify.service
286+
- secure-ai-vault-watchdog.service
287+
- secure-ai-health-check.service
288+
- secure-ai-gpu-integrity-watch.service
289+
- secure-ai-mcp-firewall.service
290+
- secure-ai-ui.service
291+
- secure-ai-registry.service
292+
- secure-ai-tool-firewall.service
293+
- secure-ai-agent.service
294+
- secure-ai-quarantine-watcher.service
295+
- secure-ai-inference.service
296+
- secure-ai-airlock.service
297+
- secure-ai-tor.service
298+
- secure-ai-searxng.service
299+
- secure-ai-search-mediator.service
300+
services_disabled:
301+
- secure-ai-diffusion.service
302+
- secure-ai-enable-diffusion.path
303+
304+
full_lab:
305+
description: "Full capability lab — all services available"
306+
mode: "online-augmented"
307+
session_mode: "normal"
308+
agent_mode: "standard"
309+
rationale: >
310+
All services enabled including diffusion activation path unit and
311+
the full search/airlock stack. Network traffic is filtered through
312+
the airlock (not direct). Suitable for evaluation, research, and
313+
model testing where maximum capability is needed.
314+
services_enabled:
315+
# All services
316+
- secure-ai-runtime-attestor.service
317+
- secure-ai-integrity-monitor.service
318+
- secure-ai-incident-recorder.service
319+
- secure-ai-policy-engine.service
320+
- secure-ai-boot-verify.service
321+
- secure-ai-vault-watchdog.service
322+
- secure-ai-health-check.service
323+
- secure-ai-gpu-integrity-watch.service
324+
- secure-ai-mcp-firewall.service
325+
- secure-ai-ui.service
326+
- secure-ai-registry.service
327+
- secure-ai-tool-firewall.service
328+
- secure-ai-agent.service
329+
- secure-ai-quarantine-watcher.service
330+
- secure-ai-inference.service
331+
- secure-ai-airlock.service
332+
- secure-ai-tor.service
333+
- secure-ai-searxng.service
334+
- secure-ai-search-mediator.service
335+
- secure-ai-enable-diffusion.path
336+
services_disabled:
337+
# Diffusion worker itself stays disabled — activated on-demand via path unit
338+
- secure-ai-diffusion.service
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Unit]
2+
Description=Watch for profile change request from UI
3+
4+
[Path]
5+
PathExists=/run/secure-ai-ui/profile-request
6+
7+
[Install]
8+
WantedBy=multi-user.target
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description=SecAI Profile Applicator (one-shot, privileged)
3+
After=secure-ai-registry.service
4+
5+
[Service]
6+
Type=oneshot
7+
ExecStart=/usr/libexec/secure-ai/apply-profile.sh
8+
ExecStopPost=-/bin/rm -f /run/secure-ai-ui/profile-request
9+
TimeoutStartSec=120
10+
11+
# Hardening — this service needs systemctl access but nothing else exotic
12+
ProtectHome=yes
13+
PrivateTmp=yes
14+
ProtectKernelTunables=yes
15+
ProtectKernelModules=yes
16+
ProtectControlGroups=yes
17+
RestrictRealtime=yes
18+
LockPersonality=yes
19+
MemoryDenyWriteExecute=yes

0 commit comments

Comments
 (0)