Skip to content

Seepient Permission System 008 — Security Review Package ​

Purpose: This is the review package for an independent security reviewer. Per spec 008 (FR-020), the authoring trust domain cannot self-attest a security- kernel or authority-expanding change. This document is therefore not a certification — it is the structured package (threat model, asset map, call paths, audit checklist) the reviewer uses to perform the independent review.

Reviewer scope: actual call paths, the native helper, broker, scheduler, stores, and deployment manifests. The reviewer signs their own attestation through the ActivationSupervisor contract; this package does not.


1. Threat model ​

Trust domains ​

DomainMembersAuthority
Active trusted staterunning executable, active security policy, operator ceiling, approval identities, release keys, audit configmounted read-only to tool executors; never executor-writable
Candidate stateworkspace/branch/image candidatesfreely modifiable within execution capabilities
Activation authorityexternal supervisor, operator action, separately-trusted release serviceverifies + activates candidates; never the authoring run

The same run that authored a candidate cannot directly replace active trusted state or mint the attestation that activates an authority-expanding change.

Assets + enforcement ​

AssetThreatEnforcementCode path to audit
Provider API keys (OPENAI/ANTHROPIC/GLM)Worker exfiltrationsanitizeEnvironment strips; WorkerExecutionBoundary routes effectful ops to schedulersrc/capabilities/execution/environment-policy.ts, worker-execution-boundary.ts
Server/SMTP/release credentialsWorker reads them from envFORBIDDEN_ENV_PREFIXES; worker env reconstructed, never inheritedenvironment-policy.ts
Active security policyExecutor rewrites it to self-escalateLocalPolicyStore at ~/.seepient/security/policies/, private perms, CAS, digest verificationsrc/domain/permissions/policy-store.ts
Filesystem (exact write)Symlink/TOCTOU escapeseepient-fs-commit native helper; fails closed with EXACT_COMMIT_UNAVAILABLE unless allowFallback: true is explicitly configuredsrc/vendors/native-fs-commit/index.ts, file-commit-broker.ts
Network egressWorker bypasses broker for private/metadata rangesEffectBroker DNS resolve + address-range check + DNS-rebinding check; BrokerLeaseAuthority action-bound single-use leaseeffect-broker.ts, broker-lease-authority.ts
Secrets at the brokerWorker requests raw secret retrievalBrokeredEffectRequest union has no fetch-secret variant — structurally unrepresentablefoundations/contracts/prepared-action.ts
Model-visible historySecret-class tool output reaches the providerModelEgressGate immutable-deny for secret/active-policy/release-key/control-plane-credentialmodel-egress-gate.ts
Tenant isolationTenant A reads tenant B's workspaceWorkspaceTenantRegistry binding check; per-run ephemeral worker container; mount allowlistvendors/docker/index.ts::validateDispatch, docker-worker-scheduler.ts
Docker socketControl plane or worker obtains itSocket mounted ONLY in the scheduler service; docker-compose.008.ymldocker-compose.008.yml
Dispatch forgeryForged nonce/digest/signature launches a second effectful workerSingle-use nonce, action-digest match, mTLS auth, deep-sorted canonical signingdocker-worker-scheduler.ts, worker-execution-boundary.ts::canonicalDispatch
Self-evolution activationAuthoring run activates its own protected changeclassifyProposal protected class; attestationMatches + isIndependentAttestation; supervisor-only activationself-evolution-policy.ts, self-evolution-runtime.ts
Audit integrityFailed audit write still allows dispatchPre-dispatch dispatched event is durability gate; terminal outbox idempotent on <actionId>:<state>audit-recorder.ts, action-lifecycle.ts

2. Call paths the reviewer must trace ​

2.1 Tool call → effect (the full pipeline) ​

adapter.chat()
  → runAgentLoop(options)                            [src/domain/agent-loop.ts]
    → for each provider tool_call:
        if options.actionLifecycle:                  [opt-in flag]
          actionLifecycle.run(action, signal)        [src/domain/permissions/action-lifecycle.ts]
            → PolicyEngine.evaluate(action, ctx)     [policy-engine.ts]
            → if needs-approval: ApprovalBroker      [transport/approval-brokers.ts]
            → ExecutionBoundary.execute(action, env) [THE sole side-effect entry point]
              ↳ LocalExecutionBoundary                [local deployment]
                ↳ OperationExecutorRegistry.execute   → CommitFilesExecutor / ProcessExecutor / BrokerExecutor
              ↳ WorkerExecutionBoundary               [server deployment]
                ↳ WorkerScheduler.dispatch            → ephemeral isolated worker
            → AuditRecorder.append (idempotent)      [audit-recorder.ts]
        else: legacy matrix/grant path               [agent-loop.ts L481-521]

