Skip to content

Commit d299c33

Browse files
committed
Improve setup UI flow
1 parent 5c3991f commit d299c33

8 files changed

Lines changed: 782 additions & 295 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ All CI jobs are defined in [`.github/workflows/ci.yml`](.github/workflows/ci.yml
316316
| Job | Workflow Link | What It Proves |
317317
|-----|--------------|---------------|
318318
| `go-build-and-test` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | 428 Go tests across 9 services with `-race` (build, test, vet) |
319-
| `python-test` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | 1,133 Python tests (unit/integration + adversarial/acceptance), ruff lint, bandit security scan (enforced on HIGH/HIGH), mypy type checking |
319+
| `python-test` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | 1,136 Python tests (unit/integration + adversarial/acceptance), ruff lint, bandit security scan (enforced on HIGH/HIGH), mypy type checking |
320320
| `appsec-lint` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | Hadolint for container build files and Semgrep project security rules |
321321
| `security-regression` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | Adversarial test suite: prompt injection, policy bypass, containment, recovery |
322322
| `supply-chain-verify` | [View job](https://github.com/SecAI-Hub/SecAI_OS/actions/workflows/ci.yml) | SBOM generation via Syft, cosign availability, provenance keywords in release/build workflows |
@@ -338,7 +338,7 @@ All CI jobs are defined in [`.github/workflows/ci.yml`](.github/workflows/ci.yml
338338
| [API Reference](docs/api.md) | HTTP API for all services |
339339
| [Policy Schema](docs/policy-schema.md) | Full policy.yaml schema reference |
340340
| [Security Status](docs/security-status.md) | Implementation status of all 54 milestones |
341-
| [Test Matrix](docs/test-matrix.md) | Test coverage: 1,561 tests across Go and Python (see [test-counts.json](docs/test-counts.json)) |
341+
| [Test Matrix](docs/test-matrix.md) | Test coverage: 1,564 tests across Go and Python (see [test-counts.json](docs/test-counts.json)) |
342342
| [Compatibility Matrix](docs/compatibility-matrix.md) | GPU, VM, and hardware support |
343343
| [Security Test Matrix](docs/security-test-matrix.md) | Security feature test coverage |
344344
| [FAQ](docs/faq.md) | Common questions |
@@ -462,7 +462,7 @@ for svc in airlock registry tool-firewall gpu-integrity-watch mcp-firewall \
462462
(cd services/$svc && go test -v -race ./...)
463463
done
464464

465-
# Python tests (1,133 total)
465+
# Python tests (1,136 total)
466466
python -m pip install -r requirements-ci.txt
467467
PYTHONPATH=services python -m pytest tests/ -v
468468

@@ -564,7 +564,7 @@ services/
564564
search-mediator/ Python -- Tor-routed web search (:8485)
565565
ui/ Python/Flask -- Web UI (:8480)
566566
common/ Python -- Shared utilities (audit, auth, mlock)
567-
tests/ 1,133 Python tests, 428 Go tests (1,561 total)
567+
tests/ 1,136 Python tests, 428 Go tests (1,564 total)
568568
docs/ Architecture, API, threat model, install guides
569569
schemas/ OpenAPI spec, JSON Schema for config files
570570
examples/ Task-oriented walkthroughs

docs/security-status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ All M5 security assurance criteria are met. The controls below have been impleme
1919
| Tool Firewall, default-deny policy | Implemented | M4 | Go tool-firewall service on :8475, default-deny egress |
2020
| Online Airlock, sanitization | Implemented | M5 | Go airlock service on :8490, disabled by default (privacy risk) |
2121
| Systemd sandboxing, kernel hardening, nftables | Implemented | M6 | Systemd unit hardening, sysctl tuning, nftables rules |
22-
| CI/CD, Go/Python tests, shellcheck | Implemented | M7 | GitHub Actions ci.yml. See docs/test-counts.json for current counts (428 Go, 1133 Python as of 2026-04-29) |
22+
| CI/CD, Go/Python tests, shellcheck | Implemented | M7 | GitHub Actions ci.yml. See docs/test-counts.json for current counts (428 Go, 1136 Python as of 2026-04-29) |
2323
| Image/video generation, diffusion worker | Implemented | M8 | Diffusion worker for image generation workloads |
2424
| Multi-GPU support (NVIDIA/AMD/Intel/Apple) | Implemented | M9 | CUDA, ROCm/HIP, XPU/Vulkan, Metal/MPS backends |
2525
| Tor-routed search, SearXNG, PII stripping | Implemented | M10 | Search mediator with Tor routing and PII redaction |
@@ -60,7 +60,7 @@ All M5 security assurance criteria are met. The controls below have been impleme
6060
| Production readiness hardening | Implemented | M45 | Incident recorder file-backed persistence (survives restarts), graceful shutdown (SIGTERM/SIGINT with connection draining) for all 9 Go services, HTTP server timeouts for mcp-firewall and gpu-integrity-watch, systemd production hardening (TimeoutStartSec, TimeoutStopSec, StartLimitInterval, StartLimitBurst) for all 12 daemon units, first-boot health validation script, audit log rotation via logrotate, CI dependency vulnerability scanning (govulncheck + pip-audit), production operations guide (upgrade, key rotation, capacity limits, monitoring) |
6161
| Operational maturity | Implemented | M46 | Bootstrap trust gap fix (cosign verify before unverified rebase, documented trust gap rationale), CI runs on all changes (removed blanket paths-ignore for .md files), Python quality gates (ruff lint + bandit security scan + split test suites into unit/integration and adversarial/acceptance), docs-validation CI job (broken link detection, required docs check, test-counts.json validation), production-readiness checklist (formal release gate), SLOs (availability/latency/correctness targets + alerting thresholds), release channel policy (stable/candidate/dev + versioning + upgrade paths + security patch SLA), support lifecycle (hardware matrix, driver versions, support windows, deprecation policy, scope boundaries), CI evidence table with current job descriptions and workflow links, sample verification output for verify-release.sh |
6262
| CI enforcement hardening | Implemented | M47 | Enforced vulnerability scanning: bandit fails CI on HIGH-severity/HIGH-confidence findings, govulncheck fails on unwaived Go vulns, pip-audit fails on unwaived Python vulns. Waiver mechanism (`.github/vuln-waivers.json`) with mandatory expiry dates for reviewed/accepted findings. mypy type checking gate for security-sensitive services (common, agent, quarantine, ui). Pinned reproducible Python CI dependencies (`requirements-ci.txt`). Go 1.23->1.25 upgrade fixing 12 stdlib CVEs (crypto/tls, crypto/x509, encoding/asn1, net/url, os). Flask 3.1.1->3.1.3 (GHSA-68rp-wp8r-4726). Verification-first bootstrap documentation (signed rebase as default quickstart, unverified bootstrap moved to labeled recovery section). |
63-
| Production hardening | Implemented | M48 | Build script fail-closed for required services, quarantine scanners, search mediator, and signing policy material; final binary verification gate; incident store fsync (f.Sync() before close on both incident persistence and audit log writes); GPU backend metadata recording (`/etc/secure-ai/gpu-backend.json` written at build time with backend/version/timestamp); llama-server watchdog (Type=notify wrapper with startup health gate + WatchdogSec=30 continuous monitoring); model catalog externalization (`/etc/secure-ai/model-catalog.yaml` with YAML loading + hardcoded fallback); circuit breaker for Python services; post-upgrade model verification in Greenboot; cosign key rotation documentation. Current automated suite: 428 Go + 1133 Python tests (1,561 total). |
63+
| Production hardening | Implemented | M48 | Build script fail-closed for required services, quarantine scanners, search mediator, and signing policy material; final binary verification gate; incident store fsync (f.Sync() before close on both incident persistence and audit log writes); GPU backend metadata recording (`/etc/secure-ai/gpu-backend.json` written at build time with backend/version/timestamp); llama-server watchdog (Type=notify wrapper with startup health gate + WatchdogSec=30 continuous monitoring); model catalog externalization (`/etc/secure-ai/model-catalog.yaml` with YAML loading + hardcoded fallback); circuit breaker for Python services; post-upgrade model verification in Greenboot; cosign key rotation documentation. Current automated suite: 428 Go + 1136 Python tests (1,564 total). |
6464
| Signed-first install path | Implemented | M49 | Signed bootstrap script (`secai-bootstrap.sh`) configures container signing policy (policy.json + registries.d + cosign public key) before first rebase -- eliminates unverified transport from production install path. Digest-pinned install flow (CI publishes image digest in build summary and release assets). First-boot setup wizard (interactive verification of image integrity, transport, vault setup, TPM2 sealing, health check). Signing policy files baked into OS image (`/etc/pki/containers/secai-cosign.pub`, `/etc/containers/registries.d/secai-os.yaml`, policy.json merge in build script). Recovery/dev bootstrap path separated into dedicated doc with clear warnings. |
6565
| Production operations package | Implemented | M50 | Backup script (`secai-backup.sh`) with full/config/logs/keys categories, age/gpg encryption, internal SHA256 manifest, LUKS header backup. Restore script (`secai-restore.sh`) with integrity verification, staging extraction, double-confirmation LUKS header restore, post-restore health check. Production operations doc extended with rollback decision matrix (Greenboot auto-rollback triggers + manual criteria), 5 break-glass recovery procedures (token loss, attestation failure, Level 1 panic lockout, signing policy break, Greenboot exhaustion), formal data retention policy (7 data classes with retention periods, disk capacity thresholds at 70/80/90/95%). |
6666
| Stronger observability | Implemented | M51 | Unified appliance health dashboard (trusted/degraded/recovery_required state derived from runtime attestor + integrity monitor + incident recorder). Live SLO compliance monitoring (in-process tracker measuring uptime % and P95 latency against docs/slos.md targets, 7-day rolling window). Webhook alerting hooks for containment events (fire-and-forget POST with retry, configurable per-event-type filtering in incident-containment.yaml). Forensic bundle export wired to HTTP mux (was implemented but unregistered), enriched with real audit log entries and policy digest, accessible via UI download button, Flask proxy, and CLI script (`secai-forensic.sh`). Recovery ceremony endpoints also wired (ack, reattest, status). |

docs/security-test-matrix.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Last updated: 2026-04-29
1919
| Emergency wipe | tests/test_emergency_wipe.py | Python | 65 | 3-level panic escalation, secure deletion, vault destruction, recovery prevention |
2020
| Update verification | tests/test_update_rollback.py | Python | 74 | Signature verification, rollback triggers, version pinning, recovery |
2121
| Vault auto-lock | tests/test_vault_watchdog.py | Python | 21 | Idle detection, lock timer, UI lock/unlock controls |
22-
| Web UI security | tests/test_ui.py, tests/test_ui_cookies.py, tests/test_ui_file_handling.py | Python | 79 total | Route protection, input validation, CSP/cookie headers, upload/path handling |
22+
| Web UI security | tests/test_ui.py, tests/test_ui_cookies.py, tests/test_ui_file_handling.py | Python | 82 total | Route protection, input validation, CSP/cookie headers, setup completion, upload/path handling |
2323
| Tool firewall | services/tool-firewall/*_test.go | Go | 15 | Default-deny policy, rule evaluation, egress filtering |
2424
| Airlock | services/airlock/*_test.go | Go | 11 | Request sanitization, policy enforcement, disabled-by-default |
2525
| Trusted registry | services/registry/*_test.go | Go | 22 | Hash pinning, cosign verification, model fetch authorization |
@@ -72,15 +72,15 @@ Last updated: 2026-04-29
7272
|------|-------|-------|
7373
| Memory protection | 37 | Prevents secrets from leaking to disk |
7474
| Vault auto-lock | 21 | Automatic vault lock on idle |
75-
| Web UI security | 79 total | CSRF, CSP, cookie flags, input validation, upload/path handling |
75+
| Web UI security | 82 total | CSRF, CSP, cookie flags, setup completion, input validation, upload/path handling |
7676

7777
## Total Test Counts
7878

7979
| Language | Current Automated Tests | Source of Truth |
8080
|----------|--------------------------|-----------------|
81-
| Python | 1133 | `docs/test-counts.json` and `pytest --collect-only` |
81+
| Python | 1136 | `docs/test-counts.json` and `pytest --collect-only` |
8282
| Go | 428 | `docs/test-counts.json` and `go test -v -count=1 ./...` |
83-
| **Total** | **1561** | Enforced by `.github/scripts/check-test-counts.sh` |
83+
| **Total** | **1564** | Enforced by `.github/scripts/check-test-counts.sh` |
8484

8585
Security coverage overlaps heavily with functional coverage, so the feature tables above use exact file or service totals rather than attempting to split each test into exclusive "security" and "non-security" buckets.
8686

docs/test-counts.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"incident-recorder": 97
1313
},
1414
"go_total": 428,
15-
"python_total": 1133,
16-
"grand_total": 1561
15+
"python_total": 1136,
16+
"grand_total": 1564
1717
}

docs/test-matrix.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Last updated: 2026-04-29
1212
| Language | Test Count | Runner |
1313
|----------|-----------|--------|
1414
| Go | 428 | `go test -race ./...` |
15-
| Python | 1133 | `pytest` |
15+
| Python | 1136 | `pytest` |
1616
| Shell | CI-scoped scripts plus Makefile target for all repo shell scripts | `shellcheck` |
1717

1818
## Go Tests (428 total)
@@ -29,7 +29,7 @@ Last updated: 2026-04-29
2929
| Integrity Monitor | services/integrity-monitor/ | 50 | Baseline computation, continuous scanning, violation detection, state machine, HMAC baselines, incident-recorder integration |
3030
| Incident Recorder | services/incident-recorder/ | 97 | Incident creation, auto-containment, lifecycle management, severity ranking, policy loading, containment execution, enforcement chain integration, recovery ceremony, severity escalation, forensic bundle export (M43), persistence durability (fsync) |
3131

32-
## Python Tests (1133 total)
32+
## Python Tests (1136 total)
3333

3434
| Test File | Location | Tests | Description |
3535
|-----------|----------|-------|-------------|
@@ -63,7 +63,7 @@ Last updated: 2026-04-29
6363
| test_search.py | tests/ | 36 | Search mediator, PII stripping, injection detection |
6464
| test_secure_boot.py | tests/ | 38 | Secure boot and measured boot behavior |
6565
| test_traffic_analysis.py | tests/ | 41 | Padding, timing jitter, dummy traffic generation |
66-
| test_ui.py | tests/ | 56 | Flask web UI routes, rendering, input handling, model catalog loading |
66+
| test_ui.py | tests/ | 59 | Flask web UI routes, rendering, setup completion, input handling, model catalog loading |
6767
| test_ui_cookies.py | tests/ | 11 | UI cookie security attributes |
6868
| test_ui_file_handling.py | tests/ | 12 | UI file upload and path handling |
6969
| test_update_rollback.py | tests/ | 74 | Signed update verification, rollback triggers, recovery |

services/ui/ui/app.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,70 @@ def has_models() -> bool:
733733
return False
734734

735735

736+
def _is_gguf_model_record(model: object) -> bool:
737+
if not isinstance(model, dict):
738+
return False
739+
model_format = str(model.get("format") or "").lower()
740+
filename = str(model.get("filename") or model.get("name") or "").lower()
741+
return model_format == "gguf" or filename.endswith(".gguf")
742+
743+
744+
def has_chat_model() -> bool:
745+
try:
746+
resp = requests.get(f"{REGISTRY_URL}/v1/models", timeout=2)
747+
models = resp.json()
748+
return isinstance(models, list) and any(
749+
_is_gguf_model_record(model) for model in models
750+
)
751+
except Exception:
752+
return False
753+
754+
755+
def _write_setup_marker(profile: str) -> None:
756+
"""Mark the first-run setup flow as complete."""
757+
SECURE_AI_ROOT.mkdir(parents=True, exist_ok=True)
758+
marker = SECURE_AI_ROOT / ".initialized"
759+
tmp_marker = SECURE_AI_ROOT / f".initialized.{os.getpid()}.tmp"
760+
payload = {
761+
"completed_at": time.time(),
762+
"deployment_mode": _deployment_mode(),
763+
"profile": profile,
764+
}
765+
with open(tmp_marker, "w", encoding="utf-8") as f:
766+
json.dump(payload, f, sort_keys=True)
767+
f.write("\n")
768+
f.flush()
769+
os.fsync(f.fileno())
770+
os.chmod(tmp_marker, 0o600)
771+
os.replace(tmp_marker, marker)
772+
773+
774+
@app.route("/api/setup/complete", methods=["POST"])
775+
def setup_complete():
776+
"""Complete the first-run setup flow and route the user to chat."""
777+
data = request.get_json(silent=True) or {}
778+
active, locked = _read_active_profile()
779+
profile = data.get("profile") or active
780+
if profile not in VALID_PROFILES:
781+
return jsonify({"error": f"invalid profile: {profile}"}), 400
782+
if (locked or _is_sandbox_deployment()) and profile != active:
783+
return jsonify({"error": "profile does not match active runtime"}), 409
784+
if not has_chat_model():
785+
return jsonify({"error": "GGUF chat model required"}), 409
786+
787+
try:
788+
_write_setup_marker(profile)
789+
except OSError:
790+
log.exception("failed to write setup marker")
791+
return jsonify({"error": "failed to complete setup"}), 500
792+
793+
_ui_audit.append("setup_complete", {
794+
"deployment_mode": _deployment_mode(),
795+
"profile": profile,
796+
})
797+
return jsonify({"success": True, "redirect": "/chat", "profile": profile})
798+
799+
736800
def load_appliance_config() -> dict:
737801
try:
738802
with open(APPLIANCE_CONFIG) as f:
@@ -745,7 +809,7 @@ def load_appliance_config() -> dict:
745809

746810
@app.route("/")
747811
def index():
748-
if is_first_boot() or not has_models():
812+
if is_first_boot() or not has_chat_model():
749813
return render_template("setup.html")
750814
return render_template("index.html", active_page="chat")
751815

0 commit comments

Comments
 (0)