App Access and Sharing¶
Forge access has three separate controls. Keep them separate during review and incident response.
| Control | Where it is set | What it changes |
|---|---|---|
| Route exposure | public_edge.exposure in mithran.yaml |
The reviewed app-route exposure value. public serves anonymously; all other values keep Forge auth required. |
| Protected sharing | access.yaml, map access plan, and map access apply |
Which signed-in users, email domains, or account: principals should be included on protected route entries. |
| Published pointer | map publish |
Which reviewed version the clean public URL serves. |
map access apply does not edit mithran.yaml, build a new version, publish a
version, or retire a route. It records the reviewed protected-sharing policy for
the app. The next route push or route reconcile carries the current
allowed_domains and share values onto served route entries.
Review Boundary¶
| Review question | Source of truth | Values that matter | Customer-visible effect |
|---|---|---|---|
| Can an anonymous user reach the app route? | public_edge.exposure in mithran.yaml |
public serves anonymously. protected, none, public-redirect, and unset require Forge auth. |
Requires a reviewed manifest commit, deploy, and publish when the clean public URL should move. |
| Which signed-in users can reach an auth-required route? | access.yaml, map access plan, map access apply |
allowed_domains and share; access-policy exposure accepts only protected or public. |
Takes effect on the served route after the next route push or route reconcile carries the policy. |
| Which reviewed version does the clean public URL serve? | map publish |
Deployment ref selected by the operator. | Moves the clean public URL to that reviewed version. |
Use mithran.yaml for route exposure. Use access.yaml for protected sharing.
Do not use access.yaml to make a manifest-protected route anonymous.
Choose the Right Change¶
| Goal | Do this | Verify with |
|---|---|---|
| Keep an app Forge-authenticated | Set public_edge.exposure to protected, none, public-redirect, or omit it, then deploy and publish when needed. |
Anonymous request is denied before reaching the app runtime. |
| Serve an app anonymously | Set public_edge.exposure: public, then deploy and publish when needed. |
Anonymous request reaches the app route. |
| Restrict a protected app to a domain or principals | Review access.yaml, run map access plan, then map access apply; update or publish the route that should carry the policy. |
Allowed signed-in user succeeds; denied signed-in user fails. |
| Move the clean public URL to a reviewed version | Run map publish for a version that reached Succeeded or Promoted. |
map versions <app> shows published on the intended deployment ref. |
| Stop new source fetches | Remove the repo from the Forge GitHub App grant. | New deploy source fetches fail or stop; existing served routes are separate. |
Request Path¶
App access is enforced before traffic reaches the app process:
- The client opens a Forge app hostname.
- The Forge edge resolves the routing record for that hostname.
- The edge checks the route exposure carried by that route entry.
- For auth-required routes, the edge checks the signed-in user against the protected sharing policy attached to the route.
- Only allowed requests are forwarded to the app runtime.
An access denial on a protected route is decided at the edge. The app process
should not implement a second copy of the same route-sharing policy just to
compensate for a missing access.yaml update.
The served route's auth_required value comes from the deployed manifest
exposure. Forge projects the recorded allowed_domains and share policy onto
the route for per-user checks when authentication is required.
Exposure¶
Set route exposure in mithran.yaml:
| Value | Route behavior |
|---|---|
protected |
Keeps auth_required: true. Forge auth is required before forwarding. |
none |
Keeps auth_required: true. |
public-redirect |
Keeps auth_required: true. This is a manifest exposure value, not an access-policy value. |
public |
Sets auth_required: false. Anonymous requests can reach the app route. |
| unset | Keeps auth_required: true. |
public_edge.type must be platform when public_edge is present.
Provider-specific WAF, Cloud Armor, or ingress expressions do not belong in
mithran.yaml.
Exposure is reviewed with the app source because it lives in the deploy manifest. Changing exposure requires a reviewed manifest commit, deploy, and publish when the clean public URL should move to that version.
Hostnames¶
The served app ref determines the hostname label. For self-service apps, keep
the served app ref aligned with metadata.app_id:
When the app is served as app:inventory-portal, the production clean public
URL is:
Changing the served app ref changes the hostname. Treat app-ref or app-ID changes as route changes: deploy the reviewed manifest, publish the reviewed version if the clean public URL should move, then verify the served hostname.
Use map versions <app> before and after publish-sensitive access work. It
shows internal versions, app-environment aliases, and the published pointer.
The clean public URL moves only when map publish pins it to a reviewed
version.
Protected Sharing Policy¶
For protected apps, keep the sharing policy in access.yaml:
app_ref: app:inventory-portal
tenant_ref: tenant:acme
account_ref: account:acme-main
exposure: protected
allowed_domains:
- acme.com
share:
- admin@example.com
- account:acme-main
| Field | Required | Notes |
|---|---|---|
app_ref |
yes, unless --app-ref is passed |
App ref, such as app:inventory-portal. |
tenant_ref |
no | Tenant ref to write with the policy. CLI flag --tenant-ref overrides the file. |
account_ref |
no | Account ref to write with the policy. CLI flag --account-ref overrides the file. |
exposure |
no | Access-policy exposure. Defaults to protected in the CLI. Must be public or protected. |
allowed_domains[] |
no | Email domains admitted by the protected sharing policy. |
share[] |
no | Explicit principals, such as email addresses or account: refs. |
Unknown fields are rejected. A typo such as allowed_domain fails instead of
silently applying a different policy.
The access-policy exposure value is not the same as manifest exposure.
Manifest exposure is reviewed from mithran.yaml. Access-policy exposure is
recorded by map access apply and accepts only public or protected; setting
access-policy exposure to public does not make a route anonymous.
To serve anonymously, deploy a reviewed manifest with
public_edge.exposure: public, and publish it when the clean public URL should
move to that version.
Matching Rules¶
Use allowed_domains for domain-level sharing:
A signed-in user whose verified email domain is acme.com is admitted by that
policy. Do not include @ in allowed_domains.
Use share for exact principals:
Email entries admit the named signed-in user. account: entries admit the
named account principal.
For a protected route with no allowed_domains and no share entries, the
route remains auth-required and admits signed-in users by default. Add at least
one domain or share principal when the app should be limited to a customer
domain or named principals.
When onboarding includes an account ref, Forge can seed that account:
principal into the app's initial sharing policy so the owner can reach the first
protected deploy. A subsequent map access apply replaces exposure,
allowed_domains, and share with the reviewed policy.
Plan and Apply¶
Plan locally before writing anything:
Text output is a summary:
would apply access for app:inventory-portal:
exposure: protected
allowed_domains: acme.com
share: admin@example.com, account:acme-main
Use JSON when reviewing the exact request body:
map access plan is local. It reads the file, applies CLI overrides, validates
the access-policy exposure, and prints the request body that apply would
send. It does not call the control plane and it does not prove that a served
route already carries the policy.
Apply the reviewed policy:
The control-plane response includes the resolved values and the effective boundary:
{
"status": "applied",
"app_ref": "app:inventory-portal",
"exposure": "protected",
"allowed_domains": ["acme.com"],
"share": ["admin@example.com", "account:acme-main"],
"effective": "next-deploy-route-push"
}
CLI flags override the file:
map access apply \
--file ./access.production.yaml \
--app-ref app:inventory-portal \
--tenant-ref tenant:acme \
--account-ref account:acme-main \
--exposure protected
Applying the policy replaces the app's recorded exposure, allowed_domains,
and share values. It does not change the deployed manifest exposure. A served
protected route receives the reviewed sharing policy on route push or reconcile.
When a Change Takes Effect¶
| Change | Effective when | What to verify |
|---|---|---|
map access plan |
Immediately, locally. | The resolved policy matches the review intent. |
map access apply |
Forge records the new policy and returns effective: next-deploy-route-push. |
Apply response and saved access.yaml match the reviewed policy. |
| Served protected sharing | The served route is pushed or reconciled after apply. | Allowed and denied requests against the hostname users open. |
| Manifest exposure | A reviewed manifest commit is deployed and the relevant route is updated or published. | map versions <app>, deploy status, and anonymous/signed-in HTTP results. |
Apply a Protected Access Change¶
Use this sequence for customer-visible protected access changes:
- Record the current route state with
map versions <app>. - Confirm the version serving the hostname has non-public exposure.
- Edit and review
access.yaml. - Run
map access plan --repo-dir .. - Run
map access apply --repo-dir .. - Update or publish the route that should carry the policy.
- Verify one allowed signed-in user or domain.
- Verify one denied signed-in user or anonymous request.
- Keep
access.yaml, plan output, apply output,map versions <app>, and allowed/denied results with the release or change record.
If the hostname does not reflect the reviewed sharing policy after apply, inspect which routing record users are reaching and move that route again. The policy write is immediate, but a served route entry receives the policy on route push or reconcile.
Change App Route Exposure¶
Route exposure is a manifest change.
- Set
public_edge.exposuretoprotected,none,public-redirect,public, or omit the exposure. - Review
access.yamlif the app should be restricted to specific users or domains. - Run
map access planandmap access applywhen protected sharing is involved. - Deploy the reviewed commit.
- Publish the reviewed version when the clean public URL should move.
- Verify the route behavior for the selected exposure: anonymous success for
public, or anonymous denial plus allowed signed-in access for protected route shapes.
Revoking Access¶
Choose the control that matches the access you need to revoke.
| Goal | Customer action |
|---|---|
| Stop Forge from reading a repo | Remove the repo from the Forge GitHub App grant, or uninstall the App from the GitHub account or organization. |
| Stop custom-CI deploy requests | Disable or remove .github/workflows/map-deploy.yml in the repo. |
| Stop the clean public URL from moving to new deploys | Do not publish a new version; map publish is the pointer movement. |
| Change who can reach a protected app | Update reviewed access.yaml, run map access plan, run map access apply, and move the served route that should carry the policy. |
Removing a GitHub App grant stops new source fetches. It does not prove an existing served route has stopped serving. Disabling custom CI stops that workflow from requesting deploys; it does not remove built-in webhooks or a served route. Changing protected sharing controls who can reach a protected route; it does not retire the route entry.
Route retirement is outside these customer-owned access controls. Use the route-retirement packet in Support and escalation when a served route itself must stop serving.
Verification Checklist¶
For protected apps:
- deployed
mithran.yamlhas non-public exposure or nopublic_edge.exposure; access.yamlcontains the intendedallowed_domainsandsharevalues;map access planoutput matches the reviewed policy;map access applysucceeds and returnseffective: next-deploy-route-push;- the route users reach has been updated or reconciled after apply;
- an allowed user or domain can reach the app;
- a denied user or anonymous request is denied before reaching the app runtime.
Troubleshooting¶
| Symptom | Check |
|---|---|
map access plan fails |
Check access.yaml for unknown fields, missing app_ref, or an unsupported exposure value. |
| Apply succeeds but access does not change | Confirm the served route was pushed or reconciled after apply, and confirm users are opening the route you changed. |
| Protected app denies an expected user | Confirm the user is signed in and the reviewed policy includes the user's domain, email, or account: principal. |
| Manifest review rejects exposure | Confirm public_edge.exposure is protected, none, public-redirect, or public. |
unknown_hostname |
Confirm the clean public URL has been published, route status is ready, and the hostname matches the app ref shown by map versions <app>. See route propagation. |