Skip to content

The Weftra Trust Page

GENERATED from committed evidence files — do not hand-edit; regenerate with npm run trust:export. Every number below carries the source path it was read from (spec 404 invariant I2); anything this generator could not find in a clean, no-network checkout renders as "not generated" with the command to produce it (AC1).

The queue, the approval, the ledger and the policy stay on your infrastructure, engine-neutral — the agent runs where you declare (a host sandbox, a vendor’s self-hosted environment, a rented harness), and FA records which. This page is the evidence behind that sentence, not a marketing restatement of it.

1. Authorization surface

Source: docs/security/authz-surface.json (machine-checked against every route by tests/authz-surface.test.ts — completeness, no stale rows, guard match, floor rules).

310 routes total, by guard tier:

tierroutes
admin98
admin-or-user69
explicit-admin15
project36
project-or-admin38
project-or-admin-or-approver8
public26
run-token5
session2
user13

By blast radius:

blast_radiusroutes
credentials28
execution16
none9
read126
secrets9
write122

The tenant-reachable set (108 routes)

A project API key (fa_…) is FA’s lowest-privilege credential (CLAUDE.md’s Trust Boundary law). These are every route a tenant key can reach, either unauthenticated (public) or via a guard that accepts a project key alongside admin/approver (project, project-or-admin, project-or-admin-or-approver) — listed explicitly, not summarized, because this is the question CLAUDE.md’s Trust Boundary law asks first.

