Data security and AI use
A statement for the firm's IT
This statement is for the IT of the firm that has to approve the installation. It describes what already holds, and it names the open points.
The support clarifications refer to package draft 0.2. It is not yet legally approved. The technical basis was checked against a fixed source revision, not against a firm installation. The other sections were not fully re-validated. The statements as of 2026-10-08 (sections 1 and 4) were checked against a later fixed source revision.
Reading note that applies to the whole paper:
Every statement below is either evidenced in code or explicitly marked as an organisational commitment. Where a property arises only from the firm’s configuration, that is stated alongside it. An assurance the software does not enforce is not an assurance — it is an intention.
01 / 09
Two supported operating models
The installation supports two operating models. The comparison sets them side by side, and the diagram shows the inference path of each.
Model B, the point that matters
The firm's contract partner is Microsoft, not us. The inference path described here runs from the firm's installation directly to its own Azure endpoint. A possible data access by us during support or maintenance must be assessed separately (section 8).
What actually leaves in Model B, in full
Not only chat prompts. For the embedding, the application assembles the title, abstract, description, and Ansprüche (claims) of a document and sends them to the provider. Anyone who uses the semantic search or the reference library thereby transmits document text, not merely questions. For section 3 this is the decisive point: the 30-day retention applies to inputs and outputs.
Model A: what “local” requires
Ollama can also run models in the cloud. An Ollama cloud model (its name ends in :cloud or -cloud, for example gemma4:cloud) runs on ollama.com, even when the Ollama server runs at the firm. The supplied Compose files therefore start Ollama with OLLAMA_NO_CLOUD=1: the server refuses cloud models and references to models on other servers. A firm that connects its own Ollama server must also set OLLAMA_NO_CLOUD=1 there. The provider posture reports a configured cloud model as a warning. It cannot detect a model with a local name that points to another server.
Explicitly not supported
Operation in the firm's cloud tenant by us, and a SaaS operated by us. Neither is a roadmap gap, but a product decision.
This is evidenced in the source: the provider selection, the services of the production stack with OLLAMA_NO_CLOUD=1, and the cloud-model check in the provider posture.
| Question | Model A — local inference | Model B — hosted inference |
|---|---|---|
| Where the language model runs | On the firm's hardware, in the ollama container | At Microsoft, Azure OpenAI |
| Where the data lives | PatentSchmiede stores the data exclusively on the firm's infrastructure. Section 5 names the configurations in which data can leave the firm, such as web research being switched on | Documents on the firm's infrastructure. The prompt content and the embedding text additionally go to the provider |
| Controller (GDPR) | The firm | The firm |
| Processor for inference | No external inference provider | Microsoft, in the firm's contract with Microsoft |
| Contract with us required | § 203 service-provider package as soon as remote maintenance, log inspection, or updates are carried out by us (section 8) | the same |
| Outbound connections in operation | None for inference | To the firm's Azure endpoint |
Without JavaScript, the page shows both diagrams one below the other and the complete table.
02 / 09
Where the data lives
The local data resides on the firm's infrastructure. In Model B, content is additionally processed at the inference provider (section 1). Separately approved support files are governed by section 8.
| Location | Content |
|---|---|
| Volume neo4j-data | Anmeldungen (patent applications), Erfindungsmeldungen (invention disclosures), Ansprüche (claims), descriptions, draft versions, features, embeddings |
| Volume uploads-data | Uploaded documents in the original |
| Volume ollama-data | Model weights (no client data) |
| Volume logs-data | Application logs (see section 6) |
| Volumes neo4j-logs, neo4j-import, neo4j-plugins | Database logs, import directory, plugins |
| Volumes caddy-data, caddy-config | TLS material, including the private key of the internal CA |
| Host Docker log store | A duplicate of the application logs. Logging additionally writes to stderr, which Docker stores under the data root |
| Backup target PS_BACKUP_DIR | Nightly copies of the database and uploads |
All these locations sit under the firm's Docker data root or in the selected backup target. This list of local storage locations describes neither the processing at the inference provider nor separately approved support copies. The production file defines nine volumes. The assignment and the encryption obligation are described in the documentation on encryption at rest.
What the backup does not include
The backup copies the volumes neo4j-data and uploads-data. Logs, TLS material, and the environment file .env are explicitly excluded. Anyone who wants to start up again after a total loss needs the environment file and the Caddy material from separate safekeeping.
03 / 09
Azure OpenAI — the often-overlooked section
Azure OpenAI does not train on customer data. That is the part most statements name. The part that matters for a professional secrecy holder is a different one:
Azure OpenAI stores inputs and outputs by default for up to 30 days for abuse monitoring. A human review of this stored content is possible.
For a law firm, precisely this review is the problem case, not the training.
Remedy
Modified Abuse Monitoring or Zero Data Retention. Both eliminate the storage, but they are
- subject to approval at Microsoft (an application, not a portal setting) and
- tied to an Enterprise Agreement or a Microsoft Customer Agreement.
EU DataZone deployments
EU DataZone deployments keep processing and storage in Europe. That is a separate question from retention: an EU deployment without an approved retention posture still stores for 30 days — in Europe.
The product asks about it and warns
This question is not only documented, but built. Under System → AI provider and data posture, the installation shows which provider is active, where it stands, and which retention posture the firm has declared. If none is declared, or the endpoint lies outside the EU, a warning appears in the header of every page and cannot be dismissed. In addition, a line in the sidebar footer says where AI processing runs. It is green (“Processed locally · no cloud channels configured”) only when every configured provider is confirmed to run inference locally, no outbound channel is switched on and no warning applies. Otherwise it is amber and names the cause.
The display visibly separates what is measured from what is declared. The application reads the endpoint host from its own configuration. Whether Microsoft has granted the firm Modified Abuse Monitoring it cannot know — that is a declaration by the firm and is marked as such, never as a verified result. The region is also only sometimes derivable: an endpoint of the form resource.openai.azure.com names none.
This is set through the parameters AZURE_DATA_RETENTION_POSTURE and AZURE_DEPLOYMENT_REGION. The rationale and the limits are recorded in an architecture decision.
04 / 09
What does not happen
| Assurance | What makes it hold |
|---|---|
| No training on client data | Model A: there is no recipient. Model B: Microsoft's commitment in the firm's contract — not enforced by us |
| No telemetry to us | The checked production stack defines no telemetry service of its own. This alone does not prove the absence of all outbound connections or externally installed tools |
| No logging of document content | A house rule in the Security-first architecture principle, implemented in the services: retry paths log only the error type and count, diagnose and backtrace are switched off on both sinks, and a length-based mechanism catches an over-long message, an over-long logged value, or an over-long exception on both sinks. See section 6 with the limitation that belongs to it |
| The external development provider is blocked outside a development environment | Direct OpenAI access (api.openai.com, for development and tests only, not Azure OpenAI) is refused outside a development environment (fail-closed, refused when in doubt). The check takes effect when the application sets up the provider. That happens at startup, so a misconfigured installation does not start. This is not a general assurance that nothing leaves the firm: this block does not cover, among others, web research, the EPO channel, a hosted fallback provider, or an Ollama cloud model. Section 5 names these configurations. The block depends on the ENVIRONMENT setting (see the following rows) |
| Four safeguards depend on the ENVIRONMENT setting | With ENVIRONMENT=production, four safeguards apply. First: the start is refused when the session signing key (JWT_SECRET_KEY) or the database password (NEO4J_PASSWORD) is empty, a default value shipped in earlier versions, or an unchanged placeholder from the shipped example configuration. The same applies to an initial password for the first admin (ADMIN_INITIAL_PASSWORD), if one is set. A signing key shorter than 32 bytes is also refused. The application checks the strength of these values no further, only the signing key has a minimum length. Second: the session cookie carries the Secure flag (the browser sends it only over HTTPS). Third: if no active admin exists at startup and the email address (ADMIN_EMAIL) or the initial password for the first admin is missing, the start aborts. Fourth: the external development provider (direct OpenAI access) is refused, see the row above |
| The start with an empty or unknown ENVIRONMENT value is refused | If ENVIRONMENT is set but empty or unknown (for example prod or staging), the start is refused and the error message names the accepted values. If ENVIRONMENT is not set, or is development, dev, local, test or testing, the development environment applies and none of the four safeguards takes effect. This is also the default of the shipped example configuration. The production stack sets production, a hand-built deployment must do so itself |
| The optional EPO channel is off | The EPO option is false by default. If it is on, a validator at startup checks that the target URL is ops.epo.org |
| Web research can be switched off and needs explicit permission for each run | The web research switch is on by default, and the air-gap package switches it off. When it is off, the application refuses research runs, web searches and retrievals. When it is on, the attorney must explicitly allow web access for each run |
05 / 09
Network exposure — checked on the running stack
Not read from the configuration, but observed on the running development stack with docker ps (2026-08-19) — complete, without omission:
patentschmiede-neo4j 127.0.0.1:7474->7474/tcp, 127.0.0.1:7688->7687/tcp patentschmiede-backend 0.0.0.0:8000->8000/tcp, [::]:8000->8000/tcp patentschmiede-frontend 0.0.0.0:3000->3000/tcp, [::]:3000->3000/tcp patentschmiede-ollama 11434/tcp
Read it like this: the database only over loopback, the language model not reachable from outside the container network at all — but the backend and frontend bind all interfaces in development and are therefore reachable on the LAN. That is a property of the development file and the reason why the security note says this state does not belong in an untrusted network. The Bolt port is 7688 on this machine instead of the 7687 of the development file — a local reassignment, not a deviation of the delivered state.
The production stack is the statement that matters
The production file gives no ports at all for neo4j, ollama, backend, and frontend. Caddy alone is reachable from the host on 80 and 443 with TLS termination.
On the statement “no outbound calls”
What the software does: the supplied configuration for Model A names no external endpoint for inference, and the external development provider is locked outside a development environment. A local endpoint alone does not prove local inference: an Ollama cloud model runs on ollama.com even when the configured endpoint is the firm's own Ollama server (section 1). The supplied Ollama therefore refuses cloud models (OLLAMA_NO_CLOUD=1).
What the software cannot do: prove that nothing actually leaves the firm. That proof is established at the network edge — a firewall rule or a separate network — and belongs to the acceptance of the installation, not to this statement. The air-gap installation path describes it.
Five configurations narrow the air-gap statement. The display from section 3 reports all five, but not with the same weight:
- 1A set fallback provider (LLM_FALLBACK_PROVIDER) to a hosted provider. On a repeatable failure of the local model, the same data goes outside. That is a warning and appears in the page header.
- 2An Ollama cloud model in OLLAMA_CHAT_MODEL or OLLAMA_EMBED_MODEL (section 1). If the Ollama server allows cloud models, every request to this model goes to ollama.com with its content. For the embedding model, this includes the text of the Erfindungsmeldung and of every source. The supplied Ollama does not allow cloud models (OLLAMA_NO_CLOUD=1). An Ollama server that the firm runs itself needs the same setting. The software recognises a cloud model only by the name ending :cloud or -cloud. That is a warning and appears in the page header.
- 3An OLLAMA_BASE_URL whose host the software cannot confirm as local. The software treats only three kinds of host as local. The first is an IP address in the loopback, private, or link-local range. The second is a name without a dot, for example ollama. The third is a name that ends in .local, .internal, .lan, or .home.arpa. A name such as ollama.kanzlei.example is not treated as local, even if it resolves to a private address. For any other host, the software cannot tell whether inference leaves the firm. That is a warning and appears in the page header.
- 4An enabled EPO channel. It transmits search queries and publication numbers to ops.epo.org and worldwide.espacenet.com — not the Erfindungsmeldung, but an outbound connection. That is a notice. It appears on the system page and, named or counted, in the sidebar line, not in the page header: it does not affect inference.
- 5Enabled web research in the research record (RESEARCH_WEB_ENABLED, on by default). In a run that the attorney starts with web access explicitly allowed, words from the research order go to html.duckduckgo.com as a search query, and pages from any public host are loaded. That is a notice and appears, like the EPO channel, on the system page and in the sidebar line. To rule out web access, set RESEARCH_WEB_ENABLED to false.
06 / 09
Encryption, roles, logging, backup
Encryption in transit
The production stack terminates TLS in Caddy. Port 80 redirects to 443. Between the containers, traffic runs on the internal Docker network, which has no host binding (section 5).
Encryption at rest
The application encrypts nothing itself. The protection comes from encrypting the host drives and is an installation prerequisite. A preflight tool helps with this, but it is not an installation gate. It returns the diagnostic status ENCRYPTED, NOT_ENCRYPTED, or UNKNOWN per target, plus an aggregated exit code (0, 1, or 2). On an explicit Windows path, a NOT_ENCRYPTED is a safe BitLocker negative finding. Cloud and hardware encryption are not assessed by it. UNKNOWN means the check allows no safe conclusion. The bundle installation prints these diagnostic codes but does not treat them as an abort condition and continues warn-only. Linux checks dm-crypt/LUKS, explicit Windows paths are checked via manage-bde -status. Cloud drives and self-encrypting disks remain UNKNOWN without provider or hardware proof. It is a warning aid, not a proof. The full picture, including the trap of an encrypted data root with an unencrypted backup target, is described in the documentation on encryption at rest.
Roles and access
There are two roles, patentanwalt and admin. Every v1 route except login requires a session. The audit trail is additionally restricted to admin. The token travels in a cookie that scripts cannot read, supplemented by a CSRF header for all changing methods. The first administrator password is one-time and must be changed at first login.
Logging
Application logs sit in the volume logs-data and additionally in the Docker log store of the firm. Passing them on for support needs a separate approval and review under the service-provider package.
The audit trail is event-based, not comprehensive, and that is more important here than the reassuring sentence. It knows a fixed list of event types and covers the security- and liability-relevant actions — among them sign-in and sign-out (including failed ones), user management and password changes, uploading, changing, and deleting library documents, attaching and detaching references to an Anmeldung, DOCX export, draft generation, Freigabe (sign-off), the management of the firm’s own prompt cards, and the adoption of machine-generated text variants (with model and prompt version). The binding list is the AuditAction type, not this enumeration.
Not covered are, among others, creating an Anmeldung, recording an Erfindungsmeldung, saving a draft version, and feature maintenance. For these operations, the who can be answered afterwards only where the record itself keeps it (the creator of a draft version). Anyone who needs a gap-free attribution does not have it today — see section 9.
Deletion of an Anmeldung (Art. 17 GDPR)
The deletion request for an Anmeldung deletes the Akte (case file) and everything hanging off it (Erfindungsmeldung, draft versions with Freigaben, clarifying dialogue, references) immediately and irrevocably — no deletion period, no restore. It may be run by admin, or by the patent attorney who created the Akte. For a legacy Akte without a reconstructable owner, it stays admin-only. The proof sits in the audit trail: a deletion event names the removed counters (number of drafts, sessions, clarifications), never document content. Existing events for that Akte remain, even after the record itself is gone. Earlier versions left two kinds of records behind when an Akte was deleted: the instructions for single sections and the saved versions of the drafting plan. Only their link to the Akte was removed. The application can no longer open these leftovers, but they contain text written by the attorney. At the first start after updating to the version with this fix, an automatic cleanup deletes them. If the cleanup fails, the system tries again at the next start. The log shows only their number, never content. Backups made before a deletion or before the cleanup still contain the data until the backup retention rule removes them. The update guide for operators describes the count query and the steps.
The limitation that belongs to it
“No logging of document content” was an enforced house rule and review practice without a mechanism behind it. Since then, a length-based filter in the logging catches every rendered message, every value set via logger.bind(), and the text of an attached exception that exceeds a fixed character limit, and replaces it with a redaction note — before either sink (stderr, rotation file, also in JSON format) sees the value. A separate cap on the exception summary ensures that the error-identifier mapping always survives this filter, even for a very long exception. That is a mechanism, but deliberately not a content classifier: a document fragment below the character limit on a single channel — for instance several individually short logged values that only read together form a document — is not caught by the length check and remains dependent on the house rule. One already known exception: the validation library pydantic carries the rejected value in its own text representation regardless of its length, so it is always summarised on the exception channel and in an additional value down to one nesting level — but not when it is embedded in the log message itself, and not from the second nesting level on. Both remaining gaps are recorded with dedicated tests as known-not-covered, not silently assumed fixed. An exceptional case below the limit that does not match a known pattern of this kind would still be a defect — not a state prevented by the system. Anyone who needs a harder assurance samples the logs in operation.
Backup and restart
The backup backs up the database and uploads and is intended for a nightly run. The firm’s IT sets the schedule itself (a crontab entry). The product plans nothing on its own, the same situation as with encryption. Restore with a documented drill, incident patterns and restart, and the upgrade are described in the operations runbooks.
07 / 09
Who needs which access
In the intended normal operation, the provider gets no access. For support, maintenance, or update, the purpose, people, rights, and duration are fixed per engagement. Synthetic examples and the guidance of the firm’s IT are used first.
The checked production stack defines no dedicated remote-maintenance service. That does not prove which additional accesses are set up on the host. The firm checks actual accesses and support tools before the first session. An access is opened by it and closed after completion.
The access description separates technical possibilities and organisational limits. In particular, logs, screen sharing, migrations, and admin recovery are not categorically free of secrets. Privileged host or database access is not limited to a single Akte by the application roles.
08 / 09
Confidentiality in support and maintenance
Patent attorney services are subject in particular to § 39c PAO and § 203 StGB. For lawyers, § 43e BRAO may be decisive. What matters is the concrete involvement of the service provider and the secret access opened, not the operating model alone. Even with a local installation, maintenance, log inspection, or on-site support can make secrets accessible. A pure software delivery without access is not the same thing.
The § 203 service-provider package contains a contract draft, a personal undertaking with instruction, an access description, and templates for approval and completion. Package version 0.2 is a draft. Before customer use, approval by the provider's legal advisors is missing.
A possible processing agreement for support is assessed separately from the direct Azure use. A required support data-processing agreement must be in place before the corresponding access. The Microsoft contract does not answer this question.
A fully network-separated installation can be supported on site. A remote connection, by contrast, means a break in the full network separation. It needs a separate approval and documented proof of the separation restored afterwards.
The sources and technical evidence name the legal bases as well as the limits of the source review.
09 / 09
Open points, honestly named
These points are open and are not glossed over here.
No certification
That is a decision, not a gap in the schedule. With an on-premise delivery, we run no service that an auditor could audit. A SOC 2 statement about us would be irrelevant to the firm’s installation, because it assesses an operating organisation that nobody runs here. And in German procurement, ISO 27001 is the sought-after standard anyway, not SOC 2. The burden of proof is carried instead by this statement and the service-provider contract from section 8.
Encryption at rest is an installation prerequisite
It is not a property of the application (section 6). Anyone who does not establish it does not have it. The preflight classifies the demonstrable local cases but cannot attest cloud or hardware encryption. These cases remain UNKNOWN and need proof from the provider or the firm’s IT.
No logging of document content — a mechanism, but not a complete one
The length-based filter from section 6 catches an over-long message on both sinks. A fragment below the character limit remains a house rule, not a mechanism.
The declared retention posture in Model B is a declaration
The product shows it and marks it as unverified. It cannot ask Microsoft. There is nothing to fix about that — it is the plainest form of this statement.
The audit trail does not cover every changing action
Section 6 names the limit. Taking stock and closing it is an open task.
The posture page knows the session-scoped choice
The per-session switchable provider of the claim agent is recorded on the session after every run. The posture query reads it when a session parameter is passed, and the claims page links the session as soon as one exists. It shows the path on which the last executed run of that session actually ran — not necessarily the path of a mode selected afterwards but not yet executed. An unknown session ID is explicitly shown as “unknown”, never as “runs on the default path”. These are two different statements, and the display does not confuse them. Without a linked session, the page stays on the configured default path and continues to show that limit visibly. The evaluation judge is a global, not a session-scoped setting — the default path above already describes it fully.