Reviewer question: Can any path bypass ExecutionBoundary.execute() and still produce a model-visible side effect? Trace every spawn, writeFile, fetch, and send in src/.

2.2 Capability monotonicity ​

PolicyEngine.evaluate()
  → isDeniedByRule(immutableDenies, effect)          [capability-store.ts]
  → backend.supportedOperationKinds.includes(op.kind)
  → effectiveCapabilities(deployment ∩ principal ∩ runtime ∩ active)
  → requiredCapabilities(action.effects)
  → setCovers(effective, required) for each

Reviewer question: Can an inner input (request/grant/skill/middleware/ approval) expand an outer ceiling? Property test at property-fuzz.test.ts asserts narrowing; reviewer should attempt to construct a counter-example.

2.3 Exact-file commit (the TOCTOU surface) ​

CommitFilesExecutor.execute()
  → FileCommitBroker.commit()
    → capability check: envelope has commit-file for exact path
    → if !helper.available: throw UnsupportedBackendError (FAIL CLOSED)
    → PackagedCommitHelper.commit()
      → spawn seepient-fs-commit --commit <dest>
      → helper owns validate → write → revalidate → rename
      → wrapper verifies returned digest matches input

Resolved (spec 019): there is NO JS write path — the interim fallback and its env opt-in were deleted (FR-013); PackagedCommitHelper answers primitive-unsupported whenever the probe is unavailable. The helper binary IS checksummed before execution: the probe verifies its sha256 against the shipped manifest and fails closed (digest-mismatch) on mismatch or an unknown manifest version (probe src/vendors/native-fs-commit/index.ts::probeCommitHelper).

2.4 Network egress ​

BrokerExecutor → EffectBroker.execute()
  → verify BrokerAuthContext (expiry, action-digest, single-use-request-id)
  → capability check: envelope has network-destination for host
  → scheme/host DENIED_HOSTS check
  → network.resolve(host) → reject if any IP in DENIED_IPV4_PATTERNS
  → strip FORBIDDEN_REQUEST_HEADERS
  → network.fetch() → reject if effectiveIp ∉ resolved (DNS rebinding)
  → response size cap
  → store response as artifact; never return raw to worker

