Multicorn

Permissions

Define what your agent can access using scopes, and revoke permissions when they are no longer needed.

Scope format

Every permission in Shield is expressed as a scope string with the format permission:service:

code
read:gmail
write:calendar
execute:payments
publish:web
create:public_content

A scope has two parts:

  • Permission level — what kind of access: read, write, execute, publish, or create
  • Service — the target service: a lowercase identifier using letters, digits, hyphens, or underscores

Permission levels

LevelMeaningExample
readObserve data without modificationReading emails, viewing calendar events
writeCreate or modify dataCreating calendar events, updating contacts
executeTrigger side-effectsSending emails, making payments, posting messages
publishMake existing content publicly accessibleDeploying to GitHub Pages, publishing a blog post
createCreate new content that is immediately publicSending a tweet, posting to a forum, pushing a public commit

Service names

Service names must start with a lowercase letter and contain only lowercase letters, digits, hyphens, or underscores. Examples: gmail, calendar, my-service, slack_bot.

Defining scopes

Pass scope strings when requesting consent. Shield parses and validates them automatically:

typescript
const decision = await shield.requestConsent({
  agent: 'OpenClaw',
  scopes: ['read:gmail', 'write:calendar', 'execute:payments', 'read:slack'],
  spendLimit: 200,
})

High-risk scopes

Some scopes are classified as high-risk because they allow agents to publish content publicly on the internet. These scopes require explicit opt-in and default to OFF in the consent screen:

  • publish:web — Publishing content accessible on the open internet (e.g., deploying to GitHub Pages, publishing a blog post)
  • create:public_content — Creating content that is immediately public (e.g., sending a tweet, posting to a forum, pushing a public commit)

When an agent requests high-risk scopes, the consent screen displays a warning: "This agent is requesting permission to publish content publicly on the internet". These scopes are disabled by default, and users must explicitly enable them.

typescript
const decision = await shield.requestConsent({
  agent: 'BlogBot',
  scopes: ['publish:web', 'create:public_content'],
  spendLimit: 100,
})
// User will see a warning and must explicitly enable these scopes

If any scope string is malformed, requestConsent() throws a ScopeParseError with a descriptive message:

typescript
await shield.requestConsent({
  agent: 'OpenClaw',
  scopes: ['delete:gmail'], // "delete" is not a valid permission level
})
// ScopeParseError: Unknown permission level "delete" in scope string "delete:gmail".
// Valid permission levels are: read, write, execute, publish, create.

Checking granted scopes

After consent, use getGrantedScopes() to see what the user approved:

typescript
const scopes = shield.getGrantedScopes('OpenClaw')
// [
//   { service: "gmail", permissionLevel: "read" },
//   { service: "calendar", permissionLevel: "write" },
// ]

Each scope is a structured object with service and permissionLevel fields, not the raw string.

Revoking permissions

Revoke a specific permission at any time with revokeScope():

typescript
shield.revokeScope('OpenClaw', 'write:calendar')

After revocation, any logAction() call targeting the revoked service will throw an error. Revocation takes effect immediately.

You can verify the revocation:

typescript
shield.revokeScope('OpenClaw', 'write:calendar')

const scopes = shield.getGrantedScopes('OpenClaw')
// write:calendar is no longer in the list

await shield.logAction({
  agent: 'OpenClaw',
  service: 'calendar',
  action: 'create_event',
  status: 'approved',
})
// Error: Agent "OpenClaw" does not have permission to access "calendar".

Parsing scopes manually

If you need to parse or validate scope strings outside of requestConsent(), Shield exports utility functions:

typescript
import { parseScope, parseScopes, tryParseScope, isValidScopeString } from 'multicorn-shield'

// Parse a single scope (throws on invalid input)
const scope = parseScope('read:gmail')
// { service: "gmail", permissionLevel: "read" }

// Parse multiple scopes
const scopes = parseScopes(['read:gmail', 'write:calendar'])

// Parse without throwing
const result = tryParseScope('bad-scope')
if (result.success) {
  console.log(result.scope)
} else {
  console.error(result.error)
}

// Quick validation check
isValidScopeString('read:gmail') // true
isValidScopeString('delete:gmail') // false