Afunana reads the legacy source that runs the business — often an institution's most sensitive asset — so security is a design constraint, not a bolt-on. The platform runs inside the customer's own network; nothing about the analyzed systems leaves that boundary unless the customer explicitly configures an outbound integration (an LLM provider, a SIEM, or the source system). Compliance & Standards maps these controls to OWASP, ISO/IEC 27001, and SOC 2.
Signing in, and what each person can see
Two questions are answered separately on every request: what this person is allowed to do, and which systems they are allowed to see it for. Both have to pass. Sessions expire and can be revoked, people can sign in through the organisation's own identity provider, and AI tools and scripts go through the same checks as the browser.
Authentication
Sessions are carried by HS256-signed JWTs. The signing key must be at least 256 bits and is a bootstrap secret — from a mounted container secret or the environment, never the database — and the process refuses to boot if it is unset or too short.
Passwords are hashed with bcrypt (per-password salt) with enforced complexity, and are never logged or stored in plaintext; reset tokens are kept only as SHA-256 hashes with a short expiry. Against enumeration, the forgot-password flow returns a generic response whether or not the address exists, and login equalizes response timing for unknown-user and wrong-password outcomes. Repeated failed logins lock the account; that lockout is persisted, survives a restart, is audited, and guards the SSO callback path.
Session management
Two independent clocks bound every session: an idle timeout that expires a token after a configurable period of inactivity, and an absolute lifetime (configurable, shipped at 8 hours) enforced on every refresh and not extendable. Revocation is per-token on logout, checked on every authenticated request, and per-user by an administrator, who can also force every user to re-authenticate. Both are audited, and logout propagates across browser tabs.
Roles, and the data a role reaches
Authorization answers two independent questions, and both must pass before a request succeeds.
What may this person do? Every user holds one role, and a role is a named bundle of permissions — collection.view, collection.build, collection.delete, source.write and so on. Six roles ship: admin (everything), pgmr (defines and builds collections, edits source, administers nobody), qa (adds the review screens and the audit log), analyst (reads and asks questions), user (the default), and viewer (read-only, with no chat, so it spends nothing on the LLM). A single permission can be granted or denied to one person on top of their role, for the exception that does not justify a new role.
Which data may they see? Separately, a person is given collections — either one at a time, or a whole application, which reaches every collection filed under it and under anything nested inside it, including collections created later. The role decides what kind of operation is allowed; this decides which data it may touch. Neither substitutes for the other: a permission to delete a collection does not say which collections exist for that person.
An application is a named business division of the estate — Life insurance, and inside it Life calculations. Applications nest, and a grant travels downward only: holding Life holds Life calculations, and holding Life calculations holds nothing above it.
Access can be handed out by the customer's own directory rather than maintained here. A mapping row of the form "anyone in group Claims-IT holds the Claims application" is re-evaluated at every login, and it withdraws access as well as granting it — so a person who transfers out of a department loses that department's documentation at their next sign-in, with nothing entered on our side.
The backend is authoritative: every protected endpoint declares its guard server-side and requests without it return 403; frontend route guards are usability only. The permission check fails closed — if the tables cannot be read the answer is no, because admitting somebody on the strength of a broken query is how an authorization check becomes decorative. An administrator cannot be denied the two permissions that would prevent them from reversing the denial.
Single sign-on
Afunana supports OpenID Connect (OIDC), used with Azure AD (Microsoft Entra ID), Okta, Google Workspace, and generic providers. ID tokens are validated against the provider's published JWKS keys with issuer, audience, and expiry checks; one whose signature cannot be verified is rejected, with no fall-through to an unverified decode. Discovery is SSRF-guarded — a discovery or JWKS URL must be HTTPS and resolve to a public address, so it cannot be pointed at internal services. Each login mints a random, single-use, time-boxed state parameter and a nonce, defeating CSRF and replay.
Afunana also supports SAML 2.0 as a service provider, for the many enterprises that federate applications to an internal ADFS or a SAML tenant rather than issuing OIDC registrations. Assertions are verified against the certificate the administrator configured, never one carried inside the response, and everything read afterwards comes from the subtree that signature actually covered — which makes XML Signature Wrapping structurally impossible rather than a check that has to be remembered. Beyond the signature, an assertion is accepted only if its issuer, audience, recipient and destination all name this install, it is inside its validity window, it answers an authentication request this install issued, and its ID has not been seen before. XML is parsed with entity resolution, DTD loading and network access disabled, and a document carrying a DOCTYPE is refused outright.
Both protocols resolve through one identity layer, so a person is joined to their provider by its immutable subject rather than by email, and directory group membership can grant roles and collection access as data an administrator edits rather than a code change. Auto-provisioning is optional in both and its default role can never be admin, either can be restricted to an email-domain allow-list, and every step is audited.
Request defences
For the local (non-SSO) session, CSRF defense uses Origin validation on every mutating request, checked against the configured CORS origins, the frontend URL, and the request's Host header; a mismatched or missing Origin is rejected. Rate limiting is per client and returns HTTP 429 when exceeded, strictest on the authentication endpoints — it throttles volume from an address, while lockout stops guessing against an account.
Security headers
The application sets these headers in middleware, not the proxy, so they apply even behind a customer's own reverse proxy.
| Header | Value | Purpose |
|---|---|---|
Content-Security-Policy |
Restrictive policy | Limits script/style/connect sources |
Strict-Transport-Security |
max-age=31536000 |
Forces HTTPS for one year |
X-Frame-Options |
DENY |
Prevents clickjacking |
X-Content-Type-Options |
nosniff |
Prevents MIME sniffing |
Referrer-Policy |
strict-origin-when-cross-origin |
Limits referrer leakage |
The audit trail
Every security-relevant action is written to a log the database will not let anyone edit or delete, and each record is chained to the one before it, so a change to any historical record can be detected. Nothing is forwarded outside the machine unless an administrator turns it on.
The audit trail
The audit store is INSERT-only and tamper-evident. Each record's integrity hash covers the previous record's hash plus its own identifying fields, forming a SHA-256 hash chain: altering or deleting any historical record breaks the chain from that point forward, detectably. Immutability is enforced at the database layer, not just in application code — a trigger blocks any modification or deletion, and the only writer that may bypass it is the scheduled retention purge, which does so briefly and under audit.
Events are categorised as authentication, authorization, data access, chat, administration, system, and integrity — each attributed to an actor, IP address, user agent, target, and outcome. Chat query text is logged only when explicitly enabled, off by default for privacy.
Retention is configurable and defaults to 365 days. A scheduled daily purge removes only records past that window, and where SIEM forwarding is enabled it removes only records already confirmed forwarded — so nothing is discarded before it has reached the customer's own retention system.
Events can be forwarded to a customer SIEM in real time, before the local insert, precisely because the immutability trigger forbids amending a record afterwards. Transports are syslog (RFC 5424) over TLS or an HTTPS webhook; formats are CEF or JSON. A scheduler verifies chain integrity hourly and in full daily, reporting where the chain first breaks; failures raise a critical event that forwards to the SIEM.
Outbound log streams
Two independent outbound doors exist, and both are OFF by default — nothing leaves the box unless an operator turns it on. They are configured through different mechanisms on purpose:
- Audit forwarding to a SIEM — the door described above. It is controlled by settings held in the application database, edited on Configuration → Audit & SIEM, because audit events are a database concern.
- Application-log export — ships the ordinary application log stream to a collector over the same transports and formats. It is controlled by environment variables, not the database, because application logging must be running before the database is reachable. A bad address is reported and the application still starts; a mistyped collector never blocks an install.
Both doors support TLS and mutual TLS, field-level redaction and pseudonymization before egress, and multiple destinations. They share one field set and identity, so a receiver can correlate an audit event with the application-log lines from the same request.
Secrets, encryption and hardening
The source code Afunana reads is often the most sensitive asset an organisation holds, and it stays inside the organisation's own network. This section covers where credentials come from, what is encrypted in transit and at rest, how the installation itself is hardened, what an air-gapped site gives up, and how machines rather than people are authenticated.
Secret management
Secret resolution follows two deliberately different precedence chains. Bootstrap secrets — database connection settings, the token signing key, the server's role designation — come from a mounted container secret or an environment variable and never from the database: they unlock the database itself, so they cannot depend on it. Startup fails loud if one is missing.
General secrets — API keys, SMTP credentials, SIEM tokens — resolve through a layered hierarchy: an external secret vault (CyberArk CCP or HashiCorp Vault) first when configured, then a writable on-box layer allowing rotation from the admin UI with no restart, then mounted container secrets, then database configuration, then the environment. Secrets are never logged, and sensitive values are masked in audit detail.
Encryption
In transit. TLS everywhere: the bundled proxy terminates TLS with automatic certificates and enforces HSTS (max-age=31536000), an external proxy to the same standard, and database connections and outbound integrations alike require encrypted transport.
At rest. SQL Server Transparent Data Encryption (TDE), AES-256. In bundled-DB mode TDE is automatically enabled, encrypting files and backups. In external-DB mode the database is the customer's, so TDE, or an equivalent at-rest control, is the customer's responsibility to configure; Afunana cannot enable it for them. This matters for audit evidence: only bundled-DB deployments can point to Afunana as the party that enabled at-rest encryption.
Platform hardening and vulnerability management
The application runs as an unprivileged service account, never root; the production image excludes build tooling; data services are reachable only on the internal container network; and in bundled mode it uses a least-privilege database login.
Every deploy runs dependency CVE scanning for Python and JavaScript plus static analysis of Python source; scans are non-fatal, recorded but not blocking. The code-quality check catalog lives in configuration rather than code, so changes to it are governed by the same admin authorization and audit logging as all other configuration.
On-premise and air-gapped operation
Afunana runs inside the customer's perimeter with zero vendor access. In fully air-gapped mode, every LLM call routes to a local model, embeddings come from a local open-source model, and the platform installs from an offline image bundle against an internal registry mirror. No data — and no LLM prompt — leaves the box.
AI tools and the API
The Model Context Protocol server (over stateless Streamable HTTP) is protected by OAuth 2.0 Authorization Code with PKCE, so external assistants connect under the same identity model as the web UI. PKCE (S256) is required on every flow, every request is bearer-JWT validated, and the implementation follows RFC 8414, RFC 9728, and RFC 7591.
The server is mounted at /mcp (Streamable HTTP transport); a wrapper rejects any request without a valid bearer JWT before the MCP app is reached. Beyond bearer validation, each tool invocation is scoped to the collections carried in the caller's token and refuses anything outside that list, so an MCP client cannot read another tenant's collection.
Which AI models run, and who approved them
Afunana uses language models to write the documentation. Most organisations running it therefore have to be able to answer three questions: which models are involved, what they are given, and who inside the organisation approved that. Afunana keeps those records itself and generates them from the running software, so they describe what the product is actually configured to do rather than what a policy document says it does.
Nothing here is a legal opinion, and no software can decide that a requirement has been met. What it does is hold the record: who decided, when, on what basis, and what the system reported at the time.
The register of AI uses
Every place the software calls a language model is listed. The list is generated from the router — from what the software actually does — so it cannot claim a use that is not configured, and it cannot omit one that is. Each entry carries the technical half and the governance half side by side:
- What it is for, and separately what it must not be used for. The second is the half people leave blank and a reviewer reads first.
- What data it sees. For almost everything here that is source code and the documentation derived from it.
- Who owns it — a named person, not a team.
- Which model chain serves it, in order, primary and fallbacks, and whether each connection stays on the machine or reaches off it.
- What it actually cost, read from the call ledger rather than from configuration.
- What is still missing before it could be approved, named item by item rather than shown as a colour.
Who is accountable
Accountability for what an AI system does cannot be delegated to a supplier, and under most financial-sector supervision it cannot be limited by contract either. The roles a deployment needs are derived from how it was classified, and each is held by a named person with a date and an appointer. An appointment nobody has confirmed is recorded as unconfirmed.
Classification and impact assessment
Each use is placed in a risk tier by answering five questions in the order the EU AI Act resolves them: prohibited practice first, then the high-risk uses listed in Annex III, then transparency, then whatever is left. The first answer that forces a tier decides it — this is not a score — and the answers are stored alongside the conclusion, so the decision can be re-read next year.
Every use above minimal risk then carries an impact assessment covering what it touches, what it is given, how wrong it can be, and what stands in the way. It is scored on a fixed scale so two years can be compared, it names who carried it out, and it expires. Two rules apply: an unanswered question counts as its worst answer, so a half-filled assessment reads as high risk rather than low; and the person who carried out an assessment can never sign it off.
The part the software enforces
Configuration says which model an administrator wants a role to run. A separate approved list says which models are permitted, and the router resolves one against the other at call time — an unlisted model is skipped and named in the log. Both halves are needed for enforcement to begin, so a list somebody is still drafting cannot start refusing work.
Changes can be made to require a second administrator. The change waits as a request carrying the benchmark it was sent with, so the approver reads the evidence rather than taking the requester's word for it, and the person who asked for a change can never approve it. Applying a change is a separate step, and a request that has gone stale — because the value moved after it was raised — is refused rather than silently overwriting the newer value.
What you can hand over
- The governance report — where the programme stands, who is accountable, the register with its classifications and assessments, the position against each framework, the incident log, the approved list and the change history. One self-contained file that prints without network access.
- A statement of applicability, per framework.
- The egress attestation — which AI providers were actually called, local and external, built from the call ledger rather than from configuration. It reports what was called in the window; a quiet period proves nothing was sent then, not that a route does not exist.
- The incident and concern log, with severity, who was told outside, and what was done. Closing an entry requires saying what was done, because a closed entry with no resolution records only that somebody stopped looking.
What is switched on when it arrives
Nothing. The approved list ships empty, and an empty list means no policy rather than nothing permitted. Both gates ship off. No framework is declared in scope until somebody chooses one. A governance feature that broke an installation the day it shipped would be switched off before anybody read it, and a switched-off control leaves no trace at all.
Standards and regulations
Two sets of rules apply to a product like this: those that govern the software itself, and those that govern the use of AI. Both are held the same way. Each requirement is a row carrying two things side by side — the position your own people recorded, and what the running system reports about the same question. The two are never merged. Where a requirement is recorded as met but the software reports otherwise, the row is flagged and counted: either the evidence lives outside the software, or the record is wrong.
For the software itself
- OWASP Top 10 (2021) — application-security risk categories
- ISO/IEC 27001:2022 — Annex A information-security controls
- SOC 2 Type II — AICPA Trust Service Criteria
For the AI
| Framework | What it covers |
|---|---|
| ISO/IEC 42001:2023 | The certifiable AI management standard. Clauses 4 to 10 and all 38 Annex A controls, exportable as a statement of applicability. |
| EU AI Act | Article by article, including the twelve deployer obligations of Article 26 broken out separately. Each row says whether the deployer or the provider carries it. |
| NIST AI RMF 1.0 | Govern, Map, Measure and Manage, at category level — the vocabulary most enterprise risk functions already speak. |
Sector and national rules are carried the same way, written as their own requirement set and kept beside these rather than folded into them. Ask us what already exists for your regulator. Where an instrument has been announced but not issued, it is labelled as announced: preparing for it is useful, claiming compliance with an unpublished text is not.
What these mappings are, and are not
They state, for each framework item, how Afunana's implemented controls address it: an engineering self-assessment to help security, audit, and GRC teams slot the platform into their own control environment. They are not a certification or an attestation. Afunana is mapped to these frameworks; it is not certified against them. Third-party certification and a SOC 2 Type II examination are in progress, not complete. Read "addresses," "supports," or "mapped to" as an implementation claim to be verified against your own deployment — never as independent assurance.
The complete item-by-item mappings — the OWASP Top 10 categories, the ISO/IEC 27001:2022 Annex A controls, and the SOC 2 common criteria plus availability and confidentiality — are documented and available on request.
Two limits are stated plainly rather than mapped away: MFA is delegated to the identity provider via OIDC/SSO rather than implemented in the platform, and at-rest encryption is automatic only in bundled-DB deployments.
What the automated checks will not do
Alongside each requirement the software runs a narrow factual check against itself: is the change gate on, is the register complete, are audit records kept for at least the six months Article 26(6) asks for. Those checks report one of four states, and the fourth matters most: cannot tell. If a ledger is empty, a period cannot be read, or no deployment plan exists yet, the check says so rather than counting itself as a pass. An all-green compliance screen produced by checks that could not run is the failure this apparatus exists to prevent.
The compliance surface
An admin-only compliance interface turns these controls into operations an auditor can actually run: filtering audit events by type, actor, and severity; exporting the audit log as JSON or CSV (the export is itself audited); checking hash-chain integrity on demand; invalidating one user's sessions or all of them; exporting a collection's documentation, metadata, and chat history for ISO/IEC 27001 A.8.10 portability and deletion workflows; and a status summary of enabled controls.
How to read the status summary. These rows used to be literal
truevalues in code. They are now derived, with an explicitunknownstate rather than defaulting to green:
- TLS in transit is read from the scheme of the configured public URL —
httpsreports true,httpreports false, an unset URL reportsunknown. TLS terminates upstream at the proxy, so there is nothing in-process to measure; this is the best evidence available inside the application, and it is honest about being configuration-derived rather than a network probe.- Database connection encrypted is true because the application builds its own connection string with encryption on. Certificate validation is reported as a separate row, because trusting a self-signed server certificate means the connection is protected against passive capture but not against active interception — folding the two into one green tick would have overstated it.
- Session revocation is probed by checking that the table the enforcement path actually reads exists, rather than asserted.
What still has to come from the customer's own evidence: the proxy's TLS configuration and cipher policy, and — for an external database — their TDE configuration. Afunana cannot observe either.