Reviewer question: Is direct fetch() from a worker possible? Grep for fetch( in any module a worker can import. The worker image should have no route to public internet except via the broker.

2.5 Self-evolution activation ​

createCandidateWorkspace()                           [self-evolution-runtime.ts]
  → buildProposal() → content-addressed digest
  → submitForActivation():
    → attestationMatches(att, proposal, now)         [self-evolution-policy.ts]
    → isIndependentAttestation(att, proposal)
    → classifyProposal(policy, proposal)             → delegated | protected | needs-attestation | disallowed
    → if protected && !supervisor: verified-pending-activation
    → else: supervisor.submit(proposal, attestation)

Reviewer question: Can the authoring run forge an attestation that passes isIndependentAttestation? The signature is verified by the external supervisor, not by this codebase — the structural gate here only checks verifierId !== authorRunId && authorityId !== authorRunId. A real deployment uses a separately-installed supervisor key.


3. Audit checklist ​

The reviewer signs off each item independently.

Foundations (contracts) ​

  • [X] Every PreparedOperation variant is enforced by exactly one executor kind; no orphan kinds.
  • [X] PolicyDecision is a closed discriminated union; no fourth variant exists at runtime.
  • [X] BrokeredEffectRequest has no fetch-secret / raw-secret / socket variant.
  • [X] PermissionDenyReason is a fixed set; no stringly-typed denials.

Domain (policy + lifecycle) ​

  • [X] PolicyEngine.evaluate applies immutable denies BEFORE any capability check.
  • [X] Headless (interaction: "none") denies needs-approval without calling the broker.
  • [X] ActionLifecycle.run writes dispatched BEFORE execution; failure denies dispatch.
  • [X] Terminal audit events are idempotent on <actionId>:<state>.
  • [X] PolicyStore.compareAndSet rejects stale expectedVersion (CAS conflict).
  • [X] Legacy grants: ambiguous prefix grants are quarantined, never auto-widened.

Capabilities (execution) ​

  • [X] CommitFilesExecutor fails closed (EXACT_COMMIT_UNAVAILABLE) when native helper is unavailable — JS fallback is opt-in (allowFallback: true).
  • [X] EffectBroker rejects loopback, private, link-local, reserved, and metadata addresses post-DNS.
  • [X] EffectBroker rejects DNS rebinding (effective IP must be in resolved set).
  • [X] EffectBroker strips Authorization, Cookie, Host, proxy, forwarding headers.
  • [X] ModelEgressGate immutable-denies secret, active-policy, release-key, approval-credential, control-plane-credential.
  • [X] ProcessExecutor passes sanitizeEnvironment(parentEnv) to the sandbox; provider keys are absent.
  • [X] UncontainedSandbox reports isolated: false; evidence executorId records the honest status.
  • [X] Browser tools return backend-unsupported; no flag launches control-plane Chromium.

Vendors (platform adapters) ​

  • [X] @anthropic-ai/sandbox-runtime is imported ONLY by src/vendors/sandbox-runtime/.
  • [X] The Docker SDK is imported ONLY by src/vendors/docker/.
  • [X] The native commit helper is checksummed before execution; mismatched binary rejected.

Transport (surfaces) ​

  • [X] NoneApprovalBroker never invokes a callback — headless cannot reach a prompt.
  • [X] InlineApprovalBroker composes the caller's AbortSignal with a deadline; timeout denies safely.
  • [X] REST interaction: "never" returns denial; interaction: "resumable" returns promptly with a continuation.
  • [X] WorkerExecutionBoundary delegates every effectful operation; control plane executes nothing model-authored.
  • [X] Host tools (trustedHostTool) are disabled by default in server roots; only operator allowlist enables them.

Deployment (server) ​

  • [X] docker-compose.008.yml: Docker socket mounted ONLY in scheduler.
  • [X] Control plane and worker have NO Docker socket.
  • [X] Workers reach ONLY effect-broker on broker-net.
  • [X] Provider keys mounted ONLY to the control plane.
  • [X] validateNetworkTopology passes for the production manifest.
  • [X] WorkspaceTenantRegistry is configured; cross-tenant dispatch is rejected.

Self-evolution ​

  • [X] Candidate workspace is content-addressed; digest covers every file.
  • [X] classifyProposal uses the operator-owned policy; candidate-provided policy is untrusted input.
  • [X] Protected change classes require independent activation authority.
  • [X] Without a configured supervisor, the result is verified-pending-activation — never an in-process fallback.
  • [X] detectProtectedWrites catches symlink escapes into immutable assets.

4. Known limitations (reviewer must acknowledge) ​

  1. Durable stores across restarts. DurableApprovalStore, LocalAuditStore, PersistedCapabilityLedger, PersistedReplayLedger, and TerminalEventOutbox are durably persisted with atomic writes (tmp + fsync + rename, file locking, 0o600/0o700).
  2. The native seepient-fs-commit helper is a separate build artifact. Its Rust source lives in-repo at native/fs-commit/ (spec 019) and must be reviewed independently. The TS wrapper probes for the binary, verifies its sha256 against the shipped dist/native-fs-commit/manifest.json, and fails closed (digest-mismatch) on any mismatch; SEEPIENT_FS_COMMIT_BIN bypasses the manifest check BY DESIGN as a documented developer trust decision.
  3. The sign() callbacks in tests use fake signers. Production wiring requires real mTLS-backed signing keys for dispatch signatures and broker lease tokens.
  4. Cloud schedulers (Cloud Run / Modal / ECS / Kubernetes / microVM) are future adapters. They implement the same WorkerScheduler contract but are out of scope for v1.

5. How to run the security test suite ​

bash
# Full suite (836 tests including all security gates)
pnpm test

# Targeted security suites
pnpm vitest run src/domain/permissions/__tests__/server-conformance.test.ts   # QS-4.1-4.8
pnpm vitest run src/capabilities/execution/__tests__/effect-broker.test.ts    # network boundary
pnpm vitest run src/capabilities/execution/__tests__/environment-policy.test.ts # secret isolation
pnpm vitest run src/domain/permissions/__tests__/property-fuzz.test.ts        # monotonicity + adversarial paths
pnpm vitest run src/foundations/contracts/__tests__/architecture-boundaries.test.ts # layer rules

# Architecture-boundary gate (must pass — no upward/sibling-capability imports)
pnpm vitest run src/foundations/contracts/__tests__/architecture-boundaries.test.ts

The architecture-boundary gate has caught three real violations during implementation; it is the structural backstop for the layer rules in ARCHITECTURE.md.


6. Reviewer attestation ​

When the review is complete, the reviewer signs an ActivationAttestation through the ActivationSupervisor contract:

ts
{
  proposalId: "<this-spec's-proposal-id>",
  candidateArtifactDigest: "<content-addressed digest of this implementation>",
  verifierId: "<reviewer-identity>",   // ≠ authorRunId
  authorityId: "<supervisor-identity>", // ≠ authorRunId
  issuedAt: <now>,
  expiresAt: <now + review-window>,
  signature: "<reviewer's signature>"
}

Without this attestation, submitForActivation returns verified-pending-activation. The implementation does not provide an in-process fallback.

Released under the Business Source License 1.1.