9 new cases locking in the behavior added by the previous two
commits. Each was verified to fail before the fix and pass after.
Greptile — quote-aware depth counting:
- blocks $(echo ")"; (npm run dev))
- blocks (echo ")"; npm run dev)
- allows $(echo "(npm run dev)") — () inside double-quoted body is literal
Greptile — brace groups:
- blocks { npm run dev; }
- blocks echo hi && { npm run dev; }
- allows {npm run dev} — bash brace-group syntax requires a space after {
CodeRabbit — missing package-manager variants:
- blocks yarn run dev (yarn 1.x convention)
- blocks bun dev (bun bare form)
CodeRabbit nitpick — symmetric quote test:
- blocks echo "$(npm run dev)" — double-quoted substitution still substitutes
The `{npm run dev}` allow case is intentional: bash treats `{` as
a reserved word only when followed by whitespace. The pre-fix code
already passed this through, but until now we never asserted it,
so a future change to brace handling could silently start blocking
literal `{npm` tokens.
Two false-negatives surfaced in PR #1889 review:
1. Brace-group bypass (Greptile).
`{ npm run dev; }` evaluates the dev command in the *current*
shell — semantically distinct from `( ... )` but with the same
effect for this hook. `splitShellSegments` correctly cleaves the
group at `;` into `["{ npm run dev", "}"]`, but the first segment's
leading token under `readToken` is the bare `{`, which was not in
`DEV_COMMAND_WORDS`, so the dev-pattern check was skipped.
Fix: treat `{` and `}` as no-op tokens in `getLeadingCommandWord`
so we keep walking to the real command word. Matches how shell
itself parses brace groups (the braces are reserved words, not
commands). Bash requires a space after `{` and a terminator before
`}` for an actual group, so `{npm run dev}` correctly remains
allowed (single token `{npm`, not in `DEV_COMMAND_WORDS`).
2. Missing yarn-run / bun-bare variants (CodeRabbit).
Both `yarn dev` *and* `yarn run dev` are valid (the latter is what
`package.json` actually wires `dev` to under yarn 1.x). The same
`(run )?` symmetry applies to bun. The previous `DEV_PATTERN` only
matched `yarn\s+dev` and `bun\s+run\s+dev`, allowing the cross
forms to pass through silently.
Fix: `yarn(?:\s+run)?\s+dev` and `bun(?:\s+run)?\s+dev` — same
shape `pnpm(?:\s+run)?\s+dev` was already using.
Verified after this commit (every form now exits 2):
{ npm run dev; }
{ npm run dev ; }
echo hi && { npm run dev; }
({ npm run dev; })
$( { npm run dev; } )
yarn run dev
bun dev
Verified still allowed (no regression):
echo "{ npm run dev; }" # literal inside double quotes
{npm run dev} # not a brace group per bash syntax
Greptile flagged a bypass in PR #1889: `$(echo ")"; (npm run dev))`
threaded the depth-counting loops in `extractCommandSubstitutions`
and `extractSubshellGroups` to terminate early, because a literal `)`
inside double quotes was treated as a real closing paren. The
truncated body then ended in a dangling `"` that toggled `inDouble`
in the outer scan, masking the subsequent `(npm run dev)` group from
extraction.
Reproduced (before this commit) by piping the synthetic PreToolUse
payload `{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}`
into `scripts/hooks/pre-bash-dev-server-block.js` and observing
exit 0 (allow) where the dev pattern is clearly present.
Fix: each `$(...)` and `(...)` body loop now tracks its own
single/double quote state and only treats `(` / `)` as depth
delimiters when outside quotes. The quoted `)` no longer closes
the group early, the body now extends to the real closing paren,
and the outer scan's quote state remains untouched.
After this commit:
$ echo '{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}' \
| node scripts/hooks/pre-bash-dev-server-block.js; echo $?
2
The symmetric form `$(echo "(npm run dev)")` correctly remains
allowed (bash does not honor `(...)` inside double quotes).
Lock in the behavior added by the previous commit. Each new case was
verified to fail before the fix and pass after.
Bypasses now blocked (exit 2):
- \$(npm run dev) command substitution
- \`npm run dev\` backtick substitution
- echo \$(npm run dev) substitution inside an argument
- (npm run dev) plain subshell group
- \$(echo a; npm run dev) substitution containing a sequenced segment
- (pnpm dev) plain subshell group, alt package manager
Allow cases — explicitly proven NOT to regress so the fix doesn't
over-block legitimate uses:
- (tmux new-session -d -s dev "npm run dev") tmux launcher inside ()
- git commit -m '(npm run dev)' literal in single quotes
- echo "(npm run dev)" literal in double quotes
(bash does NOT subshell () inside double quotes)
- git commit -m '\$(npm run dev) fix' literal in single quotes
Single- and double-quote allow cases are important: they distinguish a
real subshell construct from one that's just text inside a string,
which is what `extractSubshellGroups` / `extractCommandSubstitutions`
quote-awareness is for.
Before this commit the dev-server-block hook ran the leading-command
and dev-pattern check only against the top-level segments returned by
`splitShellSegments`, which doesn't split on `$(...)`, backticks, or
plain `(...)`. That left the policy bypassable by wrapping a dev
command in any of those constructs:
$(npm run dev)
`npm run dev`
echo $(npm run dev)
(npm run dev)
Each verified by piping a synthetic PreToolUse payload into the hook
on this branch: every form above returned exit 0 (allow) where a plain
`npm run dev` correctly returned exit 2 (block).
Fix: expand the check space before running the leading-command rule.
A small BFS walks the raw command, harvesting bodies from
`extractCommandSubstitutions` (`$(...)` and backticks) and from
`extractSubshellGroups` (plain `(...)`), then splits each harvested
body through `splitShellSegments` and feeds the result into the
existing `isBlockedDevSegment` check.
This preserves every existing allow case (`tmux new-session -d -s dev
"npm run dev"`, quoted-string mentions like `git commit -m "npm run
dev fix"`, `echo hi`) because the leading-command rule is unchanged —
only the set of segments it runs against grew.
Known limitation, not fixed here: `eval "$(echo npm run dev)"` still
slips through because the substitution body's leading command is
`echo`, and statically modeling echo's output to recover the executed
command is out of scope. The same class affects `gateguard-fact-force`
(via `eval "$(echo rm -rf /)"` etc.) and is best addressed in both
hooks together as a follow-up rather than as a one-off here.
`extractCommandSubstitutions` only walks `$(...)` and backticks — the two
shell constructs whose bodies are captured as strings. Bash also has
plain `(...)` subshells (e.g. `(npm run dev)`), where the body executes
in a child shell but is not value-captured. Our PreToolUse hooks need
to peer inside those too, because a `(...)` group bypasses the
top-level segment splitter just like `$(...)` does.
This commit adds a sibling extractor with the same conventions as
`extractCommandSubstitutions`:
- single quotes literal — `'(npm run dev)'` is a string, ignored
- double quotes literal for parens — `"(npm run dev)"` is a string
(bash only honors `$(...)`, not bare `(...)`, inside double quotes)
- skips `$(...)` and backtick spans so we don't double-extract
bodies the other helper already handles
- recurses into its own bodies for nested groups
No consumer yet; the next commit wires both extractors into
`scripts/hooks/pre-bash-dev-server-block.js` to close the subshell
bypass surface.
Extract the `extractCommandSubstitutions` function originally
introduced in scripts/hooks/gateguard-fact-force.js (PR #1853
round 2) into scripts/lib/shell-substitution.js so other PreToolUse
hooks can reuse the same single-quote-aware, double-quote-aware,
nested-subshell-aware parser without duplicating it.
No behavior change in this commit — the function body is copied
verbatim and exposed via `module.exports`. The next commit wires it
into scripts/hooks/pre-bash-dev-server-block.js to close that hook's
own subshell-bypass holes.
gateguard-fact-force.js still defines its own private copy of the
function; consolidating both call sites onto this shared lib is a
follow-up worth doing once this PR lands, but is intentionally out
of scope here to keep the diff focused on the dev-server-block fix.
Make the ECC 2.0 GitHub/Linear/handoff/roadmap progress-sync model part of the local observability readiness gate instead of leaving it as roadmap prose only.
- add `docs/architecture/progress-sync-contract.md` for GitHub, Linear, handoff, roadmap, and work-items sync
- add a `Tracker Sync` check to `scripts/observability-readiness.js`
- update observability tests with passing and missing-contract coverage
- update observability and GA roadmap docs so the local readiness gate is now 18/18 and records #1848 supply-chain hardening evidence
Validation:
- node tests/scripts/observability-readiness.test.js (9 passed, 0 failed)
- npm run observability:ready -- --format json (18/18, ready true)
- npx markdownlint-cli 'docs/architecture/progress-sync-contract.md' 'docs/architecture/observability-readiness.md' 'docs/ECC-2.0-GA-ROADMAP.md'
- git diff --check
- node tests/docs/ecc2-release-surface.test.js (18 passed)
- node tests/run-all.js (2378 passed, 0 failed)
- GitHub CI for #1849 green across Ubuntu, Windows, and macOS
No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
Add a repo-level supply-chain incident response playbook for npm/GitHub Actions package-registry incidents, anchored on the May 2026 TanStack compromise and prior Shai-Hulud-style npm incidents.
- add `docs/security/supply-chain-incident-response.md` with exposure checks, immediate response steps, workflow rules, publication rules, and escalation triggers
- link the playbook from `SECURITY.md`
- reject `pull_request_target` workflows that restore or save shared dependency caches
- add a regression test for the new `pull_request_target + actions/cache` guardrail
Validation:
- node tests/ci/validate-workflow-security.test.js (12 passed, 0 failed)
- node scripts/ci/validate-workflow-security.js (validated 7 workflow files)
- npx markdownlint-cli 'SECURITY.md' 'docs/security/supply-chain-incident-response.md'
- npx markdownlint-cli '**/*.md' --ignore node_modules
- git diff --check
- node tests/run-all.js (2377 passed, 0 failed)
- GitHub CI for #1848 green across Ubuntu, Windows, and macOS
No release, tag, npm publish, plugin tag, marketplace submission, or announcement was performed.
Require npm registry signature verification wherever workflow npm audit checks run.
- add npm audit signatures to CI Security Scan and maintenance security audit jobs
- teach the workflow security validator to reject npm audit without signature verification
- keep the repair and Copilot prompt tests portable across Windows path/case and CRLF frontmatter behavior
Validation:
- node tests/run-all.js (2376 passed, 0 failed)
- CI current-head matrix green on #1846
Adds GitHub Copilot VS Code instruction and prompt files for ECC workflows, with VS Code prompt frontmatter/settings aligned to current docs and tests covering the surface.
Co-authored-by: Girish Kanjiyani <girish.kanjiyani5040@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove shell access from two agents that do not need it and reword PyTorch autograd guidance that AgentShield flagged as encoded-payload-like text. AgentShield remains B/75 while findings drop 316->310 and high findings drop 26->21. Local tests passed 2369/2369; full GitHub Actions matrix green.
Backport Jamkris's fix for case-insensitive core.hooksPath overrides and the git commit -tn template-path false positive. Verified locally on current main with 25/25 block-no-verify tests and node tests/run-all.js passing 2369/2369.
Add compact prompt-defense baselines to active ECC prompt surfaces and copied CLAUDE examples. AgentShield prompt-defense findings are now zero; local tests passed 2366/2366.
- run non-test workflow installs with npm ci --ignore-scripts where lifecycle scripts are not needed\n- reject plain npm ci in workflows with write permissions\n- reject actions/cache in id-token: write workflows to reduce OIDC publish cache-poisoning risk
* feat: add homelab config skills (VLAN, Pi-hole, WireGuard)
Adds three homelab configuration skills, extracted from the stale PR #1413
with the same safety treatment applied to the previously accepted batch:
- homelab-vlan-segmentation: IoT/guest/trusted/server VLAN design for UniFi,
pfSense/OPNsense, and MikroTik. All firewall rules add isolation, not remove
protections. Added change-window guidance and AP trunk port clarification.
- homelab-pihole-dns: Pi-hole install, blocklists, DNS-over-HTTPS, local DNS
records, troubleshooting. Docker is now the lead install method; bare-metal
uses inspect-first pattern before running the installer script.
- homelab-wireguard-vpn: WireGuard server, peer config, split tunnel, DDNS.
Replaced broad iptables FORWARD ACCEPT with scoped directional rules
(wg0→eth0 forward + established return only). Credentials moved to env
files with explicit notes against inline secrets and version control.
Continues the contribution from PR #1413; the eight skills/agents from
that PR are already in main via #1729 and #1731.
* docs: harden homelab skill pack
---------
Co-authored-by: Affaan Mustafa <affaan@dcube.ai>