security: RBAC viewer role, identity label length, error sanitization

- RBAC: Viewer role changed from prefix "system." to explicit allowlist
  of safe read-only methods. Prevents Viewer access to system.factory-reset,
  system.shutdown, system.reboot, system.disk-cleanup.
- identity.create: Name/label param now enforces max 100 chars.
- sanitize_error_message: Changed from contains() to starts_with() for
  prefix matching, preventing internal errors that happen to contain
  user-facing keywords from leaking through.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 22:37:08 +00:00
parent 2c5180bfdc
commit 7cf87efbaa
3 changed files with 13 additions and 5 deletions

View File

@@ -60,8 +60,11 @@ impl RpcHandler {
let name = params
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("Personal")
.to_string();
.unwrap_or("Personal");
if name.len() > 100 {
anyhow::bail!("Identity name must be 100 characters or fewer");
}
let name = name.to_string();
let purpose_str = params
.get("purpose")

View File

@@ -87,7 +87,7 @@ fn sanitize_error_message(msg: &str) -> String {
"Session",
];
for prefix in &user_facing_prefixes {
if msg.starts_with(prefix) || msg.contains(prefix) {
if msg.starts_with(prefix) {
// Truncate long messages and strip file paths
let sanitized = msg.replace("/var/lib/archipelago/", "[data]/")
.replace("/usr/local/bin/", "[bin]/")

View File

@@ -32,8 +32,13 @@ impl UserRole {
match self {
UserRole::Admin => true,
UserRole::Viewer => {
// Read-only methods
method.starts_with("system.")
// Read-only system methods (explicit allowlist — NOT prefix "system."
// which would grant access to system.factory-reset, system.shutdown, etc.)
method == "system.stats"
|| method == "system.processes"
|| method == "system.temperature"
|| method == "system.disk-status"
|| method == "system.detect-usb-devices"
|| method.starts_with("node.")
|| method.starts_with("federation.list")
|| method.starts_with("dwn.status")