Reading your Instagram login history
What Login Guardian computes from your export's login file — the six fields it gets, the exact 0–100 score arithmetic, the verdict bands, and the honest limits: a UTC window, Meta's own location labels, recent history only, and no 2FA data parsed.
Somewhere in your export is a file most people never open: a table of every recent
sign-in Meta recorded — when, from where, on what. It is the closest thing the
archive has to a security camera, and like most security cameras it is grainy,
windowed and honest only about what it actually saw. This post reads that file with
you: where it lives, the six fields it contains, what this product's Login Guardian
does with them (including the exact score arithmetic — no black box), and the limits
that decide how much a "fair" verdict should worry you.
The feature itself lives in Settings → Account → Login Guardian and computes
entirely on your device the moment an archive is loaded — no account, no upload,
no server round trip.1 What follows is its whole logic, because a security
score you cannot audit is a mood, not a measurement.
Your export's login_activity file gives Login Guardian exactly six fields per
sign-in: IP, user agent, device, location, time and the raw time label. From
those it flags logins between 00:00 and 05:00 UTC, logins with no device string
and logins with no location; it counts distinct cities and IPs; it reads password
and bio changes from your account-history file as context; and it starts every
account at 100, deducting for wide location spread (>2 cities), many IPs (>3),
a high odd-hours share (>10/20/40%), and recently-seen devices — clamping to 0–100
and mapping to verdicts: ≥85 excellent, ≥70 good, ≥50 fair, below that at-risk.
The honest limits matter as much as the score: locations are Meta's own text labels
(no IP geolocation happens here), the export carries only recent sign-in history,
HTML exports have no device strings at all, timestamps that fail to parse drop out
of the ratio, and no 2FA or session data is parsed — the report can recommend
turning two-factor on, but it cannot see whether you already did.
- 1minwhere the file is and the six fields it holds.
- 3minthe flag rules and the score's line-by-line arithmetic.
- 5minthe limits that decide what a verdict can and cannot mean.
Where the file lives, and what is in it
The source is login_activity.json — or login_activity.html in older exports —
inside the security_and_login_information folder of your archive.2 The
parser accepts both: the JSON path reads the export's string_map_data rows, the
HTML path converts the table, and both land on the same record shape:3
| Field | What it is | What it is *not* |
|---|---|---|
| `ip` | The address string Meta recorded | Never geolocated or resolved by this app |
| `user agent` | Browser/OS string | Not parsed into device models |
| `device` | Meta's device label, when present | Blank for HTML exports — always |
| `location` | Meta's free-text place label | Not a coordinate; a string, bucketed verbatim |
| `time` | Parsed instant (unix seconds) | `0` when nothing parses — and it happens |
| `time label` | The raw string as written | Kept for display when the parse fails |
A second file feeds context, not scores: account_history (fromaccount_history.json or profile_changes.json) contributes password-change and
bio-change counts — "1 password change(s) on record" is shown as security context,
not as a deduction.4
That is the entire sensor. Six values per sign-in, one file of profile edits. The
next section is what gets computed from it.
What the Guardian computes
Everything runs through one function the moment your archive loads — deterministic,
repeatable, and testable: the repo's test suite asserts the model version, the
0–100 bounds and the verdict bands on every run.5
The three flags. Each recent login is checked for:
- odd-hours — the login's hour is between midnight and 05:00 UTC. The code
comment states the reason for the fixed window plainly: the report is computed
once and cached, and a local-time hour "would make the same login flag
differently per viewer TZ".6 Deterministic beats clever here — but see the
limits section, because one product card phrases this differently than the code. - unknown-device — the
devicefield is empty. - no-location — the location is empty or the placeholder
—.
The counts. Distinct devices, distinct IPs, and a location histogram where—/empty buckets as "Unknown". Locations display sorted by frequency, capped at
ten; the recent-logins list shows twelve; flagged odd-hours entries show twenty.7
The deductions. Score starts at 100 and loses, in order:8
| Trigger | Deduction |
|---|---|
| More than 2 distinct locations | −10 each (max −30) |
| More than 3 distinct IPs | −5 each (max −20) |
| Odd-hours share above 10% / 20% / 40% | −8 / −15 / −25 |
| Devices seen in the last 90 days with no older record: any | −7 |
| …more than 3 | −15 total |
Then clamp 0–100, map to bands — excellent ≥85, good ≥70, fair ≥50, else
at-risk — and write findings: location count (warn past 3 cities, alert past 6),
odd-hours share, new-device count (alert past 3), password-change context, a
recommendation to change the password and enable two-factor when the score lands
below 50, and a coverage line that tells you how far back the file reaches.9
Note the shape of the table: every deduction is a quantity rule — how many
cities, how many addresses, what share of rows — because quantities are the only
thing six fields can support honestly. There is no provenance logic, no ASN or
VPN lookup, no comparison against anyone else's account: the model can notice that
the file is wide, and it cannot notice that the file is wrong. The findings
translate each quantity into language ("If you travel this is normal — otherwise
review each one below") precisely because the arithmetic alone reads like an
accusation.
The findings are the disclosure mechanism. The score is a heuristic — model versionguardian-1.0, an LMKFR construction, not a Meta measure — and the product's own
score-honesty sweep kept this one precisely because its inputs are visible on
screen: the arithmetic above is the spec.10
What the score is, and what it is not
Two things make this 0–100 safe to show, and one makes it worth showing at all.
It is safe because the deductions are published (the table above is the whole
model — there is no learned weight, no hidden factor, no data leaving the device to
produce it), and because the findings explain it: a number without its reasons
is a nag; a number with its reasons is a report. It is worth showing because
security is the one area where a coarse signal genuinely helps — "at-risk" plus
four visible causes beats a nagging feeling you cannot name.
It is not a measure of whether your account was compromised. It cannot know.
There is no breach feed here, no password-check service, no session inspection —
the arithmetic sees quantity and timing of recorded sign-ins, nothing more. Two
cities and a new phone can push a perfectly healthy account into "fair"; a quiet,
compromised account with one stolen session in the export's window can look
"excellent". The verdict describes the pattern in the file, and the file is a
window (see the limits below).
The honest limits, in the order they bite
It is recent history, not your life. The coverage finding says so itself:
"Instagram exports only include recent history — check Settings → Security in the
app for the live session list".11 The export is a snapshot of a window;
the live session list on your phone is the authority on what is signed in right
now. This is the same date-range honesty the FAQ applies to the whole archive:
the export is only as complete as the range you requested.12
Locations are Meta's labels. No IP→city lookup happens in this product — the
location string is copied verbatim and bucketed by exact match. Where Meta wrote a
city, you get a city; where it wrote —, you get "Unknown". The score penalises
the number of distinct labels, never their provenance: it cannot tell a frequent
flyer from two home cities, a VPN from a second household, or a spoofed address
from a real one. The findings say as much — "If you travel this is normal —
otherwise review each one below."
Device strings are only as good as the export's. The HTML table path never
fills the device field, so every HTML-export login is an automaticunknown-device flag — a parser limitation, not a fleet of strange machines.13
There is no hardware fingerprinting; "unknown" means "the column was empty".
Odd-hours needs a parseable timestamp. Logins whose time fails to parse becometime = 0: they still count in the total, but drop out of the odd-hours ratio and
the flagged list. A file full of unparsed times would read as calm — which is why
the report prints the coverage line instead of hiding the denominator.
No 2FA data is read. The export folder contains two-factor activity, but this
product's parser does not turn it into findings — the report can recommend
enabling 2FA when the score falls below 50, and it has no way to know whether you
already have it. The live settings page on your device remains the only place that
answer exists.14
Scores are inputs-disclosed heuristics, not verdicts on your security. Read
the deductions, read the findings, then go look at the actual session list the
coverage line points you to. The score's job is to make you open that list.
Where do I find the raw login file in my export?
security_and_login_information/login_activity.json (or .html in older
exports). The parser matches the filename wherever it appears in the ZIP, so a
re-organised folder structure still works — the presence ledger simply marks it
unused if the file is absent, and the Guardian shows its "load an export" notice
instead of a score.
Is the security score a black box?
No — the deduction table above is the entire model: no learned weights, no hidden
factors, nothing computed off-device. It is a heuristic (guardian-1.0) precisely
because it is a summary of a visible checklist over recorded sign-ins, and the
findings list always shows its reasons. What it must never be read as is a verdict
about your engagement or your life — those questions have no answer in a login
file, and this product does not pretend otherwise.
Why does my brand-new laptop show up as an unknown device?
Either the device field for those rows is empty (guaranteed for HTML exports), or
the label Meta recorded differs from anything in the older 90-day window — the
"new device" deduction counts devices with no record older than 90 days. Both are
explainable patterns, not alarms; the recent-logins card shows the dates so you can
confirm it was you.
The score dropped after I travelled. Did something happen?
Probably not. More than two distinct location labels deducts 10 each regardless of
what the places are — the model cannot see intent, only quantity. That is the
trade-off of a heuristic you can audit: it over-weights breadth on purpose, and the
findings text tells you to review each location rather than assume the worst.
Can this tell me if someone else is logged in right now?
No. The export is a window of recorded past sign-ins; live sessions live in the
platform's own Settings → Security, which the coverage finding points you toward.
What the Guardian can do is make the past pattern visible enough that you know
whether opening that list is a chore or an urgent five minutes.
Questions this comes up
The security-privacy post for the archive's whole privacy surface; the
keeping-control post for local erase and what this audit touches; the
what's-in-the-export post for the folder this file came from.
1: apps/web-next/src/views/Settings.tsx (Guardian mounted inside Settings
→ Account) and views/LoginGuardian.tsx — the report is built withbuildLoginGuardian(archive) in component state; nothing is transmitted.
2: packages/shared/src/parsers/privacy.ts — filename match/login_activity\.(?:json|html)$/i, with an HTML→JSON conversion for the table
form.
3: types/privacy.ts — LoginRecord: ip, userAgent, device, location,
time, timeLabel; time parse order is the time value, then the row timestamp,
then the title, else 0.
4: analytics/login-guardian.ts — password changes matched by /password/i
in title or value, bio changes by /^bio$/i, from archive.privacy.accountHistory.
5: analytics/login-guardian.ts (MODEL_VERSION = 'guardian-1.0') andtest/run-tests.ts — asserts the version, score ∈ 0..100, band membership and
that the location total equals the archive's login count.
6: analytics/login-guardian.ts — flag condition getUTCHours() < 5, with
the inline rationale: local-time hours "would make the same login flag differently
per viewer TZ".
7: analytics/login-guardian.ts — locations top 10, odd-hours rows first 20,
recent logins first 12.
8: analytics/login-guardian.ts — the deduction table and 0–100 clamp,
verbatim source of the table above.
9: analytics/login-guardian.ts — findings builder: counts, location
warn/alert thresholds (3/6), odd-hours warn (>0.2), new-device alert (>3),
password-change context, sub-50 recommendation, coverage line.
10: Score-honesty sweep record in the project state log: the Guardian score and
privacy-footprint percentage were kept specifically "which are disclosed with their
inputs" — unlike the engagement-style scores removed from Insights.
11: analytics/login-guardian.ts — "History covers logins up to {date}.
Instagram exports only include recent history — check Settings → Security in the
app for the live session list and enable two-factor authentication there."
12: The FAQ's date-range answer — the export is only as complete as the
range you request; "All time" is the only safe setting.
13: parsers/privacy.ts — the HTML extraction path leaves device empty by
construction; only the table path fills it from the user-agent column.
14: types/privacy.ts — PrivacyData carries no 2FA, verification-method or
login-success field; the report's 2FA mentions are recommendations only.
Footnotes
- where
- file
- parse
- history
- model
- utc
- caps
- score
- findings
- why
- coverage
- range
- html
- nofa