Skip to content

Agent Skills: lockfile, capability label, prose diff and approval gate - #10

Merged
bnymnDev merged 17 commits into
developfrom
feature/skills-lock
Oct 3, 2026
Merged

bnymnDev merged 17 commits into
developfrom
feature/skills-lock

Conversation

@bnymnDev

@bnymnDev bnymnDev commented Oct 3, 2026

Copy link
Copy Markdown
Owner

What

This adds agentgate skills, a lockfile for Agent Skills. skills.lock pins every file of a skill with a Merkle root. It also records a label of what the skill can do (shell, network, secrets, writes outside the project, package installs, remote content and more) and the findings someone approved.

The subcommands:

  • lock and approve record the current state.
  • verify fails on any change nobody approved.
  • diff shows a changed SKILL.md sentence by sentence.
  • label shows what a skill can do.
  • scan vets a skill before it is installed.

The GitHub Action takes the new inputs skills, skills-lockfile and skills-fail.

How

  • 42 deterministic rules, among them:
    • Unicode tag, bidi, zero-width and variation-selector tricks
    • look-alike hosts
    • encoded payloads and curl | sh
    • conditional triggers, exfiltration and persistence
  • No network access and no model.
  • The design, its decisions and the threat model are in docs/design/skills-lock.md and docs/skills.md.
  • Four adversarial reviews are recorded in the design doc.

Two things are deliberately not built and are on the roadmap:

  • policy integration (decision 11)
  • the WASM demo

The README now names the skills roadmap under Next.

Effect on enforcement and the audit log

None. The proxy, the policy evaluation and the audit store are unchanged. Skills are checked by a separate command and by the Action, not by the proxy.

Checklist

  • make fmt vet lint test passes
  • make e2e passes, if the proxy, CLI or audit store changed
  • make golden was run and the diff reviewed, if policy evaluation changed (not affected)
  • make docs was run, if a command, flag, matcher or redaction pattern changed
  • docs/ and the README reflect any user-visible change

Generated by Claude Code

A skill is a directory with a SKILL.md: instructions a coding agent loads
and follows, often with scripts next to them. internal/skills pins every
file of a skill in an RFC 6962-shaped Merkle tree, derives a label of what
the skill can do (shell, network, secrets, file writes outside the project,
package installs, remote content, hooks, pre-approved tools, ...), runs 40
deterministic rules for the tricks poisoned skills use - hidden Unicode,
look-alike hosts, encoded payloads, curl | sh, conditional triggers,
exfiltration, persistence - and compares all of it with a lockfile.

Every rule has a fixture and a golden file; ordinary skills must trip none.
skills.lock sits next to the project's skills. lock pins what is not pinned
yet, verify fails on any change nobody approved, diff shows a changed
SKILL.md sentence by sentence with new imperatives and the rules each added
sentence trips, approve accepts a skill as it is, label shows what a skill
can do and where it says so, and scan vets a skill before it is installed.
verify and diff write Markdown for a pull request or a job summary; label
writes a badge.
A new skills input names a project directory; the step writes the skills
report to the job summary, hands it out as the skills-report output for a
pull request comment, and fails on any change nobody approved unless
skills-fail is false. Without the input nothing changes. make golden
re-approves the fixture project CI checks the Action against.
Also catches curl -F field=@file as exfiltration: the form field name sat
between the flag and the @.
tabwriter counts the bytes of colour codes, so a coloured status pushed
every column after it out of line. The table is plain, as lock's is; the
details below it carry the colour.
… bytes

From review:
- A skill whose SKILL.md is a symlink was not a skill at all; it is now
  found, pinned and scanned. A symlink to a file is pinned by its target
  and its content, and the content is scanned.
- Only a real repository (HEAD, objects, refs) at the root of a skill is
  left out, and its presence is pinned with a git-repository finding. A
  .git directory anywhere else, .DS_Store and Thumbs.db are hashed.
- A text file with one NUL or invalid UTF-8 byte was treated as binary and
  skipped every rule. It is now scanned with the bad bytes replaced, and
  flagged by malformed-text.
- A repository that is itself one skill (--path .) gets the key '.', and
  the lockfile inside it is not hashed as part of it.
- An HTML comment that starts a line runs to its -->, so a fenced block
  inside one is read as the hidden comment it is.
- Variation selectors, the grapheme joiner and soft hyphens are folded out
  before matching, curly quotes are straightened and runs of spaces are one;
  a selector after an ASCII character is itself a finding.
- Keyword windows start a line before the keyword, for wrapped prose.
- curl | /bin/bash, | /usr/bin/env bash and | tee f | sh are pipe-to-shell.
- An added sentence is read as prose and its inline code as code, and every
  sentence of the diff carries the line it starts on.
- Line numbers are counted once per pass, and one rule is reported at most
  50 times per stretch of a file.
- A skill is matched by its directory, never by the name it gives itself.
verify needs a lockfile (or --missing-ok), --dir has to be a directory and
an explicit --path has to name something. A name that matches more than one
skill is refused. Everything printed from a skill stays on its line, lock
--json prints [] for nothing pinned, and the lockfile is never hashed as
part of a skill it sits in.
A skills check that cannot run - an agentgate without skills, a missing or
broken lockfile - fails the step instead of passing as drift or as nothing
at all, and skill text in the log cannot issue workflow commands.

gendocs put generated text into a regexp replacement template, where a $
is a submatch reference: ${IFS} vanished from the rule table.
The status section listed what comes next for the proxy only. It now also
names the next steps for skills - signed approvals, a SessionStart hook
and SARIF output - and links the full roadmap in the design notes.
@bnymnDev
bnymnDev merged commit a4dfc19 into develop Oct 3, 2026
16 checks passed
@bnymnDev bnymnDev mentioned this pull request Oct 3, 2026
5 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant