Repository navigation
Security and lifecycle hardening for Agent Plugins, Agent Skills, and daemon/browser ownership, driven by a full adversarial audit of the 0.7.x extension surface.
Security
- Project-scoped extensions are no longer loaded by default.
TIDESURF_EXTENSIONSnow defaults touser, so plugins and skills shipped inside a cloned repository (.tidesurf/plugins/,.agents/skills/,.tidesurf/skills/) load only after an explicitTIDESURF_EXTENSIONS=allopt-in. An invalid value disables extensions with a diagnostic instead of loading everything. This closes a clone-and-run code-execution vector: a malicious repository could previously auto-start stdio MCP servers from.tidesurf/plugins/the moment any TideSurf command ran in it. - Plugin MCP servers no longer inherit the parent process environment. Stdio plugin servers receive an allowlisted minimal environment plus their declared
enventries, so credentials and session tokens in the TideSurf process are no longer exposed to plugin-declared commands. - Plugin MCP servers are never started in
--read-onlymode or when TideSurf itself is running as a plugin MCP child (TIDESURF_PLUGIN_MCP_CHILD=1), closing a read-only policy bypass and the unbounded recursion where TideSurf's own shipped plugin re-spawned TideSurf MCP servers. - Plugin data directories are scope-isolated and private. User plugins resolve to
~/.tidesurf/plugin-data/user/<name>and project plugins to~/.tidesurf/plugin-data/project/<project-id>/<name>(both created0700), so a project plugin can no longer read a same-named user plugin's state. Legacy~/.tidesurf/plugin-data/<name>directories migrate atomically into the user scope with symlink rejection and fail-closed diagnostics. - Remote
streamable-httpplugin servers can no longer redirect the MCP client: redirects are rejected, and configured headers cannot override MCP protocol or transport-controlled header names. - Page-controlled URLs can no longer forge element action markers through backslash parity, CR/LF/control characters, malformed or fragment-only URLs, or the page header:
escapeUrlMarkersnow handles existing backslashes and control characters, andpageHeaderescapes URLs the same way as element links. - Skill activation is race- and escape-safe:
read_skillre-reads and re-validatesSKILL.mdagainst discovery metadata and enforces plugin-root realpath containment at load time, so a swapped file or symlinked directory cannot combine stale metadata or escape the plugin.
Changed
plugin.jsonandmcp.jsonvalidation now enforces the closed Agent Plugins v1.0.0 schemas exactly (unknown fields rejected, realpath containment, single-token or./-relative commands), andSKILL.mdfrontmatter accepts the full supported YAML subset — flow mappings likemetadata: { author: org }, block scalars with indentation indicators, and wider mapping indentation — while rejecting invalid skills instead of partially loading them.- Plugin MCP hosting is strictly bounded: one aggregate 10-second startup deadline across all servers (concurrency-limited), a capped pre-ready client message queue (128 messages / 1 MiB), paginated
tools/listwith per-cursor, per-tool, and aggregate byte limits, tool-schema depth/size limits, and retryable cleanup that retains failed removals instead of dropping them. tidesurf skills <name>preserves the raw skill document byte-for-byte before appending the bundled-file listing.
Fixed
- Daemon session state publication and cleanup are serialized under a generation-owned mutation lock, so a stale daemon can no longer overwrite or delete a replacement daemon's state in the check-then-rename window.
- Stale-daemon recovery is fail-closed end to end: termination reports an explicit outcome, state/socket files are never removed while a recorded PID is live but unverifiable (including whitespace-containing runtime paths and Windows CIM timeouts), and shutdown uses a single deadline shorter than the SIGKILL escalation so browser cleanup cannot be killed mid-drain.
- Browser ownership is generation-bound: endpoint claims carry an ownership token so a late
close()cannot release a replacement browser on the same port, a failed launch rollback no longer leaves the port pre-verified for a foreign occupant, and failed closes are retryable with orphan records retained until Chrome actually exits and its temp profile is actually deleted. The Windows orphan reaper now verifies process identity with a bounded query instead of skipping all reaping. - Token-budget estimation matches the serializer for escaped URLs and weighted CJK content, closing adversarial overflow between estimated and emitted output.