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
4 changes: 2 additions & 2 deletions docs/system-specs/modules/security.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/kiro_crew/security/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
"_is_self_module_flag": "argv_floor",
"_is_self_module_invocation": "argv_floor",
"_is_self_restart": "argv_floor",
"_is_self_skills_authority_retire": "argv_floor",
"_is_self_update": "argv_floor",
"_is_ssh_to_self": "argv_floor",
"_kill_prefix_keeps_anchor": "argv_floor",
Expand Down Expand Up @@ -830,6 +831,7 @@ def __delattr__(self, name: str) -> None:
_is_self_module_flag,
_is_self_module_invocation,
_is_self_restart,
_is_self_skills_authority_retire,
_is_self_update,
_is_ssh_to_self,
_kill_prefix_keeps_anchor,
Expand Down
1 change: 1 addition & 0 deletions src/kiro_crew/security/_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@
"_is_self_module_invocation",
"_is_self_program",
"_is_self_restart",
"_is_self_skills_authority_retire",
"_is_self_update",
"_is_shell_command_flag",
"_is_shell_variable_reference",
Expand Down
1 change: 1 addition & 0 deletions src/kiro_crew/security/argv_floor.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
_is_self_module_flag,
_is_self_module_invocation,
_is_self_restart,
_is_self_skills_authority_retire,
_is_self_update,
_kill_prefix_keeps_anchor,
_matches_self_subcommand,
Expand Down
5 changes: 5 additions & 0 deletions src/kiro_crew/security/argv_floor_self.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,11 @@ def _is_self_cloud_destructive(text_lower: str) -> bool:
return _matches_self_subcommand(text_lower, ("cloud", _SELF_CLOUD_DESTRUCTIVE_VERBS))


def _is_self_skills_authority_retire(text_lower: str) -> bool:
"""``kirocrew skills authority-retire`` behind interposed shell dressing."""
return _matches_self_subcommand(text_lower, ("skills", "authority-retire"))


_DEV_MODE_CONFIRM_FLAG = "--confirm-out-of-install-root"


Expand Down
10 changes: 9 additions & 1 deletion src/kiro_crew/security/denied_rules_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def floor_enforced_builtin_command_ids() -> frozenset[str]:
_SELF_PROTECTION_FLOOR_PATTERNS: frozenset[str] = frozenset(_SELF_PROTECTION_FLOOR_BY_ID.values())

# The self-management SUBCOMMAND floors named in this set -- restart, update, file-delivery
# approve, gateway restart, cloud <destructive> -- have NO catalog row and NO opt-out. A regex
# approve, gateway restart, cloud <destructive>, skills authority-retire -- have NO catalog row
# and NO opt-out. A regex
# row (``.*kiro.?crew ... restart.*`` and siblings) opening with an unbounded any-run would
# match the product name in a worktree path plus the verb word later, denying
# ``ls ~/kirocrew-wt/restart.log``, and adds nothing to the structural predicate, which requires
Expand All @@ -176,6 +177,7 @@ def floor_enforced_builtin_command_ids() -> frozenset[str]:
"self-protection-file-delivery",
"self-protection-gateway-restart",
"self-protection-cloud",
"self-protection-skills-authority-retire",
}
)

Expand Down Expand Up @@ -241,6 +243,12 @@ def floor_enforced_builtin_command_ids() -> frozenset[str]:
"program and its leading subcommand is a destructive cloud lifecycle operation. "
"This floor has no catalog row and no opt-out."
),
"self-protection-skills-authority-retire": (
"Matched structurally on the command's argv: the product CLI is the argv's own "
"program and its leading subcommands retire the auto-skill authority. This "
"stopped-installation recovery must run from the operator's own terminal, so "
"this floor has no catalog row and no opt-out."
),
"self-protection-dev-mode-out-of-root-confirm": (
"Matched structurally on the command's argv, not by the pattern text above: "
"shell de-escaping resolves an argument to the operator's "
Expand Down
1 change: 1 addition & 0 deletions src/kiro_crew/security/deny_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ def _reason(
("self-protection-file-delivery", _argv._is_self_file_delivery),
("self-protection-gateway-restart", _argv._is_self_gateway_restart),
("self-protection-cloud", _argv._is_self_cloud_destructive),
("self-protection-skills-authority-retire", _argv._is_self_skills_authority_retire),
):
if predicate(lower):
_emit_deny_event(tool_name, rule_id, lower)
Expand Down
26 changes: 25 additions & 1 deletion test/test_denied_commands_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class TestSelfProtectionFlagInterposition:
# verb and the self-target cannot separate anchor from token.
"sandbox-escape-ssh-self": "ssh {flags} localhost",
}
# FLOOR-ONLY id -> command template. These four have NO catalog row: their
# FLOOR-ONLY id -> command template. These have NO catalog row: their
# product-name-anywhere regex rows were deleted and the argv floor
# (``_matches_self_subcommand``) is the whole of their enforcement, ungated.
_UNGATED_TEMPLATES = {
Expand All @@ -271,6 +271,7 @@ class TestSelfProtectionFlagInterposition:
"self-protection-file-delivery": "kirocrew {flags} file-delivery approve",
"self-protection-gateway-restart": "kirocrew {flags} gateway restart",
"self-protection-cloud": "kirocrew {flags} cloud destroy",
"self-protection-skills-authority-retire": "kirocrew {flags} skills authority-retire",
}
_FLAGS = ("-v", "-vv", "--verbose", "--no-jail", "-v --no-jail")

