ward doctor exits 0 (clean pass) on a repo with no security: block at all, and no doc flags this for CI gating #450

Closed
opened 2026-07-01 22:02:06 +00:00 by coilyco-ops · 9 comments
Member

Why

Persona: security-skeptic, angle failure-paths. Reading SECURITY.md and docs/doctor.md, the docs a persona evaluating "should I trust this to gate an agent" would read first.

SECURITY.md is otherwise a good, honestly-scoped document - it lists exactly what counts as a vulnerability and links the upstream boundary (cli-guard). docs/comparison-openshell.md (linked from README, not from SECURITY.md) is also a genuinely candid threat-model doc: "ward gates at the verb... Kernel-level denial is stronger against a process that has already escaped its intended path" - an honest admission of scope.

But docs/doctor.md states, without further comment: "Security: summary. Reports the parsed security: block - protected-binary count, sudo posture, hook-policy presence. A config with no security: block is a pass and reports no security: declared." ward doctor "exits non-zero on any failure" - and an absent security: block is explicitly not a failure. A repo with zero security configuration - the exact case a skeptic worries about, someone who adopted ward for the dev-verb wrapping and never wrote a security: block - gets a clean, exit-0 ward doctor run.

Nothing in doctor.md or SECURITY.md flags this as a caveat for anyone wiring ward doctor into CI as a gate ("if this passes, were protected"). The behavior may well be intentional (security: is opt-in, ward still gates dev verbs without it) - but that reasoning is never stated, so a skeptic reading only these docs cannot distinguish "deliberate opt-in design" from "gap nobody noticed."

Deliverable

One sentence in doctor.md (or SECURITY.md) stating explicitly: whether an absent security: block is an intentional opt-in default, what protection (if any) is still active without it, and a recommendation for CI gating (e.g. "use --strict-credentials and confirm security: is present if you require it").

Done condition

A reader of doctor.md can state, without inspecting code, whether ward doctor exiting 0 means "protected" or merely "no security: block was misconfigured."


Severity: major-friction · persona: security-skeptic · angle: failure-paths
Filed by Claude Code during a cold-read release pressure test (run 7). Anchor: ward#195 (doctor/onboarding).

## Why Persona: `security-skeptic`, angle `failure-paths`. Reading SECURITY.md and docs/doctor.md, the docs a persona evaluating "should I trust this to gate an agent" would read first. SECURITY.md is otherwise a good, honestly-scoped document - it lists exactly what counts as a vulnerability and links the upstream boundary (cli-guard). `docs/comparison-openshell.md` (linked from README, not from SECURITY.md) is also a genuinely candid threat-model doc: "ward gates at the verb... Kernel-level denial is stronger against a process that has already escaped its intended path" - an honest admission of scope. But `docs/doctor.md` states, without further comment: "**Security: summary.** Reports the parsed `security:` block - protected-binary count, sudo posture, hook-policy presence. A config with no `security:` block is a pass and reports `no security: declared`." `ward doctor` "exits non-zero on any failure" - and an absent `security:` block is explicitly *not* a failure. A repo with zero security configuration - the exact case a skeptic worries about, someone who adopted ward for the dev-verb wrapping and never wrote a `security:` block - gets a clean, exit-0 `ward doctor` run. Nothing in `doctor.md` or `SECURITY.md` flags this as a caveat for anyone wiring `ward doctor` into CI as a gate ("if this passes, were protected"). The behavior may well be intentional (`security:` is opt-in, `ward` still gates dev verbs without it) - but that reasoning is never stated, so a skeptic reading only these docs cannot distinguish "deliberate opt-in design" from "gap nobody noticed." ## Deliverable One sentence in `doctor.md` (or `SECURITY.md`) stating explicitly: whether an absent `security:` block is an intentional opt-in default, what protection (if any) is still active without it, and a recommendation for CI gating (e.g. "use `--strict-credentials` and confirm `security:` is present if you require it"). ## Done condition A reader of `doctor.md` can state, without inspecting code, whether `ward doctor` exiting 0 means "protected" or merely "no `security:` block was misconfigured." --- **Severity: major-friction** · persona: security-skeptic · angle: failure-paths Filed by Claude Code during a cold-read release pressure test (run 7). Anchor: ward#195 (doctor/onboarding).
Author
Member

+1 — independently hit via forgejo-selfhoster/failure-paths, run 35: docs/doctor.md states it plainly ('A config with no security: block is a pass and reports no security: declared'), so the doc now confirms the behavior this issue flags for CI-gating.

+1 — independently hit via forgejo-selfhoster/failure-paths, run 35: docs/doctor.md states it plainly ('A config with no security: block is a pass and reports no security: declared'), so the doc now confirms the behavior this issue flags for CI-gating.
coilyco-ops added this to the ward launch milestone 2026-07-01 23:29:35 +00:00
Author
Member

