Origins Extension

The full normative specification is at spec/hushspec-origins.md.

Overview

The Origins extension provides origin-aware policy projection. When an agent receives work from different sources - Slack channels, GitHub repositories, email threads, Discord servers - different security profiles can apply based on the source context.

Origin profiles narrow the base policy. They can only make rules more restrictive, never more permissive. The base policy remains the security floor.

Origins is declared under extensions.origins in a HushSpec document. When a conformant engine supports the origins extension, incoming requests are matched against origin profiles before rule evaluation begins. The matched profile's constraints are applied as additional restrictions on top of the base policy.

Schema

yaml
extensions:
  origins:
    default_behavior: <"deny"|"minimal_profile">  # OPTIONAL. Default: "deny".
    profiles:
      - id: <string>                     # REQUIRED. Unique profile identifier.
        match:                           # OPTIONAL. Matching criteria.
          provider: <provider>
          tenant_id: <string>
          space_id: <string>
          space_type: <space_type>
          visibility: <visibility>
          external_participants: <bool>
          tags: [<string>...]
          sensitivity: <string>
          actor_role: <string>
        posture: <state_name>            # OPTIONAL. Initial posture state override.
        tool_access:                     # OPTIONAL. Tool access overrides.
          allow: [<string>...]
          block: [<string>...]
          require_confirmation: [<string>...]
          default: <"allow"|"block">
          max_args_size: <integer>
        egress:                          # OPTIONAL. Egress overrides.
          allow: [<string>...]
          block: [<string>...]
          default: <"allow"|"block">
        data:                            # OPTIONAL. Data handling controls.
          allow_external_sharing: <bool>
          redact_before_send: <bool>
          block_sensitive_outputs: <bool>
        budgets:                         # OPTIONAL. Budget overrides.
          tool_calls: <integer>
          egress_calls: <integer>
          shell_commands: <integer>
        bridge:                          # OPTIONAL. Cross-origin controls.
          allow_cross_origin: <bool>
          allowed_targets:
            - provider: <provider>
              space_type: <space_type>
              tags: [<string>...]
              visibility: <visibility>
          require_approval: <bool>
        explanation: <string>            # OPTIONAL. Why this profile exists.

Field Reference

Top-Level Fields

FieldTypeDefaultDescription
default_behaviorstring"deny"What happens when no profile matches. "deny" rejects entirely (fail-closed). "minimal_profile" proceeds under base policy with no origin-specific extensions.
profilesarray-Array of origin profile objects, each with a unique id.

Match Object Fields

FieldTypeDescription
providerstringSource provider (e.g. slack, github).
tenant_idstringTenant or workspace identifier.
space_idstringSpecific channel, room, or repo ID. Highest-priority match.
space_typestringType of space (e.g. channel, pull_request).
visibilitystringVisibility level (e.g. private, public).
external_participantsbooleanWhether external users are present in the space.
tagsarray of stringAll tags must match (AND semantics).
sensitivitystringSensitivity classification label.
actor_rolestringRole of the requesting actor.

Profile Fields

FieldTypeDescription
idstringREQUIRED. Unique profile identifier.
matchobjectOPTIONAL. Matching criteria. Empty or absent match acts as a default profile.
posturestringOPTIONAL. Initial posture state override. Must reference a state in extensions.posture.states.
tool_accessobjectOPTIONAL. Tool access overrides (allow, block, require_confirmation, default, max_args_size).
egressobjectOPTIONAL. Egress overrides (allow, block, default).
dataobjectOPTIONAL. Data handling controls.
budgetsobjectOPTIONAL. Budget overrides (tool_calls, egress_calls, shell_commands).
bridgeobjectOPTIONAL. Cross-origin data flow controls.
explanationstringOPTIONAL. Human-readable reason for why this profile exists.

Standard Providers

ProviderDescription
slackSlack workspace.
teamsMicrosoft Teams.
githubGitHub (issues, PRs, discussions).
jiraAtlassian Jira.
emailEmail (any provider).
discordDiscord server.
webhookGeneric webhook source.
customEngine-defined provider.

Engines may support additional providers as strings. Unknown providers should not cause document rejection.

Space Types

Space TypeDescription
channelChat channel (Slack, Teams, Discord).
groupGroup chat or group DM.
dmDirect message.
threadThreaded conversation.
issueIssue tracker entry.
ticketSupport or service ticket.
pull_requestPull or merge request.
email_threadEmail conversation thread.

Visibility Levels

VisibilityDescription
privateVisible only to invited members.
internalVisible within the organization.
publicVisible to anyone.
external_sharedShared channel with external participants.

Match Priority

When an incoming request carries origin context, the engine determines which profile applies using this deterministic priority order:

  1. Exact space_id match (highest priority). If a profile's match.space_id equals the request's space ID, that profile is selected. If multiple profiles match by space_id, the first in document order wins.
  2. Most specific match by field count. Among remaining profiles, the one with the greatest number of matching match fields is selected. Each non-null match field that equals the corresponding request field counts as one match point. tags counts as one match point only if all tags in the profile are present in the request.
  3. Provider-only match. A profile matching only on provider is less specific than one matching provider + space_type.
  4. Default profile (empty match). A profile with an empty or absent match object matches all requests at the lowest specificity. At most one default profile should exist.
  5. default_behavior fallback. If no profile matches at all, the default_behavior field applies.

In case of a tie in match specificity (same number of matching fields, no space_id match), the first profile in document order wins.

Composition Rules

Origin profiles narrow the base policy. The most restrictive rule wins at every level.

Tool Access Composition

ElementComposition Rule
AllowlistsIntersection of base and origin profile. A tool must appear in both to be allowed.
BlocklistsUnion of base and origin profile. A tool blocked by either is blocked.
Require confirmationUnion of both require_confirmation lists.
DefaultIf either the base or origin specifies "block", the effective default is "block".
Max args sizeThe smaller of the two values applies, if both are specified.

