The detection service sees 100% of your personal data. Not a sample, not the sensitive subset, all of it, because it has to read everything to decide what is sensitive.
Every other component in your estate sees a slice. The sidecar sees the whole thing, in plaintext, at the moment it is most concentrated. That inverts the usual risk calculation. A component with no business logic, no database, and no user interface has become one of the most security-relevant things you run.
Seven controls follow from that, in rough order of how much they matter.
Use TLS between the gateway and the sidecar, on a private network, inside the same cluster, without exception. The word doing the work in that sentence is the last one.
The objection is that internal traffic is already isolated, and the objection has been wrong for about a decade. Cluster networks are shared, service meshes route through infrastructure you do not own, cloud provider networking has had cross-tenant issues, and "internal" is a statement about intent rather than about who can observe a packet.
builder.Services.AddHttpClient<PresidioAnalyzer>(c =>
{
c.BaseAddress = new Uri("https://presidio-analyzer:3000");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
// Pin to your internal CA; do not fall back to the system trust store.
RemoteCertificateValidationCallback = InternalCa.Validate
}
});Where your platform offers mutual TLS cheaply, take it, because it also authenticates the caller and control 6 needs that anyway. Where it does not, one-way TLS plus a shared secret is an acceptable stopping point.
The sidecar should write nothing to disk. No request bodies, no temporary files, no spooled uploads, no model cache containing user text.
Enforce it rather than requesting it:
# The only compose fragment in this book.
services:
presidio-analyzer:
image: presidio-analyzer@sha256:<digest>
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
user: "10001:10001"
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
networks: [detection]
environment:
- LOG_LEVEL=WARNING
networks:
detection:
internal: true # no route to the internetA read-only root filesystem turns "we do not write files" from a claim into a property. If something tries, it fails, loudly, in testing, rather than quietly succeeding in production.
The one thing that legitimately persists is the token vault from Chapter 9, and it is the most sensitive store you own, because it holds the mapping from token back to person. Three requirements:
Follow the logic. The detector reads all your personal data. Default log levels in most frameworks write request bodies at debug or trace. Someone raised the log level during an incident in March and it was never lowered.
You now have a single, centralised, indexed, long-retention store of every piece of personal data in your organisation, sitting in your observability platform, searchable by everyone with a dashboard login, replicated wherever your vendor replicates.
Turn request-body logging off explicitly, and then assert it:
[Fact]
public async Task Gateway_NeverLogsPromptContent()
{
var sink = new CapturingLogSink();
var gateway = BuildGateway(sink);
await gateway.CompleteAsync(
new GatewayRequest(Prompt: "Contact Sarah Whitfield at swhitfield@example.com"), default);
Assert.DoesNotContain(sink.Entries, e =>
e.Contains("Sarah Whitfield", StringComparison.OrdinalIgnoreCase)
|| e.Contains("swhitfield@example.com", StringComparison.OrdinalIgnoreCase));
}That test is worth more than a policy document. It fails when someone adds a helpful debug line, and it fails in CI rather than in an audit.
Chapter 16 takes this further, because the problem is not confined to the sidecar.
Download the full PDF for free?
Free download — no account required