diff --git a/docs/setup/marketplace.md b/docs/setup/marketplace.md index ea9092f9b..2e53c4945 100644 --- a/docs/setup/marketplace.md +++ b/docs/setup/marketplace.md @@ -346,7 +346,7 @@ For this repo that resolves three different ways: | Installed root | Manifest Codex actually reads | |---|---| | the repo root | root [`plugin.json`](../../plugin.json) — its AP1 `$schema` matches — with `.codex-plugin/plugin.json` merged over it. No catalogue installs this root as a Codex plugin | -| `plugins/magpie-setup/`, `plugins/magpie-utilities/` | their own `.codex-plugin/plugin.json`, read **instead of** `.claude-plugin/plugin.json` (an overlay applies only over an AP1 root manifest), so it is a complete manifest: the same name, description and shared metadata, `skills`, and a `hooks` path — see below | +| `plugins/magpie-setup/`, `plugins/magpie-utilities/`, `plugins/magpie-security/` | their own `.codex-plugin/plugin.json`, read **instead of** `.claude-plugin/plugin.json` (an overlay applies only over an AP1 root manifest), so it is a complete manifest: the same name, description and shared metadata, `skills`, and a `hooks` path — see below | | every other `plugins/magpie-/` | `.claude-plugin/plugin.json`, reached by fallback: those roots carry neither a `plugin.json` nor a `.codex-plugin/` | The last row is why the family plugins install into Codex at all despite @@ -356,11 +356,11 @@ carrying only a Claude-Code manifest. Codex takes a plugin's hooks from its manifest's `hooks` entry, or, with none, from `hooks/hooks.json`. It parses each hook file strictly, as an object with only `description` and `hooks`. Any other top-level key rejects the whole file: Codex reports a load warning and runs none of its hooks. -The `hooks/hooks.json` of `magpie-setup` and `magpie-utilities` belongs to Claude Code and carries `$schema` and `modules` for the [Claude Code mods](../when-to-use-mods.md), so Codex cannot use it. -Each of those two plugins therefore has a `.codex-plugin/plugin.json` whose `hooks` names `./hooks/codex-hooks.json`, a file in the shape Codex parses. +The `hooks/hooks.json` of `magpie-setup`, `magpie-utilities` and `magpie-security` belongs to Claude Code and carries `$schema` and `modules` for the [Claude Code mods](../when-to-use-mods.md), so Codex cannot use it. +Each of those three plugins therefore has a `.codex-plugin/plugin.json` whose `hooks` names `./hooks/codex-hooks.json`, a file in the shape Codex parses. Claude Code reads neither file and keeps using `hooks/hooks.json`. -For `magpie-setup` that file wires the [upgrade check](#automatic-upgrade-detection); for `magpie-utilities` it is empty, which only stops Codex from warning about `hooks/hooks.json` on every start. -`tools/dev/check-family-plugins.py` generates both files with `--fix`, and fails any plugin in the Codex catalogue whose Codex-visible hook file carries another top-level key. +For `magpie-setup` that file wires the [upgrade check](#automatic-upgrade-detection); for `magpie-utilities` and `magpie-security` it is empty, which only stops Codex from warning about `hooks/hooks.json` on every start. +`tools/dev/check-family-plugins.py` generates these files with `--fix`, and fails any plugin in the Codex catalogue whose Codex-visible hook file carries another top-level key. > [!WARNING] > A plugin root's `plugin.json` must be a regular file. If it is a **symlink**, diff --git a/tools/spec-loop/.last-sync b/tools/spec-loop/.last-sync index 70991c999..af0e82439 100644 --- a/tools/spec-loop/.last-sync +++ b/tools/spec-loop/.last-sync @@ -1 +1 @@ -cb51b53b5c75dc4d98421da894417a32dd7872cd +e03b74530acecd3b2312adb22ff68f220bef7c00 diff --git a/tools/spec-loop/specs/adapters.md b/tools/spec-loop/specs/adapters.md index 55207a5a9..291dcfb4b 100644 --- a/tools/spec-loop/specs/adapters.md +++ b/tools/spec-loop/specs/adapters.md @@ -65,6 +65,8 @@ by swapping the adapter, not the skill. - `tools/mail-archive/` — static mailing-list archive reader for projects whose list history is in a local mbox/Maildir export. - `tools/jira/` — issue-tracker adapter for projects on Jira. + Its `bridge.groovy` refuses a non-HTTPS base URL and does not follow + redirects (#1862). - `tools/security-tracker/` — Markdown contract spec for `contract:security-tracker`: the operations the `security-*` skills perform on `` (view, list, labels, field get/set, create, edit, comment, @@ -167,6 +169,9 @@ by swapping the adapter, not the skill. one; a new rollup always gets the canonical marker, in `github-rollup` and `vetted-op-tracker rollup-append` alike (#1665). A comment's id is read from its `#issuecomment-` URL. + A comment counts as the rollup only when its author is the + authenticated `gh` user or a repository collaborator, in `github-rollup` + and vetted-ops alike, so a non-collaborator cannot plant one (#1862). `tools/github/status-rollup.md` calls the tool instead of carrying its own script. Under the secure agent setup both tools' `gh` runs sandboxed and fails, @@ -267,6 +272,9 @@ by swapping the adapter, not the skill. status (`builds.sr.ht`), and repository reads (`git.sr.ht`/`hg.sr.ht`) via GraphQL. Capability: `contract:tracker + contract:source-control + contract:mail-archive`. + Comment bodies go through `--body-file` only, a GraphQL mutation needs + `--allow-mutation`, and the service must be a bare sr.ht subdomain + (#1862). - `tools/asf-svn/` — ASF Subversion distribution backend: staging area reads, `svn` command-sequence generation for releases and KEYS updates. Never runs `svn commit`; emits paste-ready commands for the Release @@ -325,6 +333,8 @@ by swapping the adapter, not the skill. printing one JSON document; `create_draft` only `APPEND`s to the drafts folder and never sends, and an operation whose folder is unset or missing declines with exit 3 so the resolution chain falls through (#1466). + It connects with a verified TLS context (`ssl.create_default_context()`, + #1862). `tools/mail-source/mailman3/` is a doc-only, read-only backend over Hyperkitty's JSON API (`list_recent_threads`, `read_thread`, `thread_url`); it has no drafts or sent view, so it pairs with a drafting @@ -347,6 +357,8 @@ by swapping the adapter, not the skill. patch-over-Jira adapters; implement `contract:change-request` for projects that land patches via mailing-list review or Jira rather than GitHub PRs. + `jira-patch` pins the reviewed attachment by id and sha256 before it is + applied (#1862). - `tools/chat/` — the `contract:chat` interface (pure Markdown) for a project's public chat. Three verbs, `list_channels`, `resolve_user(github_handle)`, and diff --git a/tools/spec-loop/specs/adversarial-review.md b/tools/spec-loop/specs/adversarial-review.md index 29c213a64..032fb82e6 100644 --- a/tools/spec-loop/specs/adversarial-review.md +++ b/tools/spec-loop/specs/adversarial-review.md @@ -131,7 +131,12 @@ before the PR is created, through one shared block. `stream-json` user message and no permission flag, so headless Antigravity denies every tool that needs approval (the first denial ends its turn with an empty answer, so its brief asks for file reads and searches only, and a - run that still hits one is an `error` naming the denied tool); `goose run --no-session` with + run that still hits one is an `error` naming the denied tool). + The brief names the tools that work (`list_dir`, `find_by_name`, + `grep_search`, `view_file` / `read_file`) and forbids subagents, because + headless agy asked to list or search a tree hands off to a research + subagent and ends its turn at once; a run that does so is reported as a + subagent handoff, and an empty run says why (#1871, #1852); `goose run --no-session` with `GOOSE_MODE=chat`, Goose's chat-only mode, which runs no tools at all; `claude -p` with `--strict-mcp-config` and Bash, the editing tools, web access and `Task` @@ -154,7 +159,10 @@ before the PR is created, through one shared block. private tracker the report says so in `warnings`. - **One findings schema, merged.** Every reviewer answers against the same schema (`severity`, `file`, `line`, `claim`, `evidence`). Parsing keeps the - good findings from a partly malformed reply and reports the bad ones; + good findings from a partly malformed reply and reports the bad ones, + with the reviewer's stderr and its raw output cut to the first 500 and + last 1500 characters, since a streamed reply's last event says how the + run ended (#1854); findings are de-duplicated across reviewers, keeping every reviewer's name, and sorted by severity. - **Bounded and interruptible.** Reviewers run in parallel, each with a diff --git a/tools/spec-loop/specs/agent-isolation-sandbox.md b/tools/spec-loop/specs/agent-isolation-sandbox.md index c152c10b1..f5365ff9e 100644 --- a/tools/spec-loop/specs/agent-isolation-sandbox.md +++ b/tools/spec-loop/specs/agent-isolation-sandbox.md @@ -170,6 +170,20 @@ See `docs/adapters/goose.md`. start no watcher when both are `off` (#1715). `cached`, any other value, and an unknown policy (no card, no gpg, no `uif:` record) keep the window. + A dismissal (Esc or a click) answers one signature only: its marker + names the signer processes it was made for, stops counting when one of + them ends or an unnamed signer starts, and never lasts past + `DISMISS_TTL_MIN`, so the next signature asks again even while other + sessions stay armed. + A window that closed unanswered (it lost the keyboard and exited with + `OVERLAY_UNANSWERED`, could not be drawn, or crashed) is no dismissal and + is shown again after `RESHOW_DELAY` while the signature still blocks + (#1832). + The matcher arms for every git that can reach the key — global options + with quoted values, git or ssh named by path or passed to a shell as a + string, remote-talking subcommands beyond fetch and push (`lfs`, + `archive --remote`, `subtree`, `svn`, `maintenance prefetch`), and the + `gh` commands that run git over ssh (#1833). A definite answer is cached in the runtime directory for ten minutes, but an `off` only for one, so a policy turned on since is honoured on the next burst of commands (#1723). The @@ -338,8 +352,8 @@ See `docs/adapters/goose.md`. the update skill's drift check and verify check 8 recognise the dispatcher flavour's symlinks as its installed shape, not as drift or as inert per-repo hooks (#1322, #1358). -- `tools/hook-manager/` — the contract for git-hook managers (prek - today) under the secure setup, `contract:hook-manager`: a manager's +- `tools/hook-manager/` — the contract for git-hook managers under the + secure setup, `contract:hook-manager`: a manager's install puts its hook in the repository's own `$(git rev-parse --git-common-dir)/hooks`, where the dispatcher chains, and its home is split — `env.` gives sandboxed runs a @@ -368,6 +382,14 @@ See `docs/adapters/goose.md`. shim, verify check 19d; the default-home deny and the dispatcher's host home stay on for every registered manager, because both homes are per user (#1819). + `registry.json` registers prek (install strategy `git-dir-flag`) and + four managers with install strategy `none`, which already write to the + repository's own hooks directory whatever the global `core.hooksPath`: + `simple-git-hooks`, `cargo-husky`, `git-build-hook` and + `git-code-format-maven-plugin` (#1838). + A `none` manager gets no shim; its worktree caveats (cargo-husky skips + hook install in a linked worktree, the JGit plugins target + `/hooks`) are recorded in the README's adapter table. - `plugins/magpie-setup/hooks/check-upgrade.sh` — magpie-setup's `SessionStart` hook. It runs outside the sandbox over a sandbox-writable tree, so it trusts @@ -460,7 +482,17 @@ The reference model is four layers, layered: for the same reason (#1384). 2. **Filesystem + network sandbox** — Linux `bubblewrap` + `socat` SNI proxy; macOS `sandbox-exec`. Default-deny reads outside the tree and - egress to non-allowed hosts. `sandbox.excludedCommands` carves out + egress to non-allowed hosts. + `sandbox.network.allowUnixSockets` is macOS-only: on Linux Claude Code's + seccomp filter rejects every `AF_UNIX` socket, so the container gateway + and ssh-agent signing need a per-machine `allowAllUnixSockets: true`, + which is only safe paired with absolute `denyRead` entries for the host's + daemon and IPC sockets (Docker / Podman, `/run/user/` with the user + D-Bus and systemd manager, the system bus, X11, WSL interop). + `docs/setup/secure-agent-setup.md` lists them; verify check 12e + (whenever the flag is set) and a `sandbox-lint` invariant require that + cover, the whole `/run/user/` of the current uid included (#1809). + `sandbox.excludedCommands` carves out commands that need host auth the sandbox blocks: - `gh` (OS keyring and, on macOS, Security.framework TLS verification); the blast radius is held by layers 3 (`gh auth diff --git a/tools/spec-loop/specs/cve-tooling.md b/tools/spec-loop/specs/cve-tooling.md index 517085ea9..8815846cf 100644 --- a/tools/spec-loop/specs/cve-tooling.md +++ b/tools/spec-loop/specs/cve-tooling.md @@ -88,6 +88,14 @@ reviewable. `--allocate` checks the allocate token without allocating). The tool never sees the operator's password, MFA factor, or session cookie. +- Reviewer comments round-trip through the tracker + (`tools/cve-tool-vulnogram/record.md`): `security-issue-sync` reads the + comment notification mail, proposes the body-field change, regenerates + and pushes the JSON, then appends the comment's slug to the + processed-comments ledger in the status rollup and drafts a courtesy + reply. Vulnogram has no resolve action for a comment, in the API or the + UI, so the ledger and the reply are the only record that it was handled + and sync never asks the operator to "resolve" one (#1840). With `vulnogram-api-setup --pmc [--scope read|write|allocate|auto]` the token arrives without copy-paste: the operator approves it in the browser on `/users/token/authorize`, and the script receives it over a loopback diff --git a/tools/spec-loop/specs/marketplace-distribution.md b/tools/spec-loop/specs/marketplace-distribution.md index 50ea98292..4134f814b 100644 --- a/tools/spec-loop/specs/marketplace-distribution.md +++ b/tools/spec-loop/specs/marketplace-distribution.md @@ -92,6 +92,15 @@ directories (`plugins/magpie-/skills//SKILL.md`); the flat `skills/` tree mirrors each one back with a single-hop symlink. Their manifest is Claude Code's `.claude-plugin/plugin.json`, but they are not Claude-Code-only: Codex installs them by falling through to that same file. +The exception is a plugin whose `hooks/hooks.json` carries Claude Code mods +(`$schema`, `modules`), which Codex's strict hooks schema rejects whole: +`magpie-setup`, `magpie-utilities` and `magpie-security` each carry a +complete `.codex-plugin/plugin.json` (read instead of the Claude manifest) +whose `hooks` names a clean `hooks/codex-hooks.json` — the `startup` +upgrade check (`/bin/bash -p check-upgrade.sh`) for `magpie-setup`, an empty +file for the other two (#1836, #1676). +`tools/dev/check-family-plugins.py --fix` generates them and fails a Codex +catalogue plugin whose Codex-visible hook file has any other top-level key. `magpie-setup` also carries two non-skill payloads as real files inside its plugin root, because `setup config` installs them from the plugin on a diff --git a/tools/spec-loop/specs/overview.md b/tools/spec-loop/specs/overview.md index 785238d89..5ccafda40 100644 --- a/tools/spec-loop/specs/overview.md +++ b/tools/spec-loop/specs/overview.md @@ -31,9 +31,9 @@ Each mode is an independently toggleable set of skills. Maturity mirrors | Mode | Spec | Maturity | |---|---|---| | Agentic Triage | [triage-mode.md](triage-mode.md) | stable (security) / experimental (PR, issue, contributor-nomination) | -| Agentic Mentoring | [mentoring-mode.md](mentoring-mode.md) | experimental (5 skills) | +| Agentic Mentoring | [mentoring-mode.md](mentoring-mode.md) | experimental (7 skills) | | Agentic Drafting | [drafting-mode.md](drafting-mode.md) | stable (security) / experimental (issue, audit-finding-fix, release-announce-draft) | -| Agentic Pairing | [pairing-mode.md](pairing-mode.md) | experimental (2 skills) | +| Agentic Pairing | [pairing-mode.md](pairing-mode.md) | experimental (3 skills) | > **Agentic Autonomous** is the fifth MISSION mode but is deliberately **off** by > sequencing policy (`.asf.yaml` `allow_auto_merge: false`) — it has no diff --git a/tools/spec-loop/specs/pr-management-family.md b/tools/spec-loop/specs/pr-management-family.md index eec8fe907..68d7a4f08 100644 --- a/tools/spec-loop/specs/pr-management-family.md +++ b/tools/spec-loop/specs/pr-management-family.md @@ -100,7 +100,11 @@ same directory; the behaviour is unchanged. posts on maintainer confirmation. Ships `mode: Triage` + `experimental`. Selection, scans, the slop threshold, disposition, inline anchoring and the review payload run as code in `pr_management.code_review`; the agent judges - H1/H5/S2, writes the findings and assigns severity. Detail files include + H1/H5/S2, writes the findings and assigns severity. + An inline comment carries the finding as the review body does (its + `quoted_rule` and `suggestion`), minus the excerpt it is anchored on, and + a folded finding falls back to its suggestion before its rule, never the + bare rule label (#1853). Detail files include `prerequisites.md`, `review-flow.md`, `review-loop.md`, `criteria.md`, `adversarial.md`, `posting.md`, one file per outcome under `classifications/` and per category under `criteria/`. diff --git a/tools/spec-loop/specs/sandbox-diagnostics.md b/tools/spec-loop/specs/sandbox-diagnostics.md index 023b09639..e64c13b79 100644 --- a/tools/spec-loop/specs/sandbox-diagnostics.md +++ b/tools/spec-loop/specs/sandbox-diagnostics.md @@ -47,7 +47,7 @@ error, and two skills that probe or verify the setup on demand. **Root cause** (which sandbox layer blocks it and why), **Fix** (a settings widening with per-entry rationale, or — for the `gh` entry — an invocation-shape rule, because there is nothing to widen), and - **Notes**. Twelve entries today: SSH agent / Yubikey, signed commit + **Notes**. Fourteen entries today: SSH agent / Yubikey, signed commit failing before any touch (`gpg.format=ssh` key unreadable), signed commit failing with `cannot exec` of the touch-overlay wrapper (`gpg.ssh.program` under the read-denied `~/.claude/scripts/`), @@ -66,7 +66,17 @@ error, and two skills that probe or verify the setup on demand. skipped for sandboxed commits (whole-user `core.hooksPath` under the read-denied home, #1364), and reads of `/tmp/claude-` asking for approval every time (`permissions.blockReadsOutsideWorkingDirectories` - on and the directory not a working directory, #1418). + on and the directory not a working directory, #1418), every Bash call + failing with `bwrap: Can't create file … Read-only file system` (an + `allowRead` parent of an `allowWrite` path makes bubblewrap mount it + read-only, #1809), and `git status` listing files nobody created + (`.mcp.json`, `.bash_profile`: the character-device placeholders + bubblewrap mounts over denied paths, #1809). + The Docker / Podman entry also covers the container gateway's policy + refusals (BuildKit, a socket bind-mount, an untyped `docker inspect`): + a tool that drives the docker CLI may hide the 403 body or report only + `unable to upgrade to h2c, received 403`, and the expected behaviour is + to report the refusal, never to route around the gateway (#1810). - `tools/agent-isolation/sandbox-error-hint.sh` — a Claude Code `PostToolUse` hook on the `Bash` matcher. Scans the tool's stdout + stderr for the catalogued symptom strings and, on a match, prints @@ -152,7 +162,21 @@ error, and two skills that probe or verify the setup on demand. `.roborev.toml` under the hook, ⚠; roborev agent hooks or MCP servers in the harness, ⚠). Check 9 also fails a vetted-ops rule that spells the versioned plugin-cache path with a `*`, since the rule must - name `~/.claude/magpie/vetted-ops` (#1406). The conditional checks — + name `~/.claude/magpie/vetted-ops` (#1406). + Checks are written to catch false passes (#1808): check 1 evaluates the + merged project, project-local and user settings, requires the home + `denyRead` entry, and names the file each key came from; check 7a probes + credential paths with `test -r` / `stat`, never `cat`, and check 7b lists + variable names only and warns on an IDE-launched session that never went + through `claude-iso`; checks 5, 10a and 13c report unchecked when the + pre-flight finds no framework checkout; check 10's overlay sub-checks + apply only to a hardware key (a card `gpg --card-status` finds, whatever + `gpg.format` is); and check 12e fails `allowAllUnixSockets` on Linux + without the host-socket `denyRead` cover, whether or not a container + runtime is present (#1809). + Verify and doctor both say how to recognise the sandbox's `/dev/null` + placeholders and to take host state from git or the user. + The conditional checks — 9, 10, 12, 13, 14, 17 and check 8's whole-user branch — live in `conditional-checks.md`; the body keeps each one's condition and its **n/a** answer, so a run diff --git a/tools/spec-loop/specs/security-issue-lifecycle.md b/tools/spec-loop/specs/security-issue-lifecycle.md index 5dc380122..abcce016f 100644 --- a/tools/spec-loop/specs/security-issue-lifecycle.md +++ b/tools/spec-loop/specs/security-issue-lifecycle.md @@ -389,6 +389,22 @@ publication, with a human gate and an audit-log entry at every step. paths outside the agreed file list, and print the commands, and `commit --scrub` / `new-branch --scrub` repeat the scrub before the VCS runs (exit `3` on a hit). +- **A deterministic public-surface scrub mod backs the skill's own check + (#1676).** In Claude Code with the `magpie-security` plugin enabled, + `plugins/magpie-security/hooks/public-surface-scrub.ts` intercepts Bash + calls to `git commit` / `tag` / `merge`, `gh pr`, `gh issue`, + `gh release create` and `gh api`, reads the message or body they carry + (`-m`, `-F`, `--body-file`, heredocs and pipelines included), and blocks + the call on a forbidden term, at zero token cost when the text is clean. + It keeps no list of its own: `forbidden-terms.ts` is generated from + `magpie_vcs.publication.DEFAULT_TERMS` by + `tools/dev/sync-forbidden-terms.py`, which a pre-commit hook checks. + A command aimed at `tracker_repo` by an explicit `--repo` is exempt; + one aimed at `upstream_repo` or run in an upstream working tree is + scanned, and unrelated repositories pass unchecked. + `MAGPIE_ALLOW_EMBARGO_TERMS=1` is the maintainer's escape hatch after + disclosure, and the block message does not tell the agent how to + bypass it. - **Adversarial review before a PR, on public content only.** `security-issue-fix`, `security-issue-import-from-pr` (over the patch it verifies) and `security-issue-import-from-scan` carry the shared diff --git a/tools/spec-loop/specs/security-model-preparation.md b/tools/spec-loop/specs/security-model-preparation.md index 73d0c1eeb..8a64866d7 100644 --- a/tools/spec-loop/specs/security-model-preparation.md +++ b/tools/spec-loop/specs/security-model-preparation.md @@ -60,7 +60,10 @@ triage history back into the document. (verify also `project.md`), so the generated pre-flight stops a run that lacks it (model-update gained its declaration in #1444). - Threat model: skill family F in `docs/security/threat-model.md` - (F.1–F.7, mitigations M.30–M.36). + *Appendix B — Security skill family STRIDE model* (F.1–F.7, + mitigations M.30–M.36); since #1859 the document's main body is the + project-wide model on the Alpha-Omega rubric (§1.1–§1.19), and + Magpie's own `SECURITY.md` points at it. - Scanner copy (#1786): `security-submit-to-oss-scan` (Drafting, `plugins/magpie-security/skills/submit-to-oss-scan/SKILL.md`, file contracts in `references/enrolment-files.md`) prepares the `enrol` or