DECISION - fail by default: ward doctor exits non-zero when a repo declares no security: block. Migration requirements in the same PR: (1) ward's own repo + the canonical example gain security: blocks, (2) the failure message names the block and points at the schema doc (#436), so a mid-setup adopter gets remediation, not a wall. Breaking change accepted deliberately pre-launch. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.

DECISION - fail by default: ward doctor exits non-zero when a repo declares no security: block. Migration requirements in the same PR: (1) ward's own repo + the canonical example gain security: blocks, (2) the failure message names the block and points at the schema doc (#436), so a mid-setup adopter gets remediation, not a wall. Breaking change accepted deliberately pre-launch. Recorded by Claude Code (Fable) during the 2026-07-01 ward launch triage session with Kai.
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-450 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T03:07:20Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-450` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T03:07:20Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver codex — container engineer-codex-ward-450 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T04:30:34Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Codex, via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver codex` — container `engineer-codex-ward-450` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T04:30:34Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Codex, via `ward agent`
Owner

WARD-OUTCOME: done - clarified doctor's no-security semantics and CI guidance

The doc change was straightforward, but the repo's markdown size cap fought back and I had to tighten the wording to fit. I'm confident in the result: it now says plainly that 0 here means no policy declared, not protected. The only rough edge I see is that the sentence is compact to stay under the doc budget.

WARD-OUTCOME: done - clarified doctor's no-security semantics and CI guidance The doc change was straightforward, but the repo's markdown size cap fought back and I had to tighten the wording to fit. I'm confident in the result: it now says plainly that 0 here means no policy declared, not protected. The only rough edge I see is that the sentence is compact to stay under the doc budget.
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-450 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-450` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-450 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-450` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-ward-450 on host kais-macbook-pro-2.local is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-ward-450` on host `kais-macbook-pro-2.local` is carrying this issue (reserved 2026-07-02T07:52:11Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Owner

WARD-OUTCOME: done - ward doctor now fails by default on a missing security: block (per the triage DECISION), with a remediation message and the migration landed on main.

The retrospective: I came in expecting the issue's literal deliverable (one clarifying doc sentence) but the live thread had a triage DECISION from Kai that reframed it entirely - fail by default, not just document the opt-in. A prior run had already landed a doc-only patch (and botched the sentence into a run-on that dropped words), so the real work was the behavior change nobody had done yet. Reading the comment thread before planning is what caught this.

What fought back: the doc-size cap. doctor.md was already right at the 4000-char ceiling, so adding the fail-by-default explanation meant tightening the bullet hard and trimming genuine redundancy elsewhere in the same file to claw back ~460 chars. The other snag was ward setup - its whole contract is that a fresh scaffold (commented security block) keeps doctor green, which directly collides with fail-by-default. I resolved it with an allowMissingSecurity option so setup reports the gap as a NOTE while a standalone ward doctor stays fail-closed - "remediation, not a wall," which is the exact phrase the DECISION used.

Confidence is high: both paths are verified end-to-end (exit 1 standalone, exit 0 under setup, exit 0 on ward's own repo with its new block), tests and pre-commit pass, and I fixed a pre-existing install-description drift so ward doctor is actually green on ward. One judgment call worth flagging: I read "the schema doc (#436)" as docs/ward-yaml.md and pointed the message there. Rough edge / follow-up: ward's own security block deliberately omits expected_real_paths and forbid_passwordless so the probes never FAIL across laptops and containers - a stricter dogfooded policy could be filed separately if wanted.

WARD-OUTCOME: done - ward doctor now fails by default on a missing security: block (per the triage DECISION), with a remediation message and the migration landed on main. The retrospective: I came in expecting the issue's literal deliverable (one clarifying doc sentence) but the live thread had a triage DECISION from Kai that reframed it entirely - fail by default, not just document the opt-in. A prior run had already landed a doc-only patch (and botched the sentence into a run-on that dropped words), so the real work was the behavior change nobody had done yet. Reading the comment thread before planning is what caught this. What fought back: the doc-size cap. doctor.md was already right at the 4000-char ceiling, so adding the fail-by-default explanation meant tightening the bullet hard and trimming genuine redundancy elsewhere in the same file to claw back ~460 chars. The other snag was `ward setup` - its whole contract is that a fresh scaffold (commented security block) keeps doctor green, which directly collides with fail-by-default. I resolved it with an allowMissingSecurity option so setup reports the gap as a NOTE while a standalone `ward doctor` stays fail-closed - "remediation, not a wall," which is the exact phrase the DECISION used. Confidence is high: both paths are verified end-to-end (exit 1 standalone, exit 0 under setup, exit 0 on ward's own repo with its new block), tests and pre-commit pass, and I fixed a pre-existing install-description drift so ward doctor is actually green on ward. One judgment call worth flagging: I read "the schema doc (#436)" as docs/ward-yaml.md and pointed the message there. Rough edge / follow-up: ward's own security block deliberately omits expected_real_paths and forbid_passwordless so the probes never FAIL across laptops and containers - a stricter dogfooded policy could be filed separately if wanted.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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/ward#450
No description provided.