Security and trust

Will Guild Stack change your code without asking? No. /guild:init writes its notes into .guild/ and does not touch your code or tests. When you ask for a change, Guild Stack changes the files the task needs — and you review the diff. Nothing leaves your machine: no credentials are read, and no data goes to telemetry endpoints.

Try it. This audit reads Guild Stack's own scripts, hashes them, and flags code that matches network-call and file-write patterns. It never runs the scripts. The report lands in .guild/audit/:

/guild:maintain audit

The rest of this page is the precise version of that promise.

Guild Stack runs hook scripts inside your host session and ships two MCP servers. This page states what each one is trusted to do, which settings change that trust, and which checks fail closed.

Trust boundaries in Guild Stack: hook scripts, tooling scripts, read-only MCP servers and skills sit inside the project's .guild/ directory; the only network-capable specialist is the researcher.
Open full size

What Guild Stack executes

SurfaceHow it runsTrust claim
Hook scripts (hooks/)On SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, SubagentStop, Stop, TaskCreated, TaskCompleted, TeammateIdleWired in hooks/hooks.json
Tooling scripts (scripts/)Only when a skill or a maintain verb invokes themNot auto-run
MCP servers (mcp-servers/)Long-lived stdio subprocessesRead-only, checked by the absence of writeFile / appendFile in src/
Skills and agentsMarkdownNo executable code

What Guild Stack does not do

  • No host-native web tool is granted outside the researcher role. Only templates/specialists/researcher.md declares WebFetch / WebSearch. Another specialist gets one only through a task-scoped operator approval recorded before dispatch.
  • That claim covers WebFetch / WebSearch only. Build roles may receive Bash, which stays under the approved task autonomy and network policy.
  • No credentials are read, stored, or transmitted.
  • No data goes to telemetry endpoints. .guild/runs/ and .guild/wiki/ are project-local.
  • Updates are notify-only by default. SECURITY.md says version changes go through /plugin update guild@guild.

The update-check SessionStart hook defaults to defaults.update.mode: notify. If you opt in to auto, it stages a headless update that takes effect next session. It does this only on a host whose capability row declares auto_capable. Every other host degrades to notify-only.

Audit it yourself

/guild:maintain audit runs a static audit. It reads source, hashes it, and writes .guild/audit/<YYYY-MM-DD>.md. It never executes the scripts it audits. Per script it records:

  • SHA-256 and line count.
  • Declared tools: / allowed-tools: scope.
  • Network evidence (fetch, http.request, WebFetch, WebSearch, and similar matches).
  • Filesystem writes outside .guild/ paths.

This is declaration versus egress. The report also scans specialist definitions in agents/, templates/specialists/ and .guild/agents/, and flags any network tool declared by a role other than researcher. It compares hashes with the previous audit and flags changed, new and removed scripts. Run it before trusting a fork.

Secrets policy

