Queries · q110 · Chief Information Security Officer · week 1, night 4
“Platform-wide, how old are our currently active API keys — do we have a meaningful population of long-lived, apparently never-rotated credentials, even though usage patterns look healthy?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
The manager, at the start of week 1
Subject: Where I'd like your help first — account security snapshot
Hi Noah,
Good to have you working with me this week. I'm still building my mental map of the business, so let's start broad and sharpen later. Here's what's on my mind, roughly in priority order:
What I need this for: I'm walking into enterprise deals where security questionnaires will ask us point-blank about authentication strength, access governance, credential hygiene, and auditability. I need an honest, evidence-based picture — good or bad — so I can brief the CEO and sales on what we can credibly claim today versus what needs fixing before the next review cycle.
Given the slot budget, let's pick whichever 3 of these will give us the most defensible starting picture this week, and we can go deeper on the rest next week.
Thanks, Henrik
The analyst · week 1, night 4, request 2 of 3
Night 3 brought the access-governance story to a clean, actionable finding: all 16 dormant Owner/Administrator accounts sit on CHURNED tenants only (9 tenants), with one as stale as 599 days since last login — meaning the real gap isn't "someone's not reviewing active-merchant access," it's that user accounts simply never get deactivated when a tenant churns (status still reads 'active'). That's a crisp, fixable finding for the memo. The API-key report came back almost too clean — zero active keys anywhere show as never-used or unused 90+ days, even though the same report shows oldest-active-key ages running into the thousands of days (one at 2,743 days, ~7.5 years, which predates Tidewick's 2023 warehouse cutover and even stretches toward the 2018 founding). That combination — heavily used, never rotated — is exactly the "long-lived token nobody rotates" risk Henrik named, so I want a platform-wide age/rotation distribution rather than relying on a handful of per-tenant data points. The audit-log report surfaced the most actionable gap of the week: 'delete' actions show only 27 events company-wide across three years, across just 16 of 120 tenants (13.3%) — implausibly low for an e-commerce platform this size, and worth testing directly against actual soft-deletions in the database rather than assuming it's a true absence of deletes.
Tonight (final night) I'm spending all 3 slots closing out the two topics we hadn't quantified yet (API-key rotation hygiene, audit-trail completeness) plus checking whether existing compliance monitoring is a real control or just theoretical, since that's the credibility test for "can we reconstruct who did what" claims in the memo.
The engineer · 1 dry run on the empty schema, then the submission
with active_keys as ( select id, tenant_id, created_at, last_used_at, date '2026-01-05' - created_at::date as age_days from api_keys where status = 'active' ), bucketed as ( select *, case when age_days < 90 then '1 - under 90 days' when age_days < 365 then '2 - 90 days-1 year' when age_days < 730 then '3 - 1-2 years' when age_days < 1460 then '4 - 2-4 years' else '5 - 4+ years' end as bucket_sort from active_keys ), totals as ( select count(*) as total_active from bucketed ) select case b.bucket_sort when '1 - under 90 days' then 'under 90 days' when '2 - 90 days-1 year' then '90 days-1 year' when '3 - 1-2 years' then '1-2 years' when '4 - 2-4 years' then '2-4 years' when '5 - 4+ years' then '4+ years' end as age_bucket, count(*) as active_key_count, round(100.0 * count(*) / t.total_active, 1) as share_pct, count(distinct b.tenant_id) as distinct_tenants, max(b.age_days) as oldest_key_age_days, count(*) filter (where b.last_used_at is null) as never_used_count, count(*) filter (where b.last_used_at is not null and b.last_used_at < timestamp '2025-10-05') as unused_90d_plus_count from bucketed b cross join totals t group by b.bucket_sort, t.total_active order by b.bucket_sort
“Query compiles and returns the expected columns. Submitting.”
“Submitted. The report buckets currently-active API keys into the five fixed age ranges (youngest to oldest), with count, share of total, distinct tenants, oldest age in the bucket, never-used count, and count unused 90+ days — computed against the fixed reference date 2026-01-05 as requested.”
As Noah Fischer saw it, from the small dataset the agents worked on
age_bucket | active_key_count | share_pct | distinct_tenants | oldest_key_age_days | never_used_count | unused_90d_plus_count ---------------+------------------+-----------+------------------+---------------------+------------------+---------------------- under 90 days | 1 | 0.9 | 1 | 68 | 0 | 0 90 days-1 year | 11 | 9.7 | 11 | 327 | 0 | 0 1-2 years | 14 | 12.4 | 9 | 699 | 0 | 0 2-4 years | 11 | 9.7 | 10 | 1346 | 0 | 0 4+ years | 76 | 67.3 | 32 | 2743 | 0 | 0 (5 rows)
The memo and the queries that followed; links found by title, so inferred
Noah Fischer's memo to Henrik Aalto at the end of the week:
4. API keys: well-used, but poorly rotated. No tenant shows an active key that's unused or abandoned (API key hygiene). But age tells a different story: 67.3% of active keys (76 of ~113) are 4+ years old, including one 2,743 days old (Active API key age distribution). There's no rotation field or process for api_keys in the schema — usage looks healthy, rotation hygiene does not.
Later queries that build on this report:
It builds on:
Changed only where the benchmark needs the same result on every system
with active_keys as ( select id, tenant_id, created_at, last_used_at, date '2026-01-05' - created_at::date as age_days date '2026-01-05' - CAST(created_at AS date) as age_days from api_keys where status = 'active'),bucketed as ( select *, case when age_days < 90 then '1 - under 90 days' when age_days < 365 then '2 - 90 days-1 year' when age_days < 730 then '3 - 1-2 years' when age_days < 1460 then '4 - 2-4 years' else '5 - 4+ years' end as bucket_sort from active_keys),totals as ( select count(*) as total_active from bucketed)select case b.bucket_sort when '1 - under 90 days' then 'under 90 days' when '2 - 90 days-1 year' then '90 days-1 year' when '3 - 1-2 years' then '1-2 years' when '4 - 2-4 years' then '2-4 years' when '5 - 4+ years' then '4+ years' end as age_bucket, count(*) as active_key_count, round(100.0 * count(*) / t.total_active, 1) as share_pct, 100.0 * count(*) / t.total_active as share_pct, count(distinct b.tenant_id) as distinct_tenants, max(b.age_days) as oldest_key_age_days, count(*) filter (where b.last_used_at is null) as never_used_count, count(*) filter (where b.last_used_at is not null and b.last_used_at < timestamp '2025-10-05') as unused_90d_plus_countfrom bucketed bcross join totals tgroup by b.bucket_sort, t.total_activeorder by b.bucket_sortorder by b.bucket_sort NULLS LAST
with active_keys as ( select id, tenant_id, created_at, last_used_at, date '2026-01-05' - CAST(created_at AS date) as age_days from api_keys where status = 'active' ), bucketed as ( select *, case when age_days < 90 then '1 - under 90 days' when age_days < 365 then '2 - 90 days-1 year' when age_days < 730 then '3 - 1-2 years' when age_days < 1460 then '4 - 2-4 years' else '5 - 4+ years' end as bucket_sort from active_keys ), totals as ( select count(*) as total_active from bucketed ) select case b.bucket_sort when '1 - under 90 days' then 'under 90 days' when '2 - 90 days-1 year' then '90 days-1 year' when '3 - 1-2 years' then '1-2 years' when '4 - 2-4 years' then '2-4 years' when '5 - 4+ years' then '4+ years' end as age_bucket, count(*) as active_key_count, 100.0 * count(*) / t.total_active as share_pct, count(distinct b.tenant_id) as distinct_tenants, max(b.age_days) as oldest_key_age_days, count(*) filter (where b.last_used_at is null) as never_used_count, count(*) filter (where b.last_used_at is not null and b.last_used_at < timestamp '2025-10-05') as unused_90d_plus_count from bucketed b cross join totals t group by b.bucket_sort, t.total_active order by b.bucket_sort NULLS LAST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Noah Fischer (analyst for the Chief Information Security Officer): Title: Active API key age distribution (rotation hygiene) Question: Platform-wide, how old are our currently active API keys — do we have a meaningful population of long-lived, apparently never-rotated credentials, even though usage patterns look healthy? Grain: one row per age bucket of currently active api_keys Columns: - age bucket (fixed labels: 'under 90 days', '90 days-1 year', '1-2 years', '2-4 years', '4+ years', based on 2026-01-05 minus created_at) - active key count in bucket - share of all active keys (%) - distinct tenants represented in bucket - oldest key age in days within bucket - count with last_used_at is null (never used) in bucket - count with last_used_at before 2025-10-05 (unused 90+ days) in bucket Details: Source: api_keys where status = 'active'. Compute age_days = 2026-01-05 minus created_at (fixed reference date per house standards, no CURRENT_DATE). Bucket into the 5 fixed ranges above. Share of all active keys = bucket count / total active keys across all buckets, rounded to 1 decimal percent. Order rows by bucket from youngest to oldest in the fixed sequence listed (not alphabetical) so the trend from new to old is readable top to bottom. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runwith active_keys as ( select id, tenant_id, created_at, last_used_at, date '2026-01-05' - created_at::date as age_days from api_keys where status = 'active' ), bucketed as ( select *, case when age_days < 90 then '1 - under 90 days' when age_days < 365 then '2 - 90 days-1 year' when age_days < 730 then '3 - 1-2 years' when age_days < 1460 then '4 - 2-4 years' else '5 - 4+ years' end as bucket_sort from active_keys ), totals as ( select count(*) as total_active from bucketed ) select case b.bucket_sort when '1 - under 90 days' then 'under 90 days' when '2 - 90 days-1 year' then '90 days-1 year' when '3 - 1-2 years' then '1-2 years' when '4 - 2-4 years' then '2-4 years' when '5 - 4+ years' then '4+ years' end as age_bucket, count(*) as active_key_count, round(100.0 * count(*) / t.total_active, 1) as share_pct, count(distinct b.tenant_id) as distinct_tenants, max(b.age_days) as oldest_key_age_days, count(*) filter (where b.last_used_at is null) as never_used_count, count(*) filter (where b.last_used_at is not null and b.last_used_at < timestamp '2025-10-05') as unused_90d_plus_count from bucketed b cross join totals t group by b.bucket_sort, t.total_active order by b.bucket_sort
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: age_bucket, active_key_count, share_pct, distinct_tenants, oldest_key_age_days, never_used_count, unused_90d_plus_count
submitwith active_keys as ( select id, tenant_id, created_at, last_used_at, date '2026-01-05' - created_at::date as age_days from api_keys where status = 'active' ), bucketed as ( select *, case when age_days < 90 then '1 - under 90 days' when age_days < 365 then '2 - 90 days-1 year' when age_days < 730 then '3 - 1-2 years' when age_days < 1460 then '4 - 2-4 years' else '5 - 4+ years' end as bucket_sort from active_keys ), totals as ( select count(*) as total_active from bucketed ) select case b.bucket_sort when '1 - under 90 days' then 'under 90 days' when '2 - 90 days-1 year' then '90 days-1 year' when '3 - 1-2 years' then '1-2 years' when '4 - 2-4 years' then '2-4 years' when '5 - 4+ years' then '4+ years' end as age_bucket, count(*) as active_key_count, round(100.0 * count(*) / t.total_active, 1) as share_pct, count(distinct b.tenant_id) as distinct_tenants, max(b.age_days) as oldest_key_age_days, count(*) filter (where b.last_used_at is null) as never_used_count, count(*) filter (where b.last_used_at is not null and b.last_used_at < timestamp '2025-10-05') as unused_90d_plus_count from bucketed b cross join totals t group by b.bucket_sort, t.total_active order by b.bucket_sort
Submitted to tonight's batch. Your work on this request is done.