Privacy, examined
AI-privacy audit
How your messages and identity are handled, and what we verified.
What this review found
Reviewed 23 September 2026
The reviewed code keeps your GPPT account details out of AI requests and leaves a new chat unsaved unless you choose to save or share it. Live checks also confirmed the required retention settings for the Bedrock route tested.
The reviewed protections are now deployed: saved chats are encrypted in GPPT’s database, share links show a fixed copy for seven days, and older session and usage details have a cleanup schedule. Existing saved-chat fields were checked after encryption and remained readable.
This is an AI-assisted code review with limited live checks. The evidence and its limits are available below.
What the AI host receives
AI requests go through GPPT’s server using GPPT’s service account. The reviewed requests do not attach your GPPT user ID, email address, browser IP address, sign-in cookie or payment details.
That provides a useful layer of privacy: the service receiving our request sees GPPT’s connection and account. GPPT’s account and payment records are not automatically disclosed to an AI host.
The host does receive your message and the conversation context needed to answer it. A name, address or identifying detail in that text can still identify you or someone else. Conversation history also lets the host connect messages within that chat.
The distinction: GPPT keeps account identifiers out of the AI request; it does not automatically remove identifying details from what you type. GPPT itself still knows which account is signed in.
Technical detail: identity and request data
Code inspection covered opening answers, follow-ups, retries and model comparisons. The outgoing request builders do not add a stable end-user identifier, browser network address or billing-account field. New tests populated a synthetic signed-in account and inspected OpenRouter normal/streaming parameters, Bedrock Converse parameters/SDK headers, Bedrock compatible-API bodies, and the opening/follow-up message builders.
OpenRouter uses GPPT’s server-held API key and fixed app headers: HTTP-Referer: https://gppt.com.au and X-Title: GP Power Tools. The optional top-level user field is not set. These are application identifiers, not the signed-in user’s identifiers. A message’s role: user describes its role in the conversation; it does not identify a GPPT account.
Bedrock uses GPPT’s AWS authentication. The reviewed calls omit user-specific request_metadata, user, safety_identifier and similar fields. Outbound HTTP requests are constructed on the server; browser cookies, browser User-Agent and forwarding headers are not copied. Normal server/SDK headers can still identify the software and service account.
The network paths are browser → GPPT → AWS Bedrock, or browser → GPPT → OpenRouter → selected serving provider. OpenRouter’s onward request is generated by OpenRouter; this code review does not inspect that internal hop. A provider may still observe timing, message size, token counts and other operational metadata. No claim of complete unlinkability is made.
GPPT can associate signed-in activity and billing with an account and retains provider request IDs in operational records until the applicable cleanup. That is separate from sending those account details in the AI request. Tests explicitly confirm that a synthetic patient name inside message text remains in the outgoing prompt.
For Bedrock, AWS is the host. AWS states that model developers do not have access to Bedrock deployment accounts, prompts or completions. This is an AWS commitment, not something this review can independently observe. OpenRouter documents its optional user identifier and application headers in its API reference.
What happens to your chat
- A new, unsaved chat: the reviewed code keeps the conversation in the browser while you use it. It sends the text for inference but does not add it to GPPT’s saved-chat database or the shared answer cache.
- A saved or shared chat: GPPT stores it for you. Later replies in a saved chat are stored too. Anyone with a working share link can read the shared content and may keep their own copy.
- A zero-data-retention option: this concerns how the selected AI host handles inference content. It does not delete a chat you chose to save on GPPT.
- Account and billing records: GPPT keeps records needed to run your account and charge for usage. These records are separate from the content sent to the AI host.
Technical detail: storage and deployed protections
AreaReviewed controlVerification status Unsaved inferenceInteractive controllers do not supply a public cache scope. The resource-search index remains paused, so clinical queries do not enter its retrieval or embedding paths.Code and local tests; limited live database scans described below. Saved contentActive Record encryption covers conversation titles, message content, structured payloads, generation metadata and share snapshots. Keys are configured separately from the database. Existing saved-chat fields were encrypted and successfully decrypted during production verification.Deployed. Production verification checked all 11 non-null saved-content fields: none remained plaintext or unreadable. Legacy plaintext reads are disabled. Encryption protects a database-only disclosure; the app can still decrypt messages. Share linksA fixed copy of completed messages, expiring after seven days. Later messages/edits are excluded. Revocation or deletion blocks future access, not copies already taken.Deployed. Local browser tests and production Rails integration checks passed for shared snapshots, exclusion of later messages and revocation. Older links without a snapshot and expiry need to be shared again. SessionsThirty-day server-side expiry; new session records omit IP/browser fields. Daily cleanup removes expired sessions and clears those fields from older records.Deployed and first cleanup completed. No remaining session records contained IP/browser fields at the check. Server access logs can still contain IP/browser information. Usage detailsAfter 30 days, daily cleanup removes detailed provider usage, request IDs, route snapshots and the billing-request link from resolved usage rows. Financial ledgers, costs/model references and unresolved billing work remain.Deployed; cleanup ran successfully and the daily schedule, worker and scheduler were confirmed active. The next scheduled run was not observed in this review. Daily cleanup can lag by a day. Logs and browserTyped metadata allowlists and filtered error reporting; restricted scripts/connections, referrers withheld from other sites, no-store/no-cache for private pages, and clearing chat state on navigation.Deployed; local browser tests and live public response headers were checked. These controls cannot erase downloaded copies, extensions, screenshots or device-level traces.What was verified
Live Bedrock checks passed. On 23 September, the tested account in the Sydney region reported retention set to none and invocation content logging disabled. A normal reply and a streamed reply both completed successfully on the tested zero-retention route.
The synthetic message marker was absent from the stores successfully checked. It was not found in the new probe’s process log or 54 database tables. Two table scans timed out and remain unverified. Earlier same-day app/proxy log checks also found no marker from their probe.
Local checks passed. The new identity checks support the account-separation finding above. Tests also cover encryption, sharing, retention and browser controls. The release passed 858 application tests and 37 browser tests; production encryption and cleanup checks passed too.
These observations support the specific protections tested. They do not reveal a host’s internal storage, establish what is in historical backups, or amount to a complete production security audit.
Live evidence: route, settings, databases and logs
The operator runner checked Bedrock account retention and invocation logging in ap-southeast-2, using the same application configuration as inference. Both checks reported acceptable settings. The synthetic probe ran against qwen-chat-zdr at 2026-09-23T07:45:59Z. It used the new deployed provider adapter and billing meter at revision ed1abc6c0e98714aaa8da99a63e6fdbd09463401. Both normal and streaming inference passed. This probe was not a browser/HTTP end-to-end test or a deployed failure-path test.
resource_chunks and resource_document_versions. The previously timed-out resource_documents scan completed in this run.
Queue, cache and cable databasesNo marker in 13 queue tables, three cache tables and three cable tables.
Probe process logNo marker found.
Earlier same-day proxy/app logsThe earlier probe’s marker was absent from 88,786 bytes of proxy access log or 166 application log lines. The inspected proxy error log was empty.
The probe searches raw text/JSON and uncompressed binary, with a five-second deadline per table. It cannot establish absence from every encrypted/compressed representation, replica, backup or external monitoring sink. A timeout is not a passing result.
The inspected app nginx configuration did not reference $request_body. Host rotation configuration showed daily/seven rotations for Dokku and daily/fourteen for nginx; the inspected container had a 10 MB json-file log limit. Global nginx formats, all historical logs and third-party error monitoring were not established by this inspection.
The deployed revision was confirmed from the runtime at 2026-09-23T07:48:52Z. The new database probe exited unsuccessfully because of its two scan timeouts; they are not counted as passes. Earlier proxy-log observations relate to the earlier 2026-09-23T03:36:32Z probe, not the new marker.
Remaining operational checks
The application deployment, existing-chat encryption, strict encrypted reads and first cleanup are complete. Production Rails integration requests checked saved-chat access, profile and billing pages, sharing, exclusion of later messages and revocation, using synthetic records inside a rolled-back transaction. These are in-process checks, not external browser checks. The daily cleanup schedule and healthy workers were observed; a future scheduled cleanup and full failure-path coverage remain to be observed.
Review backups, replicas, exports, WAL, host crash dumps/swap and external log retention. Encrypting current database rows does not erase older plaintext copies. Establish a bounded recovery-data lifecycle and test recovery before changing it.
A proposed IAM policy prevents the runtime role from changing Bedrock retention/logging and Mantle project settings. It parses locally but has not been attached or verified against the actual runtime role. AWS template validation was unavailable with the documented local CLI profile. Other administrators and organization-wide permissions remain unreviewed.
The new mandatory evidence checks reject missing, expired or revoked reviews and preserve route identity before inference. They passed local tests. Deployed checks confirmed seven enabled ZDR routes had complete evidence and that an older Qwen/Vertex route with missing evidence was unavailable. Provider/model eligibility and cross-region destinations require their own evidence. These live checks add no OpenRouter account-settings verification.
Technical evidence and method
This report combines code inspection, local automated tests and the dated live observations above. Expand the evidence to see what each finding rests on.
Reviewer, scope and confidence
Reviewer: Codex, one AI coding assistant. Exact model build not recorded. This was not a second-model review, independent human audit, penetration test or certification.
The review traced browser/controller inputs through opening answers, follow-ups, model comparisons, provider adapters, billing metadata, persistence, sharing and selected logging paths. It separately assessed account identifiers and identifiers contained in message text. Test requests and live probes used synthetic data.
Findings about outgoing identity fields rest on the code and captured local request boundaries, not packet capture at a provider. Finding no marker in a store is a bounded observation rather than proof that no message was ever stored. Code cannot observe provider internal storage. Account privacy, message confidentiality, host retention and GPPT’s saved-chat storage are separate questions.
Working-copy base revision: dec0284df6638d038d0e17fc29c8e5968b67e629. Pre-release file snapshot: 2026-09-23T05:31:06.839178+00:00. The checksums below retain the pre-release review snapshot. The release subsequently deployed as ed1abc6c0e98714aaa8da99a63e6fdbd09463401. Final validation corrected the referrer policy to same-origin for Rails CSRF compatibility and updated older browser-test selectors. The deployment checks below identify that later validation; this privacy review is not a full audit of the accompanying billing/profile features.
Fresh identity and provider checks: 34 tests passed
34 tests, 270 assertions; no failures, errors or skips. Four new identity tests cover OpenRouter normal/streaming requests and app headers; Bedrock Converse parameters/SDK headers; Bedrock Responses/Chat Completions bodies; and opening/follow-up message construction. Identifying text entered in a prompt is deliberately preserved. Existing provider and audit-page tests ran alongside them.
Provider calls were simulated. Ruby 4.0.6 and local PostgreSQL were used. Rubocop passed for the new test file.
PARALLEL_WORKERS=1 bin/rails test test/services/ai/request_identity_test.rb test/services/ai/bedrock_provider_test.rb test/services/ai/client_test.rb test/controllers/privacy_audits_controller_test.rb
Run options: --seed 62893 # Running: .................................. Finished in 1.039930s, 32.6945 runs/s, 259.6329 assertions/s. 34 runs, 270 assertions, 0 failures, 0 errors, 0 skips
Release checks and additional safeguards validation
Final release checks: 858 application tests / 4,177 assertions; 37 browser tests / 387 assertions; and a final privacy/report controller run of eight tests / 52 assertions. Each finished with no failures, errors or skips. Rubocop inspected 396 files with no offenses. Brakeman reported no unignored security warnings (five existing ignores); Ruby and browser dependency audits reported no known vulnerabilities.
PARALLEL_WORKERS=1 bin/rails test PARALLEL_WORKERS=1 bin/rails test:system PARALLEL_WORKERS=1 bin/rails test test/controllers/privacy_browser_controls_test.rb test/controllers/privacy_audits_controller_test.rb bundle exec rubocop --no-parallel --cache false bin/brakeman --no-pager bin/bundler-audit bin/importmap audit
The full browser run exposed a real incompatibility: no-referrer made ordinary form POST origins opaque, causing Rails CSRF rejection. The corrected same-origin policy sends no referrer to other sites and preserves same-site form origins. Existing model-picker and Stimulus test selectors were also brought up to date. All 37 browser tests then passed, including repeated chat submissions with CSRF protection enabled.
An isolated upgrade from the previously deployed schema rehearsed all three migrations, legacy chat encryption and strict decryption, share changes, session cleanup and exactly-once fulfillment of a legacy checkout. Existing balances were preserved; new coupon and purchase-offer switches remained off by default. Production migrations and startup health checks subsequently passed.
Earlier same-day runs covered AI routing, controllers, sharing, billing metering and privacy controls: 294 tests / 1,442 assertions, plus seven browser tests / 94 assertions. Focused referrer/sharing/audit-page checks later passed with 13 tests / 83 assertions. These are separate runs, not a combined independent-review score.
A synthetic legacy-data backfill and encrypted-storage verification passed inside a rolled-back test transaction. Rubocop passed for 33 changed Ruby files, followed by two checks after the referrer amendment. These earlier runs predate the fresh identity tests and report rewrite.
Development checks had exposed a derived-field assignment error and outdated assertions, which were corrected. An overlapping database/browser run deadlocked against shared fixtures; the final regression run was sequential and passed. No failing production scan is counted as a pass.
Run options: --seed 21458 # Running: ...................................................................................................................................................................................................................................................................................................... Finished in 23.125082s, 12.7135 runs/s, 62.3565 assertions/s. 294 runs, 1442 assertions, 0 failures, 0 errors, 0 skips
Run options: --seed 6063 # Running: Capybara starting Puma... * Version 8.0.2, codename: Into the Arena * Min threads: 0, max threads: 4 * Listening on http://127.0.0.1:56573 ....... Finished in 6.071900s, 1.1529 runs/s, 15.4812 assertions/s. 7 runs, 94 assertions, 0 failures, 0 errors, 0 skips
Run options: --seed 15344 # Running: ............. Finished in 1.577928s, 8.2387 runs/s, 52.6006 assertions/s. 13 runs, 83 assertions, 0 failures, 0 errors, 0 skips
{"encrypted_records":{"AiConversation":1,"AiMessage":1}}
{"AiConversation":{"fields_checked":1,"plaintext_fields":0,"unreadable_fields":0},"AiMessage":{"fields_checked":2,"plaintext_fields":0,"unreadable_fields":0}}
Backfill round-trip and encrypted-storage verification passed (test database; rolled back).
Repeat the operator probe with MODEL_KEY=qwen-chat-zdr bin/rails privacy:probe. It makes two small paid synthetic requests and fails if a checked store reports a match or scan error. See docs/privacy-hardening.md for the rollout sequence and docs/ai-route-privacy.md for route evidence requirements.
Source files and checksums
Account separation: the provider adapters, Ai::Tasks::ClinicalAnswerTask, Ai::FollowupReply#llm_messages, Ai::ModelPanel#invoke and Billing::Meter. Storage: AiConversation, AiMessage, encryption configuration and PrivacyCleanupJob. Route checks: Ai::RoutePolicy and Ai::BedrockRetention. Browser controls: the private-page/chat controllers and response policy configuration.
SHA-256 hashes identify this review snapshot. Earlier test runs have the timing/coverage qualifications given above.
9de0ba9841590fdf26652542df07166c06a3fbfa7ada2ea78b751ff4433a5a04 app.json 1efdca59447e48a9df5f89e09ddff84a5664e5a127db4641aff0bd4866ed1c5f app/assets/stylesheets/privacy_audit.css d7aec4ede86e28da15e6f13e53d10530bb050ac5e0a9ad57ab78b40e945e4d81 app/controllers/ai_lab_controller.rb 281818774e7df638bafd8e488e0756f1e715cabe2d6c19b70c0ae7fd3b2b4446 app/controllers/ai_model_panels_controller.rb 67b94fc659149129015c0a01e597d9c45755bbb549410c1baf7a53378847f304 app/controllers/application_controller.rb 0885c7f2af3cf1d54f583df0758cb4f3cdbff074d2e3108fe80291f7f0ccd319 app/controllers/concerns/ai_chat_turns.rb 3a087a7a2175570f9597b7b5106d5fa2e0878d68f650578fda13767fd196e446 app/controllers/concerns/authentication.rb 22e4e93af1ef7c433a39ffab09594418a9ad1103607d514cb8c6cb8d47790cfb app/controllers/privacy_audits_controller.rb 64db49db1c737c88866c070bc0049200418dc845ed4ce6bf7df6617a25d981e8 app/controllers/shared_conversations_controller.rb 0cbd8daad5e44058e7808d0202c64baaf64df4b34585511aaf005c37dc0b05d4 app/javascript/controllers/chat_controller.js a6d7bbacd0ce3e55e693f97cef6c3eaaeae7ad3cfc894482a164ced2ee62a5a6 app/javascript/controllers/private_page_controller.js 502a4da9c1c7b89e03f2522d13461a6443b770d92265d38d886777e786c08062 app/jobs/privacy_cleanup_job.rb 83ad0144a804c5bb85ec8611e80a362977870901210568d66012bfbfde555dfd app/models/ai_conversation.rb 703990e98e4b871565cf19fea4c9b2a9663d2c524c20fd1bbf607756e262ccc2 app/models/ai_message.rb 17adc2227dfe650a41b2e6d9d7c214194bfb3a6cfcd75a0ea96e21482a2ffd1b app/models/ai_model.rb a22c3b6475546039789ed9516f3e9099574c291df13c932a8c6ea24d70f14ab6 app/models/session.rb 9436f19fdb3dc0ad8faebdfa5cbbbd832ad5028ddd39cae079346742c98de819 app/services/ai/answer_pipeline.rb 9fcfdcf5f301e000a4fd10436118d6ffcbe44e5fb1f94f9cfdf15f9da533b3f5 app/services/ai/client.rb 88a481dd9e54032c79b8da35db9f8cd69f5d7b6e3131f8cf1d540060aee2a4f9 app/services/ai/conversation_transcript.rb ca47d5e3cbdca653b27161c4fa89f5d27faa86a6396aaf83bf33f6765c03fda7 app/services/ai/followup_reply.rb 686c61e3e7cf08297b97f4b8ddb31e0539118b0a595a4678293871eacd140c4a app/services/ai/model_panel.rb d7be5dbd43e1cf933ebc3410ef414d19c9109d78dbfcda9e6984a7c210934b50 app/services/ai/panel_suggestion.rb ffe46177c1913461d5dade4b838e2d9018c7d0d2991d26e9bb90f1e6b6859300 app/services/ai/privacy_probe.rb fcc8e4b7cb8e8af294b224167a1628eb15dc8892eb0ea1c3db77bc9634a3592b app/services/ai/privacy_route_setup.rb 1b60a23241de3f04401567166fe970fba0e69372e02333428624ba41fdeccf49 app/services/ai/provider_metadata.rb 4c5b7c3851430ed88adf004028f269e7d2505f472ef6915831eecb5711d57dba app/services/ai/providers/bedrock.rb 79daecd9802d298f591c9f0e4c9751f3f3c2de427aa8808533f58d011f1143b5 app/services/ai/providers/open_router.rb 011390d762b529e1da00381323ee69a36351f02a46b4ece2d1cba62bf63dc9a4 app/services/ai/route_policy.rb 7b4ff6a456fb6e58a24ff119f32ae4fa04b7120f9e6d20e928ea664b8b893b54 app/services/ai/tasks/base_task.rb 6c42a2e12a8e0174d6d636589fe2027a0461dd045b37f9ec542fa30833293446 app/services/ai/tasks/clinical_answer_task.rb 5968c478db0a307b54593f971ebc1d26a05432b8f440476e6d84ee6e7146c8d4 app/services/billing/meter.rb cb61a49f7c69fe0e18f2d74f89ccfea5e95b964b077c2a61962d65565d51641a app/views/ai_chats/show.html.erb a172f11693dcf72b8b434669b9e5ee6d43f56d9c8e6e1c8bf54f373e0ce8f4e3 app/views/layouts/application.html.erb ce085fad95f241bc7efa80c7ac78b9ea23c5b522c4db4d2c25c2c6acc5c18bcb app/views/privacy_audits/show.html.erb b0274609fb529c2005b419d425d18001294adf9623315291a0b97fae44547ff6 app/views/shared_conversations/show.html.erb 90b00f734663aaa5cb6f284ce0e940cbd4847c15076b71c81d4bd0c85ee7e0c8 config/environments/test.rb c4be67e6fab2f0b927c0a34a1c830454dde862235f04fd97c7e62c3367eb7ade config/initializers/chat_encryption.rb 5eaa02e6d649ac0598b7db9440e7b786bac791341418a0059811cbca80a98f19 config/initializers/content_security_policy.rb 4cca2d14f632364a798cbe686bdc0fba7614325fddd2fab40d5397edc9c6e844 config/initializers/filter_parameter_logging.rb 93930dc3bfa30b55db92f0f60d36b878a1c5f048889de51599cdd84b924379ff config/recurring.yml b135d48b1620d476d40f05e333b2b0bcca4f9711664e0ae6c04c7dbee8df93a4 db/migrate/20260923040000_add_private_chat_sharing.rb f9d50818511f83a067e7d76567c0c227b2159d22af996d27754247f7d7283721 docs/privacy-hardening.md ee1e32e4cfb3bbb1e8802044f908ef1109d35b7c1eed16d53f51b70b55914523 infra/bedrock-runtime-privacy.yml b6df1977b255dd4b41e91fa986f388716bd9d50eb1d9f279f42cdbc8b716021f lib/tasks/ai_models.rake b1f52853e1ec5acf1bd45f27e3c23ea8d4374915094879d1268d75e15ce60b47 lib/tasks/privacy.rake 58a06c9ce3ba6ce10d2abfab7ef657c720b11a4d8cb0984b6a250146a34075c8 test/controllers/privacy_audits_controller_test.rb 9c122d0bd3b082163a7b78252a4ef24896b99d2cf94f116fbb1e4d652bf43172 test/controllers/privacy_browser_controls_test.rb b42ab9d9fb1a23ab18e7477f00aaca57e96f79230f18f7d3a63040686682a734 test/services/ai/bedrock_provider_test.rb 9c92b30b098ebab9a40a6c6d8923b91b3b39476b75facdb359de9dbeafff5b57 test/services/ai/client_test.rb 92117c9362331c7b1fa5293fb51342847e1b97ec00d57adee2ab4e428f1456ce test/services/ai/privacy_probe_test.rb c6a507ede295922c833ca082fd202dafce2e531feea6417662f8e86337fb85ff test/services/ai/provider_metadata_test.rb e0acaff6fa2e35a7d134caaf8ccc97e99b77bf4c1b59246dff6034606d693675 test/services/ai/request_identity_test.rb db3f7946cf88fc1faf93283219af7d70e6f0907e94a451eba7fa43ebd2cc736c test/services/ai/route_policy_test.rb 94972fac6509087a242eaac1d7f43195b006f2c8c1b1daa973e20678917d1dd1 test/services/privacy_controls_test.rb e6400d12800ace088dc8aeb25d103d5b24d800f1963f2dc346511dff764f59a0 test/system/share_chat_test.rb
Provider documentation
Consulted on 23 September 2026. Provider documentation describes commitments and API behaviour; it does not independently establish the provider’s internal storage or our account configuration.
- AWS Bedrock data protection: the distinction between AWS hosting and the model developer’s access.
- AWS retention controls, model-specific abuse detection and invocation logging: separate checks relevant to zero retention. Eligibility remains model/API specific.
- OpenRouter API reference: optional end-user identification and app-attribution headers.
- OpenRouter zero data retention: request-level routing requirements and qualifications; account logging, sharing and export settings still need separate review.