Egress Composition

ElementComposition Rule
AllowlistsIntersection of base and origin profile.
BlocklistsUnion of base and origin profile.
DefaultIf either specifies "block", the effective default is "block".

Budget Composition

When both the base posture and the origin profile specify budgets for the same key, the smaller value applies. Origin budgets cannot increase base budgets.

Posture Composition

If an origin profile specifies a posture state, it overrides the base posture initial state for requests from that origin. The referenced state must exist in the posture extension's states map.

Data Policy

The data object controls how content is handled when flowing through or out of the origin context.

FieldTypeDefaultDescription
allow_external_sharingbooleanfalseWhether content may be shared outside the origin context.
redact_before_sendbooleanfalseWhether sensitive content must be redacted before output.
block_sensitive_outputsbooleanfalseWhether outputs containing sensitive patterns are blocked entirely.

Data policy fields default to false (restrictive). The detection of "sensitive content" for redact_before_send and block_sensitive_outputs is governed by the core rules.secret_patterns configuration and any active detection extension. Engines must document their redaction strategy.

Bridge Policy

The bridge object controls whether and how data may flow between origin contexts.

FieldTypeDefaultDescription
allow_cross_originbooleanfalseWhether cross-origin data flow is permitted.
allowed_targetsarray of BridgeTarget[]Specific targets permitted for cross-origin flow.
require_approvalbooleanfalseWhether cross-origin flow requires user/operator approval.

Each entry in allowed_targets specifies a permitted destination with optional fields: provider, space_type, tags, visibility. A bridge target matches if all specified fields match; absent fields are wildcards.

Bridge Semantics

  • When allow_cross_origin is false, no data from this origin context may flow to another origin context.
  • When true, data may flow only to destinations matching an entry in allowed_targets.
  • If allowed_targets is empty and allow_cross_origin is true, data may flow to any origin (no target restriction).
  • If require_approval is true, all cross-origin flows require user/operator approval before proceeding.

Examples

Slack Workspace with Public/Private Channel Profiles

yaml
extensions:
  origins:
    default_behavior: "deny"
    profiles:
      - id: "slack-private"
        match:
          provider: slack
          space_type: channel
          visibility: private
        tool_access:
          allow: ["read_file", "write_file", "search", "deploy"]
        data:
          allow_external_sharing: false
        explanation: "Full access in private channels"

      - id: "slack-public"
        match:
          provider: slack
          space_type: channel
          visibility: public
        tool_access:
          allow: ["read_file", "search"]
          block: ["deploy", "write_file"]
        data:
          allow_external_sharing: false
          redact_before_send: true
        explanation: "Read-only with redaction in public channels"

      - id: "slack-shared"
        match:
          provider: slack
          external_participants: true
        tool_access:
          allow: ["search"]
        data:
          redact_before_send: true
          block_sensitive_outputs: true
        bridge:
          allow_cross_origin: false
        explanation: "Minimal access in shared channels with external users"

GitHub-Aware Profile for Code Review Bots

yaml
extensions:
  origins:
    default_behavior: "deny"
    profiles:
      - id: "github-pr"
        match:
          provider: github
          space_type: pull_request
        posture: "standard"
        tool_access:
          allow: ["read_file", "write_file", "search"]
          block: ["deploy"]
        data:
          allow_external_sharing: false
        budgets:
          tool_calls: 100
        explanation: "Code review context - read/write files, no deploy"

      - id: "github-issue"
        match:
          provider: github
          space_type: issue
        posture: "restricted"
        tool_access:
          allow: ["read_file", "search"]
        budgets:
          tool_calls: 30
        explanation: "Issue triage - read-only access"

Multi-Provider Setup with Bridge Controls

yaml
extensions:
  origins:
    default_behavior: "deny"
    profiles:
      - id: "eng-private"
        match:
          provider: slack
          space_type: channel
          visibility: private
          tags: ["engineering"]
        posture: "standard"
        tool_access:
          allow: ["read_file", "write_file", "search", "deploy"]
        egress:
          allow: ["api.openai.com", "**.googleapis.com"]
        data:
          allow_external_sharing: false
          redact_before_send: false
        bridge:
          allow_cross_origin: true
          allowed_targets:
            - provider: github
              space_type: pull_request
          require_approval: false
        explanation: "Full access for private engineering channels"

      - id: "shared-channel"
        match:
          provider: slack
          external_participants: true
        posture: "restricted"
        tool_access:
          allow: ["read_file", "search"]
          block: ["deploy"]
        egress:
          allow: ["api.openai.com"]
        data:
          allow_external_sharing: false
          redact_before_send: true
          block_sensitive_outputs: true
        budgets:
          tool_calls: 20
          egress_calls: 10
        bridge:
          allow_cross_origin: false
        explanation: "Restricted access for shared channels with external participants"

      - id: "github-pr"
        match:
          provider: github
          space_type: pull_request
        posture: "standard"
        tool_access:
          allow: ["read_file", "write_file", "search"]
        data:
          allow_external_sharing: false
        explanation: "Code review context, no deploy"

Merge Rules

When a child document extends a base document containing origins configuration, the following merge rules apply under deep_merge strategy:

ElementMerge Behavior
ProfilesChild profiles override base profiles by id. If a child defines a profile with the same id as a base profile, the child's profile entirely replaces the base's. New child profiles (with IDs not in the base) are appended. Base profiles whose IDs are not in the child are preserved.
Default behaviorIf the child defines default_behavior, it overrides the base's value. Otherwise the base value is preserved.

Under replace strategy, the child's origins object entirely replaces the base's.