KeyDefaultEffect
secrets_policy.redaction_patterns[]Your regexes. They run after the five built-in redaction groups and replace matches with [REDACTED].
secrets_policy.fail_mode_durableclosedApplies to handoffs, learnings, provenance, wiki, review and bus writes. A scrub failure blocks the write.
secrets_policy.fail_mode_telemetryopenApplies to logs/*.jsonl. A scrub failure warns and writes the built-in-redacted value. closed suppresses the field instead.
secrets_policy.env_allowlist[]Array of strings. The validator checks the type; this page does not describe its runtime use.

Every durable .guild/ write goes through one choke point, scrubbedWrite. On a fail-closed block it emits a secret_scrub_blocked security event and writes an approval_request to the agent bus so a person can decide. No raw secret goes into an event, warning or log. An invalid custom regex is skipped and recorded, so scrubbing degrades to built-ins only.

Bypass policy

security.bypass_permissions_policy (deny, audit or allow; default audit) applies to a tool call outside a lane's declared capability scope while the host is in bypass-permissions mode.

SituationResult
Out of scope, host not in bypassask, always. The event is logged.
Bypass, policy denyHard block.
Bypass, policy audit (default)Proceeds and logs the violation.
Bypass, policy allowProceeds and logs a bypass_permission_allowed record.

Under the non-interactive autonomy modes (auto_approve, autonomous_after_plan_approval) the policy is forced to deny whatever you configured. The security event records the forcing.

Security-sensitive keys fail closed

The config schema marks a key security-sensitive when it governs autonomy, bypass posture, secrets, cross-host dispatch or MCP pinning. That covers auto_approve, defaults.gates.auto_approve, codex_skip_enforcement, host_mode, defaults.dispatch.block_unmarked_lanes, mcp.tool_description_hashes, and every key under security., secrets_policy. and defaults.cross_host. Reconcile never clobbers a value you set. When a reconciled security value is malformed, repair picks the safe side:

KeyRepaired to
security.bypass_permissions_policydeny
codex_skip_enforcementblock
secrets_policy.fail_mode_durableclosed
secrets_policy.fail_mode_telemetryclosed
host_moderead_only
auto_approve, defaults.gates.auto_approve[]

defaults.dispatch.block_unmarked_lanes has no repair target. A malformed value is left untouched and surfaced to you, so a corrupted config cannot start blocking real dispatches. You cannot change these keys with config set in v3.0.0: it writes only the 15 policy keys and refuses every other key (see Config reference).

MCP trust surface

Both shipped servers, guild-memory and guild-telemetry, are read-only by invariant: they never call writeFile or appendFile, and guild-telemetry never mutates .guild/runs/.

Description pinning guards against a server that changes a tool description after you approved it. Pin the SHA-256 of a description in mcp.tool_description_hashes; PreToolUse recomputes the live hash and checks it.

  • Pin a tool. config update-mcp-hashes --tools <json-file> --scope workspace|project|local. The file maps mcp__<server>__<tool> names to description text. Hashes merge into the existing map; --replace overwrites it.
  • Verify statuses. unpinned, match, mismatch, unverifiable. A pinned tool whose live description cannot be obtained is unverifiable. Then: a third-party tool proceeds and the status is logged; a Guild Stack tool proceeds only when the verified Guild Stack MCP binary vouches for its description; any other Guild Stack tool gets an ask.
  • Security events. mcp_description_mismatch, mcp_description_unverifiable, mcp_description_unpinned.
  • Guild Stack's own tools are pinned at build. runtime/mcp-descriptions.pins.json is keyed by server and checked against the binary's SHA-256. An exact server-name match is required, so a third-party server named ..._guild-memory inherits nothing. If the pin file is missing or malformed, Guild Stack-owned tools fail closed.

Wiki write and share policy

defaults.wiki.share_mode takes team (default) or private. defaults.wiki.autopromote defaults to true. This page does not describe what private changes at runtime.

A lane worker may not write under the wiki. PreToolUse refuses Write, Edit, MultiEdit and NotebookEdit there, and refuses any Bash command whose text names a path under the wiki. Lanes read the wiki with Read, Grep and Glob. Targets are resolved physically, so symlinks and .. cannot escape the check. A path built at run time is not visible to the text check. A refusal emits lane_wiki_write_refused. Wiki content is written by the lead through harvest.

The PreToolUse policy gate

One PreToolUse hook, matcher *, runs these gates. Each refusal is a permissionDecision of deny, or ask where the host supports it.

  1. Capability scope. A call is allowed when it matches the lane's declared capability_scope OR the project-wide baseline in defaults.allowed_tools, AND it matches the run's autonomy contract if one is set. The baseline widens scope; it never bypasses the autonomy mask. Enforcement engages only when a lane scope is present, so the lead session is unaffected.
  2. Task projection. A worker with a run id and task id but no cell instance id is denied, because its projection cannot be verified.
  3. MCP description pin (above).
  4. Lane wiki guard (above).
  5. Ingest pause. While wiki ingest is paused, writes to the candidate path are refused, and clearing the pause is routed to you.
  6. Dispatch attribution and backend-degradation checks for lane dispatch.

If a host lacks pre_tool_use_ask, an ask becomes a deny and an approval request is written to the bus. Every decision lands in <runDir>/logs/security-events.jsonl.

Release signing custody

scripts/sign-release-attestation.ts is never invoked by hooks or ordinary sessions. In production it needs --custody-root-path, a directory owned by the current OS user, real (not a symlink), mode 0700. Signing material, the used-key registry, output and locks stay beneath it. This is a single-user custody boundary. It does not defend against an uncooperative process running as the same user. See Attestation and custody.

Reporting

Email security@lookatitude.com with [Guild security] in the subject. Do not open a public issue. Acknowledgement is within 3 business days, with a fix or mitigation targeted within 14 days of confirmation.