# Aurisar Ledger — Information Security Policy

| Field | Value |
| --- | --- |
| Document Owner | Aurisar Ledger (operator / data controller) |
| Effective Date | 2026-04-25 |
| Last Reviewed | 2026-04-25 |
| Next Review | 2026-10-25 (semi-annual) |
| Version | 1.0 |
| Classification | Public |

---

## 1. Purpose

This policy defines how Aurisar Ledger identifies, mitigates, and monitors
information-security risks across its application, infrastructure, and
operational practices. It applies to the production deployment of Aurisar
Ledger and all data the application processes — including data received from
the Plaid API.

## 2. Scope

This policy covers:

- The Aurisar Ledger application source code (this repository)
- Production hosting: Render (application server), Supabase (PostgreSQL),
  Netlify (static frontend / API proxy)
- Third-party integrations: Plaid, Finnhub, CoinMarketCap
- The operator's workstation(s) used to develop, deploy, and administer the
  application
- Cloud-console accounts used to administer the above (GitHub, Render,
  Supabase, Netlify, Plaid Dashboard)

Aurisar Ledger is a **single-owner personal-finance application**. Each
deployment is operated by and serves a single end-user (the operator). There
are no employees, contractors, or sub-processors beyond the named cloud
platforms.

## 3. Roles & Responsibilities

| Role | Responsibility |
| --- | --- |
| Operator (Aurisar Ledger) | Owns this policy, operates production, applies patches, responds to incidents, performs reviews. |
| Render | Application runtime hosting, network controls, TLS termination, OS patching. |
| Supabase | Managed PostgreSQL — storage-level encryption, backups, OS patching. |
| Netlify | Static asset hosting, TLS, CDN, /api proxy to Render. |
| Plaid | Source of consumer financial data; consent capture during Link. |
| GitHub | Source-code hosting, Dependabot vulnerability alerts, CI runner. |

## 4. Risk Management

The operator maintains an informal risk register reviewed semi-annually
(see *Section 14*). Risks are scored on impact × likelihood and tracked
against the mitigating control. Current top risks:

| # | Risk | Likelihood | Impact | Mitigation |
| --- | --- | --- | --- | --- |
| R1 | Compromise of operator credentials → unauthorized data access | Low | High | bcrypt password (cost 12), TOTP, WebAuthn passkeys, account lockout, audit log |
| R2 | Stolen JWT or refresh token | Low | High | 15-min access TTL, hashed refresh tokens, revocation, rotation on logout |
| R3 | Plaid access-token disclosure | Low | High | AES-256-GCM at-rest encryption with `ENCRYPTION_KEY` |
| R4 | Vulnerable npm dependency | Medium | Medium | Dependabot daily checks + weekly CI `npm audit` (Section 8) |
| R5 | TLS / certificate misconfiguration | Low | Medium | Managed TLS (Render/Netlify), HSTS preload, CSP `upgrade-insecure-requests` |
| R6 | DB credential leak in repo / logs | Low | High | `.gitignore` excludes `.env`; secrets stored in platform vaults (`sync: false`) |
| R7 | XSS / injection | Low | High | Helmet CSP, parameterised SQL via `pg`, JSON body limit, no `eval`/`Function` |
| R8 | DoS / brute force | Medium | Medium | `express-rate-limit` global + per-route, account lockout |
| R9 | Runtime EOL → unpatched Node CVEs | Low | Medium | LTS-only Node policy: pinned to even-major Active LTS, manual annual bump, Dependabot ignores docker majors (see [`VULNERABILITY_MANAGEMENT.md §3.5`](./VULNERABILITY_MANAGEMENT.md)) |

## 5. Identity & Access Management

- **Application access** is gated by a per-user bcrypt password (work
  factor 12, stored in `users.password_hash`) plus optional TOTP and/or
  WebAuthn multi-factor. The first admin is auto-seeded from
  `ADMIN_EMAIL` / `ADMIN_PASSWORD` env vars when the users table is empty.
- **JWT access tokens** are signed with a 256-bit `JWT_SECRET` and expire
  after 15 minutes.
