Review follow-ups: serve text/html sibling assets under the artifact sandbox CSP (direct navigation no longer bypasses it); resolve MIME from the request name first with target extension as fallback; make security fixtures cross-platform (no angle brackets in filenames, skip when symlink creation is denied). Residual check-then-read TOCTOU documented as accepted for the loopback-local threat model. Tests 33/33 GREEN, ESLint clean.
Serve artifact pages with a sandbox-only CSP mirroring the viewer iframe, fail-closed realpath confinement for sibling assets, and escape the artifact path in the missing-artifact 404. TDD: 4 RED failures (CSP header, 404 escaping, symlink escape) before the fix, 31/31 GREEN after; markdown/sessions/hook suites and ESLint clean.
render() ran after load()'s ordered failure path and after shownLoad
had moved on. A snapshot the board could not show (a session whose
detectedHarnesses is stored as an object) threw past the ordering, and
an older valid snapshot could no longer take the board. render() now
runs inside that path, and shownLoad moves only once it returns.
Run posted state.query, the query typed last. When an older load
succeeds after a newer one failed, the board shows the older query's
results and recall command while Run acted on the newer query. Each
load now keeps its query, render() records it for the board, and Run
posts that.
The test harness assigns new arrays instead of pushing onto
page.pending, and records requests the same way.
Two cases the finished-load rule got wrong. When the first snapshot was
still loading and a live refresh failed, the first snapshot arrived
late and was dropped, leaving the pane empty. And manual loads, the
Refresh button and a query, reported their failures outside the
ordering, so a late manual failure could cover newer data and an older
success could clear a newer manual failure.
The board now shows the newest data any load brought, and the error
box the outcome of the newest load that has finished. load() reports
its own fetch failures, as a live-refresh failure when the interval
started it and as a plain error otherwise, so every load is ordered the
same way. Data that arrives after a newer failure still shows, and the
failure stays, dated by that data.
A newer refresh that failed was shown, and then an older one that
succeeded cleared the message, so the board looked live although no
load that started after the failed one had succeeded.
The board now follows the newest load that has finished, with its data
or with its failure. A load that finishes after a newer one leaves the
board alone: its data is not rendered and its failure is not reported.
This also keeps a late response from replacing newer data.
A failed refresh was dropped whenever any load finished while it ran.
When two interval refreshes overlapped and the older one succeeded, the
newer one's failure was dropped too, so the board looked live although
its latest refresh had failed.
Loads are now numbered as they start. A failure is dropped only when
the board shows data from a load that started after it.
A live refresh that fails after a newer load has already replaced the
board no longer describes what is shown, so it is only reported while
the data it was meant to replace is still on screen. The time of that
data now includes its date, for a pane left open overnight, and it is
kept in its own binding instead of on the shared state object.
The 15-second refresh caught every error with `load().catch(() => {})`,
so a control pane that had lost its server kept showing the last
snapshot as if it were current. A failed refresh now shows the error box
with "Live refresh failed. The data below is from <time>." and the
error, and the next successful refresh clears it, as load() already
clears the box.
The new test runs the served page script in a vm with a stand-in for the
browser APIs it uses, fails a refresh, and checks both transitions.
Fixes#3176
The default chrome-devtools connector is launched with `npx -y` and
`@latest`, so every session start can silently install whatever version
was most recently published to npm. Pinning makes the default connector
reproducible and removes the unpinned-npx finding that /security-scan
(AgentShield) reports against ECC's own .mcp.json.
The same spec is pinned in the Codex merge script so both harnesses
stay in sync, and the Codex merge test is updated to match.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove unused spawn import from control-pane.js (left behind during openBrowser refactor). Add missing spawn import to plan-canvas.js (same refactor left a dangling reference).
* fix(gateguard): sanitize dangerous invisible unicode in denial paths
sanitizePath only stripped control characters and bidi overrides, so
the 394 codepoints that the repo-wide unicode safety policy classifies
as dangerous-invisible (zero-width U+200B-200D, U+2060, U+2061-2064,
variation selectors U+FE00-FE0F, tag block U+E0000-E007F, Hangul
fillers, U+FEFF, U+180E, U+3164) plus the U+2028/U+2029 line and
paragraph separators passed verbatim into Edit/Write denial messages.
Invisible characters in a quoted file path let a malicious path look
clean to a human reviewer.
Align sanitizePath with the CI policy list and cover it with a
hook-output regression test that asserts no dangerous invisible
character survives into the denial reason while visible path text
stays intact.
* refactor(gateguard): name the sanitizePath unicode policy constants
Address review feedback: move the code points and ranges out of
sanitizePath into descriptive constants mirroring
scripts/ci/check-unicode-safety.js, so the policy is auditable in one
place. Also seed the regression test input with U+2029 so the
paragraph-separator assertion is exercised rather than vacuous.
* fix(gateguard): sanitize C1 control characters in denial paths
Follow-up on the sanitizePath policy alignment: the Unicode C1 control
block (U+0080..U+009F) is invisible in every renderer like the ASCII
controls below 0x20, but the strip only covered the ASCII range. Add
C1_CONTROLS to the policy constants and seed U+0091 into the
regression test input.
* fix(gateguard): detect destructive SQL passed quoted to SQL clients
Quoted SQL like psql -c "drop table users" was stripped before the DESTRUCTIVE_SQL_DD regex ran, making it unreachable. Check dequoted tokens for known SQL clients (psql, mysql, sqlite3, ...) so real invocations are gated while git commit -m / echo mentions stay allowed. Fixesaffaan-m/ECC#3024.
* fix(gateguard): unwrap sudo/env flags and ignore SQL string literals
Address review: sudo -u/env VAR= wrappers now resolve to the real SQL client; SQL single/double/dollar-quoted literals are stripped before the destructive regex so SELECT 'drop table' stays allowed. Adds deny/allow regression tests for quoted SQL, wrappers, literals, and non-SQL clients.
* test(gateguard): cover env PGPASSWORD wrapper variant
* fix(gateguard): close wrapper sh-c, env dir, tagged literal gaps
Review 5189686662: recurse sh -c from unwrapped wrapper index (sudo/doas/env + sh -c now denied); consume env -C/--chdir separate values; strip tagged dollar-quoted literals ($tag$...\baseline). Tests: 200 passed.
Resolve the 3 conflicts from 104 upstream commits (2.2.1 -> 2.2.2).
Merge resolution:
- .claude-plugin/plugin.json, .claude-plugin/marketplace.json: take the
upstream 2.2.2 version bump, keep the catalog counts at 69 agents /
293 skills. Upstream released 2.2.2 still describing 68/292, so neither
side could be taken whole.
- docs/ja-JP/README.md: upstream restructured the page, moving the hero
above the fold and replacing both markdown language selectors with a
single centered HTML block that also gained Español. Take that structure
and re-add বাংলা to the new selector.
- rules/common/agents.md: merged clean, but the roster line main added
reads "68 agents". Resync to 69 alongside the other catalog counts.
Verified: catalog.js reports 69 agents / 293 skills and confirms the
documentation counts match the repository catalog; the nine CI validators
and the command-registry check pass; bengali-language-surface.test.js is
7/7, including the selector and locale-parity assertions.
Co-Authored-By: Kishor Morol <kishormorol@gmail.com>
scripts/plan-canvas.js and scripts/control-pane.js both define their own
openBrowser helpers for launching the user's default browser. The two
implementations have diverged:
- plan-canvas.js dispatches across darwin/win32/linux but its
try/spawn try/catch does not catch async child errors (ENOENT/EACCES
on hosts without the launcher command). Returns a bare true/false.
- control-pane.js is darwin-only and silently returns early on
Windows/Linux, so the two scripts behave inconsistently across
platforms.
When the launcher command is missing (e.g. headless CI without xdg-open)
the JSON output still says 'browser: opened', lying to the agent.
Changes:
- scripts/lib/platform-launch.js: shared openBrowser helper that
dispatches per platform, wires child 'error' so ENOENT/EACCES
propagate, and returns {opened, reason} instead of a bare boolean.
- scripts/plan-canvas.js: drops its local helper, imports the shared
one, and adds browserReason to its JSON output.
- scripts/control-pane.js: replaces its darwin-only branch with the
shared helper and logs the structured reason on failure.
- tests/lib/platform-launch.test.js: 7 node:test cases covering
opener-command selection, invalid-URL guard, structured-result
shape, and a smoke run for the actual spawn.
Verification: node --test tests/lib/platform-launch.test.js → 7/7 pass.
Co-authored-by: auyua9 <auyua9@users.noreply.github.com>
The hook now skips pytest unless the opt-in is set. Also skip the
case-fold interpreter case on case-sensitive filesystems, and do not
read adapter.id when a copy-file plan has no adapter.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
Keep ${1} and echo $1 as Claude substitutions. Do not fail VAR=$1
function parameters or Perl/awk $1 compares. Sync catalog counts to
68 agents, 293 skills, and 95 commands after rebase onto main.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
Add scripts/ci/validate-skill-host-compat.js and run it from the Validate
Components job and npm test after validate-skills.js. The check fails on
Claude-only substitutions in bash fences and on Codex copies that use keys
outside name, description, metadata, license, and allowed-tools. CONTRIBUTING
checklist items warn unless --strict-checklist.
Also add /skill-host-compat and skills/skill-host-compat with a Codex copy
that drops origin. Move configure-ecc plugin-root examples into prose so the
live catalog stays green without a skip list.
Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com>
Extract contained-fs (TOCTOU-safe fs ops) and json-ops helpers into scripts/lib/install/. install-lifecycle 2453->1957 lines, public API unchanged (66/0 lifecycle tests pass). Harden tests/run-all.js with exit-code-first verdict, TAP fallback, file-level counts and filter arg. Add warn-level eslint guards (prefer-const, complexity, max-depth, max-params). Split test scripts into test:validators/test:js/test:python and coverage:js/coverage:python.
Windows: compare repo identity via normalizeRepoPath/sameRepoIdentity (8.3 short names, case, separators; inode fallback). All nine windows-latest jobs green on 52587005.
Preserve current main hook-loader and silent-output changes alongside the reviewed optional Polish documentation module. Package delta only adds docs/pl to shipped files.
Source-PR: https://github.com/affaan-m/ECC/pull/3121
Source-Head: 071f2dbd28
Use the checkout containing the new locale, document prerequisites, and distinguish preview, docs-only installation, and explicit full hook activation. Preserve contributor locale implementation and current sponsor content.
Source-PR: https://github.com/affaan-m/ECC/pull/3121
Source-Head: 071f2dbd28