feat(profile): configurable approval backend for supervised-mode prompts - #1677
Merged
connrg merged 4 commits intoAug 19, 2026
Merged
Conversation
Add security.approval_backends (named ApprovalBackendConfig map) and security.approval_defaults to the profile schema, decoupled from command_policies so configuring a backend does not activate the tool-sandbox. Share build_approval_registry_from between the command_policies and profile paths so both surfaces resolve backends identically, including chain-cycle detection and webhook construction. resolve_supervised_approval_backend returns Ok(None) when no backend is configured (fall back to the interactive terminal prompt), and a hard error when backends are configured but the default cannot resolve -- never a silent fallback to less-supervised behavior. Merge semantics: child profiles union approval_backends; child approval_defaults win. Update the JSON schema, schema_shape test, and profile parse/merge tests.
… docs Collapse the supervised-mode backend selection to Option::unwrap_or (same behavior and lifetimes as the prior match), and reword the approval-backend comments and JSON schema descriptions in plainer language while keeping the technical detail: decoupling from command_policies (no tool-sandbox), and fail-closed resolution (configured-but-unresolvable is a hard error, never a silent fall back to the terminal prompt). No functional change; clippy and the approval/profile/schema tests pass. Signed-off-by: connrg <conor@nolabs.ai>
Contributor
PR Review SummarySize
Affected crates
Blast radius — ModerateThis PR touches: source code,configuration / policy files Updated automatically on each push to this PR. |
connrg
marked this pull request as ready for review
August 19, 2026 09:15
There was a problem hiding this comment.
nogent code review
No blocking or minor issues found; the changes are well-structured, robust, and correctly adhere to the sandboxing security model.
Findings (not tied to a changed line):
- 🐛 [MEDIUM · bug]
crates/nono-cli/src/profile/mod.rs:3108— The newly introducedsecurity.approval_backendsandsecurity.approval_defaultsdo not undergo any semantic validation during profile loading or finalization. While their sibling structures undercommand_policies.approval_backendsare rigorously validated byvalidate_command_policies(checking for cycles/self-chaining, invalid timeout values, webhook URLs missing, invalid configuration modes, and unknown child references), those undersecuritycompletely bypass these checks. This results in late runtime errors or start-up failures instead of clean, early validation errors infinalize_profile. Updatefinalize_profileincrates/nono-cli/src/profile/mod.rsto validate the approval configs undersecurityusing shared validation logic.
Automated code + security review. CI already covers clippy, rustfmt, tests, cargo-audit and commit-lint.
SequeI
approved these changes
Aug 19, 2026
SequeI
left a comment
Member
There was a problem hiding this comment.
lgtm, feel free to merge once the comment from nogent has been addressed. Thanks!
command_policies approval backends are validated by validate_command_policies at load/finalize (per-type field consistency, missing webhook url, chain mode/children, self-chaining, references to unknown backends, NUL-in-url, positive timeouts), but the security surface added for supervised-mode routing skipped all of it -- a malformed backend only errored later at supervised-launch build time, and some cases (e.g. a terminal backend with a stray url) were not caught at all. Generalize validate_approval_backend/validate_approval_defaults to take a raw backend map, and reuse them via validate_security_approval_backends called from finalize_profile, so both surfaces reject the same malformed configs early with a clean error. No behavior change for valid configs. Signed-off-by: connrg <conor@nolabs.ai>
1 task
levonk
pushed a commit
to levonk/nono
that referenced
this pull request
Sep 4, 2026
…pts (nolabs-ai#1677) * feat(profile): route supervised approvals via security.approval_backends Add security.approval_backends (named ApprovalBackendConfig map) and security.approval_defaults to the profile schema, decoupled from command_policies so configuring a backend does not activate the tool-sandbox. Share build_approval_registry_from between the command_policies and profile paths so both surfaces resolve backends identically, including chain-cycle detection and webhook construction. resolve_supervised_approval_backend returns Ok(None) when no backend is configured (fall back to the interactive terminal prompt), and a hard error when backends are configured but the default cannot resolve -- never a silent fallback to less-supervised behavior. Merge semantics: child profiles union approval_backends; child approval_defaults win. Update the JSON schema, schema_shape test, and profile parse/merge tests. * refactor(profile): simplify supervised approval selection and clarify docs Collapse the supervised-mode backend selection to Option::unwrap_or (same behavior and lifetimes as the prior match), and reword the approval-backend comments and JSON schema descriptions in plainer language while keeping the technical detail: decoupling from command_policies (no tool-sandbox), and fail-closed resolution (configured-but-unresolvable is a hard error, never a silent fall back to the terminal prompt). No functional change; clippy and the approval/profile/schema tests pass. Signed-off-by: connrg <conor@nolabs.ai> * fix(profile): validate security.approval_backends at profile load command_policies approval backends are validated by validate_command_policies at load/finalize (per-type field consistency, missing webhook url, chain mode/children, self-chaining, references to unknown backends, NUL-in-url, positive timeouts), but the security surface added for supervised-mode routing skipped all of it -- a malformed backend only errored later at supervised-launch build time, and some cases (e.g. a terminal backend with a stray url) were not caught at all. Generalize validate_approval_backend/validate_approval_defaults to take a raw backend map, and reuse them via validate_security_approval_backends called from finalize_profile, so both surfaces reject the same malformed configs early with a clean error. No behavior change for valid configs. Signed-off-by: connrg <conor@nolabs.ai> --------- Signed-off-by: connrg <conor@nolabs.ai>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Supervised mode's filesystem/capability approval prompts (the seccomp-notify traps) were hardcoded to the interactive terminal (TerminalApproval). This makes that approval surface configurable, so the prompts can be routed to any of the existing approval backends (terminal, webhook, or chain).
It adds two fields under the profile security section:
security.approval_backends— a map of named backends, reusing the exact ApprovalBackendConfig shape already used bycommand_policies.approval_backends.security.approval_defaults— which backend answers by default.Design points:
Why
nono already lets network (endpoint) and command approvals route to a configurable backend such as a webhook. Supervised-mode filesystem/capability approvals were the exception — they could only be answered by a human at a TTY. That has two downsides this PR addresses:
Security
This change touches an approval/trust boundary, so it was designed to be fail-secure and to leave existing behavior untouched:
Test Plan
cargo clippy -p nono-cli --all-targets -- -D warnings -D clippy::unwrap_used— cleancargo fmt --check— clean