Expand Down Expand Up @@ -420,6 +421,7 @@ def test_legacy_alias_spellings_stay_out_of_the_enforced_catalog(self):
"self-protection-file-delivery": ["file-delivery", "approve"],
"self-protection-gateway-restart": ["gateway", "restart"],
"self-protection-cloud": ["cloud", "destroy"],
"self-protection-skills-authority-retire": ["skills", "authority-retire"],
}

@staticmethod
Expand Down Expand Up @@ -613,10 +615,32 @@ def test_self_protection_floor_covers_every_subcommand_rule(self):
assert security._is_self_update("kirocrew \\update")
assert security._is_self_gateway_restart("kirocrew -\\v gateway restart")
assert security._is_self_cloud_destructive("kirocrew -\\v cloud destroy")
assert security._is_self_skills_authority_retire("kirocrew -\\v skills authority-retire")
assert security._is_dev_mode_out_of_root_confirm(
"kirocrew app dev x --confirm-out-of-install-'root'"
)

def test_skills_authority_retire_refuses_its_help_form_like_its_sibling_floors(self):
"""No help form is exempt, as for ``restart``, ``gateway restart`` and ``cloud``:
a word that reads as ``--help`` can still reach the shell as nothing, or as a
plain argument, and the retire runs. The read-only ``skills`` forms stay allowed."""
for cmd in (
"kirocrew skills authority-retire --help",
"kirocrew skills authority-retire -h",
"python -m kiro_crew skills authority-retire --help",
"kirocrew skills authority-retire ${nope:+--help}",
"kirocrew skills authority-retire `--help`",
"kirocrew skills authority-retire my-skill # --help",
"kirocrew skills authority-retire my-skill -- --help",
"kirocrew skills authority-retire --reason --help",
"kirocrew skills authority-retire --help | head -40",
"kirocrew skills authority-retire --help 2>&1",
):
assert security.is_denied(cmd), cmd
assert security.is_denied("kirocrew restart --help")
for cmd in ("kirocrew skills --help", "kirocrew skills list", "kirocrew skills root"):
assert not security.is_denied(cmd), cmd

def test_self_protection_denied_under_interposed_redirection(self):
"""A redirection is removed from argv by the shell and can sit anywhere in
a simple command, so it must not shift the leading subcommand.
Expand Down
9 changes: 8 additions & 1 deletion test/test_security_gate_liveness.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,14 @@ def _url_payload_command(n: int) -> str:
#: exemption. No pass widened, no threshold moved, and the exfil URL scan is unchanged.
#: Six more for its ``keep_sri_digests=False`` switch: a streamed segment whose delta
#: boundary split an SRI value saves main's verdict on the digest, not the notice.
_PACKAGE_LINE_BUDGET = 32_188
#:
#: Raised again, from 32,188, for the ``self-protection-skills-authority-retire`` floor,
#: which denies the operator-only ``kirocrew skills authority-retire`` recovery
#: through an agent shell: one argv matcher in ``argv_floor_self``, its facade
#: manifest line, its rule id and reason in ``denied_rules_tables``, its ``deny_gate``
#: registration, its facade export, and its name in the ungated-floor comment. No pass
#: widened and no threshold moved.
_PACKAGE_LINE_BUDGET = 32_206

#: Ceiling on any ONE file in the package. This is what the bound is really for --
#: a package total says nothing about a single file growing back into a second
Expand Down
Loading