30-day window, immutable audit trails, and hard evidence of containment
Australia's Notifiable Data Breaches (NDB) scheme is live and getting teeth. Since 2018, any breach of personal information affecting Australians triggers a legal obligation: notify affected individuals within 30 days or face OAIC fines. But here's the catch—you need proof. Proof of *when* you detected it, proof of *what* was exposed, proof of *how* you contained it. If your logging is a mess, your investigation becomes a legal liability.
Velocity X treats incident response as a first-class architecture problem. Immutable session logs, soft-delete with forensic trails, encrypted-at-rest tokens, and audit triggers mean you can investigate a breach in hours, not weeks. When you call OAIC compliance, you've already got timestamps, affected user IDs, and containment evidence in hand.
The NDB Scheme: Definition & Scope
The *Notifiable Data Breaches* scheme applies to organisations holding Australian personal information. A breach is "an unauthorised disclosure or loss" of data likely to result in serious harm (identity theft, financial loss, serious embarrassment). You must:
1. Detect. Know that a breach has occurred. 2. Investigate. Determine scope, affected individuals, and data exposed. 3. Notify. Tell OAIC and affected people within 30 days. 4. Document. Keep records of the breach and your response for audits.
Small breaches (e.g., a password reset email sent to the wrong address) may not meet the "serious harm" threshold. But if an attacker dumps your user table? That's notifiable. If a support staff member accidentally exports customer financials? Notifiable. You don't get to decide alone—OAIC does, and they're conservative. Assume everything is notifiable unless you have legal sign-off otherwise.
Architecture: Audit Logs, Immutable Sessions, Encrypted Tokens
Velocity X bakes three forensic layers into the database from day one. When a breach happens, you query them sequentially: *When was it detected?* (logs) → *Who was exposed?* (sessions) → *How do we prove we fixed it?* (encryption).
Immutable Session Log
Every login, permission change, and data export is recorded in an immutable append-only table. No updates, no deletes—only inserts. This is your timeline.
{`create table audit_sessions (
id bigserial primary key,
user_id uuid references auth.users(id),
session_token_hash text not null, -- SHA256 of session token
ip_address inet,
user_agent text,
action text, -- 'login', 'logout', 'export_data', 'permission_grant'
resource_type text, -- 'users', 'payments', 'documents'
resource_count int, -- how many rows accessed
created_at timestamp default now()
);
-- Never allow updates or deletes
revoke update, delete on audit_sessions from authenticated;
grant insert, select on audit_sessions to authenticated;`}
When you detect a breach (e.g., logs show unusual export volume at 3am from an unfamiliar IP), you query this table to find the exact session, timestamp, and data volume touched. OAIC auditors will ask: "Who accessed what and when?" Answer: query the table, export a JSON report. Done.
Data Access Triggers
Every select, insert, update, or delete on sensitive tables triggers a log entry capturing the user, timestamp, and data (old vs. new values).
{`create table data_audit_log (
id bigserial primary key,
user_id uuid,
table_name text,
operation text, -- 'SELECT', 'INSERT', 'UPDATE', 'DELETE'
row_count int,
old_values jsonb, -- only for UPDATE/DELETE
new_values jsonb,
queried_at timestamp default now()
);
create or replace function audit_table_access()
returns trigger as $$
begin
insert into data_audit_log (user_id, table_name, operation, row_count, old_values, new_values) values (
(auth.jwt() ->> 'sub')::uuid,
tg_table_name,
tg_op,
1,
to_jsonb(old),
to_jsonb(new)
);
return coalesce(new, old);
end;
$$ language plpgsql security definer;
-- Attach to every sensitive table
create trigger audit_users after insert, update, delete on users
for each row execute function audit_table_access();
create trigger audit_payments after insert, update, delete on payments
for each row execute function audit_table_access();`}
If an attacker exfiltrated payment data, this log tells you: *which* rows, *when*, and *by whom*. Forensics in a query.
Encrypted Tokens at Rest
API keys and OAuth tokens are encrypted in the database using AES-256-GCM with a unique nonce per token. If an attacker steals a database backup, they get ciphertext.
{`create table oauth_tokens (
id uuid primary key,
user_id uuid,
provider text,
token_encrypted bytea,
token_nonce bytea,
created_at timestamp default now()
);
-- Tokens are encrypted before insert; plaintext is never stored
-- Decryption happens in-memory only when needed (e.g., API calls)`}
Your incident report can honestly say: "OAuth tokens were not exposed. They are encrypted at rest with AES-256-GCM. The attacker accessed ciphertext only." That's a strong position with OAIC.
Incident Response Playbook: Contain, Investigate, Notify
Phase 1: Contain (Hours 0–2)
**Assumption:** you've detected unusual activity (e.g., spike in exports, failed logins from suspicious IPs). First, stop the bleeding. Revoke tokens, reset sessions, block IPs.
{`-- Revoke all active sessions for a user
delete from auth.sessions where user_id = 'compromised-user-id';
-- Revoke API keys
update api_keys set revoked_at = now() where user_id = 'compromised-user-id';
-- Block suspicious IP from login
insert into ip_blocklist (ip_address, reason, blocked_at) values (
'203.0.113.45'::inet,
'suspicious_export_activity',
now()
);`}
Phase 2: Investigate (Hours 2–8)
Query your audit tables to understand scope. Timeline example:
{`-- Find all sessions from the compromised IP
select user_id, created_at, action, resource_count
from audit_sessions
where ip_address = '203.0.113.45'::inet
order by created_at;
-- Count affected users
select distinct user_id from data_audit_log
where queried_at > '2026-06-10 03:00:00' and queried_at < '2026-06-10 04:30:00'
and operation = 'SELECT';
-- Export breach details for OAIC
select user_id, email, dob, phone, data_types_exposed, first_exposed_at
from users u
join (select distinct user_id, min(queried_at) as first_exposed_at from data_audit_log where operation='SELECT' group by user_id) logs on u.id = logs.user_id
where queried_at > '2026-06-10 03:00:00';`}
30 minutes of SQL gets you: list of affected users, exposure window, data types compromised, and evidence for OAIC.
Phase 3: Notify (Day 1–30)
OAIC and affected individuals must be notified within 30 days. Your notification includes:
· Nature of breach. "An attacker accessed our database between June 10, 3am–4:30am UTC." · Data exposed. "Names, email addresses, and encrypted payment tokens. Tokens are AES-256-GCM encrypted and were not decrypted." · Mitigation. "We revoked all sessions, blocked the attacker's IP, and reset all API keys. Fresh tokens are generated on next login." · Timeline. Attach your audit log exports showing exact timestamps and user IDs.
Frequently Asked Questions
What if I'm not sure if it's a notifiable breach?
Notify OAIC anyway. They'll advise. Better to over-notify than under-notify and face penalties later. Breaches that don't meet the threshold are documented but no public notification is required; still, OAIC gets the report.
Do I need to keep audit logs forever?
No. OAIC expects logs for 1–2 years post-incident. After that, archive them. Velocity X defaults to 12-month retention for audit tables with a similar soft-delete + auto-purge pattern as user data.
What if an insider (employee) breaches data?
Still notifiable. Insider breaches are actually more common than external hacks. Your audit logs show *which* employee accessed *what* and *when*. That evidence is crucial for investigating whether it was negligence or malice, and for legal action if needed.
Can encryption at rest be bypassed?
Not without the encryption key. Velocity X stores keys in environment variables, never in code or database. If an attacker has shell access to your production servers, they can read env vars and decrypt tokens. But if they only have database access (via SQL injection or a stolen backup), encryption holds.
Do I notify users or just OAIC?
Both. The law says: "A data holder must take such steps as are reasonable in the circumstances to notify each individual to whom the breach relates." That means email the affected users directly with breach details, mitigation steps, and a link to monitor their accounts. OAIC gets a formal notice. Both are mandatory.
The Bottom Line
30 days sounds long until you're in the middle of a breach with no logs and a panicked CEO. Velocity X's immutable audit trails, session logs, and encrypted tokens turn panic into evidence. You detect, query, notify, and close the loop. When OAIC calls, you're ready. Not scrambling. Not retrofitting. Just solid engineering that anticipated this day from the start.