MethodPathGuardTierBlast radius
GET/.well-known/oauth-authorization-servernonepublicread
GET/.well-known/oauth-protected-resourcenonepublicread
GET/api/audit/exportrequireProjectOrAdminAuthproject-or-adminread
GET/api/audit/manifestrequireProjectOrAdminAuthproject-or-adminread
GET/api/audit/recordsrequireProjectOrAdminAuthproject-or-adminread
POST/api/builder/agent/interviewrequireProjectAuthprojectwrite
POST/api/builder/agent/interview/:id/draftrequireProjectAuthprojectwrite
POST/api/builder/agent/interview/:id/refinerequireProjectAuthprojectwrite
GET/api/builder/branchesrequireProjectAuthprojectread
POST/api/builder/clarifyrequireProjectAuthprojectwrite
POST/api/builder/draftrequireProjectAuthprojectwrite
POST/api/builder/estimaterequireProjectAuthprojectread
POST/api/builder/product/:id/artifactsrequireProjectOrAdminAuthproject-or-adminwrite
POST/api/builder/product/:id/artifacts/:lineageId/versionsrequireProjectOrAdminAuthproject-or-adminwrite
POST/api/builder/quickpickrequireProjectAuthprojectwrite
POST/api/builder/refinerequireProjectAuthprojectwrite
POST/api/builder/submitrequireProjectAuthprojectwrite
GET/api/enginesresolveSessionAuth+requireCsrfpublicread
GET/api/features/requireProjectAuthprojectread
POST/api/features/requireProjectAuthprojectwrite
GET/api/features/:idrequireProjectAuthprojectread
PATCH/api/features/:idrequireProjectAuthprojectwrite
GET/api/features/:id/artifactsrequireArtifactAuthproject-or-adminread
POST/api/features/:id/artifactsrequireArtifactAuthOrSessionAdminproject-or-adminwrite
DELETE/api/features/:id/artifacts/:artifactIdrequireArtifactAuthproject-or-adminwrite
GET/api/features/:id/artifacts/:artifactIdrequireArtifactAuthOrSessionAdminproject-or-adminread
POST/api/features/:id/cancelrequireProjectAuthprojectwrite
GET/api/features/:id/checkpointsrequireProjectOrAdminAuthproject-or-adminread
GET/api/features/:id/checkpoints/:a/compare/:brequireProjectOrAdminAuthproject-or-adminread
GET/api/features/:id/checkpoints/:checkpointIdrequireProjectOrAdminAuthproject-or-adminread
POST/api/features/:id/clarifications/:clarificationId/answerrequireProjectAuthprojectwrite
POST/api/features/:id/create-prrequireProjectAuthprojectwrite
GET/api/features/:id/escalationsrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverread
POST/api/features/:id/escalationsrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverwrite
GET/api/features/:id/eventsrequireProjectAuthprojectread
GET/api/features/:id/evidence-packrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverread
POST/api/features/:id/evidence-pack/verifyrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverwrite
POST/api/features/:id/notifyrequireProjectAuthprojectwrite
POST/api/features/:id/permissions/:permissionId/resolverequireProjectAuthprojectwrite
GET/api/features/:id/provenancerequireProjectAuthprojectread
GET/api/features/:id/relationshipsrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverread
POST/api/features/:id/relationshipsrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverwrite
DELETE/api/features/:id/relationships/:relationshipIdrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverwrite
GET/api/features/:id/replayrequireProjectAuthprojectread
POST/api/features/:id/rerunrequireProjectAuthprojectwrite
POST/api/features/:id/retryrequireProjectAuthprojectwrite
POST/api/features/:id/reviserequireProjectAuthprojectwrite
POST/api/features/:id/wont-mergerequireProjectAuthprojectwrite
GET/api/features/cost-summaryrequireProjectAuthprojectread
GET/api/features/permissions/pendingrequireProjectAuthprojectread
GET/api/features/reportrequireProjectAuthprojectread
GET/api/features/report/timeseriesrequireProjectAuthprojectread
GET/api/instance/verification-keynonepublicread
DELETE/api/mcp/requireProjectAuthOrMcpOAuthTokenprojectnone
GET/api/mcp/requireProjectAuthOrMcpOAuthTokenprojectnone
POST/api/mcp/requireProjectAuthOrMcpOAuthTokenprojectwrite
GET/api/product/openapi.jsonnonepublicread
GET/api/project/requireProjectAuthprojectread
PATCH/api/project/requireProjectAuthprojectwrite
GET/api/project/usagerequireProjectAuthprojectread
GET/api/projects/:id/discoveryrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/discovery/:runIdrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/env/checkrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/env/check/logsrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/env/scaffoldrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/env/scaffold/logsrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/knowledgerequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:id/knowledgerequireProjectOrAdminAuthproject-or-adminwrite
PATCH/api/projects/:id/knowledge/:kidrequireProjectOrAdminAuthproject-or-adminwrite
GET/api/projects/:id/product/artifactsrequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:id/product/artifactsrequireProjectOrAdminAuthproject-or-adminwrite
GET/api/projects/:id/product/artifacts/:lineageIdrequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:id/product/artifacts/:lineageId/propose-readyrequireProjectOrAdminOrUserAuthproject-or-admin-or-approverwrite
POST/api/projects/:id/product/artifacts/:lineageId/reviewrequireProjectOrAdminAuthproject-or-adminwrite
POST/api/projects/:id/product/artifacts/:lineageId/versionsrequireProjectOrAdminAuthproject-or-adminwrite
GET/api/projects/:id/product/connectors/:connectorId/ingestsrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/product/connectors/:connectorId/ingests/:runIdrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/product/connectors/:connectorId/sourcesrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/product/exportrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/product/sources/:lineageIdrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/skillsrequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:id/skillsrequireProjectOrAdminAuthproject-or-adminwrite
GET/api/projects/:id/skills/:skillIdrequireProjectOrAdminAuthproject-or-adminread
GET/api/projects/:id/skills/:skillId/effectivenessrequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:id/skills/:skillId/reviserequireProjectOrAdminAuthproject-or-adminwrite
GET/api/projects/:projectId/triggersrequireProjectOrAdminAuthproject-or-adminread
POST/api/projects/:projectId/triggers/:providernonepublicwrite
POST/api/projects/:projectId/triggers/rotate-secretrequireProjectOrAdminAuth+closeDevModeOpenPassForTriggerSecretRotationproject-or-admincredentials
GET/api/statusresolveSessionAuth+requireCsrfpublicread
GET/api/verify/nonepublicread
GET/auth/githubnonepublicnone
GET/auth/github/callbacknonepubliccredentials
GET/auth/gitlabnonepublicnone
GET/auth/gitlab/callbacknonepubliccredentials
GET/auth/googlenonepublicnone
GET/auth/google/callbacknonepubliccredentials
POST/auth/local/loginnonepubliccredentials
POST/auth/local/resetnonepubliccredentials
POST/auth/local/reset-requestnonepubliccredentials
POST/auth/login/apikeynonepubliccredentials
POST/auth/logoutnonepubliccredentials
GET/auth/oidc/callbacknonepubliccredentials
GET/auth/oidc/loginnonepublicnone
GET/auth/statusnonepublicnone
GET/healthresolveSessionAuth+requireCsrfpublicnone
POST/oauth/mcp/registernonepublicwrite
POST/oauth/mcp/revokenonepubliccredentials
POST/oauth/mcp/tokennonepubliccredentials

