kosa8 docs

Guides / Policy reference

Policy reference

kosa8 reads two documents an organisation signs. The organisation policy sets rules for the machine: which registries, which network, which models, who may approve. The MCP policy sets rules for AI tools: which tool servers, which tools, with which arguments. This page lists every field of both, where each one is enforced, and what you see when it refuses.

If you have not installed a policy before, start with Company rules, which walks through it. This page is the reference you return to.

Organisation policyMCP policy
Commandskosa8 policykosa8 mcp-policy
Installed at~/.kosa8/policy.json~/.kosa8/mcp-policy.json
Signed withThe same organisation key. Its public half is pinned at ~/.kosa8/policy-trust.pub.
DecidesImages, network defaults, approvals, audit, models, snapshots, agent identityTool servers, tools, arguments, tool definitions

Part A. The organisation policy

A JSON object. Every field except name is optional, and a field left out restricts nothing. "Enforced" names the place in kosa8 that can refuse.

FieldTypeWhat it doesWhere it is enforcedWhat you see
namestringThe policy's name. It appears in every refusal and in the audit log.Nowhere on its own.—
allowed_registrieslist of stringsThe only registry hosts images may come from. An image with no registry in its name counts as docker.io. Empty means any.Wherever the service fetches an image: kosa8 pull, a sandbox pull, the pull a run does for a missing image, and the Docker-compatible API.policy "example-corp" forbids registry "quay.io" (allowed: docker.io, ghcr.io)
default_egressstringThe network rule for a sandbox created without one: deny or allow:a.com,b.com. Empty means open.When a sandbox is created. It is a default, not a ceiling: a request that names its own rule keeps it.Nothing is refused. The sandbox's audit entry carries egress=deny (org default).
require_sandboxtrue or falseContainers must run in a sandbox of their own, not on the shared engine.kosa8 run, interactive runs, and container create on the Docker-compatible API.policy "example-corp" requires containers to run in a sandbox, and this would run on the shared engine; use `kosa8 sandbox create` and `kosa8 sandbox run`
allowed_mcplist of stringsThe only images a third-party tool server may run from, matched as whole names. Empty means any.kosa8 mcp add, and again when the gateway starts the server.policy "example-corp" forbids MCP image "IMAGE" (allowed: …)
require_fipstrue or falsekosa8 must be a build that uses a FIPS 140-3 validated cryptographic module.Once, when the service starts. It refuses to start otherwise.kosa8d: policy "example-corp" requires a FIPS 140-3 validated cryptographic module, and this binary was not built with one (rebuild with GOFIPS140=certified, or install a FIPS release)
approverslist of approversThe people whose signed approvals count. Listing anyone turns signed approvals on.In front of every route of the service, so the CLI, AI tools and the Docker-compatible API are covered alike.A refusal that names the action, says it needs signed approval before it can run, lists who can sign, and gives the commands to request it.
approval_ruleslist of rulesWhich actions need signed approval, and from how many people. With approvers and no rules, the rule is one approver for escalating actions.Same place. Every rule that matches an action must be met.As above.
allow_unlisted_requesterstrue or falseLets someone who is not an approver ask for an approval. Off by default, so a listed approver cannot request under a throwaway key and approve under their own.When an approval request is recorded.The request is refused.
require_audit_forwardingtrue or falseAudit entries must leave the machine: at least one sink must be configured in ~/.kosa8/audit-config.json.Once, when the service starts.kosa8d: policy "example-corp" requires audit records to be forwarded off this machine, and no sink is configured: add one to ~/.kosa8/audit-config.json …
min_audit_retention_dayswhole numberThe shortest time entries stay in the live log. Local settings can keep them longer, never shorter.When retention runs, hourly in the service and in kosa8 audit prune.Younger entries are kept. The prune report says org policy "example-corp" keeps entries for at least 400 days.
labelsobjectThe classification levels a sandbox or snapshot may carry and what each permits: destinations, shared folders, widest network. Left out, the levels are UNCLASSIFIED and CUI.When a sandbox is created, started, relabelled, or a snapshot leaves the machine.The request is refused with the label rule it broke. kosa8 label ls shows what is allowed.
allowed_modelslist of modelsThe only model files the local model runtime may load, each named by the SHA-256 of its file. Empty means any.kosa8 model serve. The built-in assistant also refuses a hosted provider, whose model cannot be pinned.policy "example-corp" does not approve model NAME: its weights hash to sha256:…, which is not on the approved model list …
model_recordingstringWhat the model gateway records about each request: off (the default), digests, or content (digests plus the text, encrypted).The model gateway, on every request it serves.Nothing is refused. Entries appear in the audit log.
require_snapshot_encryptiontrue or falseEvery snapshot is encrypted as soon as it is taken, and may leave or enter the machine only encrypted.The service at start (it needs a usable key), on every snapshot, and on push and pull.policy "example-corp" requires snapshots to be encrypted, and this transfer would send it in plaintext; push it with --encrypt …
snapshot_key_scopestringThe key scope incremental transfers must use: snapshot (one key per snapshot) or lineage (one per sandbox, which also permits snapshot). Empty means snapshot when encryption is required.Incremental snapshot transfers.policy "example-corp" requires each snapshot under its own key, so that deleting it crypto-shreds every copy; use --key-scope snapshot
require_agent_identitytrue or falseA request that says it comes from an agent must carry a token made with kosa8 agent token create.The service, on every request marked as an agent's; and kosa8 mcp serve, which refuses to start without a token.the org policy requires agents to identify themselves, and this request carries no agent token

