-
Notifications
You must be signed in to change notification settings - Fork 293
Expand file tree
/
Copy pathAUTOSDE.yaml
More file actions
295 lines (269 loc) · 16.4 KB
/
Copy pathAUTOSDE.yaml
File metadata and controls
295 lines (269 loc) · 16.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# KiroCrew Backend AutoSDE Rules
#
# Backend (Python) counterpart to website/AUTOSDE.yaml (React/TypeScript
# frontend rules). Rules here govern the Python package under src/kiro_crew/.
# Both files are enforced by the Claude and Codex PR reviewers.
#
# De-Amazoned from the internal package rule set (internal links removed).
# Blocking rules are backed by real vulnerabilities/incidents in this codebase.
custom-rules:
- id: backend-security-controls
blocking: true
file-patterns:
- "src/kiro_crew/**/*.py"
rule: |
Enforce KiroCrew backend security controls, based on real vulnerabilities
found and fixed in this codebase.
- Never read sensitive credential paths (~/.aws, ~/.ssh, ~/.gnupg,
~/.docker, ~/.kube, ~/.npmrc, ~/.pypirc, ~/.netrc, ~/.git-credentials,
~/.kiro/crew/.env and the pre-move ~/.kirocrew/.env). File reads of
user- or LLM-influenced paths MUST go
through hooks.py, which enforces is_sensitive_path(); trusted
fixed-path internal config/package reads are exempt. Never weaken that
keystone.
- Never trust LLM output. Scan with redact_exfiltration_urls() AND
redact_credentials() before posting to Slack, the dashboard, or any
external surface. redact_credentials() catches raw credential patterns
(AKIA*, SecretAccessKey=, private-key headers) and base64 variants.
- Validate all MCP tool inputs via validation.py schemas. Never pass raw
LLM input to the filesystem, a subprocess, or a database.
- Authorization must be deny-by-default: reject unless positively
confirmed. Never write `if x and y and z` guards where a falsy value
silently skips the check (the fail-open bug class this rule guards).
- All agent subprocesses must go through AcpClient._spawn() (or the
provider spawn helpers), which apply an OS-level sandbox via wrap_argv().
- The dashboard binds to 127.0.0.1 by default (local-only). Binding to
0.0.0.0 is permitted ONLY when token-auth middleware is active. Never
bind to 0.0.0.0 without token auth — see origin.py:is_local_only() and
token_auth.py:token_auth_middleware().
- All tool invocations and permission decisions MUST emit a SEL audit
event via sel.py.
- Never hardcode secrets. Store them in ~/.kiro/crew/.env with chmod 600,
enforced at load time (log a warning if the chmod fails).
- Auto-approve / YOLO mode: an AD-HOC activation (Slack command, dashboard
picker, API) MUST auto-expire — either on a timed TTL capped at the
SafetyOverride 24h ceiling, or, when the operator selects the
`until_shutdown` duration, when the process stops. Never persist an
ad-hoc grant across a restart. A grant the operator DECLARED in
owner-only config (`agent.dangerously_skip_permissions`; the camelCase and legacy `yolo` spellings are also read) MAY
persist without expiry and IS re-established at each startup: it is a
standing instruction, is held in memory only (never written to disk), is
cleared when another approval mode is selected, and MUST remain deniable
by the enterprise governance ceiling (the `yolo_duration` scope's
`permanent` and `until_shutdown` members, evaluated fail-closed against
the HOST profile). There is deliberately NO dashboard toggle for the
declared grant. Every activation still routes through the SafetyOverride
singleton (safety_override.py) and emits a SEL event. Never set the mode
globals directly.
- Mermaid securityLevel must be 'strict' (iframe sandbox), never 'loose'
(which allows JS execution from diagram payloads).
- id: no-blocking-call-on-event-loop
blocking: true
file-patterns:
- "src/kiro_crew/**/*.py"
rule: |
Do NOT add a blocking syscall that runs on the asyncio event loop. The
gateway runs everything on one loop; a single blocking call there freezes
every task — the user's chat turn AND the liveness heartbeat — until the
watchdog kills the process and the supervisor respawns into the same
condition (a crash loop). This has caused multiple production wedges.
Calls that block in the kernel and MUST NOT run on the loop:
- subprocess.run / call / check_output / check_call / Popen(...).communicate()
- os.close() on a file descriptor (PTY master, socket) — can block on a
wedged peer
- os.waitpid / os.system / os.popen
- time.sleep (use `await asyncio.sleep`)
- synchronous network or DNS (requests.*, blocking socket.*, getaddrinfo)
- large synchronous file IO or filesystem walks
If such a call is reachable from the event loop (in an `async def`, or in
a sync helper called transitively from an async handler, a periodic task,
or a cron tick), offload it:
await asyncio.get_running_loop().run_in_executor(
subprocess_executor(), blocking_fn, *args)
or rewrite it with an async API (asyncio.create_subprocess_exec,
asyncio.sleep, aiohttp).
Allowed (do NOT flag):
- the call is already inside run_in_executor(...) or handed to a thread
- one-shot CLI / setup / packaging paths that run before the loop starts
- test code
When in doubt, offload — a leaked worker thread is survivable; a frozen
loop is not.
- id: no-new-work-on-gateway-boot-path
blocking: true
file-patterns:
- "src/kiro_crew/slack/gateway.py"
- "src/kiro_crew/dashboard/server.py"
- "src/kiro_crew/dashboard/handlers/**/*.py"
- "src/kiro_crew/cli.py"
rule: |
Do NOT add work to the gateway boot path. Everything between process
start and the moment the dashboard socket accepts requests runs once, in
order, on one thread — so every statement added there is paid by every
user on every launch, and it delays the point at which the app is usable.
A slow boot also compounds: the longer startup holds the event loop, the
more likely the loop-stall watchdog kills the process and the supervisor
respawns into the same startup, turning a latency bug into a crash loop.
The boot path means, specifically:
- `GatewayOrchestrator.run()` up to the `KIROCREW_READY` print
- `start_dashboard()` up to `await _start_site(site, port)`
- any `_init_*()` or `setup_*_routes()` reached from those
- `cli.py`'s gateway command before `asyncio.run()`
Reject a diff that puts any of these on that path:
1. A new awaited or synchronous step before the socket binds. Adding one
is a product decision about startup latency, not a detail.
2. Subsystem construction as a side effect of route registration. A
`setup_*_routes()` function must only register routes
(`app.router.add_*`). Building a pipeline, opening a store, or
constructing a client there drags that subsystem's whole init onto the
boot path, where nobody looking at the route file will find it.
3. Work whose cost scales with user data — row counts, corpus size,
file counts, session history. This is forbidden on the boot path at
any measured speed, because the measurement was taken on an empty
profile and the growth is unbounded.
4. Unconditional maintenance: orphan sweeps, integrity scans, vacuum,
reindex, or a migration that scans rather than checks a version. Boot
is not a maintenance window. Work that finds nothing on a healthy
install still costs every user every launch.
5. Eager import or instantiation of a subsystem that is optional,
disabled, or feature-flagged off. Gate the import, not just the
handler — an import the enabled-check happens after is not gated.
6. Touching a lazily-initialized accessor from boot code. A `@property`
documented as lazy-init is a contract; reading it at startup breaks
that contract silently and moves the cost to a place its author did
not intend.
Instead, defer it:
- a first-use accessor that constructs once and memoizes on the app
- `asyncio.create_task(...)` for work that may finish after readiness
- `await asyncio.to_thread(...)` when init genuinely must happen at boot
and genuinely blocks, so it does not hold the loop
- let dependent routes return 503 until the subsystem is ready, rather
than making every launch wait for a subsystem most launches never use
Allowed: route registration itself, in-memory wiring, O(1) config reads,
and bounded fail-closed safety checks that must precede serving traffic
(those still belong off the loop).
If a step genuinely must run at boot, the PR must say why it cannot be
deferred and state its worst-case bound on a large profile — not on the
author's machine.
- id: no-test-side-effects
blocking: true
file-patterns:
- "test/**/*.py"
- "tests/**/*.py"
- "src/kiro_crew/apps/builtins/**/tests/**/*.py"
- "transfer/**/*.py"
rule: |
A test must have no side effects outside its own tmp dir. Never register a
real cron job, write into the operator's real ~/.kiro/crew or HOME, start
or reconfigure a gateway/service, or create files or directories in the
repo or anywhere else that outlive the run. These shapes have already
bitten us: a unit test registered a live cron job, test runs accumulated
stray directories under the developer's home, and an empty file a test
left at the repository root was committed and shipped to main.
A CHILD PROCESS INHERITS THE CWD, and pytest's CWD is the repository root.
A test that spawns one MUST pass `cwd=` pointing inside its own tmp dir,
or anything the child writes with a relative path lands in the repo. This
is the shape the paragraph above does not catch by reading the test: no
line says `touch` or `open(...,"w")`, the write happens in a grandchild
process, and the test can assert against its tmp dir and still pass while
the artifact sits at the repo root. So for any test that runs a child:
- pass `cwd=<a directory under tmp_path>` to any child that MAY CREATE a
file, and to every helper that spawns one. A read-only query is exempt
and sometimes has to be: `git check-ignore` run against the checkout is
answering a question about the checkout, and pointing it at a tmp dir
would test nothing.
- scope an assertion about a file the child MIGHT create to where that
child's CWD actually is, not to where you hope it wrote. An assertion
over `tmp_path` proves nothing about a child that ran somewhere else,
and a security test whose payload escapes its own assertion is worse
than no test, because it reports the guarantee it failed to check.
Know which floor you are standing on — it is NOT the same everywhere:
- Tests under `test/` inherit the autouse fixtures in `test/conftest.py`
(`_isolate_kirocrew_home` pins KIROCREW_HOME per test,
`_isolate_subagents_dir`, `_isolate_sel_default_dir`, and friends).
- Tests under `src/kiro_crew/apps/builtins/*/tests/` and `tests/` see only
the ROOT `conftest.py`, which isolates $XDG_CONFIG_HOME and blocks host
service mutation but does NOT pin KIROCREW_HOME. A test there that
reaches `config_dir()` / `apps_dir()` writes the developer's real data
dir. Isolate it yourself: `monkeypatch.setenv("KIROCREW_HOME",
str(tmp_path))`.
Assert against the tmp dir, not the real one, and clean up anything you
create. If a test genuinely needs a host-level effect, it must be opt-in
and named in the corresponding conftest allowlist with a comment saying
why.
- id: top-level-imports
blocking: false
file-patterns:
- "src/kiro_crew/**/*.py"
rule: |
All imports must be at the top of the file, not inside functions or
methods. In-method imports make dependencies hard to trace, break IDE
navigation, hide circular-import issues until runtime, and can make test
mock patches target the wrong module namespace.
Exceptions:
- TYPE_CHECKING blocks: `if TYPE_CHECKING: from ...` (zero runtime cost).
- Genuine circular-import avoidance — add a `# circular import` comment
explaining why.
- Optional dependencies: `try: import foo except ImportError: foo = None`
- id: harness-parity
blocking: true
file-patterns:
- "src/kiro_crew/acp/**/*.py"
- "src/kiro_crew/providers/**/*.py"
- "src/kiro_crew/platform/**/*.py"
- "src/kiro_crew/config/loader.py"
- "src/kiro_crew/session.py"
- "src/kiro_crew/subagent.py"
- "src/kiro_crew/sandbox.py"
rule: |
Kiro Crew drives ONE first-class agent harness — kiro-cli
(ACP_BACKEND_KIRO, spelled "") — and ADAPTS the others (the dormant
ACP_BACKEND_CLAUDE seam, KAS, and any bring-your-own harness). An added
harness may only adapt itself to the seams the Kiro harness already runs
through. It may not move, widen, generalize, or add a branch to them. The
invariant ids below are defined in
docs/system-specs/modules/harness-parity.md; cite the id in the finding.
The mechanical shapes are already gated on added lines by
scripts/check_harness_parity.py, so do NOT re-report them. Report only
what a regex cannot see:
- H13 — additive at the seam. Registering a harness must be a v1 addition
at platform/interfaces.py:ProviderRegistry with no CONTRACT_VERSION
bump. FLAG a change that puts a new conditional, a new required
argument, a new awaited step, or a new failure mode on the KIRO
construction path in service of an adapter — including one that reads
as harmless refactoring (hoisting a kiro-specific step into a shared
helper, replacing an explicit kiro branch with a table lookup, or
making a kiro-only default parameter mandatory). The test is not "does
it still work for kiro", it is "did the kiro path change at all".
- H14 — the ABC is the contract. A capability the session layer reads off
a provider must be declared on providers/base.py:LLMProvider with a
safe default. FLAG a change that adds a hasattr/getattr probe on the
kiro path to accommodate an adapter, or that leaves a new capability
reachable only on AcpProvider so a missing attribute silently reads as
False for the adapter.
- H9/H10 — no lowest-common-denominator. FLAG a change that collapses a
per-harness literal (spawn argv construction, PROTOCOL_VERSION, client
capabilities, the pre-spawn agent materialization, the --model pin) into
one form every harness accepts. Downgrading the kiro session to make an
adapter fit is the defect, even when every existing test still passes.
- H1/H3 — Kiro is the floor. FLAG a change that makes the kiro harness
conditional on an edition, a feature flag, an env var, or a registry
lookup, or that makes an unusable persisted acp_backend raise instead of
degrading to kiro with a logged reason.
- H6/H7 — capability grants. A new capability predicate must be membership
in a named ACP_BACKENDS_* set in acp/types.py, and every existing
harness's membership must be an explicit decision in the diff. FLAG a
new predicate that grants a capability to a harness the PR never
mentions. For is_kiro_cli specifically the failure is OPEN — it makes
sandbox.wrap_argv SKIP Kiro Crew's own seatbelt in favour of the
harness's internal one — so treat a grant to any non-kiro harness as a
security finding.
Allowed (do NOT flag):
- The dormant ACP_BACKEND_CLAUDE / _is_claude seam and its existing
negative call sites. They predate this rule, the whole-tree backlog is a
non-failing report by design, and converting them is a separate change.
- A pre-existing negative test on a line the PR merely moved or reindented.
- An adapter-only module (acp/kas_*.py and equivalents) that is entirely
additive and reachable only when its harness is selected.
- Widening the acp_backend config enum to admit a preview harness: the
enum is the value-SURVIVAL domain and selectability is gated separately
(H4). Omitting it there is the bug, not including it.