security & data transfer
security claim and scope
The CLI makes Castia control safer and more reviewable by placing the same authenticated, Organization-bound Operator API in front of humans, agents, and partner gateways. A CLI is not a security boundary by itself. Security depends on the caller, configured origin, credential handling, Operator authorization, Castia services, and any downstream provider.
This document covers the Operator API, CLI, human and service credentials, approval adapter, Microsoft/partner adapter preview, prompts and model input, external-provider uncertainty, and the package/update chain. It describes the implemented boundary and known residual risks; it is not a compliance certification.
The present safety limits are important:
campaign.launch@v1 binds exact human approval but starts no delivery. It returns deliveryStarted: false and sends zero messages.
Generic operation.retry@v1 is not advertised and the CLI's reserved retry grammar fails closed.
The Microsoft Copilot adapter is a contract preview, not a secure partner deployment. Entra identity, on-behalf-of (OBO) exchange, tenant policy, and deployment controls remain prerequisites.
campaign.audience.create@v1 and campaign.run.create@v1 create only pinned audiences and draft-only runs. They derive authority again before writing and never start delivery.
trust boundaries and protected assets
The boundaries are: secret store to caller process; package/update source to executable; caller or partner/model environment to the network; TLS termination to Operator authentication; authentication to capability policy; policy to durable ledger; and Castia services to external providers.
Protected assets include:
human and service credentials, PKCE verifiers, Organization bindings, and delegated-user identity;
tenant business data, Company/contact personal data, campaign content, playbook evidence, and provider configuration;
exact human approvals, scope fingerprints, idempotency keys, operation history, and audit attribution;
prompt/model inputs and tool outputs that may carry confidential or adversarial text;
CLI package integrity, trusted origins, local profiles, and security-relevant configuration.
implemented boundary and credential controls
The implemented client and server boundary provides:
HTTPS-only origins. HTTP is allowed only for 127.0.0.1 tests when CASTIA_ALLOW_INSECURE_LOOPBACK=1 is set.
No HTTP redirects; the caller must name the final trusted origin.
Bearer credentials sent in headers, never in URLs.
Explicit X-Castia-Organization binding, revalidated against the credential on every request.
Human membership/workspace revalidation and service-token role, scope, expiry, revocation, and delegated-user revalidation.
JSON input capped at 64 KiB, ordinary responses capped at 1 MiB, streams capped at 16 MiB, and NDJSON lines capped at 1 MiB.
Versioned response envelopes and bounded, allowlisted safe projections.
Token/secret redaction from human, JSON, JSONL, error, and hostile-server output.
Exact revision, scope fingerprint, idempotency, and durable operation receipts for mutations.
Human-only approval actions. A service identity cannot manufacture human authority.
| Environment | Supported credential storage |
|---|---|
| macOS | Login Keychain through /usr/bin/security |
| Linux desktop | Secret Service through secret-tool |
| Windows | Current-user DPAPI through non-interactive PowerShell |
| Headless agent/CI | CASTIA_TOKEN injected for one process by a trusted secret manager |
The local config stores only profile names, Castia origins, and Organization IDs at mode 0600; secret-shaped fields are rejected. CASTIA_TOKEN takes precedence for the current process and is never persisted by the CLI.
Human device login uses a short-lived code, PKCE S256, an authenticated browser session, explicit Organization selection, and a 30-day Organization-bound credential. The server revalidates the human's current role, active membership, and workspace access.
Service credentials belong to exactly one Organization. Operators should issue separate, expiring, least-scope tokens per agent and environment and optionally bind each token to an active acting user. A human admin with operation:approve may approve or reject only the exact current operation fingerprint. Draft approval/rejection also requires a human actor; a service token cannot manufacture human authority.
threats, controls, and residual risks
| Surface | Threats | Implemented or required controls | Residual risk |
|---|---|---|---|
| Operator API | Cross-tenant object access, confused Organization binding, schema abuse, oversized payloads, replay, and secret leakage in errors | Credential and Organization revalidation on every request; role/scope/workspace policy; strict schemas and size caps; safe projections; deterministic idempotency; redaction; durable receipts | Castia terminates TLS and sees plaintext. Application, database, logging, deployment, and incident controls still matter. |
| CLI process | Token theft, malicious origin, redirect credential forwarding, unsafe config permissions, terminal/log disclosure, and hostile response content | OS secret stores or vault injection; HTTPS and redirect refusal; 0600 non-secret config; bounded responses; redaction in all output modes | A compromised device, shell, extension, subprocess, or administrator can still read process memory or user-visible business data. |
| Human credential | Stolen browser/device credential, stale role, or authorization after membership removal | PKCE device login; Organization binding; current membership, role, and workspace revalidation; expiry and revocation | Thirty-day credentials increase exposure until expiry or revocation; device posture is outside the CLI. |
| Service credential | Excessive scopes, shared-account attribution, unattended exfiltration, or long-lived agent compromise | Organization-bound token; least role/scopes; expiry/revocation; optional active delegated user; separate tokens per agent/environment | A valid broad token can exercise every capability its policy permits. Vault, job, egress, and rotation controls remain external. |
| Approval adapter | Service self-approval, stale approval reuse, changed scope after review, or a human approving an unreadable target | Human-only approval/rejection; admin plus operation:approve; exact scope fingerprint and revision; safe review projection; durable attribution | A legitimate human can still make a bad decision. Review UX, separation of duties, and organization policy remain necessary. |
| Microsoft/partner adapter | Shared service-account actions, prompt injection, capability overreach, tenant confusion, tool-output exfiltration, or misleading deployment claims | Adapter-specific routes enforce the matrix-derived allowlist before the shared Operator; tenant/role/scope/revision checks remain mandatory; partner gateway must minimize fields and keep tokens out of prompts | Current preview has no Entra/OBO user mapping or tenant deployment proof. The allowlist narrows tools but does not provide per-user Microsoft attribution. |
| Prompts and model input | Customer data or tokens copied into prompts, malicious business text steering tool calls, provider retention/training, and untrusted model output | Never place tokens in prompts; classify and minimize allowed fields; treat model output as untrusted input; schema validation; capability discovery; human approval for governed effects | Model/provider telemetry, retention, subprocessors, residency, and prompt-injection behavior require separate contractual and technical review. |
| External providers | False success, timeout after side effect, duplicate work, partial results, provider credential exposure, or policy drift | Durable operations; idempotency; held and outcome_uncertain states; explicit provider readiness/policy checks where present; separate downstream incident review | company.research@v1 uses an external dependency. No campaign-delivery adapter is connected, so delivery security and exactly-once behavior are unproven. |
| Package/update chain | Typosquatting, registry/account compromise, tampered dependency or artifact, unreviewed auto-update, and stale vulnerable versions | Use only @castia/cli or a reviewed local package; lockfile and source review; reproducible test/pack verification; no curl | sh installation | Publication, signing/provenance, maintainer security, registry policy, vulnerability response, and update rollout must be established before broad distribution. |
For a regulated partner, add an approved data-processing agreement, data classifications, residency/retention decisions, partner-device and egress controls, audit export, provider review, incident response, and an explicit prompt policy. The wrapper may reduce exposure; it cannot weaken or replace Operator authorization and it cannot make the entire transfer end-to-end encrypted.
capability data-flow inventory
Classifications used below are: security (credentials, authorization and approval controls), operational (IDs, revisions, status, timestamps and cursors), business confidential (Company, playbook, campaign and research content), and personal data (identifiable contacts or users). Every request also carries a bearer credential and Organization ID as security data; credentials are validated but never returned. Mutation outputs are durable safe receipts unless stated otherwise.
| Capability | Input fields and classification | Output fields and classification | Explicitly omitted |
|---|---|---|---|
| campaign-run.show@v1 | Operational: run ID and view; security: Organization binding | Operational and business confidential: safe review, evidence, or audit projection with run state, pins, counts, revisions, and decisions | Omitted: credentials, mailbox/provider secrets, unrestricted contact records, and other tenants |
| campaign.audience.create@v1 | Operational: Segment Playbook Version ID and idempotency key; business confidential and possible personal data: name, cold/warm relationship, and 1–20 visible target Company IDs with optional contact IDs | Operational and business confidential: receipt followed by safe audience ID, member count, Segment Playbook Version ID, and frozen authority revision | Omitted: credentials, actor/role/tenant authority, full contact payloads, provider secrets, internal playbook authority, and other tenants |
| campaign.audience.freeze@v1 | Operational: run ID, expected revision, and idempotency key | Operational: receipt for the immutable frozen audience scope and scope fingerprint | Omitted: credentials, raw contact bodies, mailbox/provider secrets, and other tenants |
| campaign.drafts.generate@v1 | Operational: run and target IDs plus expected revision; business confidential and possible personal data: subject and body | Operational: receipt, revision, status, and scope fingerprint | Omitted: credential, stored message body, model prompt/transcript, provider secrets, and other tenants |
| campaign.hold@v1 | Operational and business confidential: run ID, expected revision, idempotency key, and hold reason | Operational: receipt for the held scope | Omitted: credentials, unrelated campaign content, provider secrets, and other tenants |
| campaign.launch@v1 | Operational and security: run ID, expected revision, idempotency key, frozen scope, and exact human approval fingerprint | Operational: approval-bound receipt; current result explicitly reports deliveryStarted: false | Omitted: credentials, provider secrets, delivery payloads, and other tenants; no messages are sent |
| campaign.outcome.record@v1 | Operational: run ID, expected revision, target ID, and allowlisted outcome kind | Operational and business confidential: receipt for the recorded outcome | Omitted: credentials, message content, raw provider event, unsupported outcome fields, and other tenants |
| campaign.prepare@v1 | Operational: run ID, expected revision, idempotency key, and server-resolved frozen plan | Operational and business confidential: receipt for the immutable prepared batch | Omitted: credentials, provider secrets, unapproved mutable state, and other tenants |
| campaign.resume@v1 | Operational: held run ID, expected revision, and idempotency key | Operational: receipt for the new immutable child batch | Omitted: credentials, completed work, provider secrets, and other tenants |
| campaign.run.create@v1 | Operational: audience ID and idempotency key; business confidential: name, manual/personal-draft channel, 1–2 message variants with body templates capped at 3,500 characters each, optional angle/routing, and bounded personal-draft mailbox selection/schedule | Operational and business confidential: receipt followed by safe run ID, audience ID, draft state, Segment Playbook Version ID, and frozen authority revision | Omitted: credentials, mailbox owner/credential, provider fields, raw provider responses, internal playbook/settings authority, and other tenants; creation sends nothing |
| capability.list@v1 | Operational: API version and capability resource; security: caller scopes determine authorization | Operational: versioned capability IDs, roles, scopes, schemas, effects, and approval metadata | Omitted: credentials, tenant business records, internal handlers, and unadvertised capabilities |
| company.archive@v1 | Operational and security: Company ID, expected revision, idempotency key, and exact human approval scope | Operational: receipt for soft archive | Omitted: credentials, Company content, hard-delete controls, and other tenants |
| company.followup.schedule@v1 | Operational and business confidential: Company ID, expected revision, date, reason and temperature; possible personal data: draft message | Operational: receipt for the scheduled follow-up | Omitted: credentials, mailbox secrets, provider delivery response, and other tenants |
| company.import@v1 | Business confidential: URL/domain and name; personal data: optional contact name, email and role; operational: idempotency key | Operational: import/de-duplication receipt and resulting operation status | Omitted: credentials, duplicate candidate internals, enrichment/provider secrets, and other tenants |
| company.list@v1 | Operational: cursor and limit | Operational and business confidential: Company ID, name, domain, status, lead score, owner user ID, revision, and next cursor | Omitted: credentials, contacts, notes, raw research, provider data, and other tenants |
| company.research@v1 | Operational and business confidential: Company ID, expected revision, optional campaign play ID, and idempotency key | Operational: receipt for the governed external research attempt; research is stored behind its own bounded surfaces | Omitted: credentials, provider secrets, raw provider payload/transcript, and other tenants |
| company.show@v1 | Operational: Company ID | Operational and business confidential: ID, name, domain, status, lead score, owner user ID, and revision | Omitted: credentials, contacts, notes, raw research, provider data, and other tenants |
| credit-controls.show@v1 | Security: Organization-bound admin actor with credits:read; operational: read-only control-status request | Operational: read-only writesSupported: false status | Omitted: credentials, balance mutations, credit purchases/top-ups, Stripe actions, grant controls, and other tenants |
| credits.show@v1 | Security: Organization-bound admin actor with credits:read; operational: read-only balance request | Operational and business confidential: read-only balance, allowance, cycle spend, reset/renewal, pricing model, and automation/block status | Omitted: credentials, raw ledger rows, Stripe customer data/actions, balance mutations, purchase/top-up and grant controls, and other tenants |
| draft.approve@v1 | Operational and security: run/target ID, revision ID and number, content hash, expected revision, and human identity | Operational: human-attributed decision receipt | Omitted: credentials, approved message body, model prompt/transcript, and other tenants |
| draft.reject@v1 | Operational, security and business confidential: run/target ID, revision ID and number, content hash, reason, expected revision, and human identity | Operational: human-attributed rejection receipt | Omitted: credentials, rejected message body, model prompt/transcript, and other tenants |
| draft.revise@v1 | Operational: run/target ID and prior revision; business confidential and possible personal data: subject and body | Operational: immutable new-revision receipt | Omitted: credentials, prior message bodies, model prompt/transcript, provider secrets, and other tenants |
| icp.list@v1 | Operational and business confidential: cursor, limit, and optional search | Operational and business confidential: published ICP IDs, names, keys, kind, revision, sequence, action, publication time, and next cursor | Omitted: credentials, unpublished criteria/evidence, personal contacts, and other tenants |
| icp.show@v1 | Operational: published ICP selection ID | Operational and business confidential: published ICP identity, revision/publication metadata, name, key, kind, and status | Omitted: credentials, unpublished criteria/evidence, personal contacts, and other tenants |
| operation.approve@v1 | Operational and security: operation ID, expected revision, exact scope fingerprint, and human admin identity | Operational: human-attributed approval receipt and updated status | Omitted: credentials, unrestricted target data, changed/stale scopes, and other tenants |
| operation.cancel@v1 | Operational and security: operation ID, expected revision, and exact scope fingerprint | Operational: cancellation receipt and updated status | Omitted: credentials, unrestricted operation input/result, completed external effects, and other tenants |
| operation.follow@v1 | Operational: operation ID and optional after cursor | Operational: bounded NDJSON events with cursor, sequence, type, status, timestamp, and safe data | Omitted: credentials, authorization headers, unrestricted payloads/errors, and other tenants |
| operation.reject@v1 | Operational, security and business confidential: operation ID, expected revision, exact scope fingerprint, reason, and human admin identity | Operational: human-attributed rejection receipt and updated status | Omitted: credentials, unrestricted target data, changed/stale scopes, and other tenants |
| operation.show@v1 | Operational: operation ID | Operational and business confidential: action, status, revision, safe target, fingerprint, review, timestamps, and bounded result/error | Omitted: credentials, raw authorization, internal stack traces, unrestricted payloads, and other tenants |
| segment-playbook-version.list@v1 | Operational and business confidential: cursor, limit, and optional search | Operational and business confidential: published version identity, commercial goal, audience pin, offering/blueprint pin summary, revision, publication time, and next cursor | Omitted: credentials, unpublished playbook content, provider secrets, personal contacts, and other tenants |
| segment-playbook-version.show@v1 | Operational: Segment Playbook Version ID | Operational and business confidential: published version identity, commercial goal, audience and offering/blueprint pins, revision, and publication time | Omitted: credentials, unpublished playbook content, provider secrets, personal contacts, and other tenants |
| usage-event.list@v1 | Security: Organization-bound admin actor with credits:read; operational: opaque cursor and limit for a read-only ledger request | Operational and business confidential: read-only bounded ledger entries with amount, kind, source, pricing SKU/version, timestamp, and next opaque cursor | Omitted: credentials, internal ledger IDs, raw billing/provider records, write/top-up/grant controls, and other tenants |
| usage.show@v1 | Security: Organization-bound admin actor with both billing:read and credits:read; operational: optional 1–365 day window for a read-only request | Operational and business confidential: read-only usage window, spend, balance, allowance, burn rate, reset/renewal, pricing model, and automation/block status | Omitted: credentials, raw ledger rows, Stripe customer data/actions, balance mutations, purchase/top-up and grant controls, and other tenants |
| work.list@v1 | Operational: optional needs-human filter, cursor, and limit | Operational and business confidential: bounded tenant work summaries, status, target, fingerprint, review, and timestamps | Omitted: credentials, unrestricted operation input/result, provider secrets, and other tenants |
Microsoft Copilot preview allowlist
The preview exposes an OpenAPI-compatible description and adapter-specific routes for a closed partner gateway. The routes enforce the list derived from operatorCoverageAllowlist("microsoft-copilot") before delegating to the shared Operator. A generic credential used against the core Operator routes is not confined to this partner list, so a production gateway must use the adapter routes and restrict egress accordingly. A live deployment still needs per-user Entra identity with OBO token exchange, tenant/admin consent, DLP, approved data classes, residency/retention, audit, disable/uninstall, and incident controls.
The gateway must keep Castia credentials outside prompts, send only fields approved for the partner's data class, treat Copilot text as untrusted, and let Castia revalidate Organization, role, scope, revision, and approval on every call. The current service-token preview does not advertise human-only approve/reject; those actions require a future Entra/OBO mapping to a live Castia human actor.
| Capability | Input fields and classification | Output fields and classification | Explicitly omitted |
|---|---|---|---|
| capability.list@v1 | Operational: API version and discovery request | Operational: allowlisted capability contracts and schemas | Omitted: Castia token, internal handlers, unadvertised capabilities, and tenant records |
| company.list@v1 | Operational: cursor and limit | Operational and business confidential: bounded Company summary page | Omitted: token, contacts, notes, raw research, prompts, and other tenants |
| company.show@v1 | Operational: Company ID | Operational and business confidential: bounded Company summary | Omitted: token, contacts, notes, raw research, prompts, and other tenants |
| icp.list@v1 | Operational and business confidential: cursor, limit, and search | Operational and business confidential: published ICP summary page | Omitted: token, unpublished evidence, personal contacts, prompts, and other tenants |
| icp.show@v1 | Operational: published ICP ID | Operational and business confidential: published ICP summary | Omitted: token, unpublished evidence, personal contacts, prompts, and other tenants |
| operation.cancel@v1 | Security and operational: operation ID, exact fingerprint/revision | Operational: cancellation receipt | Omitted: token, unrestricted target content, completed effects, and other tenants |
| operation.follow@v1 | Operational: operation ID and cursor | Operational: bounded event stream | Omitted: token, raw headers, unrestricted payloads/errors, and other tenants |
| operation.show@v1 | Operational: operation ID | Operational and business confidential: bounded review/status projection | Omitted: token, raw input, internal stack trace, and other tenants |
| segment-playbook-version.list@v1 | Operational and business confidential: cursor, limit, and search | Operational and business confidential: published playbook pin summaries | Omitted: token, unpublished content, personal contacts, prompts, and other tenants |
| segment-playbook-version.show@v1 | Operational: published version ID | Operational and business confidential: published playbook pin summary | Omitted: token, unpublished content, personal contacts, prompts, and other tenants |
| work.list@v1 | Operational: needs-human filter, cursor, and limit | Operational and business confidential: bounded work summaries | Omitted: token, unrestricted payloads, provider secrets, prompts, and other tenants |