Access control policy

Household Finance · last updated 11 November 2026

This application is operated by a single household of two people, who are also its only users. There are no employees, no contractors and no other customers. Several controls a company would need therefore do not apply here, and this policy says so rather than leaving a reader to assume.

Who may access the application

Two named people, and no one else. Account creation is disabled at the identity provider, so a successful sign-in by an unknown person cannot produce an account.

Access is granted by creating a record for that person inside the application’s own database, which is done deliberately and by hand. A person who somehow obtained an authentication record without that step resolves to no household, and every database policy then denies them. They would see an empty application rather than somebody else’s money.

How people authenticate

  • Sign-in is through Google only, on accounts that carry Google two-step verification.
  • Members have no password for this application. There is no password to guess, reuse, phish or reset, because none exists.
  • Connecting a new bank requires a Google authentication within the previous 12 hours. This is checked on the server immediately before the connection interface is shown, not in the browser.

This is multi-factor authentication, but it is not phishing-resistant, because the second factor is a code rather than a hardware-bound credential. That is stated plainly here and answered the same way on security questionnaires.

How access is enforced on the data

Authorisation is enforced by the database, not by application code. This is the central control, and it is deliberate: application code can have a missing check, whereas a database policy applies to every query including one issued by a mistake.

  • Every table carries an identifier for the household that owns the row, and every table has row-level security enabled.
  • A signed-in caller can only read or write rows belonging to their own household. There is no query that returns another household’s data.
  • The anonymous role — the one the public key in the browser maps to — holds no permission on any table. It is refused before row-level security is even reached. This is verified against the live database by an automated check, not assumed.
  • The table holding bank access tokens has no permission granted to any browser-facing role at all.
  • No credential capable of bypassing row-level security exists anywhere in the application or its configuration.

Non-human access

Scheduled jobs and incoming notifications from the banking provider have no human session. They run as a dedicated service account whose writes pass through the same row-level security as a person’s, rather than as a privileged role.

  • The scheduled job authenticates with a secret compared in constant time, and is rejected otherwise.
  • The provider’s notifications are verified by checking the signature the provider attaches, against a key fetched from the provider. An unsigned or incorrectly signed notification is discarded.
  • Bank access tokens are encrypted by the application before storage and are never sent to a browser.

Administrative accounts

Four third-party accounts administer the systems behind this application: source control, hosting, the database, and the banking provider’s dashboard. All four require multi-factor authentication.

Where the provider supports hardware-bound credentials, those are used. Where it does not, the strongest factor it offers is used instead, and one provider that issues no recovery codes carries two enrolled authenticators so that a lost device does not become a lost database. Claiming uniformly strong factors across all four would be inaccurate, so it is not claimed.

Granting and removing access

Granting access requires two deliberate acts: adding the person at the identity provider, and creating their record in the application’s database. Neither happens automatically, and neither is reversible by accident.

Removing access is the same in reverse, and takes effect immediately for new requests because authorisation is evaluated per query rather than cached in the session.

There is no employee lifecycle to automate here. Automated de-provisioning for departing staff is not in place because there are no staff.

Change control

All changes are version-controlled and must pass automated checks — linting, type checking, the full test suite and a production build — before they can be deployed. Deployment follows the main branch automatically, so what was reviewed is what runs. Decisions that affect access or security are recorded with their reasoning, so that a later reader can distinguish a deliberate trade-off from an oversight.

What is deliberately not in place

A policy that claims no gaps is not a credible policy. These are the known ones:

  • No periodic access review. With two people and four administrative accounts there is nothing to drift. This is intended to be folded into the annual retention review rather than left undone indefinitely.
  • No automated de-provisioning. Not applicable: no employees.
  • No zero-trust network architecture. There is no corporate network to segment.
  • No logging or alerting on administrative access beyond what each provider retains by default.

Review

This policy is reviewed once a year, alongside the data retention policy, and whenever a change is made that affects who can reach the data.

Back to the application