An approver

FieldTypeMeaning
namestringThe person's name. Required.
emailstringWhat makes two keys the same person. Someone listed twice with one email counts once.
algorithmstringed25519 (the default), ecdsa-p256-sha256 or ecdsa-p384-sha384.
public_keystringTheir public key, from their kosa8 identity show. A key may be listed only once.
roleslist of stringsFree-form roles a rule can require, such as security.

An approval rule

FieldTypeMeaning
matchstringA class (escalating, destructive, contained, read-only), one action kind such as network-create, or *.
requiredwhole numberHow many different people must approve. Zero reads as one. The requester never counts.
roleslist of stringsAn approver must hold at least one of these to count. Empty means any approver.

A policy is checked before it is signed. A rule nobody listed could satisfy is refused then, with the count: approval rule for "network-create" needs 2 distinct approvers with role security, but the profile lists only 1 who qualify — add approvers or lower required.

An approved model

FieldTypeMeaning
namestringFor people reading the policy. Never matched: a file renamed to an approved name is still the wrong file.
digeststringsha256: and 64 lower-case hex digits of the model file. Required.
signerstringWhen set, the model must also carry a signature by this public key, made with kosa8 model sign.

A complete organisation policy

{
  "name": "example-corp",
  "allowed_registries": ["docker.io", "ghcr.io"],
  "default_egress": "deny",
  "require_sandbox": true,
  "allowed_mcp": ["ghcr.io/example-corp/github-mcp:1.4.2"],
  "require_fips": false,
  "approvers": [
    {"name": "Asha Rao", "email": "[email protected]",
     "public_key": "AoNDqYXXYG2QK5C0P+qk+xFn0a6fxXQlp7dairJCqzg=", "roles": ["security"]},
    {"name": "Ben Ortiz", "email": "[email protected]",
     "public_key": "PjsMrSBl7Xa0U/NmpPSbq/7sZEz777lQUbrjEzCHenE=", "roles": ["security", "release-manager"]}
  ],
  "approval_rules": [
    {"match": "escalating", "required": 1},
    {"match": "network-create", "required": 2, "roles": ["security"]}
  ],
  "allow_unlisted_requesters": false,
  "require_audit_forwarding": true,
  "min_audit_retention_days": 400,
  "labels": {
    "levels": [
      {"name": "UNCLASSIFIED"},
      {"name": "CUI", "max_egress": "deny",
       "mount_paths": ["/Users/Shared/cui"],
       "destinations": ["remote:r2.example.com/cui-snapshots"]}
    ],
    "downgrade_approvals": 2
  },
  "allowed_models": [
    {"name": "qwen2.5-coder-7b-q4",
     "digest": "sha256:9f2c1e0b7a6d5c4f3e2d1c0b9a8f7e6d5c4b3a2918070605f4e3d2c1b0a99887"}
  ],
  "model_recording": "digests",
  "require_snapshot_encryption": true,
  "snapshot_key_scope": "snapshot",
  "require_agent_identity": true
}

