Please do not open a public issue, discussion or pull request for a security vulnerability. Report it privately through GitHub private vulnerability reporting: the repository's Security tab → Report a vulnerability (direct link). If that is not possible, email the maintainer. We aim to acknowledge reports within a few days and to agree a disclosure date with you once a fix is available.
When reporting, include: affected component, reproduction steps, and impact.
- Threat model — STRIDE per trust boundary and module, mitigations with code references, and the prioritised list of open gaps.
- Data flow · Sub-processors (what leaves the box) · Production hardening checklist · DPA template (template, not legal advice).
- LLM keys & secrets are never committed. Set them via
dotnet user-secrets, environment variables, or the runtime Settings store (encrypted with ASP.NET Core Data Protection). See docs/SETUP.md. - API auth is Keycloak OIDC JWT bearer (RS256); tokens are signed by the Keycloak realm key and validated against its JWKS — rotate the signing key in Keycloak, not in AgentOS.NET.
- The remote dev-agent runtime executes work on a developer machine — treat the pairing
credential as a secret. Remote execution is gated by the
RemoteAgent:AutoApproveplatform setting, which Production locks off (a gate, not yet a per-run human approval — see the threat model); server-driven shell commands outside the allowlist additionally need approval at the runner console.
- Tool governance — every tool call goes through
IToolGateway→ per-tenant policy → evidence log (refusals and errors included). Production enforces a fail-closed allowlist. - Build sandbox — generated code is compiled only in an ephemeral, no-network, read-only, non-root
container with CPU/memory/PID quotas (ADR-0005);
Production locks the sandbox to
Container. - Prompt-injection fencing — third-party text (tickets, issues, repo content) is wrapped as untrusted data before it reaches a prompt.
- Operator locks — Production pins the riskiest settings so the Settings UI cannot change them
(CONFIG.md); platform-wide
settings require the
platform-adminrole. - Audit trail — every settings change (old → new, secrets masked), every direct config write (LLM keys, tokens, prompts, tool policy, budget — recorded as set / cleared, never the value), member lifecycle, invitations and sign-in land in an append-only audit log, viewable in Settings → Audit log and exportable as formula-safe CSV. Platform-scope changes are visible to platform admins only. The application's database role may only add and read audit rows (no update / delete), and each tenant's events form a SHA-256 hash chain that Verify chain checks — it names the first event that was changed or removed.
- Invitations — single-use, accepted only for the address they were sent to, revocable by a tenant admin until used, and expiring after 1–168 hours (default 24); refused attempts are audited.
- Tenant isolation — row-level EF query filters and Postgres row-level security on the tenant tables
(ADR-0012): every connection runs as a restricted role bound to
the caller's tenant, so a forgotten filter returns only the caller's rows, a write for another tenant is
refused, and a connection with no tenant sees nothing; the few cross-tenant platform operations run in an
explicit, counted platform scope. Outside Development the hosts refuse to start without it. Per-tenant keys are never mixed with another tenant's or the platform's. Cross-tenant endpoints (list / provision tenants) require
platform-admin, never the per-tenantadminrole; a real-auth E2E proves a second tenant sees none of the first's data. - QA gate before every PR — a pipeline result becomes a pull request only when QA passed on real model
output (
PrGate, enforced inside the PR service itself): failed runs, the iteration cap with QA still failing, and offline canned text are blocked with the reason on the session. - Approval policies + repo safety — per tenant / project, an approver (or admin) approves the plan, the pull
request, the change and/or the merge on Board › Tasks (audited); a gate fails closed — rejected or nobody who can
approve stops the task. AgentOS.NET never opens a pull request from, or merges, the default branch, and refuses a
merge the branch's protection would refuse (reviews,
CODEOWNERS, required checks, rulesets), also when its token could bypass it. See docs/CONFIG.md. - SSRF guard — tenant-supplied hosts are validated and limited to an allowlist.
- Deny by default — every endpoint needs a signed-in caller unless it is explicitly public (health, sign-in / sign-up, invitation preview, runner download, the self-authenticating runner hub, static files); a test pins the list of public routes. Access tokens are accepted only when signed with RS256.
- Browser hardening — both hosts send HSTS (outside Development), an enforced Content-Security-Policy
(
script-src 'self',frame-ancestors 'none'),X-Content-Type-Options: nosniff,X-Frame-Options: DENYandReferrer-Policy: same-origin. - Request limits — per-tenant, per-user and per-anonymous-client request rate limits on both hosts
(Settings → Platform → Request limits; 429 +
Retry-Afterover the limit). - Paired runners — a server-driven shell command runs on a developer's machine only if every segment of the
chain starts with an allowlisted build/test/VCS program (
REMOTE_AGENT_ALLOWto change); command substitution is refused; anything else needs approval at the runner console and is denied when nobody is there. Each command has a timeout that kills the whole process tree. The runner sends its pairing token in a header, never in the URL (the hub still accepts a legacy query-string form — tracked as a gap). - Supply chain / SAST — Dependabot, dependency review, SHA-pinned actions, CodeQL with compiler-enforced twins for its query classes. Release images are cosign-signed (keyless), carry SLSA build provenance and a CycloneDX SBOM attestation, and every release attaches the SBOMs — verify an image before deploying it (docs/RELEASE.md).
This project is pre-1.0. Security fixes land on main and ship as a patch release of the latest minor; the
previous minor gets security fixes for 90 days after the next one ships. The full policy (SemVer, prereleases,
image tags) is in docs/RELEASE.md.