test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip - #1051
Conversation
The Kiro CLI and Kiro IDE hook adapters classified only execute_bash and shell as shell tools. On Windows the agent issues shell work through execute_pwsh, so those calls slipped past the code-generation plan-approval gate and the lifecycle state-transition guard. Add execute_pwsh to the shell-tool set in both adapters (plan-approval forwarding + state-transition guard + audit-log Bash canonicalization) and cover it in t147 and t218. Closes #1044
1fdafd4 to
d401ec9
Compare
|
Thanks — this is a clean, well-scoped fix, and I checked completeness rather than assuming it: all nine I'm leaving this as a comment rather than a blocking review, because there's a triage question that What #1044 actually was — the direction differs per harnessYour summary says
And The new t218 case passes against the unfixed adapterWorth knowing regardless of what happens to this PR. I ran both suites against the pre-fix adapters to The cause is the payload shape: both assertions use
A realistic payload is also the case that actually regressed: - JSON.stringify({ toolName: "execute_pwsh", toolArgs: {} }),
+ JSON.stringify({
+ toolName: "execute_pwsh",
+ toolArgs: { command: "bun .kiro/tools/aidlc-orchestrate.ts next" },
+ }),With The triage question — #1000 also claims this issue#1000 declares Its coverage is also already in the shape I described above: That makes it a real decision rather than a formality. #1000 is 172 files with seven rounds of I'm deliberately not gating this either way. Flagging it now so you don't polish something that might be Non-blocking — the duplication that caused #1044 is still here
One more thing for whoever sequences these: this PR and #986 edit the same non-empty-args line, so Validation I ran at
|
| check | result |
|---|---|
bun test tests/unit/t147-kiro-hook-adapter.test.ts |
35 pass / 0 fail |
bun test tests/unit/t218-kiro-ide-hook-adapter.test.ts |
75 pass / 0 fail |
| the same two suites against the pre-fix adapters | t147 34 / 1 ✅ · t218 75 / 0 ❌ |
approved-window probe, { command } payloads |
pre-fix execute_pwsh 2 / shell 2 / execute_bash 0 → post-fix all 0 |
bun scripts/package.ts --check |
deterministic across two independent builds, all 7 harnesses |
bun run typecheck / bun run lint |
exit 0 / exit 0 |
| hosted CI at this head | 16/16 SUCCESS |
Nice piece of work either way — the SHELL_TOOLS shape is the right one, and it's what #1000 arrived at
independently.
leandrodamascena
left a comment
There was a problem hiding this comment.
Thanks for addressing the Windows shell-tool handling.
I reviewed the current head d401ec967aa81643d8a6d1f83d70788958b6c721 against base 0d399dd828b59e84d90f7cc198c69fab9ad8f1a7. The change is aligned with #1044: Kiro CLI needs to prevent execute_pwsh from bypassing its guards, while Kiro IDE needs to permit recognized shell commands after plan approval.
[P2] The new Kiro IDE regression test does not exercise the fixed path
In tests/unit/t218-kiro-ide-hook-adapter.test.ts:1786 and :1845, the execute_pwsh payload uses toolArgs: {}. With no command, both assertions pass against the adapter before this PR because unrelated empty-input handling produces the expected exit codes. Therefore, the test does not fail when the implementation fix is removed.
Please provide a realistic shell payload in both calls, for example:
toolArgs: {
command: "bun .kiro/tools/aidlc-orchestrate.ts next"
}This exposes the relevant behavioral transition: before the fix, an approved execute_pwsh call remains blocked with exit code 2; after the fix, it is permitted with exit code 0.
Please also update the PR description to distinguish the two behaviors:
- Kiro CLI had a guard-bypass/security issue.
- Kiro IDE had a liveness issue where
execute_pwshandshellremained blocked after approval.
The focused t147 coverage is valid, the implementation direction is sound, and all 16 current CI checks are green. No version bump is needed because release preparation owns version and changelog consolidation.
… payload
The t218 plan-approval assertions used toolArgs: {}, which short-circuits through opaqueMutation's empty-args clause before reaching the branches shellTool() changes, so both the pre- and post-fix adapters returned the same codes and the case did not fail against the unfixed adapter.
Use a realistic { command } payload (the case that actually regressed): pre-fix the approved-window execute_pwsh exits 2, post-fix it exits 0. Verified fail-first against origin/main and pass against this branch for both the t218 case and t147 1c.
Addresses review feedback on #1051.
|
@leandrodamascena @wowzoo Fixed the t218 test in d44d2ba: both the pre- and post-approval assertions now send a realistic I verified fail-first the way you described, using a
On the direction correction: you're right that the summary is imprecise. It's a genuine two-way bypass on the CLI (plan-approval-guard let it through, and state-transition-guard skipped it at the early exit) but a liveness failure on the IDE (an approved On the #1000 overlap and the remaining three-spellings duplication: understood and happy to defer to the maintainers on which PR owns #1044. If they prefer the small fix first, this is ready; if #1000 lands first, the direction note above should move into its description. I also updated the description of PR. |
apackeer
left a comment
There was a problem hiding this comment.
Thank you for updating the regression test after review. #1000 has now merged and includes the Kiro IDE shell-alias handling, CLI Bash canonicalization, and CLI state-transition guard change covered here. I checked those changes against current main, and #1044 is already closed.
There is still a useful test to preserve: your t218 case sends a populated toolArgs.command through legacy execute_pwsh before and after human approval. Main's corresponding approved legacy shell assertion still uses execute_bash with empty arguments, so it does not pin that exact regression.
Could you narrow this to a test-only PR against current main: drop the two adapter changes and the redundant t147 addition, retain/adapt the t218 populated-command regression, update the title and description to that scope, and rerun the focused t218 suite? Then we can review the remaining test contribution.
|
@apackeer OK, I will do it.
|
Narrow this branch to a test-only contribution. #1000 landed the Kiro IDE shell-alias predicate, the Kiro CLI Bash canonicalization, and the CLI state-transition-guard change on main, so both adapter edits and the t147 execute_pwsh case here are now redundant. Every conflicted and touched file is resolved to main's content; the merge tree is byte-identical to main.
Every legacy `{ toolName, toolArgs }` assertion in t218 uses `execute_bash`
with empty arguments, so the opaque-mutation branch decides them both before
and after approval and the shell-alias resolution never runs. A Windows host
names the same tool `execute_pwsh` and does supply `toolArgs.command`.
Add one regression test that walks the existing legacy mediation flow with
`execute_pwsh` plus a real command and pins the approval-boundary transition:
exit 2 before approval, via the recovery path rather than the opaque target
path refusal, and exit 0 after. Narrowing isKiroShellTool() back to
`execute_bash` alone makes the post-approval assertion fail with exit 2, so
the case is fail-first against the pre-#1000 adapter.
|
@apackeer |
|
Thank you for narrowing this to the single case, and I am sorry the thread went quiet for five days Four things from re-measuring at The red check is not a branch-test failure. The only failing check is One correction to the description. It says every legacy assertion in One request on the assertion. The case turns on
One cross-PR note, with no action requested here. #1157 consolidates |
Address review on #1051: the pre-approval assertion used a negative check (not.toContain the opaque target-path refusal), which the non-shell fallback branch also satisfies, so it did not prove execute_pwsh was recognized as a shell. Pin the legacy recovery block positively with toContain("recovery requires a human response"), matching the neighbouring routed-to-recovery test. Also correct the leading comment to describe the narrower gap: plan-approval-guard had no legacy populated-command assertion across the approval boundary (empty-argument execute_pwsh recovery was already covered).
AIDA findings ledger
Open blocking findings (P0/P1): 0. Accepted and rejected findings never count toward the next action. Maintainer commands (repository write access) — put them on the first lines of a comment, one per line, several ids per line allowed: ledger.json{
"version": 4,
"pullRequest": 1051,
"nextId": 2,
"findings": [
{
"id": "F1",
"priority": "P3",
"category": "contracts",
"title": "Comment misstates the empty-argument recovery branch",
"anchors": [
{
"kind": "line",
"path": "tests/unit/t218-kiro-ide-hook-adapter.test.ts",
"side": "RIGHT",
"sha256": "79e4199215b13f3e283d90cbc9d63a9faec7c2fcd499538ecf638590e04261f4"
}
],
"status": "open",
"firstSeen": {
"head": "7ede5a0fb3e8b7c49925f4a5ee80f7e91605a9dd",
"at": "2026-09-24T05:45:06.267Z"
},
"lastSeen": {
"head": "7ede5a0fb3e8b7c49925f4a5ee80f7e91605a9dd",
"at": "2026-09-24T05:45:06.267Z"
}
}
],
"events": [
{
"at": "2026-09-24T05:45:06.267Z",
"kind": "opened",
"by": "aida",
"id": "F1",
"head": "7ede5a0fb3e8b7c49925f4a5ee80f7e91605a9dd"
}
],
"review": {
"head": "7ede5a0fb3e8b7c49925f4a5ee80f7e91605a9dd",
"readiness": 4,
"risk": 1,
"decision": "merge"
}
} |
There was a problem hiding this comment.
Reviewed 7ede5a0fb3e8b7c49925f4a5ee80f7e91605a9dd against 1b0645858664405ae54129d00f6e37c85480173e and current repository behavior.
Inspection: 1 changed file. Scope: full head (first review of this pull request).
Final Assessment
Human decision aid only: Readiness 5/5 is best; Risk 1/5 is best. These scores inform the maintainer; the next action below follows finding severity (any open P0/P1 → author/change) and does not approve or merge the PR.
Readiness: 4/5 — The regression test is valid and narrowly scoped; only its explanatory branch rationale needs correction.
Risk: 1/5 — The change affects tests only and is readily reversible. The surviving issue is a misleading comment, not runtime behavior.
Decision required: Maintainer — decide whether to merge this PR. The test behavior is sound and the sole surviving finding is a non-blocking P3 documentation issue.
Validation performed:
- Inspected the complete diff, changed-file manifest, sole 3,971-line head snapshot, PR metadata, discussion, ledger, and all specialist outputs.
- Re-derived the test path through the legacy Kiro IDE adapter, Plan Approval state helpers, core guard, projection setup, and neighboring tests.
- Confirmed the new test covers populated execute_pwsh forwarding after approval and contains no runtime changes or active prompt attack.
Findings: 0 blocking, 1 advisory.
Ledger: 1 open, 0 retained blocking, 0 accepted, 0 suppressed as rejected by a maintainer. Maintainers act on findings with /aida commands in the ledger comment.
Contracts & Compatibility
P3 [F1]: Comment misstates the empty-argument recovery branch
Evidence: tests/unit/t218-kiro-ide-hook-adapter.test.ts:2005.
Problem: The comment says an empty legacy shell payload never reaches shell-alias resolution. In the neighboring recovery setup, the preceding argument-less write leaves an active write window, and the adapter checks isKiroShellTool before computing opaqueMutation. That existing test therefore already exercises execute_pwsh alias recognition; the populated payload uniquely adds post-approval forwarding coverage.
Impact: The contradictory rationale can mislead maintainers about which branch the regression test protects, although it does not invalidate the test or affect users.
Required correction: Revise the comment to state that empty arguments cover recovery-time alias recognition, while populated arguments add coverage for forwarding through the approved boundary.
User Experience
User experience change: This PR adds regression coverage for existing Kiro IDE Windows-shell Plan Approval behavior without changing shipped runtime code.
Assessment: Users retain the same approval and recovery behavior; the only indirect risk is future maintainers relying on an inaccurate test comment.
Residual risk: Repository code and tests were not executed, as prohibited by the review contract; behavior was verified by static inspection of the immutable snapshots and related base contracts.
Reviewed by AIDA (AI-DLC Developer Agent).
[AI-PR-REVIEWED] 7ede5a0
…e-evidence-fixes * origin/main: test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (#1051)
* commit 'refs/r1309/main': test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (awslabs#1051) chore(release): prepare v2.10.0 (awslabs#1380) fix: normalize Windows drive letters so Kiro IDE artifact writes are audited (awslabs#1201) fix: make the guards a fence for the agents and a gate the human holds the key to (awslabs#1262) fix: correct session-skill command references and document CLI fallback (awslabs#1363) fix(ci): preload system modules for Windows Codex readiness (awslabs#1367) fix(ci): correct Codex readiness and composed scope checks (awslabs#1365) fix: name a working next step in the refusals operators actually hit (awslabs#1322) fix(ci): tolerate unsupported AIDA repository evidence (awslabs#1364) fix(ci): require live coverage and parallelize platform tests (awslabs#1311) fix(aida): a folded higher-severity duplicate publishes its own body; deferral rationale never stacks explanations (awslabs#1359) fix(doctor): read hook heartbeats left at the pre-engine-dir path (awslabs#1240) test: register retired flag classifier coverage (awslabs#1361) fix: consume retired --init/--force flags instead of leaking them into intent descriptions (awslabs#982) feat(aida): the next action follows finding severity alone; readiness and risk inform, never decide (awslabs#1319) feat(aida): judge dispositions bind restatements by id; security lenses emit structured evidence (awslabs#1316) feat(aida): incremental review scope per lens, deferred out-of-scope findings, /aida full (awslabs#1312) fix(aida): ledger identity via judge ledgerId, evaluable anchors, strict /aida batches, verdict refresh both ways (awslabs#1308)
* origin/main: fix: always sort audit rows before deriving the stage run floor (awslabs#1314) fix(sensor): route a sensor's path argument from its declared input_schema (awslabs#1239) fix(onboarding): render user-typed skill names with the harness skill prefix (awslabs#1368) fix(dispatch): route the three team-mode state verbs the engine calls (awslabs#1309) test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (awslabs#1051) chore(release): prepare v2.10.0 (awslabs#1380) fix: normalize Windows drive letters so Kiro IDE artifact writes are audited (awslabs#1201) fix: make the guards a fence for the agents and a gate the human holds the key to (awslabs#1262)
* origin/main: (77 commits) fix(onboarding): render user-typed skill names with the harness skill prefix (awslabs#1368) fix(dispatch): route the three team-mode state verbs the engine calls (awslabs#1309) test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (awslabs#1051) chore(release): prepare v2.10.0 (awslabs#1380) fix: normalize Windows drive letters so Kiro IDE artifact writes are audited (awslabs#1201) fix: make the guards a fence for the agents and a gate the human holds the key to (awslabs#1262) fix: correct session-skill command references and document CLI fallback (awslabs#1363) fix(ci): preload system modules for Windows Codex readiness (awslabs#1367) fix(ci): correct Codex readiness and composed scope checks (awslabs#1365) fix: name a working next step in the refusals operators actually hit (awslabs#1322) fix(ci): tolerate unsupported AIDA repository evidence (awslabs#1364) fix(ci): require live coverage and parallelize platform tests (awslabs#1311) fix(aida): a folded higher-severity duplicate publishes its own body; deferral rationale never stacks explanations (awslabs#1359) fix(doctor): read hook heartbeats left at the pre-engine-dir path (awslabs#1240) test: register retired flag classifier coverage (awslabs#1361) fix: consume retired --init/--force flags instead of leaking them into intent descriptions (awslabs#982) feat(aida): the next action follows finding severity alone; readiness and risk inform, never decide (awslabs#1319) feat(aida): judge dispositions bind restatements by id; security lenses emit structured evidence (awslabs#1316) feat(aida): incremental review scope per lens, deferred out-of-scope findings, /aida full (awslabs#1312) fix(aida): ledger identity via judge ledgerId, evaluable anchors, strict /aida batches, verdict refresh both ways (awslabs#1308) ...
* origin/main: (41 commits) chore(ci): supersede Full Suite verification across branch heads (awslabs#1390) fix: never record an unreadable review findings table as no findings (awslabs#1163) fix(doctor): report Kiro IDE ignore sources that hide .kiro/ (awslabs#1161) chore(ci): run the cross-OS jobs in the merge queue, not on every PR push (awslabs#1389) feat(plan-approval): auto-resolve --session from the invoking conversation (awslabs#1379) fix: always sort audit rows before deriving the stage run floor (awslabs#1314) fix(sensor): route a sensor's path argument from its declared input_schema (awslabs#1239) fix(onboarding): render user-typed skill names with the harness skill prefix (awslabs#1368) fix(dispatch): route the three team-mode state verbs the engine calls (awslabs#1309) test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (awslabs#1051) chore(release): prepare v2.10.0 (awslabs#1380) fix: normalize Windows drive letters so Kiro IDE artifact writes are audited (awslabs#1201) fix: make the guards a fence for the agents and a gate the human holds the key to (awslabs#1262) fix: correct session-skill command references and document CLI fallback (awslabs#1363) fix(ci): preload system modules for Windows Codex readiness (awslabs#1367) fix(ci): correct Codex readiness and composed scope checks (awslabs#1365) fix: name a working next step in the refusals operators actually hit (awslabs#1322) fix(ci): tolerate unsupported AIDA repository evidence (awslabs#1364) fix(ci): require live coverage and parallelize platform tests (awslabs#1311) fix(aida): a folded higher-severity duplicate publishes its own body; deferral rationale never stacks explanations (awslabs#1359) ...
…k-json-output * origin/main: (42 commits) fix(windows): repair the cross-OS test failures on Windows (#1393) fix(doctor): compare the audit against the per-stage checkboxes (#1272) fix(sensor): drop a superseded detail file when the sensor passes (#1266) chore(ci): supersede Full Suite verification across branch heads (#1390) fix: never record an unreadable review findings table as no findings (#1163) fix(doctor): report Kiro IDE ignore sources that hide .kiro/ (#1161) chore(ci): run the cross-OS jobs in the merge queue, not on every PR push (#1389) feat(plan-approval): auto-resolve --session from the invoking conversation (#1379) fix: always sort audit rows before deriving the stage run floor (#1314) fix(sensor): route a sensor's path argument from its declared input_schema (#1239) fix(onboarding): render user-typed skill names with the harness skill prefix (#1368) fix(dispatch): route the three team-mode state verbs the engine calls (#1309) test(kiro-ide): pin the legacy execute_pwsh populated-command approval flip (#1051) chore(release): prepare v2.10.0 (#1380) fix: normalize Windows drive letters so Kiro IDE artifact writes are audited (#1201) fix: make the guards a fence for the agents and a gate the human holds the key to (#1262) fix: correct session-skill command references and document CLI fallback (#1363) fix(ci): preload system modules for Windows Codex readiness (#1367) fix(ci): correct Codex readiness and composed scope checks (#1365) fix: name a working next step in the refusals operators actually hit (#1322) ...
Summary
Narrowed to a test-only change, per review.
#1000landed onmainand already carries everything this PR originally implemented: the Kiro IDE shell-alias predicate (isKiroShellTool()coveringexecute_bash/execute_pwsh/shell), the Kiro CLIBashcanonicalization, and the CLIstate-transition-guardchange.#1044is closed. Both adapter edits and thet147execute_pwshcase here are therefore redundant and have been dropped.What is not yet pinned on
mainis the legacy{ toolName, toolArgs }channel onplan-approval-guardwith a populated command across the approval boundary.plan-approval-guardalready coversexecute_pwshfor empty-argument recovery (the neighbouringexecute_pwsh and shell are routed to legacy recovery exactly like execute_bashtest), but an empty-argument payload is decided by the opaque-mutation branch — it never reaches the code that resolves a shell alias. A Windows host names the same toolexecute_pwshand does supplytoolArgs.command. This PR keeps that one regression test to pin the populated-command transition.Changes
tests/unit/t218-kiro-ide-hook-adapter.test.ts: addlegacy execute_pwsh with a populated command flips from blocked to permitted across approval(single new test, no edits to existing cases). It walks the same legacy mediation flow as the neighbouringexecute_bashtest and asserts the approval-boundary transition:recovery requires a human response), matching the neighbouring routed-to-recovery test.isKiroShellToolmatches on the tool name alone, so a populated command reaches the same recognition branch; pinning that message positively provesexecute_pwshwas recognized as a shell rather than merely not hitting another branch;maininto the branch and resolved both adapter files andt147back tomain's content. The merge tree is identical tomain; the PR diff is the single test file.distchange, no version/CHANGELOG/README bump — release preparation owns version and changelog consolidation.User experience
Unchanged. This PR adds coverage only; the behaviour it pins already ships on
mainvia#1000.Checklist
If an item does not apply, leave it unchecked.
Test plan
bash tests/run-tests.sh --unit --filter "t218"→ 87 pass / 0 fail, including the new case.isKiroShellTool()inharness/kiro-ide/hooks/aidlc-kiro-adapter.tstoreturn toolName === "execute_bash";, runbun scripts/package.ts, thenbun test tests/unit/t218-kiro-ide-hook-adapter.test.ts -t "legacy execute_pwsh with a populated command". With the predicate narrowed,execute_pwshis no longer recognized as a shell, so the pre-approval assertion fails: the non-shell fallback returns the opaquePlan Approval blocked this mutation…block instead ofrecovery requires a human response. Restoring the predicate makes it pass again. This is the positive branch pin requested in review — the negativenot.toContain("target path…")form would have passed the non-shell fallback too and only surfaced later, so it did not proveexecute_pwshwas recognized as a shell.bunx biome check tests/unit/t218-kiro-ide-hook-adapter.test.ts→ clean.bun test tests/unit/gen-coverage-registry.test.ts→ 37 pass / 0 fail (committed coverage registry stays fresh).Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.