Skip to content

The github-issues quoting hardening needs a carrier an agent actually loads #152

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

#151 added a quoting overlay for the synced github-issues skill as a section in plugins/github/README.md, on the reasoning that a file outside skills/ survives an update-agent-skills pull.

It survives, but nothing reads it. This repository's own AGENTS.md states that a plugin's resources are skills/, agents/ and a bundled .mcp.json, each auto-discovered from its directory. A plugin's root README is not a resource. So an agent invoking github-issues receives skills/github-issues/SKILL.md and its references/, whose gh api examples still build -f values from issue text — the exact pattern the overlay warns about.

Raised as a P1 by Codex review on #151 and confirmed against AGENTS.md.

Why it matters

This is worse than an unfixed trap, because the repository now carries a test asserting the overlay exists. A future reader sees a guard passing and concludes the quoting risk is handled, when no agent has ever seen the guidance.

The constraint that makes it awkward

The obvious carrier — the skill itself — is a synced copy, reverted by the daily update-agent-skills workflow. Editing it there is silently undone, which is what pushed #151 outside skills/ in the first place.

Options, none free

  1. A plugin-authored skill carrying the safe-quoting guidance. Auto-discovered, and not overwritten because the sync only re-pulls the skills it owns. But every skill in this repository is currently synced from an upstream, so this introduces the first locally-authored skill into a marketplace every agent install consumes — a blast-radius decision, and it adds a permanently-listed skill for one caveat.
  2. A post-sync overlay step in update-agent-skills that re-applies a patch after each pull. Keeps the guidance in the file agents actually load, at the cost of a patch that must survive upstream edits.
  3. Upstream it to the skill's owner. The durable fix, and blocked for unattended runs by the ask-before-upstream-creates rule.
  4. Accept and document that the trap is upstream's, and drop the local layer rather than keep a hardening nothing loads.

Also to fold in

Codex's second P1 on the same PR: the --input example starts from populated $title / $description and never shows how untrusted text reaches them, which is the step where the injection actually occurs. Whatever carrier is chosen must show the ingress — stdin, argv, or an execution API that does not re-parse — rather than assuming the variables exist. Copied literally the current snippet also sends empty values, or fails under set -u.

Acceptance criteria

  • A decision is recorded on which carrier the guidance lives in, with the blast-radius trade-off stated.
  • The guidance is reachable by an agent invoking github-issues through a normal install, demonstrated rather than asserted.
  • The guidance shows a safe ingress for untrusted text, not just a safe command shape.
  • Any guard asserts the guidance is in the load path, not merely that a file exists.

Blocks #151, which is parked as a draft. Part of #116.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions