Commit Graph
1780 Commits
Author SHA1 Message Date
Seekers2001 51a0ba9ec6 fix(governance): handle documented parser forms 2026-09-07 17:37:44 +08:00
Nguyen Thanh Dat c88b3f879a test(gateguard): cover dd with an intervening option before if=
Named in review on #2829: `dd bs=1M if=/dev/zero of=/dev/sda` is the same
class as the reversed-operand case and is denied by the token-based check,
but nothing pinned it.
2026-09-07 15:48:02 +07:00
Nguyen Thanh Dat 249b0ecf7f test(gateguard): fail the allow-cases when the hook returns nothing
The "does not gate as destructive" cases guarded the decision behind
`if (output && output.hookSpecificOutput)`, so a crashed or silent hook made
parseOutput return null and the test passed having asserted nothing.

Assert the exit code and that output parsed first, then branch: a decision
object must not be a Destructive deny, and pass-through must echo the input
back — the same shape the existing retry case already checks.

Raised in review on #2829.
2026-09-07 15:48:02 +07:00
Nguyen Thanh Dat 79d3fefad1 fix(gateguard): detect dd by command word, not by text match
Review on #2829 found the regex fix widened a false positive: matching
`\bdd\s+if=` against the whole flattened line gated `echo dd if=/dev/zero`
and `grep dd if=/dev/zero file`, neither of which runs dd. That class was
already present before — `echo dd if=x` matched the old arm too — but the
boundary fix extended it to the slash and dot spellings, so the arm now
decides on text position rather than on what is being executed.

dd moves to isDestructiveDd(tokens), next to isDestructiveRm and
isDestructiveGit, and DESTRUCTIVE_SQL_DD goes back to SQL only. The
per-segment loop already tokenizes every executable body, so the check runs
where the command word is known.

This resolves four things the text match could not:

  dd if=/dev/zero of=/dev/sda    was allowed  -> denied  (the reported bug)
  sudo dd if=/dev/zero           was allowed  -> denied
  dd of=/dev/sda if=/dev/zero    was allowed  -> denied  (operands are order-free)
  echo dd if=x                   was denied   -> allowed (pre-existing false positive)

Leading sudo/doas/env, their flags, and VAR=value assignment prefixes are
skipped so a wrapped invocation still resolves to dd; flags are only skipped
once a wrapper has been seen, so the scan cannot walk into an unrelated
command's arguments.

Tests: 6 fail on upstream main, 155 pass with this change.

Refs #2642
2026-09-07 15:48:02 +07:00
Nguyen Thanh Dat 76786616f8 fix(gateguard): deny dd whose input path is not word-initial
DESTRUCTIVE_SQL_DD shared one trailing \b across every alternation arm.
`dd\s+if=` ends in `=`, and a \b after a non-word character only holds when
the NEXT character is a word character, so the arm matched `dd if=x` and
missed every path starting with `/`, `.` or a quote:

  dd if=/dev/zero of=/dev/sda    allowed
  dd if=./disk.img of=/dev/sdb   allowed
  dd if="/dev/zero" of=/dev/sda  allowed

The word-boundary suffix now applies only to the arms that end in a word
character. The split is what keeps the widening bounded: dropping the
trailing \b outright would let `truncate` match `truncated`, and dropping the
leading \b would let `dd if=` match inside `add if=`. Both are covered.

This is the half of #2642 that survived the structural findGitSubcommand()
parser, which already handles `git checkout -- .`.

Not addressed here: `dd of=/dev/sda if=/dev/zero` with the operands reversed
is still allowed, before and after, because the pattern requires `if=`
immediately after `dd`. That is a different defect from the boundary bug and
widening a P0 gate's pattern shape is a maintainer call.

Refs #2642
2026-09-07 15:47:37 +07:00
wellkilo c9dd92037d fix(audit): mirror pytest discovery rules 2026-09-07 16:04:42 +08:00
wellkilo 720796ed48 fix(audit): align pytest discovery semantics 2026-09-07 15:45:40 +08:00
Samarjeet Singh Tomar f8c0c2c182 fix(install): gate OpenCode hook activation
Signed-off-by: Samarjeet  Singh Tomar <samar_tomar@hotmail.com>
2026-09-07 02:26:37 -05:00
wellkilo d216de9207 fix(audit): recognize Python consumer test suites 2026-09-07 14:16:41 +08:00
wellkilo f59cfd57c2 fix(install): harden Claude settings lifecycle 2026-09-07 01:57:04 +08:00
wellkilo 26d3e0038b fix(install): surface Claude settings failures 2026-09-07 01:13:20 +08:00
wellkilo 569e5a36bb feat(install): register manual Claude hooks 2026-09-07 00:53:13 +08:00
Seekers2001 f5a4d33797 fix(governance): preserve inline link title context 2026-09-06 17:48:13 +08:00
Clovis cfca5c8153 fix: session-activity-tracker never records (CLAUDE_SESSION_ID unset)
session-activity-tracker.js is correctly registered in
posttooluse-dispatcher.js's SYNC_HOOKS and fires on every tool call --
but buildActivityRow() only ever read the session id from
ECC_SESSION_ID/CLAUDE_SESSION_ID environment variables, which Claude
Code does not actually set for hook subprocesses. Every row silently
came back null and nothing was ever written to
~/.claude/metrics/tool-usage.jsonl, with no error anywhere.

The session id is available in the hook's own stdin JSON payload
(input.session_id) in real invocations. This prefers that, keeping the
env vars as a fallback. New test added; all 18 existing + new tests
pass.

Also adds a live Activity view to the capabilities dashboard
(scripts/dashboard-web.js) reading that same metrics file, so Skill
vs. Agent vs. raw-tool usage is visible in real time instead of
requiring a manual tail+jq -- verified live against a real session
before this commit. Documents the loopback-only, no-auth design and
the standard SSH-port-forwarding pattern for viewing it from another
device you control.
2026-09-05 16:58:17 -07:00
haelyra 99668f0ef5 fix: resolve nested PowerShell command tokens 2026-09-05 17:38:20 -04:00
haelyra cb5311222d fix: scan inline PowerShell command parameters 2026-09-05 17:29:02 -04:00
haelyra f43195a255 fix: expand nested PowerShell command scalars 2026-09-05 16:57:10 -04:00
haelyra 8eeac94af3 fix: preserve PowerShell expansion semantics 2026-09-05 16:32:15 -04:00
haelyra 3ad828db47 fix: address PowerShell review bypasses 2026-09-05 16:12:58 -04:00
wellkilo 63dea9c925 fix: harden observer completion handling 2026-09-06 00:01:49 +08:00
wellkilo 3a384ca698 fix: require observer analysis completion sentinel 2026-09-05 23:42:46 +08:00
Seekers2001 9b21b2ccd5 fix(governance): retain malformed link follow-ups 2026-09-05 17:40:33 +08:00
5a878131de fix(block-no-verify): split scanner and close quoting bypasses
Merge buildCommentMask into buildScanBoundaries so one pass owns comment mask and scan bounds.
Extract shell-scan primitives to scripts/hooks/lib/shell-scan.js; keep policy in the hook.
Use a sticky heredoc regex instead of copying the remaining input on every <<.
Replace the short-circuiting chain test with clean-leading sequences that exercise checkCommand advance.
Add quoted-executable non-leakage coverage for the scanEnd=length path.
Treat $'…' ANSI-C quoting as a quoted executable word.
Join unquoted backslash-newline for the flag scan and heredoc-line bound.

Co-Authored-By: grok (fleet lane) <noreply@fleet.local>

Co-authored-by: groy75 <groy75@users.noreply.github.com>
2026-09-05 03:32:30 +00:00
b3a2d3586b fix(block-no-verify): bound the flag scan to the enclosing quote/heredoc line
The scanner locked onto a `git commit` literal that sat inside a quoted
string of an OUTER command (a python heredoc's string, a VAR="..."
assignment, a printf JSON payload), then hasNoVerifyFlag() re-tokenized from
that inner offset with a fresh quote state. When quote parity flipped, the
"segment" ran past the enclosing string and picked up an unrelated later
`bash -n` / `sed -n` / `grep -n` as `commit -n`. Two legitimate commands were
blocked this way in one working session.

The first attempt at this PR (d5c428c) fixed the false positive by SKIPPING
`git` tokens judged to be in "data" context. That was the wrong lever: data
becomes executable the moment it is piped to a shell (`echo '...' | bash`,
`bash <<< '...'`, `cat <<EOF | bash`), and no regex list of shell invocations
is complete (`bash --noprofile -c` slipped). Three independent reviewers
(codex, Greptile, CodeRabbit) demonstrated executing bypasses; that commit is
squashed away here and none of its behaviour survives.

This version keeps every `git` candidate visible and keeps main's blocking
surface intact. A single O(n) preprocessing pass (buildScanBoundaries)
records, for each enclosed character, the closing quote or the current
heredoc-body line end. Subcommand discovery and flag tokenization are capped
at that boundary, so tokens after the enclosing data can never leak into a
candidate's argument list. Also: comment detection is a single mask pass
instead of a repeated prefix scan; heredoc parsing accepts quoted hyphenated
delimiters and CRLF terminators and preserves the `<<` vs `<<-` tab
distinction; quote-assembled words (`g''it`) are recognized.

Verified (all payloads run through the hook as JSON on stdin):
- red-list, 24 executing forms incl. every reviewer-reported bypass -> exit 2
  on main AND here (CRLF heredoc trailer: 0 on main, 2 here);
- green-list, 16 leak-class false positives -> exit 2 on main, 0 here;
- tests/hooks/block-no-verify.test.js 84/84 (main's 25 retained verbatim,
  d5c428c's 8 removed, 59 table-driven red/green cases added);
  tests/hooks/cursor-block-no-verify.test.js 14/14;
- 204,807-byte command with 8,905 non-command `git` literals: 8.5 ms.

Co-Authored-By: Codex (GPT) <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EyuZY1LZNuShHiewExekLT
2026-09-04 22:35:09 -04:00
Brian SwigerandCopilot d58441072d feat(install): add a copilot project target for skills and agents
GitHub Copilot CLI discovers SKILL.md skills from .github/skills/ and custom
agents from .github/agents/, so ECC's catalog can be installed there without a
conversion step. This adds a managed-project install target that places both.

Scope is deliberately limited to agents and skills. ECC hooks are written
against Claude Code's event model, and commands rely on slash-command argument
substitution Copilot does not provide, so neither is installed.

Agent frontmatter goes through an allowlist transform rather than a passthrough:
Copilot resolves the session model from user configuration, so a Claude model id
makes it warn and fall back on every invocation; Claude tool names are not
Copilot tool names, and Copilot governs tool access per session via
--allow-tool/--deny-tool. Unknown keys added to ECC agents later therefore
cannot leak into the Copilot copy.

Target selection stays permissive in supportsModule so dependency-anchor modules
still resolve; planOperations is what narrows the install, matching the
convention used by the other adapters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-09-04 20:58:17 +01:00
haelyra d9f6091ee8 fix: close PowerShell destructive command gate bypass 2026-09-04 15:02:57 -04:00
wellkilo 6f9282af41 fix(gan): require Playwright capability in harness 2026-09-04 20:48:42 +08:00
Seekers2001 162bff9438 fix(governance): bound incomplete link scans 2026-09-04 17:39:54 +08:00
Seekers2001 c40595e8fe chore: merge upstream main into docs governance audit 2026-09-04 17:36:56 +08:00
Iams4kura 8eedb3d3de fix(llm): reject invalid tool arguments 2026-09-04 11:29:14 +08:00
Iams4kura 4a9bdff518 refactor(llm): isolate OpenAI message mapping 2026-09-04 11:16:14 +08:00
Iams4kura 3f702ed40c fix(llm): serialize OpenAI tool call messages 2026-09-04 10:40:20 +08:00
haelyra b74e0add1d test: synchronize dependency update coverage 2026-09-03 15:00:31 -04:00
haelyra 23fb7e0c79 ci: avoid redundant Python dependency floors 2026-09-03 14:59:01 -04:00
Seekers2001 06e67e3a2c fix(skills): harden docs governance audit 2026-09-03 12:33:52 +08:00
Seekers2001 1ea94397b5 feat(skills): add read-only docs governance audit 2026-09-03 10:00:25 +08:00
haelyra 22e8cf01d0 test(ci): scale repair timeout on Windows (#2942) 2026-09-02 20:48:57 -04:00
haelyra d3652039ac test(hooks): drain bootstrap children asynchronously (#2941)
* test(hooks): drain bootstrap children asynchronously

* test(hooks): harden async supervisor lifecycle

* test(hooks): accept Windows child stdin closure
2026-09-02 19:55:26 -04:00
Santhi Prakash 5bf96bab17 fix(skills): reduce plan-orchestrate to a truthful command router
- Problem: the emitted command does not consume the advertised agent chain
  (each command owns its own execution behavior), and steps mapped to
  argument-less commands were emitted as bare commands with no
  machine-readable link to the intended step.
- Fix: adopt the small-router role — emit only commands whose documented
  argument form can carry the [Plan: <path>#step-<id>] scope marker
  (orch-*, /plan), fail closed with a BLOCKED entry and a manual-use note
  for every other command, derive quoted argument forms from the command
  files instead of an authoritative hand-written table, and drop the
  agent-chain machinery entirely. Adds
  tests/skills/plan-orchestrate-registry.test.js, which validates every
  command the skill names against commands/<name>.md and
  docs/COMMAND-REGISTRY.json and fails when they drift.
- Verification: node tests/skills/plan-orchestrate-registry.test.js (7
  fixture groups pass; sabotage-checked against chain reintroduction,
  wrong scope-carrying claim, and retired-command reference).
2026-09-02 22:08:04 +00:00
haelyra 11813f968c test(hooks): avoid repeated giant wrapper payloads (#2940)
* test(hooks): avoid repeated giant wrapper payloads

* test(hooks): assert callback-governed wrapper exits
2026-09-02 17:47:04 -04:00
haelyra 90430ab3a7 test(ci): tolerate loaded macOS hook runners (#2939) 2026-09-02 16:37:08 -04:00
Wu Shuwen de899ac472 fix(tests): preserve session alias HOME isolation (#2877) 2026-09-02 14:24:39 -04:00
Affaan Mustafa a104765bf2 docs(ito-compute): document ito accept and ito_accept MCP workflow (#2893)
* docs(ito-compute): document ito accept and ito_accept MCP workflow

Updates the canonical ECC skill to cover the new quote acceptance path:
- CLI: ecc ito accept <ticket-id>
- MCP: ito_accept tool
- Explicit buyer-authority guard before accepting
- Clear statement that accept routes to desk, does not purchase

* test(ito-compute): assert the four-tool MCP boundary including ito_accept

The exact-boundary test pinned the three-tool description. Runtime
ito-compute-cli now exposes ito_accept (Ito-Markets/ito-cloud-runtime#1453),
so the template boundary assertion moves to four tools.

* docs(ito-compute): drop the firm-quote gate from the accept workflow

Desk quotes are indicative_paper in production (a firm quote requires the
separate human-held signing path and cannot reach the client), so gating
accept on 'a firm quote is ready' described an unfireable condition. Align
with the runtime contract: accept routes the current desk quote to human
review and the result carries quote_class (ito-cloud-runtime#1453).
2026-08-31 15:16:16 -04:00
haelyra 005eff40fd fix(install): harden universal setup release path (#2888)
* fix(install): harden universal setup release path

* docs(adal): use ecc-universal doctor command
2026-08-30 18:54:00 -04:00
haelyra 64d0d9436f fix(install): preserve opencode non-hook defaults 2026-08-30 15:35:47 -04:00
haelyra d26b9cccee test(ci): confirm hooks in packed target smoke 2026-08-30 15:19:01 -04:00
haelyra caee3ee455 test(ci): opt in to hooks in packed lifecycle smoke 2026-08-30 15:13:07 -04:00
haelyraandClaude Fable 5 bdbd90a47f test(install): scale uninstall CLI timeout for Windows CI
The uninstall cases run two full CLI passes (install, then uninstall)
over several hundred files under a flat 30s timeout, which is tight
enough on Windows CI to fail intermittently with spawnSync ETIMEDOUT.
install-apply.test.js already scales its timeout by platform for the
same reason; match that precedent rather than re-running past the flake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:09:34 -04:00
6aaa41e028 feat(install): require an explicit hook decision at the apply layer
The guided installer asks how ECC hooks should run, but that consent
lived only in the wizard path. Running install-apply directly with a
profile that includes hooks-runtime still materialized the hook runtime
with no disclosure and no decision.

Gate the apply layer instead, so every entry point is covered:

- disclose the six hook capability groups when a plan would materialize
  the hook runtime, and refuse to apply until the caller decides
- --enable-hooks confirms the hook runtime; --no-hooks installs the rest
  of the selection without it and records the reduced module closure in
  install-state
- surface the pending decision as a dry-run warning
- show the same capability disclosure in the guided installer's plan
  preview, so the wizard's hook question states what it is asking about

Plans that never materialize hooks (Kimi, --profile minimal,
--without baseline:hooks) are unaffected and need no flag. Repair and
uninstall operate on already-recorded state and stay unchanged.

The capability taxonomy and the held-materialization behavior come from
Samarjeet Singh Tomar's PR #2634, reworked to fit the single-decision
consent model that shipped with the guided installer in #2649.

Co-Authored-By: Samarjeet Singh Tomar <samar_tomar@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:09:34 -04:00
haelyra a89cec9658 Merge pull request #2854 from samartomar/codex/issue-744-pure-plan
refactor(install): expose pure manifest planner
2026-08-30 14:50:53 -04:00