feat(hh): P4 — permissions (owner/superuser + drive delegation) ⛧
App-level RBAC over the single shared PTY, enforced by the broker:
- The sandbox launcher becomes owner (superuser) and first driver; broadcasts
an encrypted {"_perm":"acl",owner,drivers} frame all clients track.
- /grant <user> and /revoke <user> (owner-only) delegate/withdraw drive rights
= delegating control of the shared (root) shell — the superuser-delegation ask.
- The broker honors {"_sbx":"input"} only from permitted drivers, keyed on the
SERVER-AUTHENTICATED sender (the message username the Sanic session stamps),
not a spoofable self-asserted field — closes the spec's identity-binding gap.
- F2 is gated: non-drivers get 'ask the owner to /grant you'; revoke drops drive
live. Roster shows roles: ⛧ owner · ◆ driver · • member.
Verified live (two TUIs): member blocked pre-grant, owner /grant member, member
then drives a command in the sandbox; roster + permission messages all correct.
cargo test: 4 pass.
Note: per the single-shared-PTY decision, drive-grant *is* the permission model;
per-user unix accounts/sudo would need per-user shells (future mode).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+9
-1
@@ -116,7 +116,15 @@ fn draw_roster(f: &mut Frame, area: ratatui::layout::Rect, app: &App, theme: &Th
|
||||
.iter()
|
||||
.map(|u| {
|
||||
let me = u.username == app.me;
|
||||
let mark = if me { "⛧" } else { "•" };
|
||||
// ⛧ owner/superuser · ◆ may drive · • member
|
||||
let owner = app.owner.as_deref() == Some(u.username.as_str());
|
||||
let mark = if owner {
|
||||
"⛧"
|
||||
} else if app.drivers.contains(&u.username) {
|
||||
"◆"
|
||||
} else {
|
||||
"•"
|
||||
};
|
||||
let color = if me { theme.roster_me } else { theme.other };
|
||||
ListItem::new(Line::from(Span::styled(
|
||||
format!(" {mark} {}", u.username),
|
||||
|
||||
Reference in New Issue
Block a user