You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit f7eeca5
Browse filesBrowse the repository at this point in the historyBrowse files
* damage-control: class-C narrow mitigations (rows 20/22/24-27)
- inline interpreter writing to a run-time-computed path while naming the
control plane asks (unverifiable)
- existing aliases reached through a literal `cd <dir> &&` are resolved
- non-bash hooks get the SIGALRM scan deadline; every hook gets a watchdog
thread that blocks before the harness timeout
- `agentwire doctor` "Residuals you own" (informational) + wiki table of
closed / fail-closed / user-owned
- rows 20 (brace cap) and in-command `ln` verified already closed; pinned
Built by dotdev.dev
* damage-control: scope the opaque-inline-write ask to the sink's own path expression (#1148 review)
Program text only from -c/-e or an interpreter heredoc; the control word must
sit in the sink's path expression; memory/wiki/artifacts exempt; reads never
fire; receiver/getattr/non-literal-mode/heredoc-program sinks now covered.
Audit replay: 6853 commands, 1 new ask. Docs/test claims corrected.
Built by dotdev.dev
* damage-control: close the memory/wiki '..' bypass, add sinks, mask string data (#1148 review 2)
The agent-owned exemption no longer applies to an expression that can climb
out of it. Adds File.open/new, io/codecs/os.open, createWriteStream, openSync,
fopen, php -r, archive extraction sinks. Calls quoted as data are not calls.
Docs: replay count and residuals corrected.
Built by dotdev.dev
* damage-control: make the memory/wiki exemption all-or-nothing (#1148 review 3)
Exempt only ONE static path literal (optionally in expanduser/Path) that lies
inside the agent-owned dir on a whole segment; any non-literal piece, or a
chdir in a program naming the control plane, means no exemption. Replaces the
'..' spelling denylist.
Built by dotdev.dev
window (see [Closed, fail-closed, user-owned](#closed-fail-closed-user-owned-1065)). Directory-extraction spellings `/.`, `//`, `/sub/..`,
412
455
`--one-top-level=` and `bsdtar` are normalized. Pins:
413
456
`tests/unit/test_control_plane_writers.py`(must-block per verb *and* must-allow
414
457
siblings).
@@ -1638,6 +1681,34 @@ silent loop with no screen to show it on.
1638
1681
1639
1682
---
1640
1683
1684
+
## Closed, fail-closed, user-owned (#1065)
1685
+
1686
+
The single honest statement of what the text hook decides. A PreToolUse hook reads
1687
+
the command text before it runs, so anything whose path or verb exists only at run
1688
+
time, or whose content is in a file the text merely names, is not decidable by
1689
+
tightening rules (the lesson of #913/#915/#917/#921: it moves the bypass to the
1690
+
next spelling). The claim is **not** "zero residuals": it is *every statically
1691
+
decidable shape closed, the rest fails closed or is yours to enforce at the OS*.
1692
+
1693
+
| Residual | State | What decides it |
1694
+
|---|---|---|
1695
+
| Literal / quoted / escaped / `$HOME` / `~` spellings of a protected path; `ln` / `link` onto one | **Closed** (block) | path ladders, masked subcommands |
1696
+
| Existing symlink / hardlink / directory alias of a protected file (pre-existing); a relative word after `cd <dir> &&` is new | **Closed** (block) | realpath + inode match at hook time (`_realpath_variants`, `_cwd_realpath_variants`) |
1697
+
| Brace expansion past the 256-variant cap (pre-existing) | **Fail-closed** (block; ask `core.control-plane-target` when only that check sees it, e.g. a protected path before the braces) | `brace_variants` returns None; path patterns treat it as a hit |
1698
+
| Inline interpreter writing to a computed path while naming the control plane | **Fail-closed** (ask, unverifiable) | `_opaque_inline_write` |
1699
+
| Unresolvable `cd` then a bare protected name; `patch` / `git apply` in a protected dir | **Fail-closed** (ask) | `unverifiable_target_verdict` |
1700
+
| Scan too large / too slow; hook outliving its timeout | **Fail-closed** (ask / block) | size gate, SIGALRM deadline, watchdog |
1701
+
| SQL fed from a file (`psql -f`, `mysql < f`) | **User-owned** | read-only database role for agent sessions |
1702
+
| `make` / `just` / package-script recipe text | **User-owned** | sandbox profile / container / separate uid |
1703
+
| Check-then-use race (TOCTOU) | **User-owned** | `chflags uchg` (macOS) / `chattr +i` (Linux) on the control files |
1704
+
| Run-time-assembled paths or verbs beyond the fail-closed shapes (decoded payloads, `chr()` chains, `$(…)` output, earlier-turn aliases) | **User-owned** | the same immutable flags, or a sandbox profile denying writes to them |
1705
+
1706
+
`agentwire doctor` prints the user-owned rows under "Residuals you own"
1707
+
(informational, never scored; the list is `agentwire/safety/residuals.py`).
1708
+
agentwire does not set these controls: the immutable flag stops agentwire's own
1709
+
edits to the file too (`chflags nouchg` to change it), which is a decision for
1710
+
the person who owns the machine.
1711
+
1641
1712
## Troubleshooting
1642
1713
1643
1714
### Hook Not Blocking Expected Command
@@ -1870,12 +1941,22 @@ against main over that slice found no decision differences). Known allow, same
1870
1941
as main: a writer AND the path both inside ONE quoted string (`ln`, `curl -o`)
1871
1942
is data in a quote, not a command.
1872
1943
1873
-
The read hook has no deadline but is linear in the path length (a 20 MB path
1874
-
takes ~4 s); edit/write scan only the path.
1875
-
1876
-
Declared residuals: the clock budget needs `SIGALRM` on the main thread (every
1877
-
hook process; not a portal worker thread or Windows, where only the size gate
1878
-
applies).
1944
+
The read hook is linear in the path length (a 20 MB path takes ~4 s); edit/write
1945
+
scan only the path. All five hooks are bounded anyway (#1065): the edit/write/read/mcp
1946
+
entrypoints run `main` under the same 10 s `SIGALRM` scan deadline
1947
+
(`run_hook_guarded(main, deadline_seconds=…)`; over it = block exit 2), and EVERY
1948
+
hook starts a 20 s watchdog thread that exits the process with a block, so a
1949
+
hook can never outlive the 30 s harness timeout into "no decision".
1950
+
1951
+
Why two: the signal is the only thing that interrupts a pattern stuck inside the
1952
+
regex engine (`re` checks for signals; a thread waiting on a timer needs the GIL,
1953
+
which a long C regex never yields) — but it needs POSIX and the main thread.
1954
+
The watchdog needs neither, so it covers Windows and any wait that releases the
1955
+
GIL (a hung `stat`/`realpath` on a network mount). **Declared residual:** a
1956
+
runaway regex on a platform without `SIGALRM` (Windows) outlives the watchdog
1957
+
too; the size gate is the only bound there. `check_command` called in-process
1958
+
from a portal worker thread keeps the old shape (size gate only; the watchdog
1959
+
exits the process and so is hook-only).
1879
1960
1880
1961
### A command inside a compound body is judged like the same command at top level (#1112)
0 commit comments