Skip to content

Campus permission model

This is the installation-authorization layer of the broader Campus security model. The Agent subprincipal and cross-user protocol are specified in Account-scoped MCP.

Core rule

An app package is a definition; an installed app is the durable security principal. Permanent authority is the tuple:

text
(installation(account-id, app-id), permission-id, resource)

Browser-only session authority adds a second tuple:

text
(active-login-session, installation(account-id, app-id), permission-id, resource)

Access is allowed only when the installation is authenticated and either its permanent grant or a matching active-session overlay covers the requested resource. The overlay is admitted only for the frontend token, exact app origin, and session public ID delivered in the trusted launch descriptor. Backend Gateway tokens see permanent grants only. Code must never accept an account-id from an app request; the account comes from the installation credential and must match the session account.

Related relations are:

text
declares(app, permission, resource)       # maximum authority the package may receive
installs(account, app) -> installation   # account-scoped app principal
grants(installation, permission, resource)
session-grants(active-session, installation, permission, resource)
owns(account, private-app)
binds(account, app, secret-slot, secret)  # additional authorization, not a grant

There are no requestable installation roles, explicit denies, or priorities. Session status is the only grant-lifetime condition; absence of a matching permanent or active-session grant is denial.

CampusOS also has a deliberately separate, nondelegable package-provenance mechanism called trustedRoles. It is used only for the small trusted-computing-base surfaces that cannot safely be made user-grantable, such as Wallet recovery, settings/account control, funding onboarding, permission review, schedule management, and Contact-intent review. A trusted role is effective only when all three are true:

text
systemSupplied(app)
AND publisher(app) == Campus
AND trusted-role(app, role) is in CampusOS's fixed allowlist

Store snapshots are forbidden from claiming systemSupplied, default/master installation, or trustedRoles. Roles therefore cannot be requested, approved, copied into installation grants, or minted by choosing a familiar app ID. Exact stock app IDs remain useful for routing, default package selection, and provenance; they are not service authorization checks.

Request and grant lifecycle

  1. A package requests permissions in campus.yaml with an ID, reason, and optional resources.
  2. Installation stores an account-specific grant snapshot, which may be a subset of the request. A grant that exceeds the published request is rejected.
  3. A later package version cannot silently gain authority. Newly requested resources become pending until approved.
  4. An SDK request is persisted and approved or denied in the trusted Permissions app. Eligible browser permissions may be granted for the originating login session instead of permanently.
  5. The account may replace or revoke the permanent grant snapshot. Service checks take effect immediately; Runtime network and public-ingress policy is reconciled fail-closed.
  6. Session overlays stop authorizing as soon as the login expires or is revoked. Uninstall removes the installation principal, all of its session overlays, and secret bindings, but preserves the app's persistent data volume.

Resource matching supports only:

  • exact match: campus-mail matches campus-mail;
  • global wildcard: *;
  • colon-prefix wildcard: openai:* matches openai:gpt-5.

