Linux forensic artefacts

Timestamps, logins, logs and shell history.

Advanced18 min · lesson 14 of 15

Once the volatile state is captured (linux-det/triage), the disk and the logs tell you what happened and when. This lesson reconstructs one small, real sequence from the traces it left on an Ubuntu 26.04 server, with the Rocky Linux 10.2 differences alongside. In the lab, a throwaway account det-art-sam failed one password over SSH, logged in with a key a few seconds later, created a file, appended to it, backdated it with touch, and ran a few commands before logging out. By the end you can read a file's four timestamps and spot a backdated one, find a login in the authentication log and the journal, know which login records each platform still keeps, read shell history knowing what it leaves out, and merge the sources into one timeline sorted by time.

Where each question is answered
Who logged in, and when
auth.log (Ubuntu), secure (RHEL)
sshd-session and PAM lines
the journal
the same lines, plus logind sessions
wtmp, btmp, lastlog
last and lastb on RHEL, lslogins on Ubuntu
What changed on disk
stat
access, modify, change and birth times
find -newerct
every file changed after an anchor
What was typed
~/.bash_history
written at logout, no times by default
Anchor on one fact, pull every source into the same window, and sort by time.

Four timestamps, and which ones touch can move

Every file carries four timestamps. The modify time (mtime) moves when the contents change. The access time (atime) moves when the contents are read, within limits set by the mount options. The change time (ctime) moves whenever the inode changes: permissions, owner, size, link count, or any write to the timestamps themselves. The birth time (btime) is set once, when the file is created. ls -l shows only the modify time, which is the one an intruder is most likely to have faked.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo ls -l /home/det-art-sam/det-art-notes.txt
-rw-rw-r-- 1 det-art-sam det-art-sam 23 Jan 15 2025 /home/det-art-sam/det-art-notes.txt
$ sudo stat /home/det-art-sam/det-art-notes.txt
File: /home/det-art-sam/det-art-notes.txt Size: 23 Blocks: 8 IO Block: 4096 regular file Device: 253,1 Inode: 575578 Links: 1 Access: (0664/-rw-rw-r--) Uid: ( 1002/det-art-sam) Gid: ( 1002/det-art-sam) Access: 2025-01-15 09:22:00.000000000 +0000 Modify: 2025-01-15 09:22:00.000000000 +0000 Change: 2026-09-27 11:05:18.526375441 +0000 Birth: 2026-09-27 11:05:14.473048051 +0000

ls -l says January 2025. stat tells the rest. Access and Modify both read 2025-01-15 09:22:00.000000000; the run of zero nanoseconds is itself a hint, because a program that writes a file almost never lands on a whole second. Birth says the file was created today at 11:05:14, and Change says something rewrote its inode four seconds later. That is exactly what touch -d "2025-01-15 09:22" did. touch calls utimensat(2), which lets the file's owner (or root) set atime and mtime to any value, and the kernel sets ctime to the current time as a side effect. No system call sets the birth time; the kernel only reports it, through statx(2). So an mtime older than the birth time marks a file whose times were set on purpose. Unpacking an archive, cp -p and rsync -t produce the same pattern legitimately, so treat it as a lead and use the ctime to find the moment in the logs and the shell history.

ctime and birth raise the cost of backdating without making it impossible: root can step the system clock back around an edit, or edit the inode directly while the filesystem is unmounted. The Rocky lab shows the same four fields on XFS, RHEL's default root filesystem. Access time is the weakest of the four, and you can damage it yourself.

deploy@web01 · Ubuntu 26.04 LTS
$ findmnt -no SOURCE,FSTYPE,OPTIONS -T /home
/dev/vda1 ext4 rw,relatime,discard,errors=remount-ro,commit=30
$ sudo cat /home/det-art-sam/det-art-notes.txt sudo stat -c 'Access: %x' /home/det-art-sam/det-art-notes.txt
first note second note Access: 2026-09-27 11:05:41.031640914 +0000

The filesystem is mounted relatime, the kernel default since Linux 2.6.30 on both platforms. Under relatime the kernel updates atime on a read only when the old atime is not newer than mtime or ctime, or is more than a day old. The backdated atime met both conditions, so the investigator's own cat moved it from 2025 to now, and the 2025 value is gone. Run stat before you open anything, and when atime matters, work on a copy or a read-only image.