- **Refresh tokens** are 320-bit random, SHA-256 hashed at rest, expire
  after 7 days, and are revocable.
- **Account lockout**: 5 failed logins in 15 minutes triggers a 30-minute
  lockout. Lockouts and login attempts are recorded in the
  `security_events` audit table.
- **Console access** to GitHub, Render, Supabase, Netlify, and the Plaid
  Dashboard MUST have MFA enabled. The operator verifies this annually.
- **Least privilege**: production database credentials are scoped to a
  single Supabase project; Plaid keys are scoped to one client; API keys
  are stored only in platform secret vaults, never in the repository.

References: [server/server.js](../server/server.js),
[server/routes/auth.js](../server/routes/auth.js).

## 6. Cryptography

| Data | Algorithm | Key location |
| --- | --- | --- |
| User passwords | bcrypt (cost 12) | `users.password_hash` column |
| JWT signing | HS256 | `JWT_SECRET` env (≥256 bit) |
| Refresh tokens | SHA-256 hashed at rest | n/a (one-way hash) |
| Plaid access tokens | AES-256-GCM | `ENCRYPTION_KEY` env (32-byte hex) |
| TOTP secrets | AES-256-GCM | `ENCRYPTION_KEY` env |
| WebAuthn public keys | stored as-is (public material) | n/a |
| Database storage | AES-256 (Supabase platform) | Managed by Supabase |
| In-transit | TLS 1.2+ | Managed by Render / Netlify / Supabase |

HSTS is enforced for one year with `includeSubDomains` and `preload`.
CSP includes `upgrade-insecure-requests`. See
[server/server.js](../server/server.js).

## 7. Network & Infrastructure Security

- All public endpoints terminate TLS at Render or Netlify; no plaintext
  HTTP listener is exposed.
- Helmet sets CSP, HSTS, Referrer-Policy, X-Frame-Options, X-Content-Type-
  Options, etc.
- CORS is restricted to `ALLOWED_ORIGIN` in production.
- Global rate limit: 300 req / min / IP. Login rate limit:
  10 attempts / 15 min / IP.
- Express body parser is capped at 5 MB.
- The Postgres pool uses TLS to Supabase (`ssl: true`).

### 7.1 Source-code confidentiality

