Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions TRIAGE.json
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,12 @@
"owner_hint": "top committer: jg (1/1 recent commits); no CODEOWNERS entry",
"missing_fields": [
"preconditions"
]
],
"remediation": {
"status": "fixed",
"where": "014-terminal-safety",
"note": "Untrusted text is escaped where it enters a front-end (`safe_text::safe_block`/`safe_line`, applied in repl::terminal's ReplOutput impl and tui::app::handle_session) and only then styled; the interactive consent prompt escapes every field it prints; and skill frontmatter carrying control or bidi characters is refused at load."
}
},
{
"id": "f041",
Expand Down Expand Up @@ -1122,7 +1127,12 @@
"owner_hint": "top committer: jg (1/1 recent commits); no CODEOWNERS entry",
"missing_fields": [
"preconditions"
]
],
"remediation": {
"status": "fixed",
"where": "014-terminal-safety",
"note": "Untrusted text is escaped where it enters a front-end (`safe_text::safe_block`/`safe_line`, applied in repl::terminal's ReplOutput impl and tui::app::handle_session) and only then styled; the interactive consent prompt escapes every field it prints; and skill frontmatter carrying control or bidi characters is refused at load."
}
},
{
"id": "f051",
Expand Down Expand Up @@ -2132,6 +2142,16 @@
"status": "fixed",
"where": "013-attenuation-inheritance",
"note": "Policy::derive now returns the effective child policy \u2014 an omitted filesystem/exec/network dimension is inherited from the parent, parent deny rules and inode pins are re-added, and a child grant reaching into an FR-008 protected region is refused (research R15)."
},
{
"findings": [
"f040",
"f046",
"f047"
],
"status": "fixed",
"where": "014-terminal-safety",
"note": "Untrusted text is escaped where it enters a front-end (`safe_text::safe_block`/`safe_line`, applied in repl::terminal's ReplOutput impl and tui::app::handle_session) and only then styled; the interactive consent prompt escapes every field it prints; and skill frontmatter carrying control or bidi characters is refused at load."
}
]
}
1 change: 1 addition & 0 deletions TRIAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ are left in place unedited for provenance.
|----------|-----|
| f018, f020, f022, f026, f028, f030 (all six HIGHs) | `8e2cdbb` + `3225d44` — closed and VM-verified (31/31 matrix) |
| f001, f002, f012, f014, f025 (+ absorbed f006, f032) | branch `013-attenuation-inheritance` — one root cause: attenuation validated only what a child *stated*, so omission widened authority. `Policy::derive` now returns the *effective* child policy (silence inherits, it does not reset) and refuses child grants reaching into FR-008 protected regions. See research R15; regression tests in `crates/core/tests/attenuation.rs`. |
| f040, f046 (+ absorbed f047) | branch `014-terminal-safety` — untrusted text is escaped where it enters a front-end (`safe_text`, applied in `repl::terminal` and `tui::app::handle_session`) and only then styled; the consent prompt escapes every field it prints; skill frontmatter carrying control or bidi characters is refused at load. |

## Act on these
### [HIGH] Provider TOML can send an arbitrary environment secret to an attacker endpoint (f018)
Expand Down
107 changes: 0 additions & 107 deletions docs/NEXT-read-write-modes-PROMPT.md

This file was deleted.

8 changes: 8 additions & 0 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ These are non-negotiable and already hold across the codebase — keep them hold
basic-ANSI output; Catppuccin ×4, Dracula, and Nord ship alongside it.
5. **Spatial stability.** Banners, status lines, and dot grids keep fixed positions and fixed column
order. Chrome doesn't rearrange itself between frames.
6. **The terminal is ours; untrusted text is data.** Model prose, tool output, audit targets, and
skill metadata are attacker-reachable, and a terminal reads text as a command language — an ESC
can clear the screen, a `\r` can rewrite the line above, a bidi override can reorder what was
already drawn. Every such string passes through `safe_text::safe_block`/`safe_line` at the point
it enters a front-end (`repl::terminal`'s `ReplOutput` impl; `tui::app::handle_session`), and
only *then* gets styled. Sanitize the payload, then paint it — never the reverse, or the escaping
would eat bee's own color.

## Identity

Expand Down Expand Up @@ -212,3 +219,4 @@ not get to pick bee's.
- [ ] Motion is decoration: with `BEE_NO_ANIMATION` set, the same content is on screen immediately.
- [ ] Any new effect goes through `tui::effects::resolve` — never registered at a call site directly.
- [ ] New chrome effects register **unkeyed**, so the agent cannot address them.
- [ ] Untrusted text is sanitized where it enters the front-end, before any styling is applied.
15 changes: 12 additions & 3 deletions src/app/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,12 +380,21 @@ fn terminal_rows() -> Option<u16> {
/// session's whole contribution to the otherwise-shared grant path.
fn prompt_consent(request: &bee::skills::GrantRequest<'_>) -> bool {
use std::io::Write;
eprintln!("\nskill '{}' requests extra capabilities:", request.skill);
// Every field here is skill-authored. Skill loading already refuses control characters in
// frontmatter, but this prompt is the consent boundary itself and takes a `GrantRequest` from
// any source, so it escapes what it prints rather than trusting an upstream check: a forged
// prompt is a granted capability. One line per field, so nothing can smuggle in a second line.
let safe = bee::safe_text::safe_line;
eprintln!(
"\nskill '{}' requests extra capabilities:",
safe(request.skill)
);
if !request.tools.is_empty() {
eprintln!(" tools: {}", request.tools.join(", "));
let tools: Vec<_> = request.tools.iter().map(|t| safe(t)).collect();
eprintln!(" tools: {}", tools.join(", "));
}
for (path, access) in request.filesystem {
eprintln!(" filesystem: {path} = {access}");
eprintln!(" filesystem: {} = {}", safe(path), safe(access));
}
eprint!("grant these (within the ceiling)? [y/N] ");
let _ = std::io::stderr().flush();
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub mod provider;
pub mod render_api;
pub mod render_spec;
pub mod repl;
// Terminal-safety for untrusted text. Ungated: every front-end and the consent prompt need it.
pub mod safe_text;
pub mod sandbox;
pub mod scenario;
pub mod search;
Expand Down
Loading