Skip to content

feat(piguard): BAA-coverable Vertex AI endpoint support for the Gemini detector - #920

Open
hyperSuperCube wants to merge 1 commit into
tokencanopy:mainfrom
hyperSuperCube:feat/piguard-baa-endpoint
Open

feat(piguard): BAA-coverable Vertex AI endpoint support for the Gemini detector#920
hyperSuperCube wants to merge 1 commit into
tokencanopy:mainfrom
hyperSuperCube:feat/piguard-baa-endpoint

Conversation

@hyperSuperCube

Copy link
Copy Markdown
Contributor

Summary

Implements finding G1 from the SOC 2 / HIPAA compliance plan (#919): the Gemini LLM-as-detector layer hardcoded the Google AI Studio consumer endpoint, which cannot sit under a Google Cloud BAA/DPA — a blocker for any deployment with confidentiality obligations, since the detector sends the message subject, sender, and up to 4,000 chars of body text.

This makes the endpoint and auth mode first-class configuration so the detector can run against project-scoped Vertex AI under the operator's Google Cloud agreement:

  • GEMINI_BASE_URL (or GeminiConfig.BaseURL) — the appended /models/{model}:generateContent path fits both the AI Studio default and Vertex bases ending in .../publishers/google.
  • GEMINI_AUTH=adc (or GeminiConfig.Auth) — OAuth bearer via Application Default Credentials, which Vertex requires; api_key stays the default and the AI Studio behavior is byte-for-byte unchanged. An unknown mode fails construction (no silent fallback to the non-contracted endpoint).
  • Bearer-token fetch failures are classified transient (retried within the existing 5s/10s budget, then StatusError so heuristics carries) and never propagate token or token-source error text.
  • inboundscreen.BuildEngine now logs when Gemini is configured but the detector can't be constructed, instead of silently degrading to heuristics-only.
  • Deployment docs + config.example.yaml now state exactly what content leaves the system and show the compliant Vertex configuration.

No new module dependencies: golang.org/x/oauth2 was already a direct dependency and go mod tidy is a no-op.

Client surface checklist

Not applicable — env/config-only change to the screening engine; no /v1 handler, schema, migration, or SDK/CLI/MCP surface is touched (verified: make test-unit green, no OpenAPI diff).

Operational risk

  • Default behavior (API-key mode against AI Studio) is unchanged; existing deployments need no action.
  • GEMINI_AUTH=adc without resolvable ADC fails detector construction at startup — screening continues heuristics-only and the condition is now logged loudly. Rollback = unset the two new env vars.
  • Fail-open semantics on scan errors are unchanged.

Test plan

  • New unit tests: bearer header attached / api-key header absent in adc mode (and vice versa), GEMINI_BASE_URL env routing, unknown GEMINI_AUTH rejected, token-fetch failure transient + non-leaking
  • go test ./internal/piguard/ ./internal/inboundscreen/ ./internal/logredact/ green
  • make test-unit, go build ./..., go vet, gofmt clean
  • Repo text-integrity and committed-credentials gates pass
  • Staging: point at a Vertex publishers/google base with workload identity and confirm a scanned message round-trips

🤖 Generated with Claude Code

…ni detector

The LLM-as-detector layer previously hardcoded the Google AI Studio
consumer endpoint (x-goog-api-key auth), which cannot be covered by a
Google Cloud BAA/DPA — sending message subject, sender, and up to 4,000
chars of body text there is disqualifying for deployments with HIPAA or
similar confidentiality obligations.

- GEMINI_BASE_URL / GeminiConfig.BaseURL: override the REST base; the
  appended /models/{model}:generateContent path fits both AI Studio and
  project-scoped Vertex AI bases ending in .../publishers/google.
- GEMINI_AUTH=adc / GeminiConfig.Auth: authenticate with an OAuth bearer
  token from Application Default Credentials (required by Vertex AI);
  api_key remains the default. Unknown modes fail construction loudly.
- Token-fetch failures are transient (retried, then StatusError) and
  never leak token or token-source error text.
- BuildEngine now logs when Gemini is configured but construction fails,
  instead of silently degrading to heuristics-only.
- Document the data flow and the compliant configuration in
  docs/deployment.md and config.example.yaml.

No /v1 API surface change; no new module dependencies (x/oauth2 was
already direct).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hyperSuperCube
hyperSuperCube force-pushed the feat/piguard-baa-endpoint branch from b6f2c96 to f54f3a9 Compare August 21, 2026 21:19
detectors = append(detectors, d)
cfg.Timeout = GeminiDetectorTimeout
log.Printf("[piguard] Gemini detector enabled (model: %s)", d.Model())
} else if os.Getenv("GEMINI_API_KEY") != "" || os.Getenv("GOOGLE_API_KEY") != "" || os.Getenv("GEMINI_AUTH") != "" {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ran a test against f54f3a97 in a clean container that sets only GEMINI_BASE_URL (no API key, no GEMINI_AUTH) and captures BuildEngine's log output.

The new else-if at line 235 checks GEMINI_API_KEY, GOOGLE_API_KEY, and GEMINI_AUTH, but not GEMINI_BASE_URL. An operator who sets only GEMINI_BASE_URL while pointing at Vertex, and forgets GEMINI_AUTH=adc, gets NewGeminiDetector failing on the default api_key path with no API key set. All three checked env vars are empty, so the else-if is skipped and BuildEngine falls through to heuristics-only with zero log output, exactly the silent degradation this PR's own comment says should never happen.

$ go test ./internal/inboundscreen/ -run TestBuildEngine_BaseURLOnlySilentlyDegrades -v
=== RUN   TestBuildEngine_BaseURLOnlySilentlyDegrades
    zz_repro_test.go:26: captured log output: ""
    zz_repro_test.go:28: expected an operator-visible log line when GEMINI_BASE_URL is set but construction fails; got: ""
--- FAIL: TestBuildEngine_BaseURLOnlySilentlyDegrades (0.00s)

Fix: add || os.Getenv("GEMINI_BASE_URL") != "" to the else-if condition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants