fix(dashboard): owner-gate the skills CRUD write endpoints (#7344) - #7393
fix(dashboard): owner-gate the skills CRUD write endpoints (#7344)#7393patrigao wants to merge 1 commit into
Conversation
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe PR is a purely additive security hardening. I've verified:
The discovery pass found no candidates; my independent falsification confirms the change is sound, and no new grounded finding at the 80+ bar surfaced. No findings. [OPUS-REVIEWED] d3f8264 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — 🟡 CONCERNSDesign-level review of Design-Verdict: CONCERNS The "gate every mutating skill handler" sweep stops at the prompts.py file boundary — WatchThe description claims the sweep covered "the rest of the mutating skill-handler family," but Suggestions
[DESIGN-REVIEWED] d3f8264 |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS One counted sibling stays open: Not justified as shipped
What this change shipsIntent: stop non-owner and app-token callers from writing skill content that gets injected into agent context — a FIX (#7344, new tests fail on base).
WatchPoint patch with 1 counted unfixed sibling: grepped [FIRST-PRINCIPLES-REVIEWED] d3f8264 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
b6995e0 to
559628f
Compare
|
Disposition: fixed in
The finding is legitimate and in scope: |
559628f to
d623352
Compare
Disposition: rebutted — the synchronous SEL call inside an async handler is the file's pre-existing, file-wide idiom, not something this diff introduces or makes newly reachable.
Evidence: |
Disposition: fixed in
Legitimate: the three post-mutation outcome audits this PR added (create/update/delete) were naked, while the file's own convention — the guard helper — explicitly swallows SEL failures to preserve the response. A raised audit after |
Disposition: fixed in
Confirmed against |
Disposition: rebutted — the enumerated claim is exact, and TTL pruning is outside the threat model the gate (and the spec sentences) address.
The docstring phrase is immediately qualified by its colon-enumerated list — CRUD writes, pending approve/dismiss/dismiss-all, pin, inject-on-trigger — which is the precise covered set; |
Open PR relationship auditThis 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
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
🤖 Kiro Crew [operator: NicholasRBowers#a942f9ca]: This PR has been inactive for 7+ days with a merge conflict. I've assessed the blockers and they appear resolvable — I'll push fixes directly to this branch as a co-author. Assessment: Readiness already passed and all five AI lanes are green; the only blocker is the mechanical rebase the relationship audit forecast when #8259 landed on the same handlers. Plan: keep both guard layers in If you'd prefer I don't touch this PR, add the |
POST /api/skills and the PUT/DELETE branches of /api/skills/{name}
performed writes with no owner gate, no app-token refusal, and no SEL
audit. Skill content is injected into agent context, so a skill write is
an instruction-injection surface — the same rationale that already
guards the skill-trust endpoints in this file.
Generalize _deny_non_owner_skill_trust into
_deny_non_owner_skill_operation (is_owner_dashboard_request already
refuses app tokens and non-owner subjects, and the helper SEL-audits
both outcomes) and apply it at the top of api_skills_create and of the
PUT and DELETE branches of api_skill_detail. Each write outcome
(ok/rejected) now also emits a SEL log_tool_invocation record, matching
the sibling skill handlers. GET behavior is unchanged.
Closes #7344
d623352 to
d3f8264
Compare
|
Rebased onto main Conflicts resolved (all mechanical, from merged #8259):
Gates run locally on changed files: black, isort, flake8 (clean; Please review the resolution. A maintainer push makes the maintainer the last pusher, so a second approver is needed under the repo's last-push rule. Reply if anything looks wrong. |
Problem / Motivation
The dashboard skills CRUD write handlers in
src/kiro_crew/dashboard/handlers/prompts.py—api_skills_create(POST/api/skills) and the PUT/DELETE branches ofapi_skill_detail— performed writes with no owner gate, no app-token refusal, and no SEL audit. Any non-owner dashboard session or app-token-bearing caller could create, edit, or delete skills.Why it matters
Skill content is injected into agent context, so a skill write is an instruction-injection surface. The sibling surfaces already treat it that way: the skill-trust handlers in the same file gate on owner + app-token via
_deny_non_owner_skill_trust, andask_question.pycarries_deny_app_token/_deny_non_owner. Leaving the CRUD writes open made the guard set asymmetric: the trust ledger was protected while the content it vouches for was not.What changed (motivation → approach → change)
Symptom: unguarded skill writes. Root cause: the guard helper was scoped to the skill-trust operations only, so the CRUD handlers had nothing to call. Change: generalize the same-file helper
_deny_non_owner_skill_trust→_deny_non_owner_skill_operation(parameterized on the operation label), and apply it at the top ofapi_skills_create, the PUT and DELETE branches ofapi_skill_detail, and — per the first-principles review round — the rest of the mutating skill-handler family:api_skill_pending_approve(promotes candidate content into the live catalog, the same instruction-injection surface),api_skill_pending_dismiss,api_skills_pending_dismiss_all,api_skill_pin, andapi_skill_inject_on_trigger. Add per-outcome SEL audit records (denied and ok) via the_sel().log_tool_invocationpattern already used byapi_prompts. GET behavior is unchanged (it already carries the foreign-app-slot guard for kiro-workspace names). The CAS/descriptor-pin half of the asymmetry is tracked separately in #5028 and is deliberately out of scope here.Same-commit spec updates:
docs/system-specs/modules/learn-cron-dashboard.mdanddocs/system-specs/modules/memory-skills-hooks.mdnow document the write-path guard.Tests
New
test/test_skill_write_guard.py(fixtures and request-identity setup follow the existing skill-trust guard tests):TestSkillLifecycleEndpointsAreGated: pending approve/dismiss/dismiss-all, pin, and inject-on-trigger each refuse app tokens and non-owner subjects with the audited 403, and the loader records zero calls (no mutation happens before the gate)test/test_skill_trust_store.pyupdated for the renamed helper;test/test_skill_pending_api.pyruns its business-logic cases as the owner (the gate has its own dedicated coverage). Focused suites: 160 passed; broader prompts/skills selection: 571 passed.Manual verification
N/A — unit coverage sufficient: the change is a handler-level guard with no UI surface, and every denied/allowed path plus its SEL record is asserted directly.
Related Issues
Closes #7344
Pattern harvest
Rule candidate: review-prompt
Pattern: guard set applied to the read/trust surface of a resource but not to its sibling write handlers — check every mutating branch of a resource's handler family when one member carries an owner/app-token gate.
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)