2. Execution boundary

CLAUDE.md’s Trust Boundary law: "Execution happens in DockerRuntime. Never on the host." Source: src/services/runtime/docker-runtime.ts (the argv builders below are pure, imported functions — not transcribed) and a static scan of src/ for every child_process import outside src/services/runtime/.

17 spawn sites outside src/services/runtime/, every one allowlisted with a reason (tests/child-process-tripwire.test.ts fails the build on a new, unlisted one):

FileWhy it is not a boundary breach
src/cli/lib/git.tssrc/cli is the standalone operator CLI (not the FA server) — execFileSync runs git with hardened flags for the operator's own local repository operations. No HTTP route, no tenant input reaches this file.
src/cli/lib/keychain.tssrc/cli operator tool — execFileSync shells out to the OS keychain (macOS security / freedesktop secret-tool) to store the operator's OWN CLI credential locally, argv never carries the secret value. No HTTP route, no tenant input reaches this file.
src/services/agent/claude-runner.tsOuter host spawn() invokes ONLY the command Runtime.wrap() resolves — docker exec under RUNTIME=docker, a bash -lc passthrough under the local dev opt-out — through the fixed HOST_SPAWN_ENV_ALLOWLIST env. The project's own commands run inside DockerRuntime's container, never as a second execution path on this host process.
src/services/agent/exec.tsFA's own git plumbing (clone/branch/commit/push) with fixed, FA-authored argv (buildGitHardeningFlags, execGitHardened) and an env built from nothing plus explicit credentials — manages the checkout itself, never runs a project-declared command. setup_command/test_command/verify_command are dispatched through Runtime.wrap() (src/services/agent/gates.ts), not this file.
src/services/agent/gates.tsSame outer-wrapper pattern as claude-runner.ts: spawn() runs only runtime.wrap('bash', ['-lc', testCommand])'s resolved {cmd, args} — under RUNTIME=docker that resolved command is a docker exec into the sandbox container, where the project's test/verify command actually executes. This call is the invocation mechanism, never a second execution path.
src/services/agent/mcp-gateway-upstream.tsSame outer-wrapper pattern — the file's own header comment: 'the gateway spawns runtime.wrap(entry.command, entry.args, ...)'s result ... this NEVER falls back to a bare host spawn' (fail-closed if wrap() is unavailable).
src/services/agent/workspace.tsRead-only docker image inspect <image> existence probe (stdio ignore) run before pulling/building a runtime image — queries the Docker CLI about a local image, executes nothing project-declared.
src/services/engine/acp-engine.tsSame outer-wrapper pattern as claude-runner.ts — the file's own header comment: 'Always spawn THROUGH the runtime — never directly against the host.' Spawns only runtime.wrap()'s resolved {cmd, args}.
src/services/engine/headless-engine.tsSame outer-wrapper pattern as claude-runner.ts/acp-engine.ts — spawns only task.runtime.wrap()'s resolved {cmd, args}; env from buildSubprocessEnv's allowlist, never a raw tenant command.
src/services/release-notes.tsRead-only git introspection (rev-parse/log/diff, spawnSync with hardened flags) over FA's OWN repository at doc-generation time — a build-time tool, not a route any tenant reaches.
src/tools/capability-docs-gate.tsCI/build gate (the capability-docs role-check script) — execFileSync runs git against a checkout with hardened flags. Not part of the running server; no tenant input reaches it.
src/tools/depcruise-runner.tsCI/lint tool (npm run arch:check) — spawnSync runs the dependency-cruiser binary with fixed args against FA's own src/ tree. Not part of the running server.
src/tools/deps-audit.tsCI/build tool (npm run deps:audit) — execSync runs npm audit --json over FA's own lockfile. Not part of the running server; no HTTP route reaches it.
src/tools/diagnose.tsOperator-run diagnostic CLI (npm run diagnose) — spawns the Claude CLI directly on the host to test the OPERATOR's own local setup. Not an HTTP route; no tenant input reaches it.
src/tools/doc-drift-gate.tsCI/build gate (the doc-drift check script) — imports execFileSync alongside the same family of git-backed doc-drift tooling as capability-docs-gate.ts (currently unused in this file's body). Not part of the running server; no tenant input reaches it.
src/tools/merge-preflight.tsCI/CLI tool (npm run merge-preflight) — execFileSync runs git with a fixed argv array (no shell). Not part of the running server.
src/tools/sandbox-exec.tsTHE canonical sandboxed-execution entry point CLAUDE.md names explicitly ("every agent run is sandboxed — src/tools/sandbox-exec.ts"). Its host spawn() invokes only runtime.wrap()'s resolved wrapper command, never a raw tenant command directly.

The sandbox argv the runtime builds

From sandboxResourceLimitArgs() (the one resource ceiling every FA-started sandbox container carries):

--cpus 2 --memory 4g --pids-limit 512

From sandboxHardeningArgs() under the default strict posture (shown with the repo-relative seccomp profile path in place of this checkout’s absolute path, for a reproducible render — the flag itself is identical):

--cap-drop ALL --security-opt no-new-privileges --security-opt seccomp=containers/seccomp/fa-default.json --read-only --tmpfs /tmp:rw,exec,nosuid,nodev,mode=1777 --ulimit nofile=65536:65536

From authArgs() — what credential material (if any) reaches the container, by auth_mode. A secret VALUE never appears in argv; api mode passes only the variable name:

api:   -e ANTHROPIC_API_KEY -e HOME=/tmp
oauth: -e HOME=/tmp
none:  -e HOME=/tmp

Network and egress defaults (src/config.ts)

SettingDefaultMeaning
RUNTIMEdockerIsolated per-run container. local is a development-only host opt-out — see docs/SETUP.md and docs/OPERATIONS.md §"Docker socket / runtime sandbox posture".
FA_RUNTIME_NETWORKbridgeThe agent job container has outbound network access by default (needed to reach the model API); filesystem isolation is unaffected.
FA_BOOTSTRAP_GIT_EGRESSenforcedThe bootstrap clone container is attached to an FA-owned --internal network behind a CONNECT forwarder permitting exactly the current run’s repo host on port 443. Fail-closed: no automatic fallback to open.
FA_RUNTIME_EGRESSopenOpt-in allowlist mode for the agent job container itself (allowlist) is available but not the default; open is byte-identical to bridge with no egress restriction.
FA_SANDBOX_HARDENINGstrict--cap-drop ALL, no-new-privileges, a seccomp profile, and a read-only rootfs. compat is an explicit, recorded operator opt-out to the pre-hardening argv — never a silent fallback.

Containment vocabulary. Every run today executes under exactly one shape: FA’s own DockerRuntime (full containment) or, under the RUNTIME=local development opt-out, the host process directly (no containment — development only, never the documented deployment shape). A formal multi-tier containment taxonomy (fa-hosted-sandbox / remote-engine, for a future engine that FA does not itself execute) is proposed in docs/design/fa-roadmap.md spec 161 and is explicitly design-only, not built — this page does not claim it exists yet.

3. Derived state

Source: docs/security/derived-state-inventory.json (machine-checked by tests/derived-state-inventory.test.ts, which parses and type-checks every src/**/*.ts module-level Map/Set declaration at test time).

153 module-level derived-state declarations, every one keyed or bounded by the scope dimension(s) that make its reads correct for the tenant, project, run, or process it belongs to (no live under-scoped cache on this baseline), by kind:

kindcount
cache12
constant96
dedup17
memo2
registry26

4. Tests and pins

Test/file counts: not generated in this render — run npx vitest run --reporter=json --outputFile=vitest-report.json and re-run npm run trust:export.

Coverage: not generated in this render — run npm test -- --coverage and re-run npm run trust:export.

Pinned invariants CLAUDE.md names (a source-tripwire test fails the build if the invariant moves without the pin being updated in the same change):

PresentInvariantTest file
A project field aliased into the feature join (PROJECT_RENAMED_FIELDS)tests/project-join-constant.test.ts
Dashboard attribute-escaping (esc() site counts, no innerHTML templates)tests/spec-259-dashboard-attribute-escaping.test.ts
Route guard roles + a matching USER_GUIDE Role: linetests/route-roles.test.ts
Operator-identity withholding from a tenant-key ledger readtests/spec-334-operator-identity-ledger-withhold.test.ts
docs/features.yml → capabilities.md byte-matchtests/capabilities-doc.test.ts
docs/features.yml → build-tracker.md byte-matchtests/build-tracker.test.ts
A tenant route refuses reviewer-arming fields before the first writetests/spec-256-reviewer-arming-guard.test.ts
A feature-selection query carries the unresolved-prior-decision guardtests/prior-decision-relaunch-guard.test.ts
The generated-path merge resolver names no specific generator/tool (§VII)tests/spec-385-generated-path-merge-resolution.test.ts
Authorization-surface inventory completeness (this page’s own source)tests/authz-surface.test.ts
Derived-state inventory completeness (this page’s own source)tests/derived-state-inventory.test.ts
Every child_process import outside src/services/runtime/ is allowlisted with a reasontests/child-process-tripwire.test.ts
This page byte-matches a fresh regenerationtests/trust-page.test.ts
This page and README/OPERATIONS/SETUP never claim an unheld certification or an unshipped integrationtests/trust-page-forbidden-phrases.test.ts

5. Supply chain

SBOM: not generated in this render — run npm run sbom and re-run npm run trust:export.

deps:audit (npm run deps:audit, CI-gated — see .github/workflows/ci.yml) is not re-run by this generator (it needs network access to the advisory database, and AC1 requires this generator to run with none). Declared waivers on file: 1 (from deps-audit-waivers.json, each carrying a justification, scope, and expiry — run npm run deps:audit for the live pass/fail result).

Node.js: 18+ (CLAUDE.md Tech Stack — not pinned in package.json engines).

6. Ledger and audit

150 distinct run-event types found by a static scan of recordRunEvent(s) call sites in src/ (a lower bound — a computed/templated type string is not literal and is not counted). The ledger itself (run_events) is append-only; no pruning code was found in src/ for it, so retention is unbounded unless an operator prunes the database file directly.

Hash-chain export routes (from docs/security/authz-surface.json):

MethodPathGuard
GET/api/audit/exportrequireProjectOrAdminAuth
GET/api/audit/manifestrequireProjectOrAdminAuth
GET/api/audit/recordsrequireProjectOrAdminAuth

Export format: hash-chained JSONL/CSV. Push integration into a SIEM platform is not shipped — it is roadmap RM-096.

7. Adversarial review coverage

Source: docs/security/adversarial-review/COVERAGE.md.

1 of 65 in-scope surfaces examined (2%). Coverage counts surfaces looked at, not surfaces proven safe — see the source file’s own "What this number does NOT say" section.

8. Incident record

PR #173 (2026-07-17) shipped a tenant-reachable deploy route with no approval that ran a request-body command on the FA host with the full process environment — a tenant’s project key could read every FA secret. It was rated auto-merge eligible by the loop’s own risk classifier and approved by the Reviewer; CI was green. Caught by the operator asking, not by any automated gate. Closed, not merged.

Full account and the fixes that followed: CLAUDE.md — "Core Law: The Trust Boundary"; this page’s own Execution boundary and Authorization surface sections above are the mechanical checks that incident produced. docs/OPERATIONS.md §"4a. Risk tiering — what the loop will and won’t auto-merge" (subsection "Why the content signal exists (2026-07-17)").

9. What is NOT claimed

  • No third-party certification is held. No SOC 2 attestation, no ISO 27001 attestation, no other compliance certification of any kind.
  • RUNTIME=local is a development-only opt-out, never the documented or recommended deployment shape — see docs/SETUP.md and docs/OPERATIONS.md.
  • Single-tenant. FA is a self-hosted, single-operator instance; Postgres-backed multi-tenant hardening is a roadmap chapter, not shipped.
  • Audit-log export is hash-chained JSONL/CSV, not a SIEM platform push. Push integration is roadmap RM-096.
  • A formal multi-tier containment taxonomy is design-only. See §2 above.
  • Accepted residuals recorded on the roadmap, including the bootstrap-clone egress residual noted in docs/design/fa-roadmap.md — recorded, not hidden, not eliminated.
  • Coverage numbers above measure attention, not proof. A no-finding from a review pass is triage, never a clean bill of health (see §7).

10. Verification

The instance’s public verification key is published at GET /api/instance/verification-key (no auth — public by construction, rate-limited). Fetch a feature’s evidence pack via GET /api/features/:id/evidence-pack and verify its signature against this key, or run npx weftra verify <pr_url|statement.json>.


Generated 2026-09-14T03:38:58.815Z (content hash 3a2c3d4a41dc over docs/security/authz-surface.json + docs/security/derived-state-inventory.json).

Released under the MIT License.