Skip to content

fix(channels): per-command trust tiers on approval cards (#5231) - #5248

Merged
bolichen97 merged 1 commit into
mainfrom
fix/channel-trust-tiers-5231
Sep 4, 2026
Merged

fix(channels): per-command trust tiers on approval cards (#5231)#5248
bolichen97 merged 1 commit into
mainfrom
fix/channel-trust-tiers-5231

Conversation

@CrysisDeu

@CrysisDeu CrysisDeu commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

Channel approval cards render the shared TrustDropdown (ApprovalCard defaults showTrust=true), which offers three trust tiers: trust, trust_command, and trust_base. But two of the three could never succeed on a channel card:

  • api_channel_approve_agent whitelisted only ("approved", "rejected", "trust") and returned 400 invalid action for trust_command / trust_base.
  • All three ChannelPage.tsx onApprove call sites dropped the pattern argument, and api.channelApproveAgent had no pattern parameter.
  • Worse, the card passed the agent role (msg.fromRole) as the ApprovalCard title, so the dropdown offered to trust a "command" like dev — and on the ACP path the approval message carried no tool name at all (event.text is empty; only title is populated), rendering ⚠️ Approval needed: ****.

Why it matters

The UI offers actions that always fail — the user picks a scoped, deliberately narrow grant ("trust this one command") and gets an invalid action error, pushing them toward the much wider blanket trust (auto-approve everything for the channel). A scoped-consent control that fails teaches users to over-grant.

What changed (motivation → approach → change)

Symptom → root cause: the trust tiers exist only in the chat approval path (chat_handlers.py writes session-scoped _trusted_patterns); channel agents had no per-command trust seam at all, and the channel card never carried the data a scoped grant needs.

Approach: give ChannelAgent the same session-scoped seam, but derive grants server-side from the pending approval's canonical shell command, with the client-supplied pattern serving as the consent proof: it must agree with the pending command, so a stale card (whose pattern describes an older command) or an LLM-influenced title that diverged from the real command fails closed with 400 approval_superseded instead of trusting a command the user never read. Display titles are LLM-influenced (an agent-authored title like Running: * must not be able to widen a grant to everything), so the pattern can only ever narrow or refuse, never widen. This is deliberately stricter than the chat path on several axes flagged by review:

  1. Server-side derivation + consent check. _stream_task stashes agent._pending_approval_command (extracted from the provider event's tool_input) while the approval is pending; the handler derives the grant from that stash and requires the card's pattern to agree with it.
  2. Grants are opaque literals — no pattern language at all. Successive review rounds each found a scope leak in a pattern-derivation scheme (raw globs matching rm secret.tmp from rm *.tmp; per-segment grants lifting rm target out of its cd /tmp/safe && context; naive first-token bases turning a quoted "./my tool" into a "./my prefix grant; env prefixes and case-folded matching), so the restructure removes derivation entirely: the exact tier stores the whole command text matched by case-sensitive string equality, and the base tier stores one shlex-derived binary name — refused outright for compound, quoted, env-prefixed, or unparseable commands (400 pattern_underivable). A grant can never cover text the user did not read. Every refusal is SEL-audited (trust_pattern_denied). A command the provider REDACTED is never a grant target (two commands differing only in credentials redact to the same text).
  3. Shell-only matching. The auto-approve gate extracts a command from tool_input only when the provider classified the tool as shell (event.is_shell). A non-shell MCP tool whose arguments carry a nested "command" key (e.g. cron_add) can never inherit a shell grant, a non-shell pending tool gets a distinct 400 pattern_underivable, and the card hides the per-command tiers for non-shell tools AND for redaction-marked inputs (both refused server-side), so the doomed options never render.

Changes:

  • src/kiro_crew/channel.pyChannelAgent._trusted_commands/_trusted_bases + _pending_approval_command (runtime-only, not persisted: grants are session-scoped like chat's); shell-gated literal-grant auto-approve in _stream_task (SEL-logged as auto_approved_trusted_pattern); the approval card names the CANONICAL command for shell tools (kiro's shell title can be model-authored prose, which would make the tiers' consent proof mismatch the real command), with event.title as the non-shell fallback.
  • src/kiro_crew/dashboard/handlers_channel.py — whitelist widened; per-tier grant semantics as above; grants SEL-logged (trust_pattern_granted with the granted patterns); ch.trusted untouched for the per-command tiers (blanket trust unchanged).
  • website/src/api/client.ts / website/src/pages/ChannelPage.tsxpattern plumbed through all three onApprove call sites (the server's consent proof); approvalToolTitle() extracts the embedded tool name so the TrustDropdown labels the real command instead of the agent role; per-command tiers render only for shell-titled cards (perCommandTiers prop on ApprovalCard/TrustDropdown, default unchanged for other surfaces).
  • docs/system-specs/modules/persistent-agent-channels.md — approval flow / security / API rows updated in the same commit.

Known limitation (deliberate): per-command tiers are shell-only in this PR — non-shell channel cards no longer offer them (blanket trust remains), and the endpoint refuses with pattern_underivable for direct API callers. Structured pattern metadata on channel approval messages (mirroring chat's perm_meta) is follow-up work — see the issue linked below.

Tests

Backend (test/test_handlers_channel_approve.py, new):

  • trust_command binds the pending command as a literal, resolves the future as approved, never sets ch.trusted
  • a stale card's pattern (older command) → 400 approval_superseded, no grant (mutation-verified: removing the consent check fails 2 tests)
  • exact grants are literals: trusting rm *.tmp never matches rm secret.tmp; a compound grant matches ONLY the identical pipeline, never a lifted segment (mutation-verified against substring matching)
  • a client-supplied pattern: "*" can never scope a grant (mismatch → fail closed)
  • trust_base refuses compound, quoted-executable, env-prefixed, and substitution commands (mutation-verified against naive tokenization); the granted binary covers simple invocations only
  • missing pattern / non-shell pending tool → distinct 400 codes, no grant, future untouched
  • matching is case-sensitive (./Deploy.sh grant never matches ./deploy.sh, mutation-verified against lowercase folding)
  • every trust refusal is SEL-audited (trust_pattern_denied)
  • regression: approved / rejected / trust / invalid action / no-pending behavior unchanged

Backend (test/test_channel_trusted_patterns.py, new):

  • matching shell command auto-approves without posting a card (SEL outcome pinned)
  • a non-shell tool with a nested "command" key never matches (mutation-verified: removing the is_shell gate fails 2 tests)
  • the LLM-authored title cannot spoof a trusted command (matching keys on tool_input)
  • stash lifecycle: set while pending, cleared after; empty for non-shell
  • _trusted_patterns / _pending_approval_command are runtime-only (not serialized)

Frontend (website/src/test/ChannelPageCoverage.test.tsx):

  • trust_command / trust_base forward the pattern to channelApproveAgent (mutation-verified: reverting the call-site plumbing fails 3 tests)
  • non-shell and redacted-input approval cards hide the per-command tiers (only "Trust all tools" renders)

Manual verification

Capture harness (website/capture/channel-trust-tiers.* + website/scripts/capture-channel-trust-tiers.mjs) mounts the REAL ApprovalCard with the title resolved by the real approvalToolTitle from a backend-shaped approval message; each scene asserts the exact-command tier offers the command (not the agent role) before writing a frame, so a before-state cannot produce these images.

Note: CI's Frontend Tests shard 4 initially failed on the known CliPanelCoverage happy-dom WeakRef leak (this PR's added test files shifted the shard boundaries onto it); main's own fix (#5252, happy-dom ≥20.11.5) landed mid-review and this branch is rebased onto it — the full frontend suite now passes with zero failures.

Local gates: isort / flake8 / mypy / black gate clean; full backend suite 61,814 passed (10 failures are pre-existing host-environment issues — /tmp cwd artifact-source classification and xdist host-budget caps — reproduced identically on unmodified code); npx tsc -b clean; full vitest 22,942 passed, 0 failed (post-rebase, happy-dom 20.11.6).

Screenshots / video

Channel approval card with the trust menu open — the tiers now name the real command:

trust menu, dark

More variants

card closed, dark

trust menu, light

Note: open PR #5233 (the #5204 rollback fix) edits the same three onApprove call sites; whichever lands second has a small mechanical conflict (combine pattern forwarding with the un-catched promise return).

Related Issues

Closes #5231

Checklist

  • Single commit with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

Pattern harvest

Rule candidate: never derive a trust/auto-approve grant from client-supplied or LLM-influenced text (display titles, glob patterns). Bind the grant server-side to the pending action's canonical text, and use the client-echoed pattern only as consent proof that must match — so client input can narrow or refuse a grant, never widen it. Every review round that found a scope leak here (raw globs, per-segment matching, first-token bases, env prefixes, case folding) was an instance of the same rule.

@CrysisDeu
CrysisDeu requested a review from a team August 23, 2026 10:24
@CrysisDeu
CrysisDeu requested a review from a team as a code owner August 23, 2026 10:24
@CrysisDeu
CrysisDeu requested a review from Zedmor August 23, 2026 10:24
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Aug 23, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 23, 2026
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — 🟡 CONCERNS

Design-level review of decedb237e30be9b8cd4166fb6f9e2f745ec9d5b — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Confirmed: the base tier renders for any shell-titled card (hasCommand && isShell), with the base derived by naive whitespace split client-side, while the server refuses compound/quoted/env-prefixed commands. I have what I need for the review.

Design-Verdict: CONCERNS

Server-side literal-grant binding is sound, but tier availability is inferred from parsed display text, so the "doomed option" harm partially survives.

Watch

  • The base tier still renders doomed for compound/quoted/env-prefixed shell commands: TrustDropdown shows it for every shell card (hasCommand && isShell, base from a naive whitespace split), while the server's _shell_base_binary refuses those with 400 pattern_underivable. For an agent's most common shape (cd repo && npm test), the card offers "Trust all cd commands" that always errors — the exact "failing scoped option pushes users toward blanket trust" harm the PR's motivation names, now on one tier instead of two.
  • Grantability rides on a stringly contract — approvalToolTitle's regex over **…**, the Running: prefix as machine marker, [REDACTED sniffing on 500-char-truncated input. Everything fails closed server-side, but each drift (copy change, marker split at the truncation boundary) surfaces as confusing approval_superseded errors. The acknowledged structured-metadata follow-up is the real fix; until it lands this coupling spans three sites.

Suggestions

  • Hide the base tier client-side when the command contains shell operators or quoting (a cheap mirror of _CHANNEL_SHELL_OPERATOR_RE), closing the residual always-fails option within this PR.

[DESIGN-REVIEWED] decedb2

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of decedb237e30be9b8cd4166fb6f9e2f745ec9d5b and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] decedb2

False positive or not applicable? A repository writer can comment:
/ai-review override gpt decedb237e30be9b8cd4166fb6f9e2f745ec9d5b: <one-sentence reason>

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — 🟡 CONCERNS

UX-level review of decedb237e30be9b8cd4166fb6f9e2f745ec9d5b — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: CONCERNS

Trust-menu consent labels under-specify their scope, and a card the server just declared stale re-arms live Approve/Reject buttons.

Watch

  • Scope-silent command tiers on a scoped-trust menu. The channel's blanket item was deliberately relabeled ("Trust all tools in this channel — persists across restarts") because "the default label reads as session-scoped", yet the two new tiers reuse the unqualified chat labels ("Trust “{{cmd}}”", "Trust all {{base}} commands") while granting agent-scoped, runtime-only trust — a user in a multi-agent channel trusts ls, gets re-prompted by the next agent (or after restart), and concludes the grant didn't take. Fails safe, but it's persistent friction on the exact surface where consent wording matters; smallest fix: channel-specific keys mirroring trust_all_tools_channel (e.g. "…for @{{role}}, until restart").
  • Stale card contradicts its own error. On approval_superseded the card shows "the approval card is stale" yet rolls back to live buttons (only 404/"no pending approval" is terminal in ApprovalCard.tsx:50), so the natural retry — Approve — resolves the current pending request, a command the displayed card never showed. Rare (race) × high impact (approving unread command); treat approval_superseded as terminal for that card.

Suggestions

  • Reword the backend refusal "pattern does not match the pending command; the approval card is stale" to user vocabulary with a next step: "This card no longer matches the agent's pending request — see the latest approval message."
  • "Shell command (allow once): {cmd}" slightly overclaims — blanket channel Trust is still offered on that card; "Shell command (can't be trusted individually):" or similar keeps the promise.

[UX-REVIEWED] decedb2

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — 🟡 CONCERNS

Premise-level review of decedb237e30be9b8cd4166fb6f9e2f745ec9d5b — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

I have everything I need. Verifying the key counts one more time before writing: the base-state test the diff removes ("offers only the plain trust action on a channel approval — no command-scoped tiers (#4421)") confirms the doomed tiers were already hidden at base, and trust_patterns.py:140 (_tool_matches lowercasing) confirms the chat sibling.

First-Principles-Verdict: CONCERNS

Framed as a fix, but #4421 already hid the doomed tiers at base — this ships a new scoped-trust capability, justified, yet with a second trust-matching language beside trust_patterns.py.

What this change ships

Intent: let a channel operator grant narrow per-command trust instead of only blanket channel-wide trust, and name the real command on the card. Framed as a FIX; the substance is an ADDITION plus one genuine fix.

  1. Shell approval cards show the canonical command, not the agent role or empty **** — justified (real display defect).
  2. Trust dropdown on shell channel cards offers exact-command and base-binary tiers — the addition; harm named (only blanket over-grant existed).
  3. Approve endpoint accepts trust_command/trust_base with a consent-proof pattern, mismatches fail closed — justified (consent boundary).
  4. Matching later shell commands auto-approve without a card, SEL-audited — justified enforcement half.
  5. Grants are runtime-only opaque literals with a bespoke matcher — justified semantics, second spelling beside trust_patterns.py.
  6. Non-shell and redacted cards hide the per-command tiers — justified fail-closed visibility change.
  7. Every human shell approval now pins executable identity via existing name_grant — reuses the chat chokepoint; extends beyond the tiers themselves.
  8. A failed approval post no longer leaks a live future/command authority — rides along, cause-level.
  9. handlers_channel.py black-reformatted and baseline-pruned — rides along.
  10. Capture harness + 3 screenshots + spec update — repo convention (181 capture files, 816 screenshots exist).

Watch

  • Framing: the description's premise — "The UI offers actions that always fail" — is contradicted by the base test this diff deletes ("offers only the plain trust action on a channel approval — no command-scoped tiers (ChannelPage approval trust tiers: role passed as command, trust_command decision coerced to rejected #4421)"). The real job is adding a capability channels lacked; the addition is well-justified, but the "fix" framing understates the new surface (2 new API actions, a grant store, an auto-approve branch).
  • Second trust language: trust_patterns.py's docstring mandates that "chat, channels, and any future approval surface must derive and enforce its scope with the same command-shaped helpers", yet channel.py hand-rolls _shell_base_binary + _match_trusted_channel_command beside the shared extract_base_command/matches_trusted_pattern (grep: 2 matchers, 2 base-derivers now exist). The channel one is deliberately stricter, so replacement would widen scope — but two divergent languages on one authorization concept must now be maintained in sync.
  • Counted unfixed sibling: the case-folding leak this PR's own test condemns ("./Deploy.sh grant never matches ./deploy.sh") is live in chat — _tool_matches lowercases both sides (trust_patterns.py:140, consumed at chat_runner.py:8076). 1 sibling; changing chat's established equivalence classes is plausibly larger than this change — accepted-and-deferred, but it should be named.
  • The description says the tiers gate on a "perCommandTiers prop on ApprovalCard/TrustDropdown"; no such prop exists in the diff — it reuses the existing hasCommand. Smaller than described (good), but the description is stale.

Subtractions

  • Defer the handlers_channel.py reformat hunks and the .github/black-baseline.txt prune to their own commit, as AGENTS.md itself instructs ("do it in its own commit") — they are pure-whitespace riders in functions this change never touches (api_channel_create, api_channel_clear_context).

[FIRST-PRINCIPLES-REVIEWED] decedb2

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed decedb237e30be9b8cd4166fb6f9e2f745ec9d5b — this comment is updated in place on each push.

Review details

No blocking issues — one advisory display bug survives.

FINDING — website/src/pages/ChannelPage.tsx (the const toolInput = msg.content.replace(/^⚠️ Approval needed:.*\n```\n?/, '') extractor in MessageBubble) — a channel agent's multi-line shell command now flows into the card header as **Running: {cmd}** (_card_name = f"Running: {_safe_cmd}" in channel.py, _cmd from extract_bash_command preserves newlines and is grantable when unredacted), so the header spans lines; the .* in the extractor is not dotall and stops at the first embedded \n, the required \n``` never follows there, the replace no-ops, and the card renders the entire raw message (header, **, fences) as the tool-input preview instead of just the command → Fix: extract the fenced body with a dotall/[\s\S] pattern anchored on the ``` fences rather than assuming the ⚠️ Approval needed: … header is a single line.

[OPUS-REVIEWED] decedb2

Verdict parsed from the review's SHA-scoped output markers for commit decedb237e30be9b8cd4166fb6f9e2f745ec9d5b.

False positive or not applicable? A repository writer can comment:
/ai-review override fable decedb237e30be9b8cd4166fb6f9e2f745ec9d5b: <one-sentence reason>

@CrysisDeu
CrysisDeu force-pushed the fix/channel-trust-tiers-5231 branch from 4c60624 to e5feded Compare August 23, 2026 11:04
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 23, 2026
@CrysisDeu

CrysisDeu commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

Dispositions for the GPT 5.6 review of 4c60624, addressed in e5feded228af44a8e1e8356047d8b9756801f51c:

Stale cards can trust a newer command (handlers_channel.py:339)fixed.

Old card click -> endpoint selects the agent's current pending command -> unintended command is approved and trusted.

The request-body pattern is now the consent proof, exactly as the finding's fix suggests: trust_command requires pattern == agent._pending_approval_command and trust_base validates each consented <base> * piece against the segment-split bases of the pending command; any mismatch returns 400 approval_superseded with no grant and the future untouched. Regression tests test_stale_card_pattern_is_refused and test_trust_base_foreign_base_is_refused (mutation-verified: disabling the check fails both).

Exact-command grants retain glob semantics (handlers_channel.py:353)fixed.

Trusting rm *.tmp -> fnmatch also matches rm secret.tmp -> a different command is silently auto-approved.

Exact grants are now glob.escape()d before storage (and trust_base escapes the base binary while keeping the deliberate * args-glob). Regression test test_exact_grant_escapes_fnmatch_metacharacters asserts the literal command matches and rm secret.tmp does not (mutation-verified: removing the escape fails it).

@CrysisDeu

CrysisDeu commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

Dispositions for the First Principles review of 4c60624, addressed in e5feded228af44a8e1e8356047d8b9756801f51c:

Blocker: dead pattern request parameter rides along in a fixfixed, by the route the GPT lane's stale-card blocker forced rather than by deletion.

The pattern is never read, logged, or compared (grepped body.get("pattern" in handlers_channel.py: 0 consumers).

The premise was correct on that head: the parameter had zero consumers. The same round's GPT review found the stale-card TOCTOU (an old card's click binds to whatever is pending NOW), and the pattern is precisely the data that closes it — it is now REQUIRED and validated as the consent proof (trust_command: must equal the pending command; trust_base: each consented base must be a segment base of the pending command; mismatch → 400 approval_superseded). The zero option was genuinely available on 4c60624 but would have left the TOCTOU open; the parameter now has a load-bearing consumer, the doc row describes it, and it can only narrow or refuse a grant, never widen one.

Watch: the chat endpoint still binds client-supplied patterns verbatimaccepted-and-deferred, filed as #5260 (together with the Design lane's shared-module suggestion, since deriving chat grants server-side naturally produces the shared engine).

Subtraction: un-export approvalToolTitle if the pattern plumbing goesrebutted as moot: the plumbing stays (now load-bearing), and the export's second consumer is the capture harness (website/capture/channel-trust-tiers.tsx), which mounts the real derivation so the PR's screenshots cannot silently diverge from the shipped code path.

@CrysisDeu

CrysisDeu commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

Dispositions for the UX review of 4c60624, addressed in e5feded228af44a8e1e8356047d8b9756801f51c:

Silent fake success on non-shell toolsfixed with the review's own smallest-fix suggestion: per-command tiers are now gated out of non-shell channel cards (perCommandTiers prop threaded ApprovalCardTrustDropdown, keyed on the shell-titled card; default true so chat/Mochi surfaces are unchanged). A non-shell card offers Approve / blanket Trust / Reject only — the doomed options never render. Pinned by hides the per-command tiers on a non-shell approval card in ChannelPageCoverage.test.tsx.

trust_base label under-states the grant on compound commandsfixed at the grant side, which is the stronger direction: consent is now label-exact. The server grants exactly the bases the card named (cat * on cat f | wc -l grants cat, never the unnamed wc; a foreign base is refused as a stale card). The label and the grant can no longer diverge — the grant is derived FROM the consented label. Pinned by test_trust_base_consent_is_label_exact_on_compound_commands.

Light-theme menu ghostingfixed: it was a mid-animation capture artifact (Radix animates the menu in with opacity). The capture script now waits for the menu's animations to finish before writing a frame, and all three screenshots were recaptured and visually re-verified — the light-theme menu is fully opaque. New frames are SHA-pinned to e5feded in the PR body.

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the Design review suggestion on 4c60624 (verdict PASS):

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 23, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/channel-trust-tiers-5231 branch from e5feded to 3eda4c0 Compare August 23, 2026 11:58
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Aug 23, 2026
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the GPT 5.6 review of e5feded, addressed in 3eda4c0118aebd6f80183cf1deac7d27ef203280:

  • Assignment prefixes become blanket command grants (handlers_channel.py:401)fixed with the finding's suggested fix.

    FOO=bar echo safe → trust base → FOO=bar rm … auto-approves without consent.

    Assignment-shaped first tokens (^[A-Za-z_][A-Za-z0-9_]*=) are now excluded from the server-derived base set, so a consented FOO=bar * piece fails the base validation and the request is refused with no grant. Regression test test_trust_base_assignment_prefix_never_becomes_a_grant. The same hole exists in the chat path's client-bound trust_base (pre-existing) — noted on Chat approval path: bind trust grants server-side and share the trust-pattern engine #5260, whose server-side rework covers it.

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Aug 23, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/channel-trust-tiers-5231 branch from 3eda4c0 to 6f65721 Compare August 23, 2026 12:32
bolichen97
bolichen97 previously approved these changes Aug 25, 2026

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving per triage sweep: readiness passed, no blocking reviews, fix-type change. Auto-merge will be enabled; branch protection still gates.

@dwu96 dwu96 added the needs-pr-triage PR scanner: awaiting automated triage label Aug 28, 2026
@iamwhatever iamwhatever removed the needs-pr-triage PR scanner: awaiting automated triage label Aug 28, 2026
@bolichen97
bolichen97 force-pushed the fix/channel-trust-tiers-5231 branch from 0a825b2 to e1e27fc Compare August 30, 2026 07:19
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: passed Eligible automated validation passed for the current revision merge conflict Branch has merge conflicts with its base — author must resolve before merge readiness: checking Automated validation is still running labels Aug 30, 2026
@github-actions github-actions Bot added the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 1, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

Audit note — part of this has already landed; the rest has not

This PR is not a duplicate and is not finished by anything on main. The audit checked it part by part against main, and some of what it does is already there. Flagging it so a reviewer does not have to rediscover the overlap, and so the PR is not mistaken for fully-covered work.

Already landed

Which parts main already has

Only scaffolding and formatting. (1) #5202 (f5faf32) landed the hasCommand prop on ApprovalCard/TrustDropdown, the {hasCommand && ...} tier gates, and hasCommand={false} at ChannelPage - so #5248's ApprovalCard.tsx and TrustDropdown.tsx hunks are comment-only, and the user-visible symptom in the PR's Problem section (a card offering tiers the endpoint rejects) is already fixed on main by SUPPRESSION. main also PINS that suppression in ChannelPageCoverage.test.tsx:191. (2) #7160 (c314ddc) landed 3 black reflow hunks in channel.py and removed its black-baseline line. (3) #6009 (7bb60b1) landed the same server-side-binding design and the identical pattern_required / pattern_underivable / approval_superseded + trust_pattern_denied vocabulary, plus shared trust_patterns.py - but for chat_handlers.py only; it covers no channel behaviour.

What is still genuinely yours

The entire capability. Backend: ChannelAgent's three new runtime fields, _shell_base_binary, _match_trusted_channel_command, the is_shell-gated auto-approve branch and its auto_approved_trusted_pattern SEL outcome, the canonical-command card title and its grantability computation, the future-ownership scope fix, the widened action allowlist, the server-side grant derivation with all three channel-side denial codes, _deny_trust_grant, and trust_pattern_granted (zero hits anywhere on main). handlers_channel.py has had ZERO commits since the merge base. Frontend: the pattern parameter on channelApproveAgent, approvalToolTitle(), the computed hasCommand, and pattern forwarding at all three call sites. Plus 656 lines of new backend tests, 5 new frontend tests, the whole Playwright capture harness, 3 PNGs, and every line of the spec update.

Suggested action: CONTINUE_DEV — the remainder is real work; rebase onto the landed part rather than closing.


From a repository-wide duplicate/overlap audit of every pull request open against main (2026-09-02, 330 PRs, one reviewer per PR). Each PR was read as its full merge-base diff plus its description and every comment and review, then compared against each candidate PR's own diff and against origin/main at 1a765b88ceb7. This PR is not being closed — the note is informational. If the reading is wrong, please correct the reasoning rather than just the conclusion.

@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • This PR is PARTIALLY_COVERED with PR #5202. Coverage is explicitly incomplete; this finding is not a completion or closure claim. Recommended action for PR #5248: REBASE. Merged PR #5202 landed only the hasCommand scaffolding and the suppression that this PR flips. It covers none of the channel trust capability, so closure is not available; the PR must be reconciled with the landed suppression and its pinning test. Files: website/src/components/TrustDropdown.tsx, website/src/pages/ChannelPage.tsx, website/src/test/ChannelPageCoverage.test.tsx.
  • This PR is PARTIALLY_COVERED with PR #7160. Coverage is explicitly incomplete; this finding is not a completion or closure claim. Recommended action for PR #5248: REBASE. Only the formatting half of the PR's baseline change has landed; the handlers_channel.py baseline line and its reflow are still the PR's own work. Files: .github/black-baseline.txt, src/kiro_crew/channel.py.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

@CrysisDeu
CrysisDeu force-pushed the fix/channel-trust-tiers-5231 branch from e1e27fc to a461bf9 Compare September 4, 2026 08:46
@github-actions github-actions Bot removed the merge conflict Branch has merge conflicts with its base — author must resolve before merge label Sep 4, 2026
@CrysisDeu
CrysisDeu force-pushed the fix/channel-trust-tiers-5231 branch from a461bf9 to 3428fd0 Compare September 4, 2026 09:30
@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the GPT 5.6 review of a461bf977 (adjudication upheld 1/1), addressed in 3428fd09fcd001c55fd1e9c8971cbf3ebc4576d5:

  • Trusted commands bypass executable-identity validation (channel.py:891)fixed with exactly the prescribed integration. The channel's per-command tier now calls the shared name_grant.refusal_for_event(event) off-loop check before honouring a grant: a refusal (shadowed resolution, agent-writable path, unpinned name) does NOT auto-approve and does NOT reject — the request falls through to the interactive approval card, and the decline is SEL-audited via name_grant.log_decline(tier="channel_trusted_pattern"). Human approvals of shell commands now pin the executable identity via name_grant.pin_human_approval (off-loop), mirroring the dashboard chat slot's integration in chat_runner.py — so a later grant for ./deploy.sh is honoured only while the same file answers to the name. Regression tests: refusal-falls-through-to-card, approval-pins-identity, rejection-pins-nothing; the auto-approve flow test stubs the platform-dependent verdict at the same seam test_name_grant_surfaces.py uses.

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the GPT 5.6 review of 3428fd09 (1/1 security-class blocking), addressed in 91fb4ad2afff3e72c709d01574a4a005bc47ac03:

  • Executable identity is pinned after execution starts (channel.py:1015)fixed with the prescribed ordering. name_grant.pin_human_approval(_cmd) now runs BEFORE approve_tool releases execution, matching the chat path in chat_runner.py — a self-replacing ./deploy.sh can no longer get its replacement pinned as the witnessed identity. The regression test test_human_approval_pins_the_shell_command_identity now asserts the exact call order (["pin:./deploy.sh", "approve"]), so a future swap of the two calls fails the test. The head also rebased onto current main (picking up the re-measured t-chunk budget from fix(ci): re-measure the stale t-chunk bundle ceiling #8412).

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the GPT 5.6 review of 91fb4ad2 (adjudication upheld 1/1), addressed in ee4c185a3732a95820ee94486d8b6f3de04d0966:

  • Raw shell inputs bypass executable-identity validation (channel.py:903)fixed with the prescribed one-line swap. The tier now calls name_grant.refusal_for_command_off_loop(_cmd) on the command the grant actually matched (recovered by extract_bash_command), instead of refusal_for_event(event), whose event.shell_command re-derivation returns None for raw non-JSON tool_input and would make the check vouch for nothing while the tier still auto-approved. Regression test test_identity_check_reaches_raw_shell_input pins the raw-input shape: it asserts event.shell_command is None, that the off-loop check received exactly the extracted command, and that a refusal on that shape falls through to the card. The test event factory was also corrected to mirror the real AcpEvent.shell_command None-on-raw semantics rather than a lenient fallback.

@CrysisDeu

Copy link
Copy Markdown
Collaborator Author

Disposition for the GPT 5.6 review of ee4c185 (1/1 security-class blocking), addressed in decedb237e30be9b8cd4166fb6f9e2f745ec9d5b:

  • Carriage returns bypass base-command authorization (channel.py:135)fixed with the prescribed one-character addition. \r is now in _CHANNEL_SHELL_OPERATOR_RE, so a command containing it has no derivable base and no runtime base-match: the parser differential (shlex treats \r as a word separator and reads a trusted ls, the shell does not and executes the planted ls\r/bin/true relative path) fails closed with 400 pattern_underivable at grant time and falls to the interactive card at match time — the regex is shared by both sides. Regression test test_trust_base_refused_for_carriage_return pins the exact scenario from the finding.

@CrysisDeu

CrysisDeu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Disposition for the Design Review (CONCERNS) on decedb23:

Base tier still renders for compound/quoted/env-prefixed commands the server refuses — deferred to #5250 (server-declared tier availability in the card metadata), with the harm now bounded: PR #5233 has MERGED, so a refused tier click rolls the card back and surfaces the error instead of dying silently — the residual failure is one visible, recoverable error, not a dead end that teaches over-granting. The suggested client-side mirror of _CHANNEL_SHELL_OPERATOR_RE is the drift this same lane has warned about (two predicates, one truth): the regex has changed four times during this PR's own review (parens, reserved words, \r), and each change would have had to land twice. Server-declared availability drifts by construction never.
Grantability rides on a stringly contract (**…** regex, Running: marker, [REDACTED sniffing) — agreed, and this review names the fix itself: the structured-metadata follow-up #5250. Everything fails closed server-side in the interim, which is the property that makes deferral safe.

@CrysisDeu

CrysisDeu commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

Disposition for the UX Review (CONCERNS) on decedb23:

Scope-silent tier labels (agent-scoped, runtime-only grants reuse chat's unqualified labels) — legitimate; folded into #5250's scope-accurate-labeling item (channel-specific keys mirroring trust_all_tools_channel, e.g. "…for @{{role}}, until restart" — noted there). Fails safe in the interim exactly as this review observes: the cost is a re-prompt, never an over-grant.
approval_superseded rolls back to live buttons; retry-Approve resolves a request the card never showed — real interaction between this PR's stale-card 400 and the rollback semantics PR #5233 merged mid-flight. Two scoping facts: plain Approve resolving the current pending future is pre-existing behavior for every channel card (cards have never bound Approve to a request identity — this PR's consent proof covers the trust tiers, which DO fail closed on staleness); and the complete fix is the card carrying its request identity, which is #5250's structured-metadata scope, where treating approval_superseded as terminal is noted as the stopgap. Deferred there rather than patched here: an ApprovalCard change re-rolls four review lanes on a PR whose blocking lanes are green.
Suggestions (user-vocabulary refusal copy; "allow once" overclaims next to blanket Trust) — both folded into #5250's copy pass, noted there.

Disposition for the First Principles Review (CONCERNS) on decedb23:

"Framed as a fix, but #4421 already hid the doomed tiers at base — this ships a new capability" — the framing is accepted and already declared: the PR body's Pattern harvest and description state that the fix "necessarily ships the missing capability". #4421's hiding was the stopgap for the 400s; the root harm it left standing (the only grant available is blanket channel trust, so scoped consent pushes users to over-grant) is what this PR fixes, and fixing it IS the capability. The inventory's items 1–8 each carry this review's own "justified/declared" verdict.
Second trust-matching language beside trust_patterns.py — deliberate, and this review marks the semantics "justified": opaque literals with a bespoke equality matcher are the outcome of this PR's review history, where every pattern-language derivation scheme leaked scope (raw globs, per-segment lifts, first-token bases, env prefixes, case folding, \r). #5260 (completed) unified the chat side and its closing note already carries the shared-home item (env-assignment regex dedup); folding the channel literal matcher into that shared home while preserving literal semantics is mechanical follow-up, not a reason to re-derive patterns here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Channel approval cards offer trust_command/trust_base tiers the channel handler rejects

4 participants