ways
Concepts

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

RuleWhat it flagsSeverity
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.jsonblock
mcp-shellA bundled .mcp.json server whose command is a shell (sh, bash, pwsh, cmd…)warn
mcp-inline-evalA server that runs inline code (node -e, python -c, ruby -e, php -r…)warn
mcp-unpinnedA server launched via npx/dlx without an exact semver — @latest, @next, @beta and ranges all resolve to whatever the registry serves at connect timewarn
hook-shell-chainA hook command that chains or substitutes another command (;, &, |, a newline, `, $(...)) — a second command hiding inside the one-line disclosurewarn

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. --strict escalates every warning to blocking.
  • --force proceeds 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 findings

Warnings, 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 time

A 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 installed

Getting 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:

  1. 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.
  2. 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.
  3. --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.

On this page