kosa8 policy init prints a much shorter starting point:

$ kosa8 policy init
{
  "name": "example-corp",
  "allowed_registries": [
    "docker.io",
    "ghcr.io"
  ],
  "default_egress": "deny",
  "require_sandbox": true
}

Part B. The MCP policy

MCP (Model Context Protocol) is how an AI client calls tools. A tool server is a program that offers tools; kosa8's gateway runs each third-party server in a sandbox and passes calls through. The MCP policy says which of those calls may happen.

How a call is decided

The order in which the MCP policy decides a tool call Five checks left to right. First the scope: the agent's own, or the policy's default. Then the server, which must be listed by exact name. Then deny_tools, where a match denies. Then allow_tools, where no match denies. Then the argument rules, where deny beats ask beats allow. scope the agent's, or thepolicy's default server listed by exactname, or denied deny_tools a match denies,whatever allows allow_tools no match denies;empty allows all rules decide by argument:deny, ask, allow
A call has to pass every box. Nothing later can re-open what an earlier one closed.
  1. The scope is the one in the agent's verified identity, or the policy's default_scope. The caller cannot pick it.
  2. The server must be listed in that scope by its exact name. A server nobody listed is denied.
  3. A tool matching deny_tools is denied, even if an allow pattern also matches.
  4. With allow_tools set, a tool matching none of them is denied. With it empty, every remaining tool is allowed.
  5. Argument rules then look at what the call asks for. Among the rules that match, deny beats ask beats allow.

The policy

FieldTypeMeaning
namestringRequired. Audit entries say which policy decided.
default_scopestringThe scope used when the caller has none. It must be one of the scopes below. It lives in the signed document so that nobody governed by the policy can choose a wider scope for themselves.
scopeslist of scopesThe named sets of rules: a team, a job, a risk tier.
rules_modestringenforce (the default) or log. In log mode an argument rule that would deny or ask is recorded as such and the call goes ahead, so a rule set can be tried on real traffic. It softens rules only: tool lists and pins still deny.
classifierobjectAn optional local scorer for ask decisions. Left out, every ask goes to a person.

A scope

FieldTypeMeaning
namestringRequired, and unique in the policy.
descriptionstringWhy the scope exists. Shown by kosa8 mcp-policy scopes.
serverslist of server rulesThe approved servers. A scope with none denies every server.

A server rule

FieldTypeMeaning
serverstringThe server's name as registered with kosa8 mcp add, matched exactly. * and ? are refused when the policy is signed.
allow_toolslist of stringsTool-name patterns that may be called. * stands for any run of characters. Empty means every tool the server offers.
deny_toolslist of stringsTool-name patterns that may never be called. Deny beats allow.
ruleslist of argument rulesDecide a call by its arguments, once the tool lists have admitted the tool.
pinslist of pinsThe exact tool definitions the organisation reviewed. When any are listed for a server, a tool that is not listed, or whose definition has changed, is withheld, and nothing can be approved locally.
Wildcards for tools, never for servers

read_* approves a family of tools without re-signing the policy each time a server adds one. A wildcard on the server name would let one *, or a plausible typo, approve servers nobody reviewed. Tool patterns are case-sensitive: read_file does not approve Read_File.

An argument rule

FieldTypeMeaning
idstringNames the rule in refusals and audit entries. Optional, but unique when set.
toolslist of stringsTool-name patterns the rule covers. Required; use * for every tool.
whenlist of conditionsAll must hold. Empty means every call of the matched tools. At most 16.
effectstringallow, deny or ask. Ask holds the call until a person approves that exact call.
reasonstringShown to whoever the rule stopped. Write it as the next step.

A condition

FieldTypeMeaning
argstringWhich argument, as a JSON Pointer: /repo, /files/0/path. Empty is the whole arguments object.
opstringThe test. One of exists, absent, eq, ne, in, not_in, prefix, suffix, contains, glob, matches, their not_ forms (not_prefix, not_suffix, not_contains, not_glob, not_matches), lt, le, gt, ge, len_le, len_ge, type.
valuedepends on opWhat to compare with. Comparison is typed: the string "1" never equals the number 1.

