[AAASM-5612] 🐛 (docs): Remove unverified SaaS operational and contractual claims - #134
Conversation
Managed-service operational and contractual claims were published on this hub without a running service, an owner, or evidence behind them. Removing them once does not stop them returning, so record per claim class why it was removed, who must approve restoring it, and the concrete evidence that approval requires. Registered in SUMMARY.md in the same commit because an unregistered page does not render, and the pages that link to it land in the following commits. Refs: AAASM-5612
…rough The page instructed readers to sign up at a URL that does not serve, copy a workspace ID and API key out of console screens that are not reachable, and point an SDK at a gateway endpoint that returns no origin. It then presented three purchasable tiers with agent quotas, retention periods, region selection, onboarding-duration estimates, support-response times, a week-by-week procurement timeline, and DPA/BAA and compliance handling — none of which has a running service, an approved commercial policy, or a legal review behind it. Removed rather than reworded: softening "99.9% SLA" into "high availability" does not reduce what a reader takes away, it only makes the claim harder to audit. What replaces it is a statement of what actually ships (the Apache-2.0 stack and its existing docs), an explicit list of what this hub will not publish and why, and a link to the publication checklist that gates each item. Refs: AAASM-5612
…n unrun service The page documented the managed control plane as if it were operating: a region list with a data-residency guarantee, tenant provisioning paths, a tenant-ID format, per-tier quotas, SSO and SCIM walkthroughs with console navigation and endpoints, a console budget form, an availability-and-support SLA table with service credits, card and invoice billing setup, and DPA/BAA handling. None of it is backed by a running service. Several items were also internally inconsistent with what this hub already documents — the budget form's fields do not match the budget schema the gateway enforces in the policy reference, which is the source of truth for budget behaviour. The page now states what the open-source stack covers instead, enumerates what will not be published and why, and links the publication checklist. Refs: AAASM-5612
…ability claims The page's framing asserted that enterprise operations "are covered by the AAA-Commercial license and available on paid SaaS tiers", that the complete feature set "is delivered" through the cloud, and that an uptime SLA and a managed compliance posture exist. None of that is true: no tier is purchasable, the licence terms are not published, and the managed service is not running. Also drops two unqualified absolutes that the page could not support — "every security control in the stack" and "the complete feature set" — and stops naming tamper-evident audit as a settled capability in the boundary rule, since that is a cryptographic-integrity claim needing a named mechanism. Refs: AAASM-5612
… and region claims A two-column tick matrix presents the commercial column as a defined product even under a "coming soon" banner. This one asserted a 99.9% uptime SLA, 24/7 and business-hours support tiers, a dedicated SRE contact, dedicated and shared SaaS regions, multi-region data residency, tamper-evident audit, SIEM export in named formats, and audit retention "up to 1 year" — and marked shared-region SaaS as already available on the open-source side, which is not the case anywhere. Split into two sections instead: what ships in the Apache-2.0 core today, each row pointing at the page that documents it, and what is intended for the commercial tier, stated as capability areas with no plan, price, quota, retention period, region, availability or compliance claim attached. Refs: AAASM-5612
… claims Three legal-adjacent statements had nothing behind them. The page named an "AAA-Commercial license" whose terms are published nowhere. It stated that enterprise features are gated behind SaaS config in a crate that has no such gate, and that the OSS codebase contains "all" interception and enforcement logic — an unqualified absolute. And the footer attributed legal sign-off to a page whose content has changed repeatedly since that sign-off was recorded. Replaces the footer attribution with the hub's standard reviewed-by line plus an explicit note that the page grants no licence beyond Apache-2.0, and adds the publication checklist to the related-documentation list so the next maintainer finds the gate before re-adding a commercial claim. Refs: AAASM-5612
…DPA availability The compliance posture table asserted "SOC 2 Type II — In preparation (target: Q3 2026)", "ISO 27001 — Roadmap", a GDPR row offering a DPA "on request", and a CCPA row claiming coverage under a SaaS Data Processing Agreement. No audit report, assessment scope, or executed legal template exists for any of these. Searched for one: the only in-repo hits are an ADR asserting the posture is clean, which is a doc claim rather than evidence, and an `aasm audit export --compliance soc2` flag that prepends a metadata header to an export file, which is a formatting feature rather than a certification. Removed the table rather than relabelling the rows Planned. A status table inside a section headed "Compliance posture" reads to a procurement reviewer as a programme with a trajectory — relabelling would have left exactly the softer promise this ticket exists to eliminate. Replaced with a plain statement that no certification is held and no DPA or BAA is available. Refs: AAASM-5612
…nce claims The page asserted that every agent action "produces an immutable log entry", that entries "are signed with HMAC-SHA256 using a log-signing key", and that "no delete or update API exists". Verified each against the Apache-2.0 source rather than against the surrounding prose. Findings: - There IS a real integrity mechanism, but it is a KEYLESS SHA-256 hash chain in aa-core/src/audit.rs, not an HMAC. No hmac import exists in aa-core, and this page's own cryptographic-primitives table has no audit-signing row — the two sections contradicted each other. - The chain hashes are NOT persisted. The audit_events schema (Postgres migration and SQLite driver) has no entry-hash, previous-hash, or sequence column, so the chain cannot be re-verified from the durable store. - Deletion IS implemented: retention pruning runs DELETE FROM audit_events in both drivers. No trigger, revoked grant, or WORM setting prevents it. - Emission is best-effort: entries go to a bounded channel via a non-blocking send, and one call site discards the error outright. Replaces the three bullets with the verified properties, and fixes the same claim where it was restated in the Telemetry layer row and the STRIDE Repudiation cell, so the page no longer contradicts itself. Refs: AAASM-5612
The page stated retention is "configurable per tenant (default: 90 days)" and that logs are "exportable in JSON or CEF format for SIEM integration". Per-tenant retention is a multi-tenant SaaS claim with no managed service behind it; the actual mechanism is an operator-set RetentionPolicy applied by the storage driver, and no 90-day default was found. The number is removed rather than corrected — an unmeasured default is not worth publishing. CEF is absent from the codebase entirely: zero occurrences across the CLI, gateway, API and core docs. JSONL export is real (`aasm audit export`, ExportFormat::Jsonl), so the claim is narrowed to that and the "SIEM integration" phrasing dropped, since no integration exists — only a file a SIEM could ingest. Refs: AAASM-5612
…y-access CTA The CTA invited readers to "talk to the team about the STRIDE model, tamper-evident audit, and your compliance path" — restating, in a sales context, the two claims this page no longer supports: an audit integrity guarantee stronger than the keyless unpersisted hash chain that exists, and a compliance path with nothing behind it. Keeps the STRIDE offer, points at the audit log's actual integrity properties, and states plainly that no certification, DPA or BAA is available. Also refreshes the reviewed-on date, since the page's claims changed materially. Refs: AAASM-5612
…list Adds seven rows for the claims removed from the security model, and sharpens the existing tamper-evidence row now that the mechanism has been read rather than inferred. Three of the new rows are marked as contradicted by the Apache-2.0 source rather than merely unevidenced, and their evidence column says so: restoring them needs a code change (a keyed construction, an enforced storage constraint, a fail-closed emission path), not an owner's approval. That distinction matters — an approver handed one of these rows should not be able to sign it off. Also records that a framework-named export flag is not compliance evidence, and adds a language rule: verify a claim against the code, not against the neighbouring prose that repeats it. Refs: AAASM-5612
… of the sinks My previous commit was wrong in two directions and is corrected here rather than rewritten, so the record of the mistake survives review. Under-stated: I described verify_integrity/verify_chain as in-memory only and said the chain "cannot be re-verified from the durable store". In fact the primary sink is a per-session JSONL file that IS hash-chained and IS verifiable today by an operator running `aasm audit verify-chain <file>`. The non-verifiable part is the database mirror, whose runtime-to-storage conversion deliberately drops seq, previous_hash and entry_hash. Understating a real control is still inaccurate. Over-stated: I wrote that the gateway records "policy decisions, event records, budget debits". Budget debits are NOT audited — BudgetLimitApproached and BudgetLimitExceeded exist in the enum, the string mapping, the read-side filter and the tests, but are never constructed anywhere, so no debit produces an entry. Also adds three properties the fuller read surfaced: an UPDATE has been issued against audit_logs by a backfill migration, the offline spill buffer evicts its oldest events on cap, and a dropped entry still advances the chain head — so verify-chain reports failure for backpressure exactly as it would for tampering, which a reader needs to know before treating a failure as compromise. Refs: AAASM-5612
…g behaviour Updates the audit rows now that the sinks have been read in full, and adds two rows the fuller read justified: chain coverage stated without naming the sink, and audit coverage listed by event category. The tamper-evidence row no longer says the claim is unrestorable. The mechanism is real and operator-verifiable for the JSONL sink, so the row now permits the phrase provided its three bounds — unkeyed, JSONL-only, says nothing about completeness — appear in the same sentence. Gating a true claim as if it were false would make the checklist a source of understatement. Adds two language rules for the same reason: understating a control is also inaccurate, and a control should name the scope it covers. Refs: AAASM-5612
…tor auth path The page advertised SSO three times: the Identity layer row claimed "operator authentication via SAML 2.0 / OIDC SSO", the STRIDE Spoofing cell for aa-api claimed "SAML/OIDC token validation on every request", and an entire "Operator to console (SAML/OIDC)" section carried a sign-in sequence diagram against a hosted console. None of it exists. Zero occurrences of saml, oidc or openid across aa-api/src, aa-gateway/src and aa-auth/src; aa-auth/src contains only api_key, config, error, gate, jwt, lib, password, rate_limit, role and scope modules. There is also no console to sign in to. This also contradicted two pages in this same PR: open-core-boundary now lists SSO as planned for an unavailable tier, and the SSO walkthrough was removed from cloud-deployment for this exact reason. Missed here because security-model entered scope late and I reviewed only the sections I was already editing. Replaces the section with the two mechanisms that do ship — API key and JWT — under a Planned label that tells a reader not to plan an IdP integration against it. Refs: AAASM-5612
…aims The STRIDE Repudiation column still asserted total logging coverage in three rows after I corrected only the Gateway row: "Every call logged with agent ID and timestamp" (SDK), "All intercepted requests logged by proxy before forwarding" (proxy), and "All mutating API calls logged with actor identity" (REST API). The page therefore contradicted its own publication-checklist register, which already lists the generic form as contradicted by the source. Each is now bounded to verified behaviour. The proxy row also gains a distinction the page never drew: the proxy emits ProxyAuditEntry to its own JSONL stream, which is explicitly NOT the gateway's hash-chained AuditEntry and loses a line on write failure rather than stopping. The REST row notes that under AuthMode::Off the recorded actor is the synthetic admin caller, so "actor identity" is not a real identity in that posture. Refs: AAASM-5612
…ships I replaced an overstated export claim with an understated one. The page said "Export is JSON Lines, via the CLI. `aasm audit export` writes JSONL." ExportFormat has three variants — Csv, Json, Jsonl (aa-cli/src/commands/audit/ models.rs) — dispatched in export.rs. `--format` is a required argument with no default, so a reader following that sentence literally hits a clap error rather than an export. A second command, `aasm audit compliance-export`, was omitted entirely. The checklist row compounded it by listing JSON export among removed unevidenced claims when JSON export ships; that row is now scoped to CEF and to "SIEM integration" as a managed capability, and says explicitly that CSV/JSON/JSONL must not be swept up in it. Only the CEF half of the original finding was right. This is the second understatement in this ticket, after 6a1f876: both came from writing the conclusion straight out of a targeted grep for the false part of a claim without reading the surrounding enum. The rule added to the checklist covers the class; this commit is the second instance of it. Refs: AAASM-5612
…oved sections The block still advertised "Cloud deployment — SSO configuration, SCIM provisioning" and "Open core boundary — which security features are OSS vs. enterprise". Both descriptors point at content this PR deleted: the SSO/SCIM walkthroughs are gone and the OSS-vs-enterprise tier matrix was replaced. I updated this block on the other three pages and missed it here. Worth noting that lychee cannot catch this class — the link target resolves fine; it is the descriptor that advertises content the target no longer has. Refs: AAASM-5612
… the hub A regression this PR introduced. The scope callout said the uptime SLAs (99.5% / 99.9%), on-call rotation, and managed compliance posture "described in Cloud Deployment" apply to the cloud only — but Cloud Deployment no longer describes any of them, so the two SLA figures this ticket set out to delete survived here and nowhere else, in a page that reads as if they are real somewhere. The callout now makes the stronger and true statement: there is no availability, on-call, or compliance commitment from anyone, for either deployment. Also refreshes two related-documentation descriptors that pointed at removed content. Refs: AAASM-5612
…rnance ADR As written this page is a locally-authored claims registry with invented governance semantics — approval owners, evidence grades, a workflow — shipped ahead of AAASM-5621, the ADR meant to define exactly those semantics org-wide. That is the hazard 5621's problem statement names. Adds a banner stating the ADR wins on conflict, that the page is expected to become an adoption record feeding the AAASM-5531 capability/evidence manifest rather than a standalone registry, and drawing the line between the two halves: the register of what was removed and why is evidence and stands; the approval workflow around it is a placeholder. Refs: AAASM-5612
… claims Found while re-scanning the whole page as the review required, not among the items it named — reporting it for veto rather than sitting on it. The Secrets management section described a managed vault: AES-256-GCM encryption at rest, a master key held in the SaaS control plane's HSM, secrets never written to disk in plaintext or logged, and rotation driven from a console. The IronClaw Vault layer row and a Cryptographic primitives row repeated it. None of it is present. No aes/gcm implementation across any aa-*/src (the single match is a ChaCha CSPRNG comment in an IPC handshake) and no aes-gcm dependency; no HSM or KMS reference anywhere; no console. Two of the bullets were also unqualified "never" guarantees. This is the most consequential removal on the page: a reader could have concluded the stack safely stores their LLM API keys. Replaced with an explicit warning that it is not a secret store and that protection at rest is the deployment's responsibility. Refs: AAASM-5612
Also found in the full-page re-scan, also outside the named review items. "Kernel event timestamps are monotonic; cannot be retroactively altered" conflated a clock property with a record-integrity property. A monotonic clock stops reordering via wall-clock adjustment; it does nothing to stop a written record being edited afterwards, and this sat in the Repudiation column where that distinction decides whether the control is load-bearing. "No MD5, SHA-1, or DES primitives are used anywhere in the stack" is unverifiable as written — "anywhere in the stack" reaches the whole transitive dependency tree, which this hub does not audit. Bounded to the primitives in the table and to first-party workspace code, with the limit stated. Refs: AAASM-5612
…ayer claim After 9ce47fc removed the secret-vault claims from the security model, the glossary was the last place in the hub asserting the vault is real — and a glossary is exactly where a reader goes to confirm a term is a genuine product concept rather than marketing. The AES-256-GCM entry said it was "used here to encrypt stored secrets at rest". It is not used at all: no aes/gcm implementation in any aa-*/src, no aes-gcm dependency, no HSM or KMS reference. The IronClaw entry listed Vault among the five layers without noting that layer ships nothing. The term is kept rather than deleted, carrying its own correction, so a reader who met the old claim elsewhere lands on the retraction instead of a missing entry. Wording matches security-model.md so the two pages cannot describe the same non-existent mechanism differently. Also corrects the Ed25519 entry, which described it as signing "agent identity tokens". It signs a one-time possession proof over a server-issued nonce at registration; the ongoing bearer credential is a random token, not an Ed25519 signature. Refs: AAASM-5612
Both entries restated claims disproved earlier in this PR. HMAC-SHA256 was defined as signing "audit-log entries and webhook payloads". The webhook half is right; the audit-log half is not — the audit mechanism is an unkeyed SHA-256 hash chain and no log-signing key exists. The entry now names the two places HMAC is genuinely used and says explicitly where it is not. The Audit log entry claimed an "append-only record of every agent action (policy checks, events, budget debits)" and framed a "tamper-evident signed audit log (HMAC-SHA256)" as an Enterprise capability. That packs four errors into two sentences: "every" (emission is best-effort), budget debits (never emitted), HMAC signing (does not exist), and an Enterprise framing for a mechanism that partly ships today in the OSS build. It now states the four bounds a reader is most likely to assume away, and links the full statement. Refs: AAASM-5612
The SSO entry said the two federation protocols are "used to let operators log in with an enterprise identity provider" — present tense, and disproved in 03c7a30: zero occurrences of saml/oidc/openid across aa-api/src, aa-gateway/src and aa-auth/src, and no console to sign in to. SCIM's definition was protocol-neutral and made no direct claim, but sitting unlabelled next to a corrected SSO entry it would read as available by contrast. Both now carry the same Planned label used elsewhere in this PR, and the SSO entry names what operators actually authenticate with. Refs: AAASM-5612
…e layer table Two banned-class claims survived the earlier scan because neither contains a listed token in a listed form. Matching vocabulary does not find them; reading each table cell as a guarantee and asking what would have to be true does. Boundary row claimed the eBPF sensor "catches kernel-level bypass attempts" — prevention inferred from observation, with no platform bound. Verified in aa-ebpf-probes/: the SSL uprobes are observe-only and blind to Go, rustls, Node/BoringSSL, GnuTLS, NSS and LibreSSL, whose traffic never reaches them. One probe does enforce (syscall_guard SIGKILLs a monitored process on a non-allowlisted syscall, Linux 5.3+) — but its own header documents a kill-after-syscall race: the offending connect/write/unlink lands once before the task dies, and unmonitored PIDs are never inspected. The row now says so, and is marked Linux-only. Note ssl_probes.rs still calls the proxy and syscall layers "the catch-all" in a comment — the same error one layer down, not evidence. Policy row claimed rules are "evaluated by the gateway policy engine before every agent action". Re-derived: handle_llm_mitm (aa-proxy/src/proxy/mod.rs:806-966) has zero gateway references and refuses locally via in_tunnel_deny_reason; the gateway is reached only from handle_non_llm_mitm (:596, passed at :1098) and evaluate_mcp_request (:409). Under the llm_only default, non-intercepted hosts are transparently tunnelled and evaluated by nothing. Bounded to the paths that hold. Refs: AAASM-5612
…-contradicting Found by re-reading every cell as a guarantee. Fixing only the three that are materially false or contradict a correction already on this page; the remaining class findings are reported to the coordinator rather than fixed, because rewriting the STRIDE table wholesale edges into the restructuring reserved for AAASM-5605. eBPF Info Disclosure said "eBPF only reads SSL buffers; no access to unrelated memory regions". False: exec tracepoints capture pid, ppid, uid and filename on every execve, fork/clone is traced for descendant tracking, and a syscall tracepoint sees syscall numbers. Understating a sensor's data reach is the wrong error to make in an Info Disclosure assessment. Gateway Elevation of Privilege said "RBAC on all administrative API endpoints". The same row already notes the REST/admin surface is off by default, and under AuthMode::Off every guarded route resolves to a synthetic admin caller — so no role check applies. The cell contradicted its own row. SDK Info Disclosure said "secrets never logged" — the same unverifiable absolute already removed from Secrets management. Bounded to what exists: a redaction scanner with finite detection coverage, which is a mitigation, not a guarantee. Refs: AAASM-5612
…and is worse
My own understatement, the fourth of this ticket, and the same failure mode: I
proved AES-GCM and HSM absent, then negated the whole category.
Re-derived. A SecretsStore trait with one implementation, InMemorySecretsStore,
IS mounted in both production constructions (aa-api/src/state.rs:449;
aa-gateway/src/server.rs:693 and :850). What is absent is everything that made the
original vault claim dangerous: no encryption at rest, no HSM or KMS, no
registration route in openapi/v1.yaml and no `aasm secrets` command — so both
constructions start empty, nothing can populate them, and every ${NAME} resolves
to UnknownPlaceholder (422 / FailedPrecondition).
The part that is worse than "nothing ships": where resolution does succeed, the
resolver returns the post-substitution arguments to the caller
(aa-api/src/routes/dispatch.rs), so the agent process holds the raw credential
rather than the gateway making the outbound call. The audit entry records
placeholder names only, so the credential stays out of the audit stream — but the
agent has it.
The operative advice is unchanged and deliberately unweakened: do not treat this
stack as a secret store. Both pages updated together so they cannot diverge. No
implication either way about whether the surface gets wired or gated.
Refs: AAASM-5612
…tion, not outbound signing Confirmed backwards. The crypto table said HMAC-SHA256 "signs outbound webhook payloads so receivers can verify authenticity", and efd28b7 copied that direction into the glossary, so a pre-existing error in a row I was not correcting spread to a page I do own. Every implementation is inbound verification of provider-signed requests: aa-devtool-saas/src/signature/{claude_ai,chatgpt,cursor}.rs, wired at aa-api/src/routes/devtools/mod.rs:142 and run before the body is parsed. Checked the outbound hypothesis specifically rather than inferring absence. aa-gateway/src/alerts.rs cannot be the signer: it is a SecretAlert struct with primary_kind/redacted_label and no delivery code at all. The only reqwest client in the gateway is a local-mode probe. No x-signature emission or outbound signing secret exists anywhere in aa-*/src, while the control search for hmac users returns ten-plus files — so the probe sees HMAC fine and the absence is real. Also corrects the rotation cadence: the shared secret is issued and rotated by the sending provider, not on a 90-day cycle owned by this software. Refs: AAASM-5612
…fied behaviour Cells edited in place; no column, row or framing change. **gRPC "internal-only; never exposed directly"** — a deployment property published as a security property, and the strongest finding here. Default bind IS loopback (aa-gateway/src/main.rs:73, 127.0.0.1:50051), but --listen binds anywhere, the transport is plaintext, and PolicyService and AgentLifecycleService are mounted with enrich_interceptor (aa-gateway/src/server.rs:721,:729), which is `Ok(req)` unconditionally (iam/grpc_auth.rs:150-160) — it attaches a caller if a token resolves and proceeds regardless if not. Only audit/approval/topology/ secrets/invalidation get the fail-closed `auth`. So anyone who can reach the port calls policy evaluation and agent registration unauthenticated. Stated plainly. **"validates every agent-plane gRPC call"** — same row, same fact, corrected with it: two services never reject. **"HTTPS-only; HSTS enforced" / "DDoS mitigation via upstream load balancer"** — infrastructure that is not deployed, present tense. Removed and labelled, not softened. **"Tenant isolation enforced at API layer"** — contradicted by aa-api/src/routes/mod.rs:107, which states per-handler scope and tenant checks remain the handler's responsibility. **"certificate validation on every upstream"** — true only where the proxy dials: skip_upstream_tls_verify swaps in an accept-any verifier, and hosts transparently tunnelled under llm_only are never terminated so nothing is validated. **"CA pinning prevents MitM spoofing by agents"** — inverted the framing: the per-host cert stops a third party impersonating the proxy to an agent trusting the CA; it does nothing about an agent declining to route through the proxy. **"Input validation on all RPCs"** — bounded to per-service message-size caps and fail-closed unknown-key rejection in policy documents. Refs: AAASM-5612
The matrix row and differentiator 5 were wrong twice over, and this change
was written but never committed in the previous push.
Over-claimed: the chain is an unkeyed SHA-256 digest chain
(aa-core/src/audit.rs:10,713 — no hmac import exists anywhere in aa-core;
the only HMAC in the repo signs REST JWTs and webhooks). "HMAC-SHA256" is
wrong and "signed" is wrong — an unkeyed chain is tamper-evident, not a
signature. "Immutable" is wrong too: retention pruning deletes rows
(aa-gateway/src/storage/sqlite.rs:715, postgres.rs:854), and the DB mirror
carries no chain metadata at all (storage/audit_bridge.rs:10-12).
Under-claimed: the 🚧 marker presented this as planned Enterprise work. It
is not — AuditWriter::verify_chain (aa-gateway/src/audit.rs:142) ships today
as `aasm audit verify-chain` (aa-cli/src/commands/audit/mod.rs:14,31,44) in
the open-source build. Understating is as inaccurate as overstating, so the
capability is stated plainly rather than deleted.
Also corrects differentiator 2 ("kernel-level enforcement" -> visibility;
eBPF returns no verdict) and differentiator 3 (per-host CA -> per-host certs
from a local root CA, plus the launch/routing precondition).
Wording matches AAASM-5612 PR #134 on security-model.md so the hub does not
ship a page making this claim and a page declaring it contradicted.
Refs AAASM-5528, AAASM-5626
…the eBPF collection scope Three controls described as if active are opt-in or default-open. This is the class I caught for llm_only and gateway auth-off and then missed three times on the same page: a real control, correctly described, with the default omitted. **Syscall guard — opt-in, disabled by default.** plan_control_ops (aa-runtime/src/ebpf_control.rs:103-122) plans the guard ONLY when a confine target PID is explicitly configured AND the policy lowers to a non-empty syscall allowlist — deliberately, since loading it with an empty allowlist would SIGKILL the confined process on its next syscall. A reviewer reading a kill-switch could otherwise credit a deployment with post-escape containment it never enabled. **Proxy egress allowlist — empty by default, i.e. default-open.** aa-proxy/src/config.rs:79-92: "When empty (the default), no allowlist filter is applied"; denied_hosts is empty too, and in_tunnel_deny_reason_empty_allowlist_is_ default_open pins the behaviour. Stated in both the Boundary and Policy rows, paired with the control that IS always on: the SSRF guard, which config.rs:183 confirms production binaries can never relax. **eBPF collection scope — a false completeness claim, and a privacy one.** "Scope is limited to these probes" omitted fourteen file-I/O kprobe targets (aa-ebpf/src/kprobe.rs:145-160) whose payload carries a 256-byte path field (aa-ebpf-common/src/file.rs:88) — full file paths — plus process-exit events. TLS, file-I/O and exec all load unconditionally. A DPIA reviewer would have mis-scoped their assessment. Also corrects three verified imprecisions: the kernel floor is 5.8+ with BTF, not 5.3 (that is only the bpf_send_signal helper floor); LibreSSL is NOT blind, since discovery is a substring match on libssl.so and LibreSSL exports SSL_read/SSL_write — Node/BoringSSL is blind because static linking leaves no libssl.so mapping; and aa-api never logs query strings at all, so the redaction sentence named the wrong verb and the wrong component. Cells edited in place — table shape unchanged. Refs: AAASM-5612
…stency SSO/SAML/OIDC, SCIM and the Vault layer carry a 🗺️ marker; the AES-256-GCM retraction relied on bold prose alone, so a reader scanning for status emoji saw no marker on the one term whose correction matters most. Refs: AAASM-5612
…ne unreachable audit stream The fourth question — does the named mechanism exist, and can a released binary reach it — applied to the proxy row, which had survived a vocabulary scan, a read-as-guarantee pass and a defaults pass because it reads as competent description. **"Connection pool limits per agent; circuit breaker on upstream failure."** Neither exists. Over aa-proxy/src, case-insensitive: pool 0, circuit 0, breaker 0, semaphore 0, concurrency_limit 0 — against CertCache 11 from the same command, so the search reached the files. No tower dependency. The accept loop is an unconditional tokio::spawn per connection with no permit, counter or cap. Replaced with the true statement: nothing bounds connection concurrency. **"The proxy writes its own JSONL audit record per intercepted request."** Unreachable in any shipped build: ProxyServer::new hardcodes the sink to None, the emit path early-returns on None, ProxyConfig has no audit-path field, and the binary path (aa-proxy/src/lib.rs:70) uses ProxyServer::new. Every caller passing Some(...) is a test. An auditor told this file exists would go looking for something no deployment produces. Also bounds the Elevation-of-Privilege cell in the same row: harden_process is PR_SET_DUMPABLE=0 on Linux, best-effort and non-fatal, which blocks same-uid ptrace and core dumps. It does not make the proxy run unprivileged or restrict filesystem writes — those are deployment properties the software does not enforce. Refs: AAASM-5612
…rrected into a false weakness The page described agent-plane auth three inconsistent ways and one of them was false in the dangerous direction: "Anyone who can reach the port can call policy evaluation and agent registration unauthenticated." A public page saying anyone can register agents unauthenticated is untrue and invites a needless incident report. The interceptor split I documented was right; what none of the three statements accounted for is that the handlers self-validate. Re-derived: - register (lifecycle_service.rs:396) calls enforce_did_key_binding (:232-239), which returns Status::unauthenticated unless the agent_id's did:key encodes the supplied public key — so an agent can only register as an identity whose key it holds. It then consumes a single-use identity-bound nonce (:454) and verifies an Ed25519 possession proof (:456). - heartbeat / deregister / control_stream validate the token. - CheckAction's validate_credential_token (policy_service.rs:1110-1128) rejects an empty or mismatched token for a registered agent, constant-time. The real gap is much narrower: an UNREGISTERED caller with an EMPTY token gets a policy evaluation, with tenancy neutralised to anonymous by apply_authoritative_tenancy (:398-412) rather than taken from the request. All three places now say the same accurate thing, and the auth-flow callout gains a dedicated block since the nuance does not fit a table cell. Earlier rounds of this ticket over-stated; this one had over-corrected, which is its own defect. Refs: AAASM-5612
…rites one fixed file
Stated in three places: the Telemetry layer row, the Audit log section, and the
glossary. All three are wrong for the only path that ships.
audit_file_path (aa-gateway/src/server.rs:74-76) formats
`{agent_id}-{session_id}.jsonl`, but both shipped serve paths call
setup_audit("gateway", "default", storage) (:627, :787) — so a real gateway
produces a single gateway-default.jsonl that grows without bound. The per-session
capability exists and is never exercised in production.
This one has operational consequences rather than just being inaccurate: an
operator planning retention, rotation or per-tenant separation around per-session
files gets none of it, and finds out from disk pressure. Called out explicitly for
that reason.
Refs: AAASM-5612
…t defaults **Schema validation** — effect partially holds, mechanism was wrong. aa-api has no JSON-Schema or OpenAPI validation middleware: jsonschema 0, openapiv3 0, validate_request 0, schema_validat 0, against serde 487 and utoipa 182 from the same command. What rejects malformed input is serde deserialisation into typed extractors; utoipa generates the spec and CI checks drift. Schema-only constraints hold only where a handler hand-wrote a check — a reader relying on `pattern` or `minLength` being enforced at the edge would be wrong. **Budget caps** — the same off-by-default shape as the egress allowlist two cells away, which is why it is worth stating twice. `budget` is Option, the tracker starts with no team limit, and enforcement is limit-gated, so an undeclared budget means uncapped spend. policy-examples/audit-only.yaml says it outright — "No `budget:` clause → no spend cap is enforced" — and no shipped example sets a team limit. Also drops the conflation of budget with rate limiting in the SDK row: budgets cap spend, not request rate. Refs: AAASM-5612
…m a released build Three findings from running "does it exist, and can a released binary reach it" over the cells nobody had flagged. **The eBPF layer is unreachable from a release.** aa-ebpf-loaderd is explicitly listed in UNRELEASED_BINARIES (scripts/check-release-completeness.sh) — "not part of the release artifact set" — and release.yml builds only aa-cli, aa-gateway, aa-runtime, aa-api and aa-proxy (Linux). Without the daemon socket the runtime silently degrades to SDK-only rather than erroring, so an operator can believe the kernel layer is active when nothing was ever loaded. The page said "Linux 5.8+ with BTF" and "loaded only by a privileged system service" and never that the daemon ships from source only. Stating the fact; the packaging decision is a product ticket, not mine. **SDK integrity** was credited to this product. Cargo/npm/PyPI checksums protect the download and are the registry's control; nothing here verifies the SDK at runtime. **Credential scanner scope** — on by default, but in RedactOnly mode (redact and forward, not block), and under the llm_only default only built-in LLM hosts are intercepted, so only those are scanned. For the record on the same sweep: the Slack/webhook alerting connectors in the Telemetry row DO exist (aa-api/src/destinations/connectors/), so that cell stands unchanged. Refs: AAASM-5612
…d name the fourth question The Pagefind negative scan caught a survivor the page edits missed: the checklist's tamper-evidence row still described "the per-session JSONL files", contradicting security-model and glossary one commit after both were corrected. The register is the artifact a future maintainer consults, so a stale mechanism description there outlives the page text. Also adds the reachability rule as its own language rule. It is distinct from the three already listed — correct wording, holds-once-enabled, and on-by-default — and it is the one that found the proxy audit sink, the loaderd exclusion and the fixed-filename gateway path. All three passed every earlier check. Refs: AAASM-5612
…opped, and it is not CAP_BPF only "Loaded via CAP_BPF only; capability is dropped after program load." Both halves are false. Exhaustive search of aa-ebpf/src against a positive control of `Ebpf` = 356 hits: drop_capabilit 0, caps:: 0, capctl 0, prctl 0, PR_CAPBSET 0, setresuid 0, cap_drop 0, SECBIT 0. The only capability dependency is libc — no capability crate. The sole mentions are systemd-unit doc comments in bin/loaderd.rs:26-27, which BOUND the set at launch rather than dropping anything afterwards, and they grant CAP_BPF *and* CAP_PERFMON, so "CAP_BPF only" is wrong too. It also cannot be true by construction: control/mod.rs:5-7 describes a long-running daemon that "owns every aya::Ebpf handle and holds the only CAP_BPF / CAP_PERFMON", servicing further LoadProbeSet and UpdateSyscallAllowlist requests over its control socket. Dropping the capability after the first load would break the daemon's purpose. The replacement keeps the privilege separation that genuinely holds rather than just deleting the claim — bounding set limited to two capabilities, NoNewPrivileges, ProtectSystem=strict, sole ownership of the Ebpf handles, and a root-owned 0600 socket across which no raw fd or handle passes. Where it hid: the Spoofing and Info-Disclosure cells in this same row were both rewritten last round while this cell carried its original text through untouched. A row is not swept because some of its cells were. Refs: AAASM-5612
|
Claude Code review complete — APPROVE-READYFive independent review rounds. Recording the verdict here because GitHub does not permit the author account to approve its own pull request. Requirements. All acceptance criteria met. No public page presents unavailable SaaS functions or commitments in the present tense; Scope grew three times during the ticket, each time deliberately. It began as the three SaaS pages, then took Claim accuracy — both directions. 32 unverified SaaS operational and contractual claims were removed rather than softened: regions, tiers, quotas, SLAs with service credits, support response times, billing and procurement, DPA/BAA, SOC 2 / HIPAA / GDPR, SSO and SCIM walkthroughs. The compliance status table was removed rather than relabelled — there was no artifact to attach a Four dead or nonexistent mechanisms found and stated as such: the proxy's per-request JSONL audit sink (hardcoded One correction went the other way and mattered most. An earlier revision stated that anyone reachable on the port could call agent registration unauthenticated. That is false: Security. Compliance and legal claims removed with a publication checklist naming, per claim, the owner and the concrete evidence required to restore it — three rows marked "contradicted by the source", meaning restoration needs a code change rather than an approval. No secrets. Tests/CI. All 38 commits build; mdBook green in both languages, three drift checks exit 0, lychee 108 OK / 0 errors on the touched pages. The 10 whole-repo lychee errors were verified byte-identical at merge-base in files this PR does not touch. Pagefind: 44 pages, both languages, 31 retired phrases absent and 8 replacement markers confirmed present — an absence-only scan cannot distinguish "fixed" from "page missing". CI 3 passed / 0 failed, zero unresolved threads, no merge commits, no force-push, no Sonar/coverage: SonarCloud green; no coverage gate configured for this repository. Known limitations. Residuals stand at four claims across three files, recorded in the PR body. |



