Trust & security
Security, written
so it can be checked
Last reviewed 10 August 2026 · Value Garage Private Limited · CIN U66190DL2025PTC453505
Report a vulnerability: security@finlenstech.com — acknowledged within five working days
Companion to the Privacy Policy, which covers what we commit to and what an end user's rights are.
FinLens takes financial documents out of a person's Gmail and hands them to one business that person named. This page describes the controls around that. It also says which of them are built and which are not.
- How to read this page
- Where FinLens is today
- What crosses the boundary
- What we ask Google for
- Encryption & keys
- Isolation between clients
- Documents never become files
- Retention & deletion
- Logs & the audit trail
- Application security
- Assessments & certifications
- Sub-processors
- Live partner register
- What is not true yet
- Reporting a vulnerability
1. How to read this page
A security page is worth reading only if you can tell a built control from an intention. This one follows two rules.
Nothing is called done unless it runs in the code
Where a control is designed but not built, it is written in the future tense — with the word will — or it is listed in §14.
Every control names its mechanism, not just its effect
"Encrypted at rest" cannot be checked. AES-256-GCM, with the record id bound as additional authenticated data so a ciphertext lifted from one row cannot be replayed into another, is something an assessor can write a test against.
A page like this is read next to a Google API verification submission or a CASA assessment. Both go badly for an applicant whose public claims outrun their system. So the gaps are collected in §14 instead of being spread through careful phrasing. We would rather publish a gap than be shown it.
2. Where FinLens is today
FinLens is pre-launch. Five questions, answered separately, because each one can be checked on its own.
| Question | Answer as of 12 August 2026 |
|---|---|
| Are any partner businesses live? | No — none. See §13. |
| Has any end user's mailbox been read? | Yes — our own, in testing. No partner's customer has used this. |
| Is the sender registry active? | Partly — 11 of 187 entries are verified from captured message headers. The rest emit no queries. See §4. |
| Has a CASA assessment been completed? | No — none completed. A Tier 2 assessment is engaged and has not begun. See §11. |
| Is the service deployed? | Yes, as production since 12 August 2026. The master key is held by a managed key service; the start-up gate that refuses NODE_ENV=production without one was satisfied, not removed. See §5. |
The controls in the rest of this page are built. An automated suite covers them and runs on every change, including tests that run against a real PostgreSQL rather than an in-memory substitute. What is missing is an external assessment and live traffic — not the controls.
3. What crosses the boundary, and to whom
FinLens transfers document content to a client. That comes first because everything else is built around it. The unlocked PDF, and the JSON read out of it, go to the one business the end user named and approved. That JSON carries account holder name, account number, address and frequently PAN. That is the product. A page implying otherwise would be describing a different system.
What carries the weight, then, is the boundary. Document content crosses to the named, consented recipient and to nowhere else. Everything the mailbox told us about a document crosses nowhere at all.
3.1 The field denylist
A list of field names is forbidden on every schema, every serialized response and every log line: message bodies and snippets, subject lines, sender and recipient addresses, filenames, attachment ids, Gmail message and thread ids, raw MIME — and separately, credentials: passwords, derived PDF passwords, access and refresh tokens, API secrets, HMAC keys.
Three checks enforce it. Each catches what the others miss.
check 1 · the schema
Strict egress schemas
Every egress schema is strict, so an undeclared key fails validation. This catches the accidental addition.
check 2 · the payload
Real payloads, not schemas
A walker crawls real serialized payloads — not schemas — and fails the build on any hit. This catches the field somebody added to a schema on purpose.
check 3 · the log line
Redaction at serialization
The logger applies the same test before serialization. This catches logging a whole request object, where the offending data never went near a schema.
All three call the same function. Two implementations of "is this field forbidden" would drift, and the drift would be silent.
3.2 Two exceptions, both named and tested
Exceptions are default-denied and opted into visibly at one call site, rather than being absences nobody notices.
Document content
The artifact and its extracted JSON are permitted on the delivery schema and nowhere else.
The webhook signing secret
It appears exactly once, in the response to a partner registering their endpoint, because the partner must hold that value to verify our signatures. It is not recoverable afterwards; losing it means rotating.
Both exceptions use the same mechanism, so there is one pattern to audit rather than two.
4. What we ask Google for, and what we do with it
- One scope, and no others. gmail.readonly. Read-only means we cannot send, modify, delete or label a message. The scope list is a fixed constant in the code, not a configuration value, so widening the grant is a code change that appears in review. It held two further scopes, openid and email, until 14 August 2026; an audit found nothing in the system ever read either, so both were dropped rather than justified.
- One recipient. What we fetch goes to the single business the end user approved by name, and to nobody else (§3).
- No person reads it. Nobody at FinLens reads an end user's mail or documents. There is no manual-review step anywhere in the pipeline, and the extraction interface has no way to express one — a test walks its whole type surface and fails the build on any review-shaped member (§7.1).
- No third party sees it. Text extraction runs in our own containers. No external service receives a document at any point (§12).
- The user can cut us off without us. Removing FinLens at myaccount.google.com/permissions takes effect at Google immediately and does not depend on anything we do.
FinLens's use and transfer of information received from Google APIs follows the Google API Services User Data Policy, including its Limited Use requirements. Because document content genuinely reaches a client, those requirements travel with it: the client is bound to them by Partner Terms §3.
4.1 We only ever fetch documents
Every mailbox query — without exception, on every tier — carries has:attachment filename:pdf. This is a control, not an optimisation. A sweep scoped only by sender returns years of transaction alerts, OTPs and marketing from the same bank domain, none of which are documents. With the gate, an eight-year sweep of a large bank returns roughly a hundred actual statements.
Worth stating plainly: this is the difference between reading less of a mailbox and only ever fetching documents.
How far back a query reaches depends on the tier:
| Tier | Applies to | History |
|---|---|---|
| Default sweep | All registered financial senders | 18 months |
| Named institution | Institutions the client names at intake | Unbounded |
| Insurance | Every registered insurer | Unbounded |
The eighteen-month figure is a single shared constant rather than three literals. The query, the scope echoed back to the client at intake, and the sentence the end user reads on the consent page have to agree. A consent page promising eighteen months over a scanner sweeping twenty-four is consent obtained for something other than what happens.
4.2 A From header proves nothing, so we check the signature
Anyone can forge a from: header. In this system that filter is a data-minimisation control and never an authenticity control. Authenticity is decided after the fetch, by reading Gmail's own Authentication-Results verdict and checking alignment.
- A message is readable only if its From-domain belongs to a registry entry and that same entry's authentication mode is satisfied. Checking the signature against one entry and the address against another would let one institution's signature authenticate another's mail.
- Every dkim= result in the header is considered, not the first. A large bank can authorise six mail providers and emit several results in one header; first-match logic silently drops legitimate mail.
- A message with no Authentication-Results header is a failure, never a pass. Defaulting absence to pass would make the whole registry decorative.
- An SPF-only mode exists for one reason: some Indian government senders publish no DKIM at all and authenticate on SPF with strict alignment. It is permitted only for .gov.in domains, requires aligned smtp.mailfrom, and is refused for any commercial sender — checked when the registry loads and again at the moment a message is authenticated.
- Domains we have examined and rejected as forged are a hard reject, not a miss.
4.3 Most of the registry is inert, deliberately
Of the registry's 187 entries, 11 are verified — promoted only after a real message from that sender was captured and its DKIM alignment checked. 156 remain seed: plausible but unconfirmed. 20 are domains we have examined and quarantined.
Queries are built from verified entries only. An unconfirmed sender therefore issues no queries and reads zero documents. Promotion requires a real header captured from a real message; Indian institutions do not publish their sending addresses, so there is no shortcut and we have not invented one. An unconfirmed entry that never matches is easier to reason about than one that quietly matches the wrong sender.
5. Encryption & key handling
5.1 What is implemented
- AES-256-GCM envelope encryption. Each record gets its own data key, wrapped by a master key. GCM is authenticated, so tampering fails at decryption rather than yielding altered plaintext.
- The record id is bound as additional authenticated data. A ciphertext lifted from one row cannot be replayed into another — a real attack against per-row encryption that omits AAD.
- Key identifiers are derived from the key material (a truncated SHA-256), not from constants. With a constant, the guard that catches a wrapped key meeting the wrong master key compares itself to itself and passes unconditionally.
- Data keys are zeroed after use rather than left recoverable in process memory.
- Inbound API secrets are sealed under the key service, not hashed. Our request signing verifies against a value we must be able to use, and a value you must use cannot be a one-way hash. Sealing means a stolen database or backup cannot forge inbound requests on its own — the attacker needs the key service too.
- Outbound webhook secrets are sealed, not hashed, and the signing key is derived by HKDF from the plaintext secret the partner holds. The inherited code signed with the stored hash column, which turned any read-only disclosure of that column into the ability to mint webhooks a partner would accept as genuine. Two guards prevent its return: the signing key is a type only the derivation can produce, so passing the stored column is a compile error rather than a review question, and the derivation refuses a hash-shaped input at runtime.
- Key material is versioned. New records are sealed under the current version and old ones open under whichever version they name, so a rotation does not orphan existing data.
5.2 The managed key service, and the gate that admitted it
The master key is held by Google Cloud KMS, in asia-south1. Wired on 12 August 2026. Until then the only providers available held the key in process memory or in a passphrase-sealed file on the same host as the database it protects, and this page said so plainly. That is recorded here rather than quietly replaced.
Every service still refuses to start with NODE_ENV=production unless the key service is managed. That gate was satisfied, not removed. What it tests is the identity of the object the process will actually use — not a constant, and not an environment variable. A provider counts as managed because it was built over a client that opens a real connection to a remote key service, and the function that records this is unreachable from outside the encryption package. A test double built over the same implementation is deliberately excluded, so nothing in a test suite can certify a process for production. Each service then wraps and unwraps a probe value before it accepts a single request, so a key that exists but cannot be used fails the deployment rather than someone's first document.
The key is software-protected rather than HSM-backed, and that was a decision rather than a default. An HSM defends against extraction of key material from inside Google's own infrastructure. What actually guards this key is a service-account credential on our host, mounted as a container secret rather than an environment variable; if that leaks, the protection level changes nothing. We would rather name the risk that dominates than buy a control that does not address it. Google's own preferred alternative to a downloaded credential — federated identity — is not available to us on our current host, and we would use it if it were.
No key has rotated, and no rotation cadence is in force. The versioned key format above is what makes a rotation possible without re-encrypting or orphaning data. There is no re-wrap path in the software today, so a rotation would add a key version rather than retire one — which is the honest reason a frequent cadence would buy less here than it sounds like it should.
6. Isolation between clients
Because document content legitimately crosses to one client, "this client and no other" is the control that carries the most weight here. It is enforced in the database, not only in application code.
- Row-level security on every tenant-scoped table — API keys, webhook endpoints, sessions, OAuth grants, audit events, pending consents, intakes, artifacts and scan outcomes — and on the tenants table itself, which names the tenants the other policies are written in terms of. Without a policy there, one client could enumerate the full client list.
- Policies are ENABLE and FORCE. The table owner bypasses row security otherwise, and the migration role usually is the owner.
- Policies carry both USING and WITH CHECK, so a scoped connection cannot write a row it would then be unable to read.
- Cross-tenant access is an explicit opt-in on the connection, never inferred from an unset value.
- The application connects as a dedicated role created NOSUPERUSER NOBYPASSRLS, re-asserted on every migration run, with data privileges only and no ability to alter a policy or drop a table. A superuser bypasses every policy silently — not overridden, not warned about, simply not applied.
This was found by negative control rather than reasoned about. The first isolation test, run as a superuser, showed one tenant reading another's session with the policies present and correct. The isolation suite now runs as the unprivileged role against a real PostgreSQL, because an in-memory test double cannot fail a row-security assertion, and a test that passes while proving nothing is worse than no test.
7. Documents never become files
A document that never becomes a file cannot be left behind by a crashed worker, cannot be read out of a container volume, and cannot outlive the retention window in §8.
- Every tool in the document path is driven through pipes. Decryption and text extraction read the document on standard input and write to standard output. No temporary file is created at any point, so there is no cleanup to forget.
- The extraction interface accepts bytes and has no path variant. That is a shape rather than a convention: adding one would undo the property for every implementation at once, so it would have to be argued for rather than slipped in.
- Subprocesses run with no shell — arguments are passed as an array, so attacker-influenced input is data and never syntax — with an empty environment (hence absolute binary paths, because there is no PATH for a planted binary to be found on), an output cap against decompression bombs, and a timeout with SIGTERM followed by SIGKILL.
- Tool stderr is captured and never surfaced to a caller or a log. Diagnostics from a PDF tool routinely quote the document they are struggling with.
7.1 Text extraction runs in-house, and no person reads a document
Documents are read by a self-hosted engine inside our own containers. There is no third-party document-processing or OCR vendor — no external service receives a document at any point in the pipeline. That was a deliberate choice over a viable vendor arrangement. Running in-house means there is no transfer to disclose, no sub-processor in assessment scope, no annual re-attestation dependency we do not control, and no external element on an assessor's data-flow diagram.
Two conditions come with it, and both are enforced structurally rather than by policy.
No human review
The extraction interface has no needs review outcome, no ticket id, no queue handle and no callback for a person to resolve later. A document is read within the call or it is not read at all. The risk is not somebody building a review console in defiance of a rule. It is the reasonable-looking change that adds "confidence below threshold, an operator confirms it". An automated test walks the entire type surface and fails on any review-shaped member.
No network egress
The engine's environment is screened at construction and refuses to start if it carries anything egress-shaped — proxy variables, URLs, endpoints, tokens. The failure this defends against is dull and likely: an engine that validates a licence over HTTPS, in a container whose namespace sets a proxy variable nobody thought of as configuration.
The stronger half of that second control is now the one doing the work. The real enforcement is the engine sitting in a network namespace with no route out, so an engine that tried to reach anything would fail at the kernel whatever the code did. That container is where text extraction runs, on a network with no gateway, and automated tests fail the build if anything else joins that network, if the engine is pointed at a setting that needs one, or if an OCR binary is put back into the service that reads your mailbox.
That last one is the part worth spelling out. Reading a mailbox requires reaching Google, so the component that does it necessarily has a route out — and until 12 August 2026 that same component also did the text extraction. It no longer does, and the OCR software is not installed in it rather than merely switched off, because a setting that can be changed is a weaker thing than a program that is not there.
8. Retention & deletion
| Data | Retained |
|---|---|
| Email content and attachments as they arrive from Gmail | Never stored. Documents pass through memory and are never written to disk |
| The artifact we produce — the document plus its extracted JSON | At most 3 hours, encrypted at rest, then hard-deleted with a receipt |
| End-user identity (name, PAN, date of birth, mobile), used only to derive statement passwords | Sealed, and destroyed when the consent link expires — well before any artifact |
| Mailbox access credential | Sealed, and destroyed when processing finishes or the user withdraws, whichever is first |
| Audit trail of actions taken | Retained as evidence; contains no email or document content (§9) |
Three things make the three-hour figure a property rather than a preference.
- It is a schema maximum, not a default. The retention setting is validated against a ceiling of 10,800 seconds, and a service configured above it fails to start. An operator raising it to debug a client integration would otherwise make the privacy policy, the consent page and this page false without a code change. A configuration value that can silently exceed a published claim is not a configuration value; it is an unreviewed policy change.
- A sweep runs on a schedule, not on request. Anything past its expiry is destroyed and a purge receipt records what went. Without a sweep behind it, an expiry timestamp is a column recording an intention.
- There is no durable outbox. The webhook is a notification; the client pulls the artifact from the API inside the window. A client's webhook outage therefore costs nothing. The corollary is stated to clients rather than discovered by them: the pull window is the retention window, so a nightly batch job against it will find nothing.
9. Logs & the audit trail
9.1 Logs are content-free by construction
Audits fail on logs far more often than on APIs. Someone logs a whole request object to debug an incident, an authorization header lands in a retention system, and it is still there a year later. So redaction happens at serialization, rather than depending on every call site being careful.
- The same denylist test used by the schema tests and the payload walker runs over every log line. It is imported, not reimplemented.
- A second pass redacts names that carry authority rather than content — tokens, secrets, cookies, API keys, and in this system connect-session ids, PKCE verifiers and CSRF state, each of which is the whole capability to complete an OAuth flow. Log lines get pasted into support tickets by people who would never paste a token.
- Errors log as a name only — never a stack, never an upstream library's message, both of which routinely carry URLs with credentials in them.
- Binary payloads are never logged. Cycles and pathological nesting are bounded rather than allowed to hang a log call.
9.2 The audit trail
- Append-only and hash-chained. Each entry commits to the one before it, so removing or editing history breaks the chain at a verifiable point. A verification pass recomputes the whole chain and reports the first sequence number that does not match.
- Reproducible by someone who is not us. Field order is fixed and detail keys are sorted, so an auditor recomputing the chain from a database dump gets the same digests.
- Content-free. Entries hold event names, timestamps, counts and outcome codes. The detail field is not free-form: forbidden keys are dropped rather than redacted, because an audit row recording the shape of a leak is still a record of one.
- Two independent defences against tampering, which is the only arrangement worth calling defence in depth. Database rules rewrite any update or delete away before privileges are consulted, and the application role separately lacks the privilege — verified by disabling the rules and confirming the statement is then refused outright.
To be precise about what that buys: the rules make tampering hard, the chain makes it detectable. A database administrator with sufficient privilege can always drop a rule, which is why detection matters more than prevention here.
10. Application security
The platform is built to OWASP ASVS Level 2 as a design target. That is a statement about how it was written, not a certification — see §11.
- Requests are signed, not bearer-authenticated. A bearer key is replayable and identical on every request. The signature covers method, path, timestamp, nonce and a hash of the body, so a captured request cannot be replayed or edited. Timestamps outside a skew window are rejected outright, and nonces are checked for reuse inside it. Comparison is timing-safe.
- Validation at every boundary. Input is parsed and validated before it touches business logic. Unvalidated input never reaches a query.
- A single error contract. One exit point for every failure, returning a code and nothing else — no stack, no upstream text, no field-level internals.
- Webhook endpoints must be HTTPS. A signature over cleartext is theatre.
- Server-side request forgery is treated as a real threat, not a URL check. Webhook delivery refuses private and link-local address ranges — including the cloud metadata range at 169.254.0.0/16 — checks every address a hostname resolves to rather than the first, connects to the address it validated rather than re-resolving the hostname, and re-runs the check on redirects. A hostname that resolves publicly today and to a metadata address tomorrow is the attack this defeats.
- Rate limiting on the public API.
- TypeScript in strict mode, lint-clean, and 952 automated tests covering the controls on this page, run on every change. Several were written as negative controls: the old implementation is restored, the new test is watched to fail, and only then is it fixed. A security test that has never failed is a function nobody has run in anger.
11. Assessments & certifications
FinLens holds no security certification, and no independent assessment has been completed
No CASA (Cloud Application Security Assessment) has been completed. A Tier 2 assessment is engaged and has not begun; it is recorded here now because this section says it will be. No ISO 27001 certification, no SOC 2 report, and no third-party penetration test.
CASA is a requirement that applies to applications using restricted Google API scopes, which FinLens will be. It is a thing we must pass, not a thing we have achieved, and the two are routinely conflated on pages like this one. We will not describe an assessment as complete before one has been completed, and this section is where it will be recorded — with the assessor, the tier and the date — when it is.
What exists in place of an assessment is the design work. The platform is built to OWASP ASVS Level 2, the standard CASA tier 2 is scoped against, and the decisions behind every control on this page are recorded in a dated architectural decision log, which we provide to assessors on request.
12. Sub-processors
No document ever reaches a third party. Text extraction runs in-house (§7.1), so there is no OCR vendor, no document-processing vendor and no external analysis service. There is no transfer to disclose, which is the strongest version of this statement and the reason the in-house path was chosen.
We are not publishing a register of one hypothetical entry
The only sub-processor FinLens anticipates is cloud infrastructure. Nothing is provisioned yet, and a register naming a provider we have not deployed to would be exactly the kind of plausible, unverifiable claim this page exists to avoid.
Commitment: a sub-processor register will be published here, naming each provider, its purpose and its location, before the first partner goes live — and any addition will be published before it is used.
13. Live partner register
No partner businesses are live
FinLens has not delivered a document to any client. When partners go live, each will be listed here by registered name with the date it was onboarded, and this register will be updated before a partner can request its first consent.
Independent of anything listed here, a business receives an end user's documents only when that user has approved that named business on a FinLens consent page. The page names the recipient, the document categories and how far back we will look, and nothing is fetched until it is approved.
Section 14 · the correction
What is not
true yet.
Collected in one place, so nobody has to assemble it from careful phrasing elsewhere. Every item below is something a security page is commonly expected to assert. None of these is true of FinLens today.
Each entry pairs the control you might have expected with what exists in its place.
A completed independent security assessment — CASA, ISO 27001, SOC 2, penetration test
instead A CASA Tier 2 assessment is engaged and has not begun. Nothing is certified, and no third party has yet examined this system. Built to OWASP ASVS Level 2 as a design target, with a decision log per control
A key-rotation schedule
instead A versioned key format, so a rotation will not orphan existing records. No cadence is set
An egress allow-list — a stated set of destinations our systems may reach
instead Container hardening and the network split are deployed: read-only filesystems, all Linux capabilities dropped, no-new-privileges, pinned image digests, and a database network with no route off the host. The allow-list is the piece that is not. The network our workers use for outbound calls reaches the whole internet, and a check inside the application on where it may send a request is the only control on that — which is a weaker thing than a network that cannot carry the connection at all, and we would rather say so than let the hardening above imply otherwise
A fully verified sender registry
instead 11 of 187 entries verified from captured headers. The other 176 — 156 seed and 20 quarantined — emit no queries at all, so mail from them is not read rather than read unchecked
A tested incident-response runbook
instead A written runbook that has never been exercised — no tabletop, no drill, no real incident. It names its own gaps, including that there is no 24/7 response. Written and untested are different from tested, and we will not describe it as the latter until an exercise log entry exists. The 72-hour breach-notification duty under the DPDP Act applies regardless and is stated in the Privacy Policy
Kill switches, anomaly detection, response watermarking, 24/7 on-call
instead Nothing. Earlier copy on this page claimed these; they were never built
Just-in-time, dual-approved, time-boxed human access to the processing boundary
instead Nothing. There is no access-approval mechanism. What does exist is that no human step appears anywhere in the document pipeline (§7.1)
A bug bounty programme
instead A disclosure address and the commitments in §15
An end-user portal, self-serve export, or a consent dashboard
instead No user portal exists. Rights requests are handled by contacting us, per the Privacy Policy
Several of these were asserted by an earlier version of this page. They were removed rather than softened. If you are re-reading this page against something you were told previously, this list is the correction.
15. Reporting a vulnerability
Write to security@finlenstech.com. Include enough detail to reproduce the issue, and give us a reasonable window to fix it before publishing.
Our commitments, sized to a pre-launch team rather than quoted from a larger company's policy:
- We will acknowledge a report within five working days.
- We will not pursue legal action against good-faith research that stays within scope, avoids privacy violations and does not degrade the service.
- We will tell you what we found and when it was fixed, and name you as the reporter if you would like that.
Please do not test against a live end user's mailbox or a client's data.
Contact
Security reports · security@finlenstech.com
Privacy questions · privacy@finlenstech.com
Grievance officer (DPDP) · Suraj Agarwalla — suraj@maximoney.in
Value Garage Private Limited · CIN U66190DL2025PTC453505
Registered office: Flat no. 26, Vandana Apartment, East Delhi, Delhi, India — 110092
What we commit to on an end user's behalf, and their rights under India's data-protection law, are in the Privacy Policy. What a client commits to when documents reach it is in the Partner Terms.