Searching a time window

With an anchor time (the first alert, or the first suspicious log line), ask the filesystem what changed after it. find -newermt compares modify times, -newerct compares change times. Here the anchor is 11:05:05, just before the failed password.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo find /home/det-art-sam -type f -newermt "2026-09-27 11:05:05"
/home/det-art-sam/.cache/motd.legal-displayed /home/det-art-sam/.bash_history
$ sudo find /home/det-art-sam -type f -newerct "2026-09-27 11:05:05"
/home/det-art-sam/.cache/motd.legal-displayed /home/det-art-sam/det-art-notes.txt /home/det-art-sam/.bash_history
$ sudo find /home/det-art-sam -type f -newerBt "2026-09-27 11:05:05"
find: This system does not provide a way to find the birth time of a file. find: invalid predicate `-newerBt'

The modify-time search misses det-art-notes.txt, because its mtime claims 2025. The change-time search finds it, because touch could not move ctime. It also lists .bash_history and .cache/motd.legal-displayed, which Ubuntu's pam_motd creates at an account's first interactive login. On a real host, widen the search to each filesystem (find / -xdev ...) and expect noise from logs and caches; ctime also moves on a chmod or a rename, so confirm each hit. Birth time is not searchable this way: GNU find on Ubuntu 26.04 reports that it has no way to read birth times and rejects -newerBt, while stat can, because it asks the kernel through statx.

The login in the logs

The lab host refuses passwords on port 22: sshd -T prints the effective setting, and the Ubuntu cloud image's own drop-in sets it.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo sshd -T | grep -i "^passwordauthentication" sudo grep -rH "^PasswordAuthentication" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
passwordauthentication no /etc/ssh/sshd_config.d/60-cloudimg-settings.conf:PasswordAuthentication no

On the Rocky lab, cloud-init writes the same line into /etc/ssh/sshd_config.d/50-cloud-init.conf. For the demonstration the logins went to a second sshd on 127.0.0.1:2240 that accepts passwords; its log lines have the same form. On Ubuntu, rsyslog writes authentication events to /var/log/auth.log with RFC 3339 timestamps, readable by the adm group (deploy is a member, so no sudo). Because RFC 3339 timestamps sort as plain text, awk can select everything after the anchor with a string comparison, as long as every line carries the same UTC offset (check that before you trust a string comparison on a host whose time zone may have changed).

deploy@web01 · Ubuntu 26.04 LTS
$ awk -v since="2026-09-27T11:05:05" '$1 >= since && /sshd-session|systemd-logind/ && /det-art-sam/' /var/log/auth.log
2026-09-27T11:05:06.215401+00:00 web01 sshd-session[254861]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=127.0.0.1 user=det-art-sam 2026-09-27T11:05:08.148881+00:00 web01 sshd-session[254861]: Failed password for det-art-sam from 127.0.0.1 port 33094 ssh2 2026-09-27T11:05:08.385044+00:00 web01 sshd-session[254861]: Connection closed by authenticating user det-art-sam 127.0.0.1 port 33094 [preauth] 2026-09-27T11:05:11.559082+00:00 web01 sshd-session[254914]: Accepted publickey for det-art-sam from 127.0.0.1 port 37296 ssh2: ED25519 SHA256:tzpwr374wOTCL98L3Yn17mx/aKIwKzEAKBywqqP1V2w 2026-09-27T11:05:11.561571+00:00 web01 sshd-session[254914]: pam_unix(sshd:session): session opened for user det-art-sam(uid=1002) by det-art-sam(uid=0) 2026-09-27T11:05:11.583281+00:00 web01 systemd-logind[905]: New session '95' of user 'det-art-sam' with class 'user' and type 'tty'. 2026-09-27T11:05:11.612753+00:00 web01 systemd-logind[905]: New session '96' of user 'det-art-sam' with class 'manager' and type 'unspecified'. 2026-09-27T11:05:28.623531+00:00 web01 sshd-session[255008]: Disconnected from user det-art-sam 127.0.0.1 port 37296 2026-09-27T11:05:28.624250+00:00 web01 sshd-session[254914]: pam_unix(sshd:session): session closed for user det-art-sam

Read it in order. pam_unix(sshd:auth): authentication failure is PAM rejecting a password for user=det-art-sam from rhost=127.0.0.1; Failed password is sshd's own record of the same attempt, and Connection closed ... [preauth] means the client gave up before authenticating. Three seconds later Accepted publickey records the key's SHA256 fingerprint, which you can match against the account's authorized_keys with ssh-keygen -lf. session opened and systemd-logind's New session ... type 'tty' mark an interactive terminal session (the second, manager session is the user's own systemd instance). Disconnected and session closed end it seventeen seconds later. Two process IDs appear: the privileged monitor logs authentication and PAM, its unprivileged child logs the disconnect, so tie lines together by the client port (37296) rather than by PID alone.

Filter on sshd-session, and on sshd-auth too
Since OpenSSH 9.8 the listener hands each connection to an sshd-session process, so login lines carry that tag, not sshd. OpenSSH 10.0 moved the authentication phase into a separate sshd-auth binary and its release notes say some messages may now come from sshd-auth. In this lab every line came from sshd-session, but a search or detection should match both names.
deploy@web01 · Ubuntu 26.04 LTS
$ journalctl --since "2026-09-27 11:05:05" -o short-iso --no-hostname -t sshd-session -g 'det-art-sam'
2026-09-27T11:05:06+00:00 sshd-session[254861]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=127.0.0.1 user=det-art-sam 2026-09-27T11:05:08+00:00 sshd-session[254861]: Failed password for det-art-sam from 127.0.0.1 port 33094 ssh2 2026-09-27T11:05:08+00:00 sshd-session[254861]: Connection closed by authenticating user det-art-sam 127.0.0.1 port 33094 [preauth] 2026-09-27T11:05:11+00:00 sshd-session[254914]: Accepted publickey for det-art-sam from 127.0.0.1 port 37296 ssh2: ED25519 SHA256:tzpwr374wOTCL98L3Yn17mx/aKIwKzEAKBywqqP1V2w 2026-09-27T11:05:11+00:00 sshd-session[254914]: pam_unix(sshd:session): session opened for user det-art-sam(uid=1002) by det-art-sam(uid=0) 2026-09-27T11:05:28+00:00 sshd-session[255008]: Disconnected from user det-art-sam 127.0.0.1 port 37296 2026-09-27T11:05:28+00:00 sshd-session[254914]: pam_unix(sshd:session): session closed for user det-art-sam

The journal holds the same events, persistently on both platforms, and journalctl -t sshd-session selects them with no text parsing. auth.log and the journal are separate copies, so a careless edit to one shows up as a disagreement with the other. Agreement proves less: on Ubuntu rsyslog receives these lines from journald, and root can rewrite auth.log and rotate or vacuum the journal in the same minute. Only a copy already off the host settles it. RHEL writes the same lines to /var/log/secure, readable by root only, in the classic syslog format with no year and no time zone.

deploy@rocky10 · Rocky Linux 10.2
$ sudo cat /var/log/secure | grep 'sshd-session.*det-art-sam' | tail -n 7
Sep 27 11:05:51 rocky10 sshd-session[141560]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=127.0.0.1 user=det-art-sam Sep 27 11:05:52 rocky10 sshd-session[141560]: Failed password for det-art-sam from 127.0.0.1 port 39290 ssh2 Sep 27 11:05:53 rocky10 sshd-session[141560]: Connection closed by authenticating user det-art-sam 127.0.0.1 port 39290 [preauth] Sep 27 11:05:56 rocky10 sshd-session[141622]: Accepted publickey for det-art-sam from 127.0.0.1 port 43996 ssh2: ED25519 SHA256:rNJancyc09bN8kuCEMHgxBIhdJQ1cNEjI7Fn/oxzC2E Sep 27 11:05:57 rocky10 sshd-session[141622]: pam_unix(sshd:session): session opened for user det-art-sam(uid=1002) by det-art-sam(uid=0) Sep 27 11:06:13 rocky10 sshd-session[141638]: Disconnected from user det-art-sam 127.0.0.1 port 43996 Sep 27 11:06:13 rocky10 sshd-session[141622]: pam_unix(sshd:session): session closed for user det-art-sam

Login records: what each platform still keeps

Traditionally four binary files recorded logins: wtmp (every login, logout and boot, read by last), btmp (failed logins, read by lastb), utmp (who is logged in now, read by who) and lastlog (each account's latest login). Their fixed 32-bit time fields run out in 2038, and Debian, and Ubuntu with it, stopped shipping the readers.

deploy@web01 · Ubuntu 26.04 LTS
$ last det-art-sam
-bash: line 1: last: command not found
$ zcat /usr/share/doc/util-linux/NEWS.Debian.gz | grep -A3 'last(1)'
* last(1) has been split off to the wtmpdb package. If you find last(1) useful, please install wtmpdb and accept the default PAM configuration changes from libpam-wtmpdb. * lastb(1) is removed. Please see syslog/journal for failed login attempts.
$ ls -l /run/utmp /var/log/wtmp /var/log/btmp /var/log/lastlog
ls: cannot access '/run/utmp': No such file or directory -rw-rw---- 1 root utmp 0 Sep 27 10:26 /var/log/btmp -rw-rw-r-- 1 root utmp 1237872 Sep 27 11:05 /var/log/lastlog -rw-rw-r-- 1 root utmp 1600 Sep 27 11:05 /var/log/wtmp
$ sudo lslogins det-art-sam
… Last login: 11:05 Last terminal: pts/0 Last hostname: 127.0.0.1 …

last is not installed on Ubuntu 26.04. The util-linux changelog says why: last moved to the wtmpdb package, which is not installed by default and records logins through its own PAM module once installed, and lastb is gone, with syslog and the journal named as the place for failed attempts. The files tell the rest. There is no /run/utmp, so who prints nothing; loginctl list-sessions shows current sessions from systemd-logind instead. wtmp and lastlog were still written at the login time, and lslogins from util-linux reads them: last login on pts/0 from 127.0.0.1. btmp is empty. The failed password never reached it, so on this server the authentication log and the journal are the only record of failures. RHEL 10 keeps the classic tools, and its sshd records the failure.

deploy@rocky10 · Rocky Linux 10.2
$ last -w --time-format iso -n 1 det-art-sam
det-art-sam pts/0 127.0.0.1 2026-09-27T11:05:57+00:00 - 2026-09-27T11:06:13+00:00 (00:00) wtmp begins 2026-09-26T19:33:15+00:00
$ sudo lastb -w --time-format iso -n 1 det-art-sam
det-art-sam ssh:notty 127.0.0.1 2026-09-27T11:05:52+00:00 - 2026-09-27T11:05:52+00:00 (00:00) btmp begins 2026-09-27T06:11:27+00:00

last shows the session on pts/0 from 127.0.0.1 and lastb shows the failed attempt as ssh:notty. Two options matter for evidence: without -w both tools truncate long user names, and --time-format iso prints seconds and the time zone instead of minutes. All of these files are plain files that root can edit or truncate, so treat them as corroboration for the logs, not as proof on their own.

Shell history, and what it leaves out

These are the eight lines det-art-sam typed in the key session, one every two seconds. The fourth starts with a space, and id was typed twice.

typed by det-art-sam
echo "first note" > det-art-notes.txt
echo "second note" >> det-art-notes.txt
touch -d "2025-01-15 09:22" det-art-notes.txt
cat /etc/hostname
id
id
uname -r
exit
deploy@web01 · Ubuntu 26.04 LTS
$ sudo cat /home/det-art-sam/.bash_history
echo "first note" > det-art-notes.txt echo "second note" >> det-art-notes.txt touch -d "2025-01-15 09:22" det-art-notes.txt id uname -r exit
$ grep -n '^HIST' /etc/skel/.bashrc
13:HISTCONTROL=ignoreboth 19:HISTSIZE=1000 20:HISTFILESIZE=2000

The history holds six of them. linux-ess/shelltips covered why: Ubuntu's default ~/.bashrc sets HISTCONTROL=ignoreboth, which drops lines that begin with a space and a line identical to the one before, and RHEL's /etc/profile sets ignoredups only, so the Rocky history keeps the leading-space line. What matters for evidence is the rest. There are no timestamps; bash writes them only when HISTTIMEFORMAT was set in the shell that saved the file. Bash saves history when the shell exits, so the file's change time marks the end of the session, and the order of lines is the order of commands.

deploy@rocky10 · Rocky Linux 10.2
$ sudo cat /home/det-art-sam/.bash_history
echo "first note" > det-art-notes.txt echo "second note" >> det-art-notes.txt touch -d "2025-01-15 09:22" det-art-notes.txt cat /etc/hostname id uname -r exit
$ grep -n HISTCONTROL /etc/profile
53:if [ "$HISTCONTROL" = "ignorespace" ] ; then 54: export HISTCONTROL=ignoreboth 56: export HISTCONTROL=ignoredups 59:export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL

History is also the easiest trace to suppress. A shell killed with SIGKILL saves nothing, and unset HISTFILE or a ~/.bash_history linked to /dev/null silence every later session. An account that logged in interactively but has no history, or a history file whose change time does not match the session end in the log, is a finding to record, not an absence of evidence.

One timeline

Each source answers part of the question. Merged and sorted, they answer it together. The journal already prints ISO timestamps; stat --printf prints each changed file's birth (%w) and change (%z) time, and the first sed puts a T between date and time so the two formats sort together. The last sed trims each line to the time of day for reading.

deploy@web01 · Ubuntu 26.04 LTS
$ { journalctl --since "2026-09-27 11:05:05" -o short-iso-precise --no-hostname -t sshd-session -t systemd-logind -g 'det-art-sam' sudo find /home/det-art-sam -type f -newerct "2026-09-27 11:05:05" -exec stat --printf '%w born %n\n%z changed %n\n' {} + | sed 's/ /T/' } | sort | sed -E 's/^[0-9-]+T([0-9:]{8})[^ ]*( [+]0000)? /\1 /'
11:05:06 sshd-session[254861]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=127.0.0.1 user=det-art-sam 11:05:08 sshd-session[254861]: Failed password for det-art-sam from 127.0.0.1 port 33094 ssh2 11:05:08 sshd-session[254861]: Connection closed by authenticating user det-art-sam 127.0.0.1 port 33094 [preauth] 11:05:11 sshd-session[254914]: Accepted publickey for det-art-sam from 127.0.0.1 port 37296 ssh2: ED25519 SHA256:tzpwr374wOTCL98L3Yn17mx/aKIwKzEAKBywqqP1V2w 11:05:11 sshd-session[254914]: pam_unix(sshd:session): session opened for user det-art-sam(uid=1002) by det-art-sam(uid=0) 11:05:11 systemd-logind[905]: New session '95' of user 'det-art-sam' with class 'user' and type 'tty'. 11:05:11 systemd-logind[905]: New session '96' of user 'det-art-sam' with class 'manager' and type 'unspecified'. 11:05:12 born /home/det-art-sam/.cache/motd.legal-displayed 11:05:12 changed /home/det-art-sam/.cache/motd.legal-displayed 11:05:14 born /home/det-art-sam/det-art-notes.txt 11:05:18 changed /home/det-art-sam/det-art-notes.txt 11:05:28 born /home/det-art-sam/.bash_history 11:05:28 changed /home/det-art-sam/.bash_history 11:05:28 sshd-session[255008]: Disconnected from user det-art-sam 127.0.0.1 port 37296 11:05:28 sshd-session[254914]: pam_unix(sshd:session): session closed for user det-art-sam

The story reads top to bottom: the failed password at 11:05:08, the key login at 11:05:11 with its terminal session, the notes file born at 11:05:14 and re-stamped at 11:05:18, and the history file written at 11:05:28, the same second as the disconnect. The history fills the gap between birth and change: the file was created, appended to, then backdated. Tag each line with where it came from and how an intruder with root could have altered it, because every source here lives on the host under investigation. The journal can at least prove that its own files were not edited, if you set it up to.

deploy@web01 · Ubuntu 26.04 LTS
$ sudo journalctl --verify --file="/var/log/journal/$(cat /etc/machine-id)/system.journal" sudo ls /var/log/journal/$(cat /etc/machine-id)/fss
PASS: /var/log/journal/7fa05bc53e6b40f880f494631079f2df/system.journal ls: cannot access '/var/log/journal/7fa05bc53e6b40f880f494631079f2df/fss': No such file or directory

journalctl --verify passed, but there is no fss sealing key in the journal directory, so the check covered internal consistency only: the file is well formed, not unedited. Forward Secure Sealing is set up with journalctl --setup-keys, which stores a sealing key on the host and prints a verification key that you keep somewhere else. With Seal=yes in journald.conf (the default once a key exists), persistent journal files are sealed at a fixed interval, 15 minutes by default, and journalctl --verify --verify-key=... then detects alteration of sealed data. It cannot stop root from deleting the files, and entries in the current interval are not yet sealed. Neither platform sets up a key by default. Forwarding logs off the host as they are written (linux-hard/logging) remains the stronger control.

Try this

On a lab host, run date -u '+%F %T' and note the time. Create a throwaway user with a key: sudo useradd -m -s /bin/bash art-try, ssh-keygen -t ed25519 -N '' -f ~/art-try-key -q, sudo install -d -m 700 -o art-try -g art-try ~art-try/.ssh and sudo install -m 600 -o art-try -g art-try ~/art-try-key.pub ~art-try/.ssh/authorized_keys. On a host hardened with AllowGroups or AllowUsers (linux-hard/ssh), sshd refuses this account until you add it to an allowed group for the exercise. Log in with ssh -t -i ~/art-try-key art-try@localhost, run echo test > probe.txt, then touch -d "2024-06-01 12:00" probe.txt, then id with a leading space, then exit. Now reconstruct it: sudo stat ~art-try/probe.txt should show Modify on 2024-06-01 with Change and Birth from the last few minutes; sudo find ~art-try -newermt "<noted time>" should miss probe.txt while -newerct finds it; journalctl -t sshd-session --since "<noted time>" should show Accepted publickey and session opened for the user; and sudo cat ~art-try/.bash_history should show the echo and touch lines but not the leading-space id on Ubuntu (on RHEL it appears). Remove the user with sudo userdel -r art-try and delete the key pair when you are done.

Takeaway

Anchor on one fact and prefer the evidence an intruder cannot simply set: ctime and birth over mtime and atime, the authentication log and journal over login records, and a copy that has left the host over anything still on it.

Quick check
01stat on a file in a web root shows Modify 2024-03-02 10:00:00.000000000, while Change and Birth are both today at 03:12. What is the most defensible reading?
Incorrect — A filesystem check does not rewrite birth times, and nothing refreshes ctime without changing the inode. The birth time says the file did not exist before today.
Incorrect — Reading can move atime, never ctime. Change time moves only when the inode itself changes.
Correct — Birth dates the file, ctime dates the timestamp write, and mtime is whatever the writer chose, so the logs and history around 03:12 tell you which it was.
Incorrect — ext4 and XFS both store a creation time and stat reads it through statx. The modify time is the one a user can set freely.
02On an Ubuntu 26.04 server you need every failed SSH password attempt from last night, and lastb is not found. Where do you look?
Correct — Ubuntu dropped lastb and its sshd left btmp empty in the lab, so the authentication log and the journal hold the failures.
Incorrect — wtmpdb records sessions through its PAM module once installed, not failed passwords, so it cannot show last night's failures.
Incorrect — On this server btmp stayed at zero bytes after a failed password, so a tool that reads it has nothing to show.
Incorrect — utmp lists current sessions, not failures, and Ubuntu 26.04 does not create /run/utmp at all.
03A user's ~/.bash_history lists wget, chmod and ./run with no times. auth.log shows their only session from 02:10:05 to 02:31:40, and the history file's change time is 02:31:40. When did ./run execute?
Incorrect — Bash writes the history when the shell exits, so 02:31:40 is the logout, not the moment ./run started.
Incorrect — The file is written by that session's shell at exit, so the session's start and end bound every line in it.
Incorrect — Bash writes those lines only when HISTTIMEFORMAT was set in the shell; by default the file has no times.
Correct — The session bounds it and the line order gives the sequence; the birth times of files those commands created can pin it closer.

Related