An argument of a type the test cannot handle counts as matching in a deny or ask rule and as not matching in an allow rule, so odd input can only make the outcome stricter. Each operator, with worked examples, is in Rules for tool calls.

A pin

FieldTypeMeaning
toolstringOne exact tool name. No wildcards. Each tool may be pinned once.
digeststringsha256: and 64 hex digits, copied from kosa8 mcp pins show SERVER. It covers the tool's name, description and schemas.

The classifier

FieldTypeMeaning
scorerstringlocal-model, the only kind. A policy naming any other is refused instead of running without one.
endpointstringThe address of a model served on this machine. It must be http:// to localhost or a loopback address: tool arguments do not leave the machine.
modelstringWhich model to ask, by the name the local model server knows it. Required: a classifier without one is refused when the policy is signed.
thresholdnumberHow confident an "allow" answer must be to act on: above 0.5 and below 1. There is no default.
timeout_mswhole numberThe longest one question may take, up to 30000. Zero means the scorer's default of two seconds.

The scorer is consulted only for a call a rule answered ask. It can spare a person the question; it has no answer that means deny, and anything short of a confident allow leaves the question with the person.

kosa8's own tools: the server named kosa8

The name kosa8 is reserved. In a policy it stands for kosa8's own tools, such as sandbox_create and container_run. A scope that lists it governs those tools with the same lists and rules as any other server. A scope that does not list it leaves them as they were, because a policy written before this existed said nothing about them. kosa8 mcp add refuses to register a third-party server under that name.

A complete MCP policy

{
  "name": "example-corp",
  "default_scope": "engineering",
  "scopes": [
    {
      "name": "engineering",
      "description": "application engineers",
      "servers": [
        {
          "server": "github",
          "allow_tools": ["list_*", "get_*", "search_*", "create_issue", "merge_*"],
          "deny_tools": ["*_delete", "delete_*"],
          "rules": [
            {"id": "only-acme", "tools": ["create_issue"], "effect": "deny",
             "when": [{"arg": "/repo", "op": "not_glob", "value": "acme/*"}],
             "reason": "issues go in acme repositories"},
            {"id": "merge-needs-a-person", "tools": ["merge_*"], "effect": "ask",
             "reason": "merging changes what ships"}
          ]
        },
        {
          "server": "kosa8",
          "deny_tools": ["sandbox_delete", "snapshot_rm"],
          "rules": [
            {"id": "open-network-needs-a-person", "tools": ["sandbox_create"], "effect": "ask",
             "when": [{"arg": "/egress", "op": "eq", "value": "any"}],
             "reason": "an open network is reviewed"}
          ]
        }
      ]
    },
    {
      "name": "contractors",
      "description": "read-only access to the docs server",
      "servers": [
        {
          "server": "docs",
          "allow_tools": ["search", "read_page"],
          "pins": [
            {"tool": "search",
             "digest": "sha256:3b1f0c9a7e5d2b4861f0a9c8e7d6b5a4938271605f4e3d2c1b0a9f8e7d6c5b4a"},
            {"tool": "read_page",
             "digest": "sha256:7b1f0c9a7e5d2b4861f0a9c8e7d6b5a4938271605f4e3d2c1b0a9f8e7d6c5b4a"}
          ]
        }
      ]
    }
  ],
  "rules_mode": "enforce",
  "classifier": {
    "scorer": "local-model",
    "endpoint": "http://127.0.0.1:7434",
    "model": "qwen",
    "threshold": 0.95,
    "timeout_ms": 2000
  }
}

What that policy answers, for an agent in the default scope:

CallAnswerWhy
github / list_issuesallowallow_tools "list_*"
github / create_issue with repo: acme/apiallowNo rule matched.
github / create_issue with repo: other/api, or no repodenyRule only-acme: issues go in acme repositories
github / merge_pull_requestaskRule merge-needs-a-person
github / delete_repodenydeny_tools "delete_*"
github / fork_repodenytool "fork_repo" is not in the allowlist for server "github" in scope "engineering" …
slack / anythingdenyMCP server "slack" is not approved for scope "engineering"; approved servers: github, kosa8 — ask your admin to add it to the org policy
kosa8 / sandbox_deletedenydeny_tools "sandbox_delete"
kosa8 / sandbox_create with egress: anyaskRule open-network-needs-a-person

