Ways Shield
Provenance proves where content came from. Shield checks whether it's safe to place.
ways add and ways install already prove where content came from: the ref is resolved to an
exact commit, the content is digested, and nothing is enabled until you approve that digest.
Ways Shield checks the other half — whether that content is safe to place — by scanning the
acquired, pinned content right before the approval gate runs.
Why provenance isn't enough
The approval plan discloses what a way declares: its skills, its rules, every command that will
run. What it can't tell you is what's inside the files it's about to place — that a bundled
SKILL.md carries a live AWS key, or that a bundled MCP server launches whatever @latest resolves
to at connect time.
Those are independent questions. "This is really the commit I approved" says nothing about whether that commit is safe. Shield is the second question, asked automatically, before you answer the approval prompt.
What it checks
| Rule | What it flags | Severity |
|---|---|---|
secret:* | Well-known credential shapes — AWS, GitHub, Slack, Stripe, OpenAI, Anthropic, Google tokens, private-key blocks — anywhere in a bundled skill/hook/rule directory, bin/, or .mcp.json | block |
mcp-shell | A bundled .mcp.json server whose command is a shell (sh, bash, pwsh, cmd…) | warn |
mcp-inline-eval | A server that runs inline code (node -e, python -c, ruby -e, php -r…) | warn |
mcp-unpinned | A server launched via npx/dlx without an exact semver — @latest, @next, @beta and ranges all resolve to whatever the registry serves at connect time | warn |
hook-shell-chain | A hook command that chains or substitutes another command (;, &, |, a newline, `, $(...)) — a second command hiding inside the one-line disclosure | warn |
The scan walks the whole directory a skill or hook ships from — a skill's scripts/*.sh and
references/*.md are placed exactly like its SKILL.md, so they're scanned exactly like it. Binary
assets are skipped; text files are read up to 256 KiB.
Severities
block— a hard refusal. The install stops, with or without--yes. Only a leaked credential blocks by default.warn— disclosed in the approval plan so you decide with it in front of you.--strictescalates every warning to blocking.--forceproceeds past a block knowingly. It's logged plainly, so it stays visible in the output and in CI logs.
npx ways.sh add ./way # findings shown in the plan; a leaked credential refuses
npx ways.sh add ./way --strict # a warning refuses too (unpinned MCP, chained hook command)
npx ways.sh add ./way --force # proceed past a block, knowingly (logged)It runs on install, not just add
ways install — the reproduce-from-lock path your teammates and CI run — scans every locked way,
unconditionally, even when approvedDigest already matches the content digest.
That case is the common one: a fresh clone reproducing an already-trusted lock skips the re-approval
gate entirely and goes straight to placement. Because provenance trust and content safety are
independent checks, a way whose provenance was already trusted doesn't get a free pass on content
safety — including a way that was approved before Shield existed. --strict and --force behave the
same here as on add.
What it looks like
Clean, in the approval plan — a way with nothing to flag says so explicitly:
executable surface (will be enabled):
! hook PreToolUse[Bash(git commit*)] → $PACK/scripts/gate-guard.sh (gate-guard)
! check: git --version
! check: gh --version
shield: no findingsWarnings, shown next to the surface they're about — the install still proceeds if you approve it:
executable surface (will be enabled):
! hook PostToolUse[Bash] → $PACK/scripts/notify.sh; curl -s https://acme.example/telemetry (notify)
! .mcp.json (MCP servers)
shield:
! [warn] hooks/notify: PostToolUse[Bash] runs a chained/substituted command — review it: $PACK/scripts/notify.sh; curl -s https://acme.example/telemetry
! [warn] .mcp.json: MCP server "acme-tools" (npx) is not pinned to a version — it fetches whatever is latest at connect timeA blocking finding never reaches the prompt:
add: ./acme-deploy
ok schema: valid (Way)
ok conformance: conformant
ok digest: new — approval required
./acme-deploy: refused by ways shield
[block] skills/acme-deploy/SKILL.md looks like a AWS access key ID — remove it before this way can be installedGetting past a false positive
A block is a hard stop, not a discouragement — but false positives happen (a docs page quoting a vendor's own published example key, say). There are three ways past one, narrowest first:
- The published-placeholder allowlist. Known-safe vendor examples (AWS's own
AKIAIOSFODNN7EXAMPLE, for instance) never flag. Matched exactly, not as a substring, so a real key that happens to contain one still blocks. shield:allow— an inline marker on the matched line, or the line right before it, suppresses that one finding.shield:allow:<rule-id>(e.g.shield:allow:aws-access-key) scopes it to a single rule, so an unrelated real secret two lines down still blocks. This is the author-side escape hatch: it travels with the content and is reviewable in the diff.--force— an operator-level override for the whole run. Broadest, and logged.
Families
Every chosen member of a WayFamily is scanned — from its own
directory, since a member's knowledge and hooks are declared relative to itself — before the
single family-wide approval prompt. A blocked member refuses the whole family install rather than
being silently dropped from the lock while its siblings install anyway.
What it deliberately doesn't do
Phase 1 is narrow on purpose: static, high-precision rules only. No entropy heuristics (they need tuning against real content or they drown real findings in noise), no network calls, no execution of the content being scanned. Every credential rule is a shape that's essentially never legitimate to commit in plain text.
External rule packs (pack: { source }) have no locally-resolved content at add time — steering
resolves those separately — so there's nothing there for Shield to scan yet.
So it's not a substitute for reading a way you have no reason to trust. It's the floor: the risk shapes that shouldn't need a human to catch, caught before the approval gate rather than after placement.
The scanner is exported for programmatic use (scanWay from the package root), and a finding is a
first-class part of the install plan — not a side channel.
Pair this with Doctor & trust, which covers the other half: pinning, content digests, and why nothing executable runs before you approve it.