From: Stefan Gasser Date: Wed, 10 Jun 2026 12:19:59 +0000 (+0200) Subject: Expose logging and secrets_detection in /info, remove unused log_content X-Git-Tag: v0.4.2~1^2~1 X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=8881fb7b4813946b83119a5afbffee757d20994c;p=sgasser-llm-shield.git Expose logging and secrets_detection in /info, remove unused log_content Follow-ups from #91: - /info now includes secrets_detection and logging sections so users can verify their config is loaded (the issue reporter could not). - The dashboard fallback message no longer claims log_masked_content is false when content is simply absent for another reason. - Remove the log_content option: it was defined in config and documented, but no code ever read it. Logging raw request/response content would contradict the privacy guarantees, so it is removed rather than implemented. Zod strips unknown keys, so existing configs that still set log_content keep loading. --- diff --git a/config.example.yaml b/config.example.yaml index a63bf0d..00d7da4 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -156,8 +156,7 @@ secrets_detection: # The 200KB default covers typical use cases max_scan_chars: 200000 - # Log detected secret types (never logs secret content) - # Even if logging.log_content is true, secret content is never logged + # Log detected secret types (never logs raw secret content) log_detected_types: true # Which message roles to scan for secrets (optional) @@ -178,11 +177,8 @@ logging: # Log retention in days (0 = keep forever) retention_days: 30 - # Log request/response content (may contain sensitive data!) - log_content: false - # Log masked content for dashboard preview (default: true) - # Shows what was actually sent to provider with PII replaced by placeholders + # Shows what was actually sent to provider with PII and secrets replaced by placeholders # Disable if you don't want any content stored, even masked log_masked_content: true diff --git a/docs/api-reference/status.mdx b/docs/api-reference/status.mdx index 2735c13..cbcead3 100644 --- a/docs/api-reference/status.mdx +++ b/docs/api-reference/status.mdx @@ -86,6 +86,17 @@ curl http://localhost:3000/info "score_threshold": 0.7, "entities": ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER"] }, + "secrets_detection": { + "enabled": true, + "action": "mask", + "entities": ["OPENSSH_PRIVATE_KEY", "PEM_PRIVATE_KEY"], + "max_scan_chars": 200000, + "log_detected_types": true + }, + "logging": { + "retention_days": 30, + "log_masked_content": true + }, "masking": { "show_markers": false } diff --git a/docs/configuration/logging.mdx b/docs/configuration/logging.mdx index a37155a..d610d13 100644 --- a/docs/configuration/logging.mdx +++ b/docs/configuration/logging.mdx @@ -7,7 +7,6 @@ description: Configure request logging logging: database: ./data/pasteguard.db retention_days: 30 - log_content: false log_masked_content: true ``` @@ -17,7 +16,6 @@ logging: |--------|---------|-------------| | `database` | `./data/pasteguard.db` | SQLite database path | | `retention_days` | `30` | Days to keep logs. `0` = forever | -| `log_content` | `false` | Log raw request/response (contains PII!) | | `log_masked_content` | `true` | Log masked version for dashboard | ## Database @@ -49,33 +47,23 @@ logging: ## Content Logging -### Raw Content (not recommended) - -Logs original request/response including PII: - -```yaml -logging: - log_content: true # Contains sensitive data! -``` - ### Masked Content (default) -Logs masked version for dashboard preview: +Logs the masked version for dashboard preview: ```yaml logging: log_masked_content: true ``` -Shows what was actually sent upstream with PII replaced by placeholders. +Shows what was actually sent upstream with PII and secrets replaced by placeholders. ### No Content -Disable all content logging: +Disable content logging: ```yaml logging: - log_content: false log_masked_content: false ``` @@ -83,6 +71,7 @@ Only metadata (timestamps, models, PII detected) is logged. ## Security -- Secret content is **never** logged, even if `log_content: true` +- Raw request/response content is **never** logged — only the masked version, and only when `log_masked_content` is enabled +- With `secrets_detection.action: route_local`, content is not logged at all when secrets are detected, since secrets stay unmasked for the local provider - Only secret types are logged if `log_detected_types: true` -- Masked content shows placeholders like `[[EMAIL_ADDRESS_1]]`, not real PII +- Masked content shows placeholders like `[[EMAIL_ADDRESS_1]]` and `[API_KEY_SK_1]`, not real values diff --git a/src/config.ts b/src/config.ts index 3f6ceb9..3791118 100644 --- a/src/config.ts +++ b/src/config.ts @@ -83,7 +83,6 @@ const ServerSchema = z.object({ const LoggingSchema = z.object({ database: z.string().default("./data/pasteguard.db"), retention_days: z.coerce.number().int().min(0).default(30), - log_content: z.boolean().default(false), log_masked_content: z.boolean().default(true), }); diff --git a/src/routes/info.test.ts b/src/routes/info.test.ts index b4aa8f0..a07d935 100644 --- a/src/routes/info.test.ts +++ b/src/routes/info.test.ts @@ -23,6 +23,21 @@ describe("GET /info", () => { expect(body.pii_detection).toBeDefined(); }); + test("includes secrets_detection and logging sections", async () => { + const res = await app.request("/info"); + + const body = (await res.json()) as Record>; + expect(body.secrets_detection).toBeDefined(); + expect(body.secrets_detection.enabled).toBeDefined(); + expect(body.secrets_detection.action).toBeDefined(); + expect(body.secrets_detection.entities).toBeDefined(); + expect(body.logging).toBeDefined(); + expect(body.logging.retention_days).toBeDefined(); + expect(body.logging.log_masked_content).toBeDefined(); + // Database path is intentionally not exposed + expect(body.logging.database).toBeUndefined(); + }); + test("returns correct content-type", async () => { const res = await app.request("/info"); diff --git a/src/routes/info.ts b/src/routes/info.ts index 5f06cd1..6fcf042 100644 --- a/src/routes/info.ts +++ b/src/routes/info.ts @@ -43,6 +43,17 @@ infoRoutes.get("/info", (c) => { score_threshold: config.pii_detection.score_threshold, entities: config.pii_detection.entities, }, + secrets_detection: { + enabled: config.secrets_detection.enabled, + action: config.secrets_detection.action, + entities: config.secrets_detection.entities, + max_scan_chars: config.secrets_detection.max_scan_chars, + log_detected_types: config.secrets_detection.log_detected_types, + }, + logging: { + retention_days: config.logging.retention_days, + log_masked_content: config.logging.log_masked_content, + }, }; if (config.mode === "route" && config.local) { diff --git a/src/views/dashboard/page.tsx b/src/views/dashboard/page.tsx index 9f438d9..f1a33bf 100644 --- a/src/views/dashboard/page.tsx +++ b/src/views/dashboard/page.tsx @@ -542,7 +542,7 @@ function formatMaskedPreview(maskedContent, entities) { if (!entities || entities.length === 0) { return 'No PII detected in this request'; } - return 'Masked content not logged (log_masked_content: false)'; + return 'Masked content not logged'; } function renderEntityList(entities) {