Description
Removes unverified managed-service (SaaS) operational and contractual claims from the three pages this ticket owns —
quickstart-saas.md,cloud-deployment.md,open-core-boundary.md— and adds a publication checklist that gates their return.The pages were labelled
Coming soonbut still described regions, purchasable plans and quotas, onboarding and console steps, support-response times, billing and procurement, SSO/SCIM configuration, an availability SLA with service credits, data residency, and DPA/BAA and compliance posture as though the managed service were operating and contractually defined.Coming soonabove a page that then tells a reader to enter card details, select a region, and expect a 4-hour support response does not neutralise those statements — a reader takes the commitment away regardless of the banner.Claims were removed, not softened. Rewording "99.9% uptime SLA" into "high availability" does not reduce what a reader takes away; it only makes the claim harder to audit. Where a capability is genuinely intended, it is stated as a capability area under an explicit 🗺️ Planned — not available label, with no plan, price, quota, retention period, region, availability or compliance detail attached.
Type of Change
Related Issues
agent-assemblyv0.0.1-rc.7Documentation Checklist
cd docs && mdbook buildpasses locally — no errors. Two pre-existing preprocessor version-skew warnings are emitted (mdbook-mermaidandgettextbuilt against different mdBook patch versions); they are present onorigin/maintoo and are non-fatallycheeresolves every target, and the "Related documentation" descriptors were re-read on all five pages, since lychee cannot detect a descriptor advertising content its target no longer has (this exact defect was caught in review onsecurity-model.mdand fixed in8f35626)docs/src/SUMMARY.mddocker-containers.md/self-host-observability.mdpagesProblem
Three public pages presented an unavailable managed service as an operating, contractually defined one. This is a P0 truthfulness risk and a commercial/legal exposure: prices, availability percentages, support-response times, data-residency guarantees and the availability of a DPA or BAA are representations a prospect can rely on.
Concretely, the pages contained:
Outcome
quickstart-saas.mdandcloud-deployment.mdnow state what actually ships (with links to the pages that document it), enumerate explicitly what this hub will not publish and why, and link the publication checklist.open-core-boundary.mdkeeps its open-core rationale — that argument is sound and unchanged — but its tier matrix is replaced by two sections: what is in the Apache-2.0 core today, and what is intended for the commercial tier, the latter under a Planned label with no operational or commercial detail.docs/src/saas-claim-publication-checklist.mdrecords, per claim class: the claim, why it was removed, the role that must approve restoring it, and the concrete evidence that approval requires. It also carries the language rules (no present tense for unshipped capability, label the maturity, no unqualified absolutes, no unmeasured numbers, remove rather than soften).Scope
docs/src/quickstart-saas.mddocs/src/cloud-deployment.mddocs/src/open-core-boundary.mddocs/src/security-model.mddocs/src/self-host-observability.mddocs/src/glossary.mdsecurity-model.mddocs/src/saas-claim-publication-checklist.mddocs/src/SUMMARY.mdOut of scope
Deliberately not touched, and reported to the ticket instead:
docs/src/comparison.mdanddocs/src/README.md— owned by AAASM-5528 in this same wave.docs/src/faq.md,docs/src/docker-containers.md— outside this ticket's path ownership. Residual claims found there are listed under Claim impact below.docs/po/zh-Hant.po— the translation catalog still holdsmsgstrentries for the removed English strings. They are unreachable (gettext only emits a translation when itsmsgidis still present in the source, and the zh-Hant build was verified clean), so they are stale catalog entries, not published claims. Left for amsgmergepass rather than hand-edited here.Acceptance criteria mapping
Coming soonpages do not contain fake-operational instructions or contractual promisesgit diffon the two SaaS pages7a87b82,6f98534coredocs, the SDK docs,policy-reference,docker-containers,self-host-observability) separated from a Planned sectionopen-core-boundary.md§"What is in the Apache-2.0 core today" vs §"What is intended for the commercial tier"Dependency mapping
comparison.mdandREADME.mdin the same wave. No file overlap with this PR, so the two can merge in either order; the residual claims listed below are handed to it.security-model.mdandglossary.mdstate that there is no HMAC over audit records.comparison.md— owned by 5528 — and the concatenatedprint.htmlstill say there is. Until [AAASM-5528] 📝 (docs): Bound absolute enforcement claims to verified paths #135 lands, the hub self-contradicts on a disproved cryptographic claim, which is a worse published state than either PR alone.v0.0.1/AAASM-5017/docs/dashboard_screenshots) — disjoint file sets.Architecture implications
None to the product. One documentation-architecture change:
docs/src/saas-claim-publication-checklist.mdbecomes a standing gate that future managed-service claims must pass through, sitting alongsidesource-of-truth.md(which owns the maturity-label vocabulary this PR reuses rather than inventing a parallel one). It is registered under About, with the pages it governs linking to it directly.Product-truth implications
This PR reduces the published surface substantially, and that is the point — the hub previously claimed more than the product does. The specific truth changes:
agent-assemblyworkspace are Apache-2.0" is bounded by the enumerated table directly beneath it.Security considerations
Tests
This repo has no test suite; the gates are the build and the three metadata checks. All run from the repo root unless noted.
cd docs && mdbook buildcd docs && MDBOOK_BOOK__LANGUAGE=zh-Hant mdbook build -d book/zh-Hantpython3 docs/scripts/generate_hub_components.py --checkpython3 docs/scripts/check_repo_names.py --checkpython3 docs/scripts/generate_compatibility.py --checklycheeover the seven changed pagesdocs/srcrun reports 11 errors, all pre-existing and in files this PR does not touch: root-relative/core/-style links that need--root-dir(they resolve on the deployed site) and an npmjs 403 bot-blocknpx pagefind@1.4.0 --site book, the same pinaggregate.shuses)#budget(policy-reference),#compose(docker-containers),#crate-licensingand#what-is-in-the-apache-20-core-today(open-core-boundary) all presentmarkdownlint-cli2 --config .markdownlint.jsonpolicy-reference.md,security-model.md)Claim-survival scan — re-grepped the built output, not the source, for the removed vocabulary (
99.9%,net-30,Stripe,purchase order,Sales Engineer,contact-sales,app.agent-assembly.com,community.agent-assembly.com,us-east-1/eu-west-1/ap-northeast-1,ws-<uuid4>,HIPAA,SOC 2,ISO 27001,24/7, tier names,dedicated SRE,SLA credit,credit card,Tenant ID,BudgetExceededError,Generate SCIM Token,Enforce SSO,latency=2ms, duration estimates):book/quickstart-saas.htmlandbook/open-core-boundary.html— 0 hitsbook/cloud-deployment.html— 1 hit, the "an earlier version of this page described…" transparency paragraphbook/security-model.html— 5 hits, every one inside an explicit negation ("There is no log-signing key, no HMAC…", "There is no CEF output…", "No SOC 2, ISO 27001, or equivalent audit has been performed…", "No Data Processing Agreement or Business Associate Agreement is available")book/zh-Hant/— identical resultsCEFmatching inside "gracefully" in the shared theme script. Re-run with\bCEF\bto reproduce cleanlybook/saas-claim-publication-checklist.html— 7 hits, all deliberate: the register names each removed claim class in an explicitly negating contextbook/searchindex-*.jsandbook/zh-Hant/searchindex-*.js— the only hits trace to the checklist register and to two pages outside this ticket's ownership (security-model.md,self-host-observability.md), listed under Claim impactSearch-snippet check (Pagefind). Previously skipped; now run. The 44 index
fragments were decompressed and scanned per page, attributing every hit to its
source URL:
quickstart-saas,open-core-boundary(both languages)cloud-deploymentsaml,oidcsecurity-modelsaml,oidc,cef,iso 27001,log-signing,aes-256-gcm,hsmsaas-claim-publication-checklistglossaryaes-256-gcm,hsm,log-signing,saml,oidc,scimConnection pool limits,circuit breaker on upstream,writes its own JSONL audit record,and agent registration unauthenticated,OpenAPI schema validation rejects,SDK integrity verified by,runs as unprivileged user,per-session JSON)security-model(nothing bounds connection concurrency,no proxy audit file is produced,cannot be activated from a released build,absent unless a policy declares,one fixed file, not per-session, …). Every survivingper-sessionoccurrence was inspected in context and is a negationcomparisontamper-evident signatures,saml,oidc,scimprint.htmlcomparison/glossary, not an independent sourceThe check earned its keep: it confirmed the
99.5%/99.9%figures now appearonly in the checklist register — i.e. the
self-host-observability.mdregression(R2) is genuinely gone from the search index, not just from the page body — and it
surfaced the
aes-256-gcmglossary entry, which was not in my earlier residuals list and became the final commits of this PR. A third run after the glossary fix confirmedscimalso appears onREADME,docker-containers,faqandsource-of-truth; onlysource-of-truthlabels it correctly, and the other three are already in the residuals below.CI expectations
Both workflows trigger on
pull_requesttomainwith no path filter, so both run:aggregate.yml) — clones every module, builds each generator, builds the hub at root plus zh-Hant, runs Pagefind, and fails if any module's output is missing or empty. The hub-side portion is what this PR affects and it builds clean locally on the same pinned mdBook 0.5.2 / mdbook-mermaid 0.17.0 the workflow installs. The deploy job is gated onrefs/heads/mainand will not run for this PR.hub-metadata-check.yml) — all three--checkscripts pass locally.Known environment risk: org GitHub Actions have been billing-blocked before, aborting jobs in seconds with a payments annotation. If that happens, check the run annotations before triaging it as a real failure — every gate was validated locally.
Sonar / coverage expectations
Not applicable. This repo has no SonarCloud project and no coverage reporting — it is a documentation site with no compiled or executed source. No change to either.
Documentation impact
This is the documentation change. Downstream effects:
quickstart-saas.mdno longer contains a runnable walkthrough, so readers arriving for one are routed to the SDK docs and theexamplesrepo, which own that content. This also brings the pages back in line with the repo's stated convention that the hub orients rather than restating component docs.(Coming soon)labels inSUMMARY.mdremain accurate and were left unchanged.cloud-deployment.mdsections that no longer exist. They are prose descriptions, not anchors, so no link breaks — but they are stale. Listed below.Claim impact
Removed or downgraded in this PR (file → before → after → reason):
quickstart-saas.mdquickstart-saas.mdquickstart-saas.mdquickstart-saas.mdAA_WORKSPACE_ID"from Settings → Workspace",AA_API_KEY"from Settings → API Keys"quickstart-saas.mdgateway_urldefaulting to a managed endpoint; "In the console, open Policies → New Policy"quickstart-saas.mdlatency=2msin a sample runtime logquickstart-saas.mdquickstart-saas.mdquickstart-saas.mdapp.agent-assembly.com/signup, contact-sales form,community.agent-assembly.comquickstart-saas.mdquickstart-saas.mdcloud-deployment.mdcloud-deployment.mdcloud-deployment.mdws-<uuid4>cloud-deployment.mdcloud-deployment.mdcloud-deployment.md/scim/v2endpointcloud-deployment.mdcloud-deployment.mdbudgetcloud-deployment.mdcloud-deployment.mdcloud-deployment.mdopen-core-boundary.mdopen-core-boundary.mdopen-core-boundary.mdopen-core-boundary.mdopen-core-boundary.mdopen-core-boundary.mdopen-core-boundary.mdaa-gatewaynote: "enterprise features gated behind SaaS config"open-core-boundary.mdopen-core-boundary.md|
security-model.md| Compliance table: "SOC 2 Type II — In preparation (target: Q3 2026)"; "ISO 27001 — Roadmap"; "GDPR — DPA available on request"; "CCPA — Covered under SaaS Data Processing Agreement" | Table removed; replaced with a plain statement that no certification is held and no DPA or BAA is available | No audit report, assessment scope, or executed template exists. Removed rather than relabelled Planned — a status table in a compliance section reads as a programme with a trajectory, and a target date reads as a commitment ||
security-model.md| "Every agent action (policy check, event record, budget debit) produces an immutable log entry" | The verified properties: hash-chained JSONL, append-only by convention, best-effort emission, budget debits not audited | Two prohibited absolutes, and contradicted by the source on three counts — see Evidence ||
security-model.md| "Log entries are signed with HMAC-SHA256 using a log-signing key" | "There is no log-signing key, no HMAC, no signature, and no external anchoring over audit records" | False. The mechanism is an unkeyed SHA-256 hash chain; nohmacimport exists inaa-core. This page's own cryptographic-primitives table has no audit-signing row — the two sections contradicted each other ||
security-model.md| "Logs are append-only; no delete or update API exists" | "Append-only by convention, not by constraint", naming the retentionDELETE, the backfillUPDATE, and spill-buffer eviction | False. Retention pruning deletes audit rows in both drivers; no trigger, revoked grant, or WORM setting exists; the JSONL file is appended withoutfsync||
security-model.md| "Log retention: configurable per tenant (default: 90 days)" | "An operator-set retention policy"; no default published | Per-tenant retention is a multi-tenant SaaS claim with no service behind it, and no 90-day default was found. The number is removed rather than corrected ||
security-model.md| "Logs are exportable in JSON or CEF format for SIEM integration" | "aasm audit exportwrites JSONL. There is no CEF output and no direct SIEM integration" | CEF is absent from the codebase entirely — zero occurrences. JSONL export is real, so the claim was narrowed rather than removed ||
security-model.md| Telemetry layer row: "append-only event log for every agent action" | Names the JSONL sink, the chain, the verify command, and the convention/best-effort bounds | The same claim restated in a summary table; fixed so the page does not contradict itself ||
security-model.md| STRIDE Repudiation cell: "Append-only audit log with tamper-evident signatures" | Names the unkeyed chain, the unprotected DB mirror, unaudited budget debits, and states repudiation cover is partial | "Signatures" is the HMAC claim restated inside the threat model ||
security-model.md| CTA: "Talk to the team about the STRIDE model, tamper-evident audit, and your compliance path" | Points at the audit log's actual properties; states plainly that no certification, DPA or BAA is available | Restated both unsupported claims in a sales context, where reliance is most likely ||
security-model.md| Identity layer: "operator authentication via SAML 2.0 / OIDC SSO"; STRIDE Spoofing: "SAML/OIDC token validation on every request"; an entire "Operator to console (SAML/OIDC)" section with a sign-in sequence diagram | SSO removed; replaced with the two mechanisms that ship (API key, JWT) under a Planned label telling readers not to plan an IdP integration against it | Zero occurrences ofsaml/oidc/openidinaa-api/src,aa-gateway/src,aa-auth/src; no console exists. Contradictedopen-core-boundary.mdin this same PR ||
security-model.md| STRIDE Repudiation: "Every call logged…" (SDK), "All intercepted requests logged by proxy before forwarding", "All mutating API calls logged with actor identity" | Each bounded to verified behaviour | Three surviving completeness claims that contradicted this PR's own checklist register. The proxy row now distinguishesProxyAuditEntry(separate JSONL stream, not hash-chained, lossy on write failure) from the gateway's log; the REST row notes that underAuthMode::Offthe actor is a synthetic admin caller ||
security-model.md| Secrets management: AES-256-GCM at rest, master key in a "SaaS control plane HSM", "never written to disk in plaintext", "never logged", console-driven rotation — plus a Vault crypto-table row and the IronClaw Vault layer row | Whole block removed; replaced with an explicit "do not treat this stack as a secret store" warning | Noaes/gcmimplementation in anyaa-*/src(the single match is a ChaCha CSPRNG comment) and noaes-gcmdependency; no HSM or KMS reference anywhere; no console. Two bullets were also unqualified "never" guarantees. Most consequential removal on the page — a reader could have concluded the stack safely stores their LLM API keys ||
security-model.md| "Kernel event timestamps are monotonic; cannot be retroactively altered" | Bounded — a monotonic clock prevents reordering via wall-clock change, not post-hoc edits | Conflated a clock property with record integrity, in the Repudiation column where the distinction decides whether the control is load-bearing ||
security-model.md| "No MD5, SHA-1, or DES primitives are used anywhere in the stack" | Bounded to the primitives in the table and to first-party workspace code, with the limit stated | Unverifiable as written — "anywhere in the stack" reaches the whole transitive dependency tree, which this hub does not audit ||
security-model.md| Related documentation: "Cloud deployment — SSO configuration, SCIM provisioning"; "Open core boundary — which security features are OSS vs. enterprise" | Descriptors rewritten; checklist and source-of-truth added | Both advertised content this PR deleted.lycheecannot catch this class — the target resolves; the descriptor is what lies ||
self-host-observability.md| "The uptime SLAs (99.5% / 99.9%), on-call rotation, and managed compliance posture described in Cloud Deployment apply to the cloud only" | "There is no availability, on-call, or compliance commitment from anyone" — for either deployment | A regression this PR introduced. Cloud Deployment no longer describes them, so the two SLA figures this ticket set out to delete survived only here ||
glossary.md| AES-256-GCM — "used here to encrypt stored secrets at rest"; the IronClaw entry listing Vault among five layers with no caveat | Term retained carrying its own retraction: "AI Agent Assembly does not use it"; Vault labelled aspirational | The glossary was the last place in the hub asserting the vault is real, and a glossary is where a reader goes to confirm a term is a product concept rather than marketing. Wording matched tosecurity-model.mdso two pages cannot describe the same non-existent mechanism differently ||
glossary.md| Ed25519 — "used here to sign agent identity tokens" | "One-time possession proof over a server-issued nonce at registration"; notes the ongoing credential is a random token | Misdescribed the mechanism's role, implying a reusable signing credential ||
glossary.md| HMAC-SHA256 — "used to sign audit-log entries and webhook payloads" | Names the JWT and webhook uses; states explicitly it is not used on audit-log entries | The webhook half was right, the audit half is the disproved log-signing claim ||
glossary.md| Audit log — "append-only record of every agent action (policy checks, events, budget debits)"; "tamper-evident signed (HMAC-SHA256) is an Enterprise capability" | States the four bounds: unkeyed chain, unverifiable DB mirror, append-only by convention, best-effort emission with budget debits unaudited | Four errors in two sentences — "every", budget debits, HMAC signing, and an Enterprise framing for a mechanism that partly ships in the OSS build ||
glossary.md| SSO / SAML 2.0 / OIDC — "used to let operators log in with an enterprise identity provider"; SCIM unlabelled beside it | Both carry the Planned label; SSO names what operators actually authenticate with | Present tense for an unimplemented capability. SCIM made no direct claim but would have read as available by contrast with a corrected neighbour ||
security-model.md| IronClaw Boundary: eBPF sensor "catches kernel-level bypass attempts" | Names the platform (Linux only), separates observation from enforcement, and states the kill-after-syscall race | Prevention inferred from observation, unbounded by platform. The SSL uprobes are observe-only and blind to Go,rustls, Node/BoringSSL, GnuTLS, NSS and LibreSSL; the one enforcing probeSIGKILLs after the offending syscall has executed, and never inspects unmonitored PIDs ||
security-model.md| IronClaw Policy: rules "evaluated by the gateway policy engine before every agent action" | Bounded to the paths that reach the gateway |handle_llm_mitm(aa-proxy/src/proxy/mod.rs:806-966) has zero gateway references and refuses locally viain_tunnel_deny_reason; the gateway is reached only fromhandle_non_llm_mitm(:596, passed at:1098) andevaluate_mcp_request(:409). Under thellm_onlydefault, non-intercepted hosts are transparently tunnelled and evaluated by nothing ||
security-model.md| STRIDE: eBPF "only reads SSL buffers; no access to unrelated memory regions" | Names what the probes actually read | False. Exec tracepoints capture pid, ppid, uid and filename on everyexecve, fork/clone is traced, and a syscall tracepoint sees syscall numbers. Understating a sensor's reach is the wrong error in an Info Disclosure cell ||
security-model.md| STRIDE: "RBAC on all administrative API endpoints" | Qualified to "only when auth is enabled" | Contradicted its own row: the gateway REST/admin surface is bypass-by-default and underAuthMode::Offevery guarded route resolves to a synthetic admin caller ||
security-model.md| STRIDE: "secrets never logged" | A redaction scanner with finite detection coverage — a mitigation, not a guarantee | The same unverifiable absolute already removed from Secrets management ||
security-model.md,glossary.md| "No secret store, encryption-at-rest, or key-management component ships today" | A store exists and is mounted, is always empty, nothing can populate it, and successful resolution returns plaintext to the caller | My own understatement, corrected.InMemorySecretsStoreis wired ataa-api/src/state.rs:449andaa-gateway/src/server.rs:693/:850, all empty; no registration route inopenapi/v1.yaml, noaasm secretscommand, so every placeholder yieldsUnknownPlaceholder(422 /FailedPrecondition). Where resolution succeeds the resolver hands post-substitution args back to the caller, so the agent holds the raw credential. Narrower and more damning than the negation ||
security-model.md,glossary.md| HMAC-SHA256 "signs outbound webhook payloads" | Inbound verification of provider-signed webhooks; no outbound signing path exists | Directionally backwards, andefd28b7propagated it from a table row I was not correcting into a page I own. All implementations are inbound (aa-devtool-saas/src/signature/{claude_ai,chatgpt,cursor}.rs, wired ataa-api/src/routes/devtools/mod.rs:142).aa-gateway/src/alerts.rscannot be the signer — it is aSecretAlertstruct with no delivery code ||
security-model.md| STRIDE: "Internal-only gRPC endpoint; never exposed directly" | States that reachability is the only control and that it is the operator's to enforce | The strongest finding on the page. Default bind is loopback (aa-gateway/src/main.rs:73), but--listenbinds anywhere, the transport is plaintext, andPolicyService+AgentLifecycleServiceare mounted withenrich_interceptor(server.rs:721,:729), which isOk(req)unconditionally (iam/grpc_auth.rs:150-160). Only audit/approval/topology/secrets/invalidation get the fail-closed interceptor. Anyone who can reach the port can call policy evaluation and agent registration unauthenticated ||
security-model.md| STRIDE: "Credential-token interceptor validates every agent-plane gRPC call" | Names the five fail-closed services and the two that never reject | Same row, same fact — the parenthetical already listed the fail-closed set, contradicting its own "every" ||
security-model.md| STRIDE: "HTTPS-only; HSTS enforced" | 🗺️ Not provided by this software — it serves HTTP; TLS termination and HSTS are the deployment's job | Infrastructure that is not deployed, in the present tense. Removed and labelled, not softened ||
security-model.md| STRIDE: "DDoS mitigation via upstream load balancer" | Per-key rate limiting inaa-authis real; 🗺️ there is no DDoS mitigation and no load balancer | Same — described infrastructure that does not exist ||
security-model.md| STRIDE: "Tenant isolation enforced at API layer; cross-tenant access rejected" | 🗺️ Not enforced at the API layer | Contradicted byaa-api/src/routes/mod.rs:107: the gate verifies an API key or JWT, and "per-handler scope and tenant checks remain the handler's responsibility" ||
security-model.md| STRIDE: "Per-host CA pinning prevents MitM spoofing by agents" | Framing inverted: the per-host cert stops a third party impersonating the proxy to an agent that trusts the CA | It does nothing about an agent declining to route through the proxy — which is the threat the wording implied it covered ||
security-model.md| STRIDE: "certificate validation on every upstream" | Bounded to connections the proxy dials |skip_upstream_tls_verifyswaps in an accept-any verifier (proxy/mod.rs:384, integration tests), and hosts transparently tunnelled under thellm_onlydefault are never terminated, so the proxy validates nothing on them ||
security-model.md| STRIDE: "Input validation on all RPCs" | Per-service decoded-message-size caps, plus fail-closed unknown-key rejection in policy documents | Real controls, but not blanket validation of every RPC ||
security-model.md| Boundary layer: the eBPF syscall guard described as enforcing, with no mention that it is off | "Opt-in and disabled by default", naming both conditions required to enable it | Off-by-default control described as active.plan_control_ops(aa-runtime/src/ebpf_control.rs:103-122) plans the guard only when a confine-target PID is configured and the policy lowers to a non-empty allowlist — deliberately, since an empty allowlist wouldSIGKILLthe confined process on its next syscall. A reviewer would otherwise credit a deployment with post-escape containment it never turned on ||
security-model.md| Boundary and Policy layers: the proxy "enforces an egress allowlist" / "applies a local in-tunnel egress allowlist" | Both now say the allowlist is empty by default — default-open, paired with the SSRF guard that always applies | Same class.aa-proxy/src/config.rs:79-92: "When empty (the default), no allowlist filter is applied";denied_hostsis empty too;in_tunnel_deny_reason_empty_allowlist_is_default_openpins it. The always-on control is the SSRF guard, whichconfig.rs:183confirms a production binary can never relax ||
security-model.md| STRIDE: "Scope is limited to these probes" after listing SSL, exec and syscall probes | Enumerates the file-I/O kprobes and their 256-bytepathfield, process-exit events, and which probes load by default | A privacy claim, and false.aa-ebpf/src/kprobe.rs:145-160registers fourteen file-I/O kprobe/kretprobe targets (openat,read,write,unlinkat,renameat2+ legacy) whose payload carriespath: [u8; 256]— full file paths (aa-ebpf-common/src/file.rs:88). TLS, file-I/O and exec all load unconditionally. A DPIA reviewer would have mis-scoped their assessment ||
security-model.md| "(Linux 5.3+)" | "Linux 5.8+ with BTF" | 5.3 is only thebpf_send_signalhelper floor; the product requires 5.8+ withCONFIG_DEBUG_INFO_BTF=y(aa-ebpf/src/loader.rs:32), andssl_probes.rsstates "we require 5.8+" verbatim. The guard can never run on 5.3–5.7 ||
security-model.md| "blind to Go,rustls, Node/BoringSSL, GnuTLS, NSS and LibreSSL" | Blind to stacks that neither exportSSL_read/SSL_writenor load as alibssl.somapping — Go,rustls, GnuTLS, NSS, and statically-linked BoringSSL as in Node | My error. Discovery is a substring match onlibssl.so(uprobe.rs:199) and attachment is bySSL_read/SSL_writesymbol — LibreSSL is an OpenSSL fork exporting exactly those and installing aslibssl.so.<ver>, so it is hooked. Node/BoringSSL is blind, but from static linking leaving nolibssl.somapping, not from symbol naming. Also notes that onlySSL_read/SSL_writeattach, so OpenSSL 3.x_excallers are missed, and only the first matching mapping is used ||
security-model.md| "Query strings carrying credential-shaped values are redacted before logging" | Query strings are not logged at all — the tracing span records method, path and request id, and.path()excludes the query | Right outcome, wrong verb and wrong component:redact_targetisaa-proxy's and is a no-op when its scanner is disabled.aa-api/src/middleware/tracing.rsnever touches the query string ||
glossary.md| AES-256-GCM retraction carried no status marker | 🗺️ marker added | Consistency — SSO, SCIM and the Vault layer all carry one, so an emoji-scanning reader saw no marker on the term whose correction matters most ||
security-model.md| Proxy DoS: "Connection pool limits per agent; circuit breaker on upstream failure" | "Nothing bounds connection concurrency" | Neither mechanism exists. Overaa-proxy/src, case-insensitive:pool0,circuit0,breaker0,semaphore0,concurrency_limit0 — againstCertCache11 from the same command, so the search reached the files. Notowerdependency. The accept loop spawns an unbounded task per connection ||
security-model.md| Proxy Repudiation: "The proxy writes its own JSONL audit record per intercepted request" | 🗺️ No proxy audit file is produced by any shipped build | Unreachable in a release.ProxyServer::newhardcodes the sink toNone, the emit path early-returns,ProxyConfighas no audit-path field, and the binary path (lib.rs:70) usesnew. Every caller passingSome(...)is a test. An auditor would look for a file no deployment produces ||
security-model.md| Proxy EoP: "Proxy runs as unprivileged user; no write access to host filesystem" |PR_SET_DUMPABLE=0on Linux (best-effort); running unprivileged and restricting writes are deployment responsibilities |harden_processsets only the dumpable flag, blocking same-uid ptrace and core dumps. It does not enforce either published property ||
security-model.md| Agent-plane auth, stated three inconsistent ways — including "anyone can call policy evaluation and agent registration unauthenticated" | One accurate statement in all three places, plus a dedicated callout block | This one was false in the dangerous direction — the previous round over-corrected.registerenforces a did:key binding (Unauthenticatedunless theagent_id's did:key encodes the supplied public key), a single-use identity-bound nonce, and an Ed25519 possession proof;CheckActionrejects a missing or mismatched token for a registered agent. The real gap is narrow: an unregistered caller with an empty token gets an evaluation, tenancy neutralised to anonymous ||
security-model.md,glossary.md,saas-claim-publication-checklist.md| "per-session JSONL" audit files (three pages) | "A shipped gateway writes one fixedgateway-default.jsonl" | Both shipped serve paths callsetup_audit("gateway", "default", …)whileaudit_file_pathformats{agent_id}-{session_id}.jsonl. The per-session capability exists and is never exercised. Operational, not cosmetic: retention, rotation and per-tenant separation planned around per-session files do not exist ||
security-model.md| REST Tampering: "OpenAPI schema validation rejects malformed inputs" | serde deserialisation into typed extractors;utoipagenerates the spec, CI checks drift | Effect partially holds, mechanism wrong. No validation middleware:jsonschema0,openapiv30,validate_request0,schema_validat0, againstserde487 /utoipa182. Schema-only constraints (minLength,pattern,enum) hold only where a handler hand-wrote a check ||
security-model.md| Budget caps as an active DoS control (two cells) | Enforced only where a policy declares a limit | The same default-open shape as the egress allowlist two cells away.budgetisOption, the tracker starts with no team limit, enforcement is limit-gated, and no shipped policy example sets one —policy-examples/audit-only.yamlsays so outright ||
security-model.md| eBPF Spoofing: "loaded only by privileged system service" | Adds that the daemon is not in the release artifact set |aa-ebpf-loaderdis listed inUNRELEASED_BINARIES;release.ymlbuilds onlyaa-cli,aa-gateway,aa-runtime,aa-api(+aa-proxyon Linux). The eBPF layer cannot be activated from a released build, and without the daemon socket the runtime silently degrades to SDK-only rather than erroring ||
security-model.md| SDK Tampering: "SDK integrity verified by Cargo/npm/PyPI package hash" | Attributed to the registry, not to this product | A registry control over the download; nothing here verifies the SDK at runtime ||
security-model.md| Proxy Info Disclosure | Adds that the credential scanner is on by default but inRedactOnlymode, and underllm_onlyonly built-in LLM hosts are scanned | Scope clause the cell lacked ||
security-model.md| eBPF EoP: "Loaded via CAP_BPF only; capability is dropped after program load" | The unit grantsCAP_BPFandCAP_PERFMON, and the daemon retains them for its lifetime; states the privilege separation that does hold | Both halves false. Exhaustive search ofaa-ebpf/srcagainst a positive control ofEbpf= 356:drop_capabilit0,caps::0,capctl0,prctl0,PR_CAPBSET0,setresuid0,cap_drop0,SECBIT0; onlylibcin deps. The sole mentions are systemd-unit doc comments (bin/loaderd.rs:26-27) that bound the set at launch. It also cannot be true by construction —control/mod.rs:5-7describes a long-running daemon that "owns everyaya::Ebpfhandle and holds the onlyCAP_BPF/CAP_PERFMON" and services further load requests, so dropping after the first load would break it |Residual claims found in files this ticket does not own — for AAASM-5528 / AAASM-5613:
comparison.md:19comparison.md:55comparison.md:82README.md:45faq.md:38-39docker-containers.md:374source-of-truth.mdis already correct — Cloud, Enterprise and Operations are all labelled 🔒 Private / 🗺️ Planned — and needed no change. This PR reuses its label vocabulary rather than inventing a second one.Evidence
app.agent-assembly.com/signup,app.agent-assembly.com/contact-sales,api.agent-assembly.com/,community.agent-assembly.com/all return HTTP 530 (Cloudflare: no origin). Their DNS resolves only to a Cloudflare wildcard shared with arbitrary non-existent subdomains.agent-assembly.com/early-accessreturns 200, so the early-access CTA was retained.policy-reference.mddocuments the enforced schema as per-agent and per-organisation USD limits on daily/monthly boundaries. Two published pages disagreed about the same feature; the one with a schema behind it wins.docs/book/, not in the source, so the check covers what a reader actually receives. See Tests.Evidence — audit-log claims verified against the source
The security-model corrections were checked against the Apache-2.0
agent-assemblytree, notagainst the surrounding prose. Summary of what is actually there:
aa-core/src/audit.rs—entry_hashover the entry's fields plusprevious_hash. Nohmacimport inaa-core; the only HMAC in the repo is unrelated (webhook signature verification)AuditWriter::verify_chaininaa-gateway/src/audit.rs, exposed asaasm audit verify-chain <file>(aa-cli/src/commands/audit/verify_chain.rs), which exits non-zero and names the first bad indexseq,previous_hash,entry_hash, and neitheraudit_events(gateway migration + SQLite driver) noraudit_logshas a column for themDELETE FROM audit_events WHERE ts < …in both the Postgres and SQLite drivers (retention pruning); anUPDATE audit_logsbackfill migration; spill-buffer eviction on cap. No trigger,REVOKE, or WORM setting anywhere in the migrationstry_sendonto a bounded channel; on backpressure the entry is dropped, counted inaudit_drops, and the response still returnsOk. Nofsync, so a crash before flush loses buffered entriesBudgetLimitApproached/BudgetLimitExceededappear only as enum variants, string mappings, read-side filter parsing, a proptest generator, and tests — never constructed. No debit emits an entryExportFormathas three variants —Csv,Json,Jsonl(aa-cli/src/commands/audit/models.rs) — plus a separateaasm audit compliance-exportcommand.--formatis required with no default. Zero occurrences of CEF anywhere74fa595One consequence worth flagging to reviewers, now documented on the page: because a
dropped entry still advances the chain head,
verify-chainreports failure for agap caused by backpressure exactly as it would for a malicious edit. A verification
failure means "investigate", not "compromise".
Compliance evidence searched for and not found. No audit report, certificate,
assessment scope, or executed DPA/BAA template exists in either the public monorepo
or the private control-plane repo. The only in-repo hits were an ADR asserting the
posture is "clean" — a doc claim, not evidence — and an
aasm audit export --compliance soc2flag that prepends a metadata header to an export file, which isa formatting option rather than an attestation.
Migration behavior
No migration. Documentation-only, no URLs removed, no pages deleted, no redirects needed:
SUMMARY.mdpositions, so every existing inbound link and bookmark still resolves.#sso-configuration,#scim-user-provisioning,#budget-configuration, and the tier anchors on the quick start). No page in this repo links to any of them — verified by grep — so no internal link breaks. An external deep link to one of those anchors will land on the page top, which now carries the Planned banner.Rollback strategy
git revertany commit independently — the commits touch largely disjoint hunks and each was verified to build on its own. Two pairs are ordered:62d3d9csupersedes6a1f876, and74fa595supersedes part of25e3197, so revert the later of a pair before the earlier. Reverting the whole PR restores the previous pages exactly.Rolling back is not a neutral act here: it re-publishes the claims this PR removed, so it should be treated as a decision requiring the same approval the checklist demands for publishing them in the first place, not as routine remediation. If a specific removal turns out to be wrong, the intended path is to restore that one claim through the checklist with its evidence attached.
Known limitations
quickstart-saas.mdno longer contains a walkthrough. That is the correct state while the service does not exist, and AAASM-5613 will add labelled design previews on top — but a reader arriving today gets orientation and links rather than a tutorial.comparison.md,faq.md,docker-containers.md) — down from twelve, assecurity-model.md,self-host-observability.mdandglossary.mdcame into scope and were fixed here.62d3d9cfixes6a1f876, which both under-stated the audit chain (calling it unverifiable whenaasm audit verify-chainships) and repeated a false claim that budget debits are audited. The correction is a follow-up commit rather than a rewrite, so the mistake stays visible in review.msgmergepass would mark them obsolete.markdownlintis not wired into this repo's CI, so the informational findings above are unenforced either way. The new page matches the existing table and footer style, including its pre-existing rule violations.