Security
Authentication
Multi-Layer Auth Model
| Layer |
Method |
Details |
| Customer onboarding |
Tripletex OAuth2 |
Standard OAuth2 authorization code flow |
| API requests to Tripletex |
Session token (Basic Auth) |
Auto-refreshing, 1-hour TTL |
| Conversation Agent → Accounting API |
Internal service auth (mTLS / signed JWT) |
Every POST /solve call is authenticated |
| Slack bot → Conversation Agent |
OAuth2 bot token + event signature verification |
HMAC-SHA256 on every webhook |
| Email webhook → Conversation Agent |
DKIM verification + shared secret |
Reject unsigned or unverified emails |
| Dashboard |
OAuth2 (Google/Microsoft SSO) |
No password-based auth |
| Accounting providers |
OAuth2 per provider |
TripletexProvider (session tokens), FikenProvider (bearer tokens) |
| LLM providers |
API keys via secret manager |
GCP Secret Manager or Vault |
Token Storage
All tokens and secrets are encrypted at rest using AES-256-GCM with per-tenant encryption keys. The master key is stored in GCP Secret Manager (or HashiCorp Vault for self-hosted).
Token lifecycle:
1. Received from OAuth flow
2. Encrypted with tenant-specific DEK (data encryption key)
3. DEK wrapped with KEK (key encryption key) in Secret Manager
4. Stored as BYTEA in PostgreSQL
5. Decrypted in-memory only when needed for API calls
6. Never logged, never returned in API responses
Encryption
In Transit
| Connection |
Protocol |
| User to dashboard |
TLS 1.3 (Cloudflare) |
| Slack webhook |
TLS 1.3 |
| Email webhook |
TLS 1.2+ |
| Backend to Tripletex |
TLS 1.2+ |
| Backend to LLM providers |
TLS 1.3 |
| Database connections |
TLS 1.3 with certificate pinning |
At Rest
| Data |
Encryption |
| OAuth tokens |
AES-256-GCM (envelope encryption via KMS) |
| Audit trail |
Database-level encryption (PostgreSQL TDE) |
| Company config |
Database-level encryption |
| PDF attachments |
AES-256-GCM per file, stored in GCS with CMEK |
| Backups |
Encrypted with separate backup key in KMS |
Bokforingsloven Compliance
The Norwegian Bookkeeping Act (bokforingsloven) imposes specific requirements on accounting systems. The AI Accountant is designed to enforce these.
Requirements and Implementation
| Requirement |
Law Reference |
Implementation |
| Every transaction must be documented |
Section 10 |
Audit trail logs every API call with full context |
| Documents must be stored for 5 years (primary: 10 years) |
Section 13 |
Retention policy: audit trail 10 years, attachments 10 years |
| Transactions must be recorded without undue delay |
Section 7 |
Real-time processing via Slack/email, automated cron |
| Audit trail must show who, when, what |
Section 13a |
user_id, timestamp, action_type, resource_id on every entry |
| No deletion of accounting records |
Section 13 |
Soft delete only; undone_at field marks reversals, original entry preserved |
| Sequential numbering of vouchers |
Section 7 |
Enforced by Tripletex; validated by ComplianceEngine |
| Separation of duties |
Section 2 |
Role-based access: employee, manager, accountant, admin |
Undo Mechanism
Instead of deleting, the AI Accountant creates a reversing entry:
Original: V-2026-0340 — Debit 6010 (2,500 kr), Credit 1089 (2,500 kr)
Undo: V-2026-0341 — Debit 1089 (2,500 kr), Credit 6010 (2,500 kr)
Description: "Reversering av V-2026-0340"
Both entries are preserved in the audit trail. The undo window is 24 hours.
Data Handling
What We Store
| Data |
Location |
Retention |
| Audit trail |
PostgreSQL |
10 years |
| Company config (tokens, rules) |
PostgreSQL |
Active + 1 year after cancellation |
| Employee identity mapping |
PostgreSQL |
Active + 90 days |
| Context cache (departments, accounts) |
PostgreSQL |
1 hour TTL |
| PDF attachments |
GCS bucket |
10 years |
| LLM conversation logs |
None |
Not stored |
What We Do NOT Store
- Financial transactions (Tripletex is system of record)
- Account balances
- Personal salary data
- Bank account numbers (only referenced via Tripletex IDs)
- LLM conversation history beyond the current task
Data Isolation
Each company's data is logically isolated:
- All database queries include
company_id filter
- Row-level security (RLS) enforced at the PostgreSQL level
- GCS buckets use per-tenant prefixes with IAM policies
- No cross-tenant data access is possible, even in error
GDPR Compliance
| Right |
Implementation |
| Right to access |
Export endpoint: GET /api/company/:id/export |
| Right to erasure |
Delete company data (except bokforingsloven-required records) |
| Right to portability |
JSON export of all stored data |
| Data minimization |
Only store what is needed for audit and operation |
| Privacy by design |
No personal data in LLM prompts beyond necessary context |
LLM Data Handling
| Concern |
Mitigation |
| Data sent to LLM |
Minimal: task description + relevant context only |
| Employee names in prompts |
Used for identity resolution, not stored by LLM |
| Financial figures |
Sent only when needed for calculations |
| LLM training |
Both Gemini (Vertex AI) and Claude API have no-training guarantees |
| Conversation logs |
Not persisted; each task is a fresh context |
Access Control
Role-Based Access
| Role |
Can Do |
Cannot Do |
employee |
Submit expenses, ask questions, request invoices |
Approve expenses, run payroll, close month |
manager |
Approve expenses up to threshold, view department budgets |
Close month, modify rules |
accountant |
All accounting operations, close month, bank reconciliation |
Modify system rules, manage users |
admin |
Everything, including rules and user management |
N/A |
Principle of Least Privilege
- Read-only queries require
employee role minimum
- Write operations check role + approval thresholds
- Sensitive operations (payroll, month-end) require
accountant or above
- Rule changes require
admin
- All role checks happen in the RulesEngine before any Tripletex API call
Incident Response
| Severity |
Example |
Response Time |
Action |
| Critical |
Token leak, unauthorized access |
15 minutes |
Revoke all tokens, notify customers, rotate keys |
| High |
Data exposure, API abuse |
1 hour |
Investigate, patch, notify affected customers |
| Medium |
Failed auth attempts, rate limit abuse |
4 hours |
Block source, review logs |
| Low |
Minor policy violation |
24 hours |
Log, review in next audit |