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.
What Guild Stack executes
| Surface | How it runs | Trust claim |
|---|---|---|
Hook scripts (hooks/) | On SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, SubagentStop, Stop, TaskCreated, TaskCompleted, TeammateIdle | Wired in hooks/hooks.json |
Tooling scripts (scripts/) | Only when a skill or a maintain verb invokes them | Not auto-run |
MCP servers (mcp-servers/) | Long-lived stdio subprocesses | Read-only, checked by the absence of writeFile / appendFile in src/ |
| Skills and agents | Markdown | No executable code |
What Guild Stack does not do
- No host-native web tool is granted outside the
researcherrole. Onlytemplates/specialists/researcher.mddeclaresWebFetch/WebSearch. Another specialist gets one only through a task-scoped operator approval recorded before dispatch. - That claim covers
WebFetch/WebSearchonly. Build roles may receiveBash, 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
| Key | Default | Effect |
|---|---|---|
secrets_policy.redaction_patterns | [] | Your regexes. They run after the five built-in redaction groups and replace matches with [REDACTED]. |
secrets_policy.fail_mode_durable | closed | Applies to handoffs, learnings, provenance, wiki, review and bus writes. A scrub failure blocks the write. |
secrets_policy.fail_mode_telemetry | open | Applies 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.
| Situation | Result |
|---|---|
| Out of scope, host not in bypass | ask, always. The event is logged. |
Bypass, policy deny | Hard block. |
Bypass, policy audit (default) | Proceeds and logs the violation. |
Bypass, policy allow | Proceeds 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:
| Key | Repaired to |
|---|---|
security.bypass_permissions_policy | deny |
codex_skip_enforcement | block |
secrets_policy.fail_mode_durable | closed |
secrets_policy.fail_mode_telemetry | closed |
host_mode | read_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 mapsmcp__<server>__<tool>names to description text. Hashes merge into the existing map;--replaceoverwrites it. - Verify statuses.
unpinned,match,mismatch,unverifiable. A pinned tool whose live description cannot be obtained isunverifiable. 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 anask. - Security events.
mcp_description_mismatch,mcp_description_unverifiable,mcp_description_unpinned. - Guild Stack's own tools are pinned at build.
runtime/mcp-descriptions.pins.jsonis 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-memoryinherits 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.
- Capability scope. A call is allowed when it matches the lane's declared
capability_scopeOR the project-wide baseline indefaults.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. - Task projection. A worker with a run id and task id but no cell instance id is denied, because its projection cannot be verified.
- MCP description pin (above).
- Lane wiki guard (above).
- Ingest pause. While wiki ingest is paused, writes to the candidate path are refused, and clearing the pause is routed to you.
- 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.