- Go 97.2%
- Python 1.6%
- Go Template 0.6%
- Makefile 0.4%
- Ruby 0.2%
|
Some checks are pending
release / release (push) Waiting to run
release / windows-build (amd64) (push) Blocked by required conditions
release / windows-build (arm64) (push) Blocked by required conditions
release / bump-formula (push) Blocked by required conditions
test / test (push) Waiting to run
Graceful in-place config reload (Caddy, sshd, anything with ExecReload) had no coily path - only restart, which is heavier and drops connections. reload joins the closed sudo-prefixed verb set. Surfaced wiring the Tangled knot deploy. closes #325 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Audit-log: coily://1779456425/AGPE7XR6 - coily ops aws sts get-caller-identity Audit-log: coily://1779456426/AGPE7XSB - coily ops aws ssm get-parameter Audit-log: coily://1779456449/AGPE7XU5 - coily exec test Audit-log: coily://1779456477/AGPE7XYJ - coily ops gh api Audit-log: coily://1779456478/AGPE7XYM - coily ops gh api Audit-log: coily://1779456522/AGPE7X5X - coily ops gh api /repos/coilysiren/coily/issues Audit-log: coily://1779456536/AGPE7X7Q - coily ops gh api /repos/coilysiren/coily/issues/324 Audit-log: coily://1779456563/AGPE7YCX - coily ops gh issue edit 324 Audit-log: coily://1779456569/AGPE7YDQ - coily ops gh api /repos/coilysiren/coily/issues/323 Audit-log: coily://1779456883/AGPE7ZJ4 - coily ops gh release list Audit-log: coily://1779456914/AGPE7ZNV - coily ops gh api repos/coilysiren/coily/compare/8397594...v2.23.0 Audit-log: coily://1779456915/AGPE7ZNX - coily ops gh api repos/coilysiren/coily/git/refs/tags/v2.23.0 Audit-log: coily://1779456952/AGPE7ZSI - coily ssh kai-server Audit-log: coily://1779456973/AGPE7ZTD - coily ssh kai-server Audit-log: coily://1779456985/AGPE7ZWJ - coily ssh kai-server Audit-log: coily://1779456986/AGPE7ZWL - coily ssh kai-server Audit-log: coily://1779457303/AGPE725B - coily ssh kai-server Audit-log: coily://1779457328/AGPE73AF - coily ops gh issue comment 324 Audit-log: coily://1779457396/AGPE73IM - coily ssh kai-server Audit-log: coily://1779457781/AGPE74RG - coily ssh kai-server Audit-log: coily://1779457891/AGPE7443 - coily ssh kai-server Audit-log: coily://1779475908/AGPFCB4H - coily exec test Audit-log: coily://1779475923/AGPFCB6A - coily exec test Audit-log: coily://1779475957/AGPFCCCF - coily exec test Audit-log: coily://1779475972/AGPFCCEA - coily ops gh issue create Audit-log: coily://1779475986/AGPFCCFX - coily ops gh issue create Audit-log: coily://1779476004/AGPFCCH5 - coily ops gh auth status Audit-log: coily://1779476153/AGPFCC2D - coily ops mcporter call luca.ask question=how many times has github been reauthorized this week Audit-log: coily://1779482104/AGPFCZQS - coily ops gh auth status Audit-log: coily://1779482158/AGPFCZXE - coily ops gh issue create |
||
|---|---|---|
| .agents/skills | ||
| .claude | ||
| .coily | ||
| .github/workflows | ||
| cmd/coily | ||
| docs | ||
| Formula | ||
| scripts | ||
| skills/coily-passthroughs | ||
| test/integration | ||
| .gitignore | ||
| .golangci.yml | ||
| .pre-commit-config.yaml | ||
| .pre-commit-hooks.yaml | ||
| AGENTS.md | ||
| config.example.yaml | ||
| CONTRIBUTING.md | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
| SECURITY.md | ||
coily
🛡️ coily is a CLI security boundary for privileged ops, 🔒 escape-hatch-resistant and with an 📜 audit trail.
Operator CLI for Kai's homelab (kai-server, coilysiren k3s cluster, AWS / Tailscale).
$ coily whoami # unified view across aws/gh/kubectl
$ coily ops aws ssm get-parameter --name '/x; cat /etc/passwd'
Error: policy: shell metacharacter rejected: arg name contains ";" at index 18
Both invocations land in ~/.coily/audit/<repo>.jsonl as structured rows.
This repo exists for three reasons.
- One audited surface for every privileged tool. External pass-throughs (
coily ops aws/gh/kubectl ...), standalone pass-throughs (coily docker,coily tailscale), and package managers (coily pkg pnpm/uv/cargo/brew) all forward verbatim, gated by argv shell-metacharacter rejection and an audit row.coily brewis a separate top-level for the four mutating brew verbs, scoped tocoilysiren/tap/*by default. - Safety boundary for AI agents. Claude Code's
deny: "Bash(kubectl delete:*)"is prefix-matched and bypassed bysh -c,env,python -c subprocess, Makefile shell-outs, etc. coily inverts the model: a narrow allowlist (Bash(coily:*)) funnels privileged ops through one Go binary that re-validates structured args and rejects metacharacter injection. Full reasoning in SECURITY.md. - Auditors love to see me coming. Every invocation is appended to a structured JSONL log with session metadata.
Install
coily is never published as a prebuilt binary; every install is a local build. Canonical: make install sudo-installs to root-owned /usr/local/bin. Brew tap is bootstrap-only (still build-from-source, doesn't preserve root-owned property).
make install # macOS, sudo-install /usr/local/bin/coily
brew tap coilysiren/coily https://github.com/coilysiren/coily && brew install coilysiren/coily/coily
make install-windows # elevated shell, installs C:\Program Files\coily\coily.exe
make deploy-server # linux cross-compile + scp + sudo-install on kai-server
make dev # ./bin/coily-dev for iteration (not on PATH)
make test defaults to gotest. To opt out: make test GO_TEST="go test". The agent's allowlist trusts coily, not coily-dev. Dev builds use -tags dev, prod uses -tags prod.
aws, kubectl, gh resolution
Via $PATH like any other binary. Argv validation + audit + the lockdown deny list carry the safety boundary; binary authenticity is the host's problem. See SECURITY.md.
Per-repo commands (.coily/coily.yaml)
Each repo drops a coily.yaml inside .coily/ to declare its dev verbs. coily exec test, coily exec lint, etc. Replaces per-repo Makefiles and pyinvoke tasks without widening the boundary.
commands:
test: go test ./...
lint:
run: golangci-lint run ./...
description: Lint with golangci-lint.
- Walks up from cwd to find
.coily/coily.yaml.$COILY_REPO_CONFIGoverrides. coily --listprints built-ins + repo commands.coily exec <cmd> --helpshows the expansion.- Every token passes
policy.ValidateArg. Metacharacters rejected at load time and at invocation. - Audit verb is
repo.<cmd>. Same log file. - Repo commands sit under
coily exec, can't shadow built-ins.
Architectural decisions
- Single binary, single trust boundary. One allowlist entry,
Bash(coily:*). - Trust
$PATHfor sub-tool binaries. Earlier sha256-pinned manifest dropped: the protection didn't justify the release pipeline. - SDK-native for simple APIs. ssh/scp + tailscale via Go SDKs. No subprocess means no argv to a shell.
- Mirror the sub-CLIs exactly.
coily ops aws ssm get-parametertakes the same args asaws ssm get-parameter. - Config is embedded. Changes require rebuild + sudo install.
- No self-update. Binary cannot rewrite itself. v2 plan around adversarial-reviewed CI installs in SECURITY.md.
- No
coily shell/coily runescape hatch.
Prior art
coily is a personal-scale remix of Teleport (scoped invocation + JSONL audit), mise (one CLI multiplexing many tools), and Dagger (validate structured args, don't just shell out).
Contributing
coily does not accept external pull requests - it is a security boundary, and an external change is an attack surface. See CONTRIBUTING.md; contribute to agent-guard instead. Issues welcome.
See also
- AGENTS.md - agent-facing operating rules.
- docs/FEATURES.md - inventory of what ships today.
- .coily/coily.yaml - allowlisted commands.
Cross-reference convention from coilysiren/agentic-os#59.