No description
  • Go 97.2%
  • Python 1.6%
  • Go Template 0.6%
  • Makefile 0.4%
  • Ruby 0.2%
Find a file
Kai Siren 96e3be3915
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
feat(systemctl): add reload to the verb set
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
2026-05-22 13:36:14 -07:00
.agents/skills chore: migrate skill directory to .agents/skills 2026-05-22 00:11:24 -07:00
.claude chore: migrate skill directory to .agents/skills 2026-05-22 00:11:24 -07:00
.coily feat(coily.yaml): expose make build as a coily verb 2026-05-22 01:36:57 -07:00
.github/workflows ci: remove close-external-prs workflow, superseded by native PR disable 2026-05-20 23:25:52 -07:00
cmd/coily feat(systemctl): add reload to the verb set 2026-05-22 13:36:14 -07:00
docs feat(session): add honest self-termination verb for finished sidequests 2026-05-21 05:31:29 -07:00
Formula chore(formula): bump to v2.22.0 [skip ci] 2026-05-22 09:30:52 +00:00
scripts chore: adopt coilysiren/agentic-os v0.2.1 upstream-ref pre-commit suite 2026-05-15 23:35:00 -07:00
skills/coily-passthroughs feat(systemctl): add reload to the verb set 2026-05-22 13:36:14 -07:00
test/integration Initial commit: coily operator CLI with AI-agent safety boundary 2026-04-22 14:47:07 -07:00
.gitignore chore: migrate skill directory to .agents/skills 2026-05-22 00:11:24 -07:00
.golangci.yml Add OpenAPI->coily generator; mount modio/glama/discord/sentry/trello-api under ops 2026-05-07 23:26:52 -07:00
.pre-commit-config.yaml chore: trim README + AGENTS + FEATURES under 100 lines and 5000 bytes 2026-05-15 23:55:31 -07:00
.pre-commit-hooks.yaml add coily lint subcommand for .coily/coily.yaml ↔ Makefile drift 2026-05-11 17:19:08 -07:00
AGENTS.md feat(gh): make GitHub Actions / CI status checking playwright-only 2026-05-21 04:49:37 -07:00
config.example.yaml refactor: delete pkg/, reusable logic lives in cli-guard, closes #214 2026-05-17 17:27:59 -07:00
CONTRIBUTING.md docs: lock down coily as a no-PR security boundary 2026-05-20 23:22:04 -07:00
go.mod Merge branch 'dispatch/issue-289' 2026-05-21 05:58:24 -07:00
go.sum Merge branch 'dispatch/issue-289' 2026-05-21 05:58:24 -07:00
Makefile build: GO_TEST falls back to go test when gotest absent, closes #265 2026-05-19 17:29:54 -07:00
README.md docs: lock down coily as a no-PR security boundary 2026-05-20 23:22:04 -07:00
SECURITY.md docs: lock down coily as a no-PR security boundary 2026-05-20 23:22:04 -07:00

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.

  1. 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 brew is a separate top-level for the four mutating brew verbs, scoped to coilysiren/tap/* by default.
  2. Safety boundary for AI agents. Claude Code's deny: "Bash(kubectl delete:*)" is prefix-matched and bypassed by sh -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.
  3. 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_CONFIG overrides.
  • coily --list prints built-ins + repo commands. coily exec <cmd> --help shows 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 $PATH for 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-parameter takes the same args as aws 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 run escape 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

Cross-reference convention from coilysiren/agentic-os#59.