Tools · AWS detection and investigation · 08 / 24
CloudTrail Lake: what one result row proves
A finished row is one API call the store recorded. An empty error code means the call was accepted, not that the account is still in that state.
One row, one recorded call
Each result row is a CloudTrail event your event data store ingested. eventTime is when the call was recorded, eventSource and eventName say which API, and recipientAccountId says which account. userIdentity.type and userIdentity.arn say which principal the log recorded. Copy those. If userIdentity.userName is present, copy it as a recorded label, the same way you would a session name, not as a person you have identified.
userAgent is a string the client sent. Any client can send a string. The note may quote it as “recorded user agent.” It is not, by itself, proof of a particular product or a particular human.
Empty error code means accepted
An empty errorCode means the service accepted that call. It does not mean the change was harmless, and it does not mean a later call left the setting where this row put it. A non-empty errorCode, including AccessDenied, means the call was attempted and refused. Denied calls stay in the note. Dropping them hides attempts that did not succeed.
readOnly true means the API was a read. readOnly false means the API was a change. The flag is about that call, not about whether you approve of the change.
A missing row is not a silent account
The store only returns events it was built to keep. A management-event store does not list object reads. A query window that starts too late does not list earlier calls. What not to trust: “this never happened” when the honest line is “this store, in this window, has no such row.” Write the store’s scope next to a zero result so the gap stays visible.
Worked case: a public-access block call
A sandbox query returns one FINISHED row. eventName is PutBucketPublicAccessBlock, readOnly is false, errorCode is empty, and the principal is an assumed role used by the account’s own setup job. eventTime is 2026-11-03T16:40:00Z. The row proves that call was accepted then. It does not prove the bucket’s block is the same tonight.
The note says accepted change, names the bucket from the request parameters, names the role, and quotes the time. A separate read, GetBucketPublicAccessBlock, run in the same sandbox, is what supports a sentence about the current setting. If that read is not in scope for the exercise, the note stops at the change event and says current state was not checked.
eventTime: 2026-11-03T16:40:00Z eventName: PutBucketPublicAccessBlock readOnly: false errorCode: <empty> principal: arn:aws:sts::111122223333:assumed-role/sandbox-setup/setup proves: this change call was accepted at eventTime does not prove: the bucket’s block setting right now
Diagram
What to cite from one CloudTrail row
Each line is a field, not a conclusion about today.
- eventTime
When this call was recorded. Not “the resource is like this now.”
- eventName and readOnly
Which API, and whether that API was a read or a change.
- errorCode
Empty means accepted. A code means refused. Both belong in the note.
- userIdentity.arn
The recorded principal. userName and userAgent stay labeled as recorded strings.
Check yourself
1. A row has an empty errorCode. Which sentence matches the row?
2. Why keep a row whose errorCode is AccessDenied?
3. The store has no row for an object read, and it only ingests management events. What may the note say?