execverb: KDL exec dialect (wrap+exec transport) for passthrough/remote-exec verbs #130

Closed
opened 2026-06-10 18:16:01 +00:00 by coilysiren · 0 comments
Owner

Why

The KDL Guardfile currently expresses only the HTTP transport (spec + base-url + auth). The coily->ward merge needs the same policy-as-sentences treatment for exec-shaped verbs: git passthroughs, brew/pkg, docker, tailscale, systemctl, gaming, and the forgejo admin/doctor leaves (kubectl-exec into the pod) - the remaining non-HTTP slice of the 40-leaf migration (coilyco-flight-deck/ward#75). Today each is hand-written Go on cli/passthrough; the policy (which subcommands, which flags refused) deserves the same reviewable, non-executable KDL form specverb proved.

Grammar (the exec dialect)

A wrap block whose transport is exec instead of spec/auth:

wrap ward git {
    exec git

    can run status
    can run log
    can run commit {
        deny-flag "--no-verify"
        deny-flag "--amend"
    }
}

wrap ward ops forgejo admin {
    exec ssh {
        argv-prefix "kai@kai-server" "k3s" "kubectl" "-n" "forgejo" "exec" "deploy/forgejo" "--" "forgejo"
    }
    can run "admin user list"
    can run "doctor check" { allow-flag "--run" }
}
  • exec <bin> - the real binary, fixed at parse; an optional argv-prefix child pins an unoverridable leading argv (the kubectl-exec / remote-exec transport).
  • can run <subcommand> - deny-by-default subcommand allowlist; multi-word sentences quote.
  • Grant children: deny-flag (default-allow minus denials), allow-flag (strict allowlist mode when present), allow-arg-re (positional shape rule), describe.

Engine (execverb, sibling of specverb)

execverb.Build(Config{Guardfile, Wrap, Runner}) mounts one leaf per grant under verb.Wrap (audit + argv metachar gate), reusing cli/passthrough's exec/env machinery. Invariants:

  • binary + argv-prefix fixed by the Guardfile, never caller input
  • unmounted subcommand = no leaf (fail-closed), unknown grant child = parse error
  • flag policy enforced before exec; metachar gate via ArgsFunc as today
  • dotted audit names (ward.git.commit), same rows as the hand-written verbs

The describe model extends naturally: each leaf reports bin, fixed prefix, flag policy - same reference-doc emission via specverb-gen (the driver grows a transport switch).

M0 scope

Parser (exec block + run grants + flag rules), engine (mount + flag policy + exec via passthrough), tests (deny-by-default, flag denial, prefix immutability, audit row), proving consumers: ward git status|log|diff|commit and ward ops forgejo admin/doctor (replacing the 4 kubectl-exec leaves). M1: egress/env policy blocks, completions, never run explicit denials.

Part of #123 (security-pure engine + thin KDL consumers). Supersedes the per-binary Go wrapper pattern named in #101.

## Why The KDL Guardfile currently expresses only the HTTP transport (spec + base-url + auth). The coily->ward merge needs the same policy-as-sentences treatment for exec-shaped verbs: git passthroughs, brew/pkg, docker, tailscale, systemctl, gaming, and the forgejo admin/doctor leaves (kubectl-exec into the pod) - the remaining non-HTTP slice of the 40-leaf migration (https://forgejo.coilysiren.me/coilyco-flight-deck/ward/issues/75). Today each is hand-written Go on cli/passthrough; the policy (which subcommands, which flags refused) deserves the same reviewable, non-executable KDL form specverb proved. ## Grammar (the exec dialect) A `wrap` block whose transport is `exec` instead of `spec`/`auth`: ```kdl wrap ward git { exec git can run status can run log can run commit { deny-flag "--no-verify" deny-flag "--amend" } } wrap ward ops forgejo admin { exec ssh { argv-prefix "kai@kai-server" "k3s" "kubectl" "-n" "forgejo" "exec" "deploy/forgejo" "--" "forgejo" } can run "admin user list" can run "doctor check" { allow-flag "--run" } } ``` * **`exec <bin>`** - the real binary, fixed at parse; an optional `argv-prefix` child pins an unoverridable leading argv (the kubectl-exec / remote-exec transport). * **`can run <subcommand>`** - deny-by-default subcommand allowlist; multi-word sentences quote. * **Grant children**: `deny-flag` (default-allow minus denials), `allow-flag` (strict allowlist mode when present), `allow-arg-re` (positional shape rule), `describe`. ## Engine (execverb, sibling of specverb) `execverb.Build(Config{Guardfile, Wrap, Runner})` mounts one leaf per grant under `verb.Wrap` (audit + argv metachar gate), reusing `cli/passthrough`'s exec/env machinery. Invariants: * binary + argv-prefix fixed by the Guardfile, never caller input * unmounted subcommand = no leaf (fail-closed), unknown grant child = parse error * flag policy enforced before exec; metachar gate via ArgsFunc as today * dotted audit names (`ward.git.commit`), same rows as the hand-written verbs The describe model extends naturally: each leaf reports bin, fixed prefix, flag policy - same reference-doc emission via specverb-gen (the driver grows a transport switch). ## M0 scope Parser (exec block + run grants + flag rules), engine (mount + flag policy + exec via passthrough), tests (deny-by-default, flag denial, prefix immutability, audit row), proving consumers: `ward git status|log|diff|commit` and `ward ops forgejo admin/doctor` (replacing the 4 kubectl-exec leaves). M1: egress/env policy blocks, completions, `never run` explicit denials. Part of https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/123 (security-pure engine + thin KDL consumers). Supersedes the per-binary Go wrapper pattern named in https://forgejo.coilysiren.me/coilyco-flight-deck/cli-guard/issues/101.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/cli-guard#130
No description provided.