research*, path globs, and arbitrary prefix matching are not generic grant syntax. A declared public route such as POST:/hooks/* is one exact permission resource whose path wildcard is interpreted by the ingress layer.

Principals and credentials

PrincipalCredentialAuthority
Account userHost-only CampusOS session cookieTrusted account UI and control-plane actions; not governed by app grants
App backendPer-installation workload Gateway tokenCampus services allowed by that installation's grants; Gateway substitutes the host-held CampusOS credential
App frontendPer-installation frontend token, exact app origin, and launch-bound active session IDPermanent grants plus the matching active-login session overlay; origin and account are checked
App launchOne installation capability delivered in the URL fragmentIts own private Gateway/backend; never the CampusOS session
App agent (target model)Short-lived agentToken derived for (account-id, app-id, agent-id)Intersection of installation grants, agent grants, and the invoked action/tool policy; not yet implemented
Campus trusted surfaceInstallation identity plus verified Campus package trustedRolesOnly its fixed nondelegable system operation; ordinary service authority still requires grants
Platform master appInstallation token plus master-account ownershipNormal grants, with additional master-only checks where required

Public ingress is deliberately unauthenticated. Authentication required by a public app route is the app's responsibility.

Permission inventory

States: active means a backend, Runtime, or ingress enforcement point exists; shell means the trusted parent shell checks the active iframe's exact source, origin, app ID, and grant before using the user's session; declared only means the ID is valid metadata but no protected app-facing operation currently depends on it.

System and app control

PermissionRiskResourceCurrent effect
system.inventory.readcritical*Active. Reads secret-free fleet inventory; installation must also belong to the platform master account.
apps.catalog.readlow*Active + shell. Reads the account-visible app catalog.
apps.installhightarget app IDShell. May request installation; the trusted UI still owns approval.
apps.createhigh*Shell. Creates a private app owned by the same account.
apps.uninstallhightarget app IDShell. Removes an installation, subject to protected-app rules.
apps.lifecyclemediumtarget app IDShell. Opens, starts, pauses, or stops an installed app.
apps.launcher.registerhigh*Active + shell. Makes the current app the account launcher; launcher baseline grants are also required.
schedules.managehigh*Active + shell. Lets a verified Campus schedules.manager-ui surface manage account schedules through the narrow CampusSDK bridge; the target lifecycle mode and tool grants remain independently required.
runtime.status.readlow*Shell. Exposes only Runtime availability context.
runtime.pausecritical*Active + shell. Stops and durably gates all account containers, routes, schedules, triggers, and app service access until trusted-shell resume.
account.profile.readlow*Shell. Exposes the Campus name and selected launcher.
runtime.chromium-sandboxhigh*Active. Enables the packaged Chromium sandbox capability in the Runtime spec.
runtime.network-tunnelcritical*Active. Applies only the fixed NET_ADMIN plus /dev/net/tun policy required by tunnel software; packages cannot request arbitrary capabilities or devices.

Agents and MCP interaction

Workloads whose Campus App declares an Agent component receive Agent. App-owned published capabilities and selected tool-shaped projections of CampusSDK capabilities are ordinary MCP tools for Agent and cross-installation use; Campus has no separate Agent addressing or delegation permission. The underlying Campus SDK/service contract remains canonical for direct application callers. The installation's mcp.tools.call and mcp.tools.serve grants, tool schemas, target audience, and live accepted friendship intersect at the account MCP boundary. Principal-bound managed tools that require an Agent-held signing identity are explicit exceptions, not a separate generic agent RPC permission. See the canonical ownership and executor rules in Campus account MCP.

Account MCP

PermissionRiskResourceCurrent effect
mcp.tools.callhigh*, qualified tool, or target-address:qualified-toolActive. Filters discovery and invocation for an authenticated source installation; target audience policy must independently admit it.
mcp.tools.proposemediumlocal tool name or *Active for non-Agent backends. Stores an inactive, installation-owned dynamic proposal for trusted owner review. Managed Agent apps have this proposal-only ability intrinsically. Neither path can activate or approve.
mcp.tools.servehighowner:name, selected:name, contacts:name, or public:nameActive. Publishes the exact matching package-declared tool/audience from the target installation.

Package MCP authority is the intersection of the mcp.tools declaration and installed serve grant. Dynamic MCP authority is the intersection of a valid proposal and the owner's separately stored, passkey-signed active snapshot. In both cases the source call grant, target audience relation, live schemas, installation state, and downstream service permissions are rechecked on every call. See Campus account MCP for the complete flow, diagrams, limits, revisions, alerts, and failure behavior.

Public-to-agent invocation is separately specified in Campus public endpoints and addresses. endpoints.public alone exposes only the backend route; it does not authorize Agent invocation.

Network, ingress, and secrets

PermissionRiskResourceCurrent effect
network.publichigh*Active. Gives a Firecracker workload public IPv4 egress while rejecting private, loopback, link-local/metadata, multicast, and reserved destinations. Without it, the app stays on restricted egress. Docker fallback attaches its development public network and is not the production isolation boundary.
network.connecthighcanonical HTTPS origin, including portActive with secrets.use. Allows the secret broker to reach that exact manifest origin. It is not currently a standalone generic proxy.
endpoints.publichighMETHOD:/path; WebSocket uses WS:/pathActive. Publishes only routes also declared in publicEndpoints. The app uses its fixed manifest container port; grants do not expose arbitrary ports.
endpoints.public.udpcriticalUDP:<container-port> or UDP:autoActive. Publishes only matching publicPorts declarations. Runtime assigns the host port and advertises the operator-configured public host.
secrets.usehighmanifest slot nameActive. Allows use of a bound slot through the outbound broker; never returns the secret value.

A secret-bound request requires all of the following:

text
granted(secrets.use, slot)
AND granted(network.connect, exact-origin)
AND manifest slot declaration
AND active account binding with the current declaration hash
AND allowed method and path prefix
AND public destination resolution

Campus strips caller credentials, refuses redirects, and injects the decrypted value only into the declared upstream header. Changing the slot declaration invalidates the old binding.

A direct UDP binding requires all of the following:

text
schemaVersion = 9
AND manifest declares publicPorts(name, udp, container-port)
AND installation grants endpoints.public.udp(UDP:container-port)
AND Runtime assigns the host port through Docker or Firecracker

For protocols that must know the binding before process startup, containerPort: auto requires UDP:auto; Runtime assigns the same host/workload port and injects it into the workload. The package cannot choose that port, add TCP, or request arbitrary capabilities/devices. runtime.network-tunnel, endpoints.public.udp, and network.public are separate conjunctive decisions; WireGuard needs all three for TUN creation, inbound encrypted packets, and forwarded Internet egress respectively.

Data and communication

PermissionRiskResourceCurrent effect
files.user.managehigh*Active. Reserved for Campus Drive to manage user-owned Files. Photos/Gallery uses Drive Content collection grants instead; ordinary app-owned files use ownership and per-file ACLs.
notifications.publishmediumtopicActive. Creates an account notification on an allowed topic.
notifications.readhightopicActive. Lists only notifications whose topics match the grant.
search.indexmediumselfActive. Replaces or updates only this installation's account-local Campus search records.
calls.usehigh*Active. Lets the system Calls app own human and Personal Agent call surfaces. Human outgoing calls also require recipient-issued Contacts authorization.
calls.owner.initiatehigh*Active. Lets the exact Personal Agent installation invoke the owner-local bounded ring path. Explicit owner Answer, cooldown, daily cap, and call expiry remain mandatory.
drop.usehigh*Active. Exchanges short-lived WebRTC file signaling and manages recipient-encrypted Drop inbox records for the system Drop app.
mail.readhighmailbox scopeActive. Reads encrypted messages for the authenticated installation's account.
mail.sendhighrecipient scopeActive. Resolves a Campus address, quotes postage, and sends an encrypted message as the authenticated account.

Logical files also have an independent ACL relation:

text
(file-id, principal-type[user|app], principal-id, action[read|write|manage])

File ownership and ACLs are a separate authorization layer. files.user.manage is the installation-grant override for user-owned files; app ACL access is account-scoped, while explicit user ACL entries identify a user principal directly.

Money, compute, intelligence, and credentials

PermissionRiskResourceCurrent effect
commerce.products.managehighselfActive. The official Seller installation may create products and append prices only for offerings owned by its authenticated account and installation. The all-in Campus product is provisioned only for the master account.
commerce.campus.readmedium*Active. Reads the account's Campus active time, invoices, and lease state.
commerce.campus.proof.submithigh*Active. Submits a final VAS proof for an existing account payment; it cannot create credit or spend by itself.
intelligence.usehighprovider:model, e.g. openai:*Active. Calls an approved model through the per-installation Gateway without receiving provider credentials. Campus-managed credentials additionally require Campus-hosted placement; BYOH may use only BYOK.
wallet.authorizations.readhigh*CampusSDK trusted-shell VAS inventory, scoped to wallets bound to the current account.
wallet.policy.proposehighselfMay open trusted approval for an exact VAS policy creation, change, or revocation. The proposal itself cannot change a policy or spend funds.
wallet.receivemediumaddressActive. Creates a receive address only for the authenticated installation's account-bound wallet. It cannot spend.
wallet.payment.proposehighaddressMay open trusted approval for an exact payment. The shell displays and binds destination, amount, memo, and fee ceiling to a fresh passkey assertion; the app cannot approve or execute it.

Grants versus policies and budgets

A proposal permission answers may this installation open this trusted approval flow? It is prompt authority, not financial authority. Only the owner's operation-specific approval decides whether a payment or policy change occurs.

Additional gates are conjunctive:

OperationGrantAdditional enforcement
Intelligence callintelligence.useProvider/model, current installation envelope, rate limit, per-app monthly token budget, and Campus-hosted placement for managed intelligence
Hosted hardwareHardware status/proof permissions for app accessPasskey-authorized VAS policy, seller key, per-payment ceiling, total allocation, validity window, verified invoice and receive proof
Exact payment proposalwallet.payment.propose(address)Trusted shell confirmation, fresh operation-specific assertion, exact signed payment request, transaction policy, and VAS enforcement
VAS policy proposalwallet.policy.propose(self)Trusted shell review, fresh passkey approval, exact policy hash/version, mutable-field restrictions, and VAS signature verification
Secret usesecrets.use + network.connectWorkload-only current installation envelope and manifest destination/method/path restrictions

VAS usage and budgets never expand a grant. A valid budget cannot compensate for a missing permission, and a grant cannot bypass an exhausted budget or failed policy.

Cross-user boundaries

  • An app cannot use another account's Gateway credential, secret binding, budget, or private app instance.
  • Cross-account MCP calls require a published target audience, the caller's live mcp.tools.call grant, and a live accepted friendship when the tool is contact-scoped. CampusOS reads that relationship itself; the caller supplies no proof.
  • Another user can reach an app instance only through an explicitly enabled HTTP/WebSocket public endpoint, a declared-and-granted native direct UDP port, or a separate object-level sharing mechanism such as a file ACL. Public network access is not a Campus user grant.

Example: Alice's Contacts app calls campus-wallet.receive-address on Bob's account address. The caller needs that exact MCP call grant and a live accepted friendship with Bob. Admission occurs before Campus wakes Bob's optional Wallet workload. This authorizes address discovery, not a payment; sending money remains a separate Wallet action subject to VAS policy and step-up checks.

Current limitations

  • network.public is all-or-nothing across publicly routable IPv4 destinations; only secret-bound traffic has an exact-origin broker. It never authorizes private or node-infrastructure addresses on the Firecracker path.
  • Direct UDP deliberately bypasses HTTP Ingress/Gateway filtering and rate limits. It relies on exact declaration/grant matching, Runtime-assigned host ports, operator firewall/NAT policy, and the application protocol's own client authentication. Direct ports support Docker and Firecracker; tunnel-device authority currently requires the Docker/container driver.
  • Grants have no expiry, rate, monetary amount, time window, or contextual conditions.
  • Risk and step-up fields are descriptive metadata; each sensitive service must enforce its own operation-specific authorization.
  • Permissions marked declared only must not be treated as security boundaries until an enforcement point exists.
  • Cross-user MCP is active, including schema checks, exact-name resolution, audience admission, live friendship evaluation, concurrency limits, and payload-free audit metadata. A separately credentialed Agent subprincipal and global abuse controls are not implemented.

Software belongs to people. Campus gives it a durable place to run.