Carl may disclose only the minimum approved SaaS Manager fields to an explicitly allowlisted person in a one-to-one Slack DM. It must remain read-only even if Slack input, LLM output, SaaS Manager data, or deployment configuration is malicious or mistaken.
Prompt instructions are not treated as an authorization boundary. Authorization, endpoint selection, scope enforcement, field redaction, pagination, and output size are implemented in deterministic code and covered by tests.
| Threat / failure | Enforced control | Residual risk / next control |
|---|---|---|
| Forged or replayed Slack request | HMAC-SHA256 signing-secret verification, constant-time comparison, five-minute timestamp window, event-ID deduplication | Deduplication is in memory; pilot pins one instance. Use shared durable idempotency storage before horizontal scaling. |
| Wrong Slack workspace/app | Fail-closed team-ID and API-app-ID allowlists | IDs must be captured and reviewed during app setup. |
| Unauthorized Slack member | Slack Enterprise Member Permissions restricted to the four-person pilot, plus a user-ID allowlist, mandatory per-user role, and per-domain capability set in Carl; silent denial | App may remain visible in Slack’s workspace app directory and to workspace/org administrators. |
| Sensitive answer posted broadly | message.im only; no app mentions or channel scopes; code rejects non-DM events |
A permitted user can still copy/screenshot a reply. Treat Slack and its retention policy as part of the data boundary. |
| PDF exported to the wrong recipient | Export is recognized outside the LLM and can use only the latest answer cached under the authenticated user + DM key. Upload code accepts only a D... destination and validates Slack’s one-time upload URL host/path. No user-selected channel or filename is accepted. |
files:write is broader than Carl’s code and technically permits app-owned file edit/delete. Keep the bot token protected, retain the server-side four-user allowlist, and review Slack file retention. |
| Over-privileged SaaS Manager client | Runtime requests exactly the feature-enabled read scopes and rejects any different scope set. Each additional read domain is independently feature-gated; named-person and workflow-run scopes also require risk acknowledgements. | Client Credentials still represents one organization-wide service principal. Configuring broad read scopes on the OAuth client increases what stolen client credentials could request even when Carl’s live token is narrow. See identity model below. |
| LLM tries a forbidden action | Only documented collection GET paths and strictly validated app-account/person-app/workflow-run subresource paths are allowlisted; no generic URL/method tool; role, per-user capability, and feature gate are rechecked during execution | Keep the invariant tests mandatory in CI. |
| Prompt injection in SaaS records | Notes, document bodies/links, custom fields, and creator/modifier identities are removed; the contract business-owner email is format-validated; remaining fields are marked untrusted in the system prompt | Product/vendor/line-item names are still untrusted strings. Deterministic allowlists and no write tools cap impact. |
SSRF / malicious next link |
HTTPS host, path, query-key, credential, and fragment validation on every request/pagination URL; redirects disabled | Revisit allowlist if 1Password changes official hosts. |
| Credential leakage | Secrets come from Secret Manager/process env; no secret values in diagnostics; upstream/Slack errors are sanitized; raw responses are not logged | Limit Secret Manager access to Carl’s runtime/deployer identities and rotate independently before GA. |
| Excess data sent to Claude | Role/capability-filtered slim records, bounded results, explicit LLM_DATA_EGRESS_ACKNOWLEDGED gate, no source contract PDFs/notes; aggregate app activity, workflow runs, audit, and console users are converted to counts before model egress; named-person output has its own approval and a minimum work-identity allowlist |
Confirm Anthropic commercial data terms, retention, geography, and Hillspire vendor approval for each data class before enabling it. |
| Sensitive People or workflow-run response | Named-person lookup removes aliases, employee ID, manager, location, cost center, phone, home address, employment dates, teams, custom fields, internal IDs, and exact login timestamps. Workflow runs remove the complete subject/context/variables/steps before egress. | The service principal still receives raw data in process. A process compromise can bypass output minimization, so keep the OAuth scope and feature off until explicitly accepted. |
| Cross-user conversation/export leakage | Conversation and export keys include user + DM; in-process only; eight model-history messages; latest exportable answer only; 30-minute TTL | Cloud memory is not a durable audit store. Slack hosts completed PDF exports under workspace retention policy. |
| Misleading financial answer | Actual Spend is unavailable; contract totals are labeled estimated line-item commitment; currencies never mixed | Validate amount/unit semantics against live tenant samples before executive pilot. |
| Operational regression | Path-scoped GitHub CI runs all invariant tests and compilation; deployment still uses a no-traffic candidate, build stamp, /diag, authenticated upstream/LLM probes, and manual traffic promotion |
Add SAST, dependency scanning, and alerting before GA. |
Carl deploys as the dedicated carl-saas-agent-runtime identity, with per-secret access
to Carl’s five secrets and the pilot’s shared Greg Claude secret. However, the existing
gw-admin-assistant project currently grants its default Compute Engine service account
project-wide roles/secretmanager.secretAccessor. Workloads still running as that legacy
identity can therefore access Carl’s secrets too. Do not describe the pilot as
cryptographically isolated from other workloads in this GCP project.
Before GA, migrate the existing services to dedicated identities with per-secret IAM and then remove the project-wide binding, or place Carl in a separate GCP project. Removing the legacy binding without that migration could break Greg/Harry and is intentionally not performed by Carl’s bootstrap.
Direct dependencies are pinned to the versions exercised by the phase 1 test run. Review and update them deliberately; add automated dependency scanning before GA.
OAuth2 Client Credentials authenticates Carl as one SaaS Manager API app. It does not
make SaaS Manager evaluate the permissions of the Slack person asking the question.
SLACK_USER_ACCESS_JSON and SLACK_USER_CAPABILITIES_JSON are therefore compensating
application-layer controls.
For the small pilot audience, this is reasonable if:
Do not use an organization-level installation merely to manage the pilot audience. Slack documents that an org-level installation notifies Org Owners and Org Admins. Keep Carl at workspace level and apply Member Permissions to the four approved people.
If Carl must mirror each person’s native SaaS Manager access exactly, phase 2 should use OAuth Authorization Code per person and maintain a verified Slack-user-to-SaaS-user binding. Confirm with 1Password that native object/field permissions are represented in delegated API tokens before treating that design as an enforcement control. Do not make the service principal a SaaS Manager “super admin” merely to avoid this decision.
inventory, contracts, or
financial, and each required capability separately.SLACK_ALLOWED_USER_IDS, SLACK_USER_ACCESS_JSON, and
SLACK_USER_CAPABILITIES_JSON./diag, run the unauthorized/role tests, then
promote.LLM_DATA_EGRESS_ACKNOWLEDGED=false or route
Cloud Run traffic to a known-safe revision, preserve Cloud/Slack audit evidence, and
notify the data owner/security lead.