An agent in contractors may call the two pinned docs tools and no other server. That scope does not list kosa8, so kosa8's own tools are not governed for it.

Part C. Signing, pinning, installing

The trust chain for a signed policy The organisation keeps a private signing key and uses it to sign the policy file. Each machine has the public half pinned in policy-trust.pub. The machine verifies the policy against the pinned key on every decision. If the signature matches, the policy is enforced. If the file was edited, deleted, or signed by another key, kosa8 fails closed and refuses. Your organisation Each machine signing key private half, kept by the org signs policy.json the rules and a signature policy-trust.pub public half, pinned once verify before the policy is used enforced the signature matches fails closed edited, deleted or signed by another key: refused
The pinned key is what makes a missing policy a refusal instead of a return to "no rules".
  1. The organisation makes one signing key and keeps the private half.
  2. It signs each policy with it. The signed file carries the rules, the public key and the signature.
  3. On each machine the public half is pinned, once.
  4. kosa8 verifies the installed policy against the pinned key before it uses it: the organisation policy each time a rule is consulted, the MCP policy each time a gateway starts.
  5. A policy that verifies is enforced.
  6. One that was edited, removed or signed by another key is not ignored: kosa8 refuses the action and says why.

Make the key

$ kosa8 policy keygen -o org.key
public:  kQ+JPoaWWs9JC1Nic/sRli1nJOAgQFAmmm+xtD0oEeo=
private key written to org.key (keep it secret)

-o writes the private key to that file and the public key to org.key.pub. Without it both are printed.

Write and sign both documents

$ kosa8 policy init > policy.json           # a template to edit
$ kosa8 policy sign org.key policy.json > policy.signed.json
$ kosa8 mcp-policy init > mcp.json
$ kosa8 mcp-policy sign org.key mcp.json > mcp.signed.json

Both sign commands check the document first and refuse one that could never work, with refusing to sign: and the reason. kosa8 policy init also takes --require-fips and --require-snapshot-encryption.

Pin the key, then install

$ kosa8 policy trust org.key.pub
trusted policy signer set
$ kosa8 policy install policy.signed.json
installed and activated policy "example-corp"
$ kosa8 mcp-policy install mcp.signed.json
installed MCP policy "example-corp" (2 scope(s))
  servers not listed in a scope are now denied

trust takes the key itself or a file containing it. Pin before you install. kosa8 mcp-policy install refuses otherwise:

no trusted policy signer is pinned, so any key would be accepted here —
pin your org's public key first with `kosa8 policy trust <PUBLIC_KEY>`

Check what is in force

$ kosa8 policy show                       # the organisation policy and its signer
$ kosa8 mcp-policy show                   # scopes, servers, allow and deny lists
$ kosa8 mcp-policy scopes                 # the scopes, with the default marked
$ kosa8 mcp-policy check github list_issues
$ kosa8 mcp-policy check --scope contractors github list_issues
$ kosa8 mcp-policy check --args '{"repo":"other/api"}' github create_issue
$ kosa8 mcp-policy events --denied        # the decisions, for a SIEM
CommandWhat it does
kosa8 policy keygenMakes a signing key pair. -o FILE writes it to files.
kosa8 policy initPrints a template organisation policy.
kosa8 policy sign KEYFILE PROFILE.jsonPrints the signed policy.
kosa8 policy trust PUBKEY_OR_FILEPins the signer on this machine, for both documents and for kits.
kosa8 policy install SIGNED.jsonVerifies against the pinned key and makes the policy active.
kosa8 policy showShows the active organisation policy. With none: no policy installed (everything allowed).
kosa8 mcp-policy initPrints a template MCP policy with two scopes.
kosa8 mcp-policy sign KEYFILE POLICY.jsonPrints the signed MCP policy. Use the same key.
kosa8 mcp-policy install SIGNED.jsonVerifies and activates it. Refuses when no key is pinned.
kosa8 mcp-policy showShows the active MCP policy, or why it cannot be used.
kosa8 mcp-policy scopesLists the scopes the policy defines.
kosa8 mcp-policy check SERVER TOOLAsks whether a scope may call a tool, and records the decision. --scope tries another scope; --actor says who is asking. Without --args only the tool lists are consulted; with --args JSON the server's argument rules are applied to those arguments as the gateway would. Exits non-zero on a denial.
kosa8 mcp-policy eventsExports the decisions. --format jsonl, cef or table; --denied; -n for the most recent few.

