104 lines
4 KiB
Rego
104 lines
4 KiB
Rego
package scobleclaw.lib
|
|
|
|
# lib.rego — shared helpers + base deny rules for all agents.
|
|
#
|
|
# Per scobleclaw#42 refactor: this file lives at `package scobleclaw.lib`
|
|
# (was `package scobleclaw` in the package-root layout). Per-agent policy
|
|
# files (main_agent, demo_concierge, demo_watchdog) `import data.scobleclaw.lib`
|
|
# to pull in the constants, helpers, and base deny rules. Each per-agent
|
|
# package re-aggregates `base_deny` into its own `deny` set so the API
|
|
# surface is `data.scobleclaw.<agent>.deny` (one path per agent).
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Input shape (L0 contract)
|
|
# ---------------------------------------------------------------------------
|
|
# {
|
|
# "agent_id": "main-agent" | "demo-concierge" | "demo-watchdog",
|
|
# "kamelet_ref": "kamelet:source/forgejo-search" | ...,
|
|
# "params": { ... agent-supplied, validated against the kamelet schema },
|
|
# "request_id": "uuid",
|
|
# "timestamp": "2026-06-08T16:00:00Z"
|
|
# }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Constants — the hard organizational boundaries
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Internal email domain. Anything else is treated as external.
|
|
internal_email_domain = "scoble.me"
|
|
|
|
# Kamelets that move money or have irreversible side-effects. They are
|
|
# ALWAYS denied at L0. L1/L2 cannot override an L0 deny.
|
|
payment_kamelets = {
|
|
"kamelet:source/stripe-charge",
|
|
"kamelet:source/paypal-transfer",
|
|
"kamelet:source/ach-debit",
|
|
"kamelet:sink/wire-transfer",
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# True if `domain` is an external email domain (i.e. NOT internal).
|
|
is_external_email(domain) {
|
|
domain != internal_email_domain
|
|
domain != ""
|
|
}
|
|
|
|
# True if `ref` is a payment kamelet. Uses v0/v1-portable set index
|
|
# membership (collection[key]) rather than the `in` operator.
|
|
is_payment_kamelet(ref) {
|
|
payment_kamelets[ref]
|
|
}
|
|
|
|
# True if the params dict looks like a Forgejo write targeting a repo
|
|
# other than the allowed owner/repo. We pattern-match on the well-known
|
|
# kamelet param names rather than hardcoding the kamelet_ref, so this
|
|
# rule catches both `forgejo-create-issue` and any future Forgejo write
|
|
# kamelet.
|
|
is_forgejo_write_outside_allowed(params) {
|
|
params.owner != "marcus"
|
|
}
|
|
|
|
is_forgejo_write_outside_allowed(params) {
|
|
params.owner == "marcus"
|
|
params.repo != "engineering"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Base deny rules — every agent inherits these by importing this package
|
|
# and re-emitting the `base_deny` set into its own `deny` set.
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
# Why `base_deny` instead of just `deny`? With per-agent packages, a
|
|
# `deny` set in this lib package would be exposed at
|
|
# `data.scobleclaw.lib.deny` (one shared path) and would force every
|
|
# caller to merge it with the per-agent set. By naming it `base_deny`
|
|
# here and letting each agent package own the final `deny` set, the
|
|
# API stays clean: one `deny` path per agent, and the lib package
|
|
# contributes only shared rules.
|
|
|
|
# Rule 1: no external email. Any kamelet whose params include a `to` field
|
|
# with an external email domain is denied.
|
|
base_deny[reason] {
|
|
input.kamelet_ref != ""
|
|
input.params.to != ""
|
|
is_external_email(input.params.domain)
|
|
reason := sprintf("external email not permitted (domain=%v, to=%v)", [input.params.domain, input.params.to])
|
|
}
|
|
|
|
# Rule 2: no Forgejo writes outside marcus/engineering.
|
|
base_deny[reason] {
|
|
is_forgejo_write_outside_allowed(input.params)
|
|
reason := sprintf(
|
|
"forgejo writes restricted to marcus/engineering (got owner=%v repo=%v)",
|
|
[input.params.owner, input.params.repo],
|
|
)
|
|
}
|
|
|
|
# Rule 3: no payment kamelets. Period.
|
|
base_deny[reason] {
|
|
is_payment_kamelet(input.kamelet_ref)
|
|
reason := sprintf("payment kamelet blocked at L0: %v", [input.kamelet_ref])
|
|
}
|