The Aurisar Ledger source repository is hosted **privately** on GitHub.
Read access is limited to the operator. Collaborators (including
compliance reviewers) are added on a per-request, time-bounded basis and
removed when their review concludes. The full git history was scanned
with [gitleaks](https://github.com/gitleaks/gitleaks) before the
repository was made private; no committed secrets were found.

Public-facing compliance evidence (Privacy Policy, Security Policy,
Data Retention, Vulnerability Management, Security Controls) is served
from the production deployment at `https://aurisar-ledger.com/<doc>`
rather than from GitHub URLs, so reviewers do not require repo access
to verify the policies.

CI workflows (`dependabot.yml`, `security-audit.yml`) continue to run
unmodified on the private repository under GitHub's free-tier
private-repo quotas (2,000 Actions minutes per month).

## 8. Vulnerability Management

The operator runs an automated vulnerability-management programme that
combines GitHub-native tooling with CI checks. See the dedicated
[Vulnerability Management Policy](./VULNERABILITY_MANAGEMENT.md) for full
details.

Summary of automation (configured in this repository):

- **`.github/dependabot.yml`** — daily checks for `npm` dependencies and
  weekly checks for `github-actions` and the `docker` base image.
  Security updates are auto-PR'd. The Node base image is pinned to
  Active LTS; Dependabot ignores `node` major-version bumps so the
  runtime can only move to a new LTS via deliberate operator action
  (see [Vulnerability Management Policy §3.5](./VULNERABILITY_MANAGEMENT.md)).
- **`.github/workflows/security-audit.yml`** — runs on every push, every
  pull request, and on a weekly schedule. Includes:
  - `npm audit --audit-level=high` (fails build on high/critical)
  - `npm audit --audit-level=moderate` (informational, does not fail)
  - Hard-coded-secret scan (gitleaks-style regex)
  - License-compatibility check
- **Runtime version pinning** — production Node runtime is fixed to
  Active LTS (currently **Node 24**, supported through Apr 2028) in
  both [`Dockerfile`](../Dockerfile) and
  [`render.yaml`](../render.yaml). Annual LTS bump cadence; non-LTS
  Node majors are not deployed.

Patch SLAs:

| Severity | Time to patch in production |
| --- | --- |
| Critical | 48 hours |
| High | 7 days |
| Medium | 30 days |
| Low | Best-effort, next scheduled release |

Operator workstation security: the operator's primary development machine
runs full-disk encryption (BitLocker / FileVault), automatic OS updates,
and platform-managed antivirus. The operator does not develop on shared
machines.

## 9. Software Development Lifecycle

- All code lives in this Git repository; protected `main` branch requires
  PR review for collaborator changes (single-operator commits permitted).
- Every PR triggers `security-audit.yml`.
- Dependencies are pinned in `package-lock.json`.
- No use of `eval`, `Function()`, dynamic `require`, or string-built SQL.
- Database queries use parameterised `?` placeholders translated to `$1…$n`
  in `server/db.js`.

## 10. Logging & Monitoring

- The `security_events` table records `login_success`, `login_failed`,
  `login_locked`, `token_refreshed`, `refresh_failed`, `logout`,
  `passkey_login_success`, `passkey_login_failed`. Each entry stores
  IP address, user-agent, and event details.
- Render and Supabase retain their own platform logs (request logs, slow
  queries, auth events) per their platform retention.
- The operator reviews `security_events` and Render logs after any
  unexpected lockout or alert.

## 11. Incident Response

If the operator suspects unauthorized access or data disclosure:

1. **Contain** — rotate `JWT_SECRET`, `ENCRYPTION_KEY`, the Plaid secret,
   and Supabase password; force-reset every affected user password via
   `node server/scripts/seed-admin.js --email=… --password=… --force`;
   revoke all refresh tokens (`UPDATE refresh_tokens SET revoked = true`);
   revoke all WebAuthn credentials if needed.
2. **Investigate** — pull `security_events`, Render request logs, Supabase
   logs covering the suspected window.
3. **Notify** — if Plaid-derived consumer data is implicated, notify Plaid
   per the data-protection terms within 72 hours.
4. **Remediate** — apply the patch / configuration fix; document in
   `docs/INCIDENT_LOG.md` (created on first incident).
5. **Review** — update this policy and the risk register if the root
   cause reveals a control gap.

## 12. Data Privacy

Data classification, retention, deletion, and consumer rights are governed
by:

- [Privacy Policy](./PRIVACY_POLICY.md)
- [Data Retention & Deletion Policy](./DATA_RETENTION_POLICY.md)

## 13. Third-Party Risk

The operator only integrates with vendors that publish a SOC 2 Type II or
ISO 27001 attestation:

| Vendor | Purpose | Attestation |
| --- | --- | --- |
| Plaid | Bank data | SOC 2 Type II, ISO 27001 |
| Render | App hosting | SOC 2 Type II |
| Supabase | Database | SOC 2 Type II |
| Netlify | Frontend / CDN | SOC 2 Type II |
| GitHub | Source / CI | SOC 2 Type II, ISO 27001 |
| Finnhub | Quotes (no PII sent) | Public commercial provider |
| CoinMarketCap | Crypto quotes (no PII sent) | Public commercial provider |

## 14. Policy Review

This policy is reviewed at least every 6 months and after any:

- Material change to the application architecture
- Security incident
- Change in regulatory or Plaid-contractual requirements

Reviews are recorded by updating the *Last Reviewed* and *Next Review*
fields above and committing the diff.

## 15. Exceptions

Any deviation from this policy must be documented in `docs/EXCEPTIONS.md`
with justification, compensating control, and expiry date.

---

*This policy is operationalised through the controls in this repository.
Cross-references in square brackets point to the implementing code.*