Making a key, signing, pinning and installing need the Governance subscription or a trial. Enforcing an installed policy does not.

Part D. When a policy is missing or wrong

Both documents fail closed, with one deliberate difference in the second row.

SituationOrganisation policyMCP policy
No key pinned, no policynothing restricted The machine is unconfigured.nothing restricted Every registered server is reachable.
Key pinned, policy file missingrefuses a trusted policy signer is pinned but no policy is installed (was it deleted?); re-install with `kosa8 policy install`denies every third-party server a trusted policy signer is pinned but no MCP policy is installed (was it deleted?); re-install it with `kosa8 mcp-policy install`
kosa8's own tools still work
Policy file editedrefuses installed policy failed verification: signature does not match profile (tampered or wrong key)denies everything, kosa8's own tools included. the installed MCP policy failed verification: signature does not match MCP policy (tampered or wrong key); re-install a policy signed by your org with `kosa8 mcp-policy install`
Policy signed by another keyrefuses installed policy failed verification: profile is signed by an untrusted keydenies everything … failed verification: MCP policy is signed by an untrusted key …

The organisation policy is read from disk each time a rule is consulted, so a change takes effect at once. A tool gateway (kosa8 mcp serve) reads the MCP policy once, when it starts, so that the rules do not change under a running agent: a newly installed, edited or deleted MCP policy applies from the next time the gateway starts.

"Refuses" for the organisation policy means each place that consults it stops, with its own words in front of the reason above:

WhereWhat it says
The service, at startkosa8d: org policy present but failed verification, refusing to start: …
Fetching an imageorg policy present but failed verification, refusing to pull: …
Running on the shared engineorg policy present but failed verification, refusing to run: …
Creating a sandboxorg policy present but failed verification, refusing to proceed: …
Starting tool serversorg policy present but failed verification, refusing to start MCP servers: …
An agent with no tokenorg policy present but failed verification, so an unidentified agent cannot be served: …
Why a missing MCP policy does not stop kosa8's own tools

An organisation can pin a key for its organisation policy and never write an MCP policy. Third-party servers are denied there. Treating that absence as a denial of sandbox_create and the rest would take kosa8 away from every such organisation. The cost is stated plainly: deleting mcp-policy.json lifts the rules it held for kosa8's own tools. A policy that is present and does not verify denies both. For actions that must not be self-authorised, use signed approvals (approvers above), which do not live in that file.

Part E. Checking a machine

kosa8 doctor --compliance checks this machine against the hardening baseline: whether a policy is installed and what it requires, among other controls. It reports; the fields above are what enforce. See the doctor reference page.

What this does not do

  • Enforcement is on the machine being governed. kosa8 checks the policy with code and files that belong to the user it constrains. Someone who can delete both the policy and the pinned key, ~/.kosa8/policy-trust.pub, returns the machine to "no rules". Provision that file out of band and write-protect it, for example owned by an administrator account.
  • With no key pinned, a signature proves little. kosa8 policy install then accepts a policy signed by any key, including one a developer made. Pin first.
  • No central distribution. There is no admin console, and no SSO, SAML or SCIM. A policy is a signed file you deliver to each machine yourself, and a new version reaches a machine when someone installs it there.
  • default_egress is a default. A sandbox created with an explicit network rule keeps that rule. To require review of a wider one, add an approval rule for escalating actions.
  • Tool rules see names and arguments, not meaning. A deny rule on /path does nothing about a server that also accepts /file, and nothing here reads the text a tool returns.
  • Deleting the MCP policy lifts its rules on kosa8's own tools, as described above.

Where to go next