The assumption
"We have ActionTrail enabled" was, for a while, a sentence I treated as equivalent to "we can reconstruct what happened." Enabling an audit trail feels like flipping a switch from no visibility to visibility: a single binary state. The reality is closer to a dial with several positions, and most accounts I've inspected are sitting on a position several notches short of where their owners assume.
The key idea
An enabled audit trail is not the same claim as a complete audit trail. The difference is a specific, checkable list of what a given trail configuration does and doesn't capture: not a vague caveat to wave at later.
The problem
ActionTrail distinguishes between management events (API calls that create, modify, or delete resources: creating an ECS instance, changing a RAM policy) and data events (operations on the data inside a resource: reading or writing an object in an OSS bucket, a row in a table). A default trail captures management events across all regions. Data events are opt-in, configured per resource, and carry a real cost at scale, which is exactly why they're not on by default.
That distinction rarely shows up until the moment someone needs it. "Enable ActionTrail" is a checkbox most teams complete once during initial account setup, and it produces a trail that answers "was this ECS instance created or modified" extremely well, while answering "was this specific object in this bucket read by this identity" not at all, because that's a data event, and data events were never turned on.
Why the boundary exists where it does
The management/data split isn't arbitrary, and understanding why it's drawn where it is matters for predicting where else it bites. Management events are comparatively rare (an account might generate a few thousand a day even under heavy automation) and each one is inherently interesting: something about the account's shape changed. Data events are potentially enormous in volume (a single busy OSS bucket can see millions of reads a day) and the overwhelming majority of them are operationally uninteresting: a service doing exactly what it's supposed to do, at the volume it's supposed to do it at. Logging all of them by default would multiply most accounts' audit-log volume, and bill, by orders of magnitude, for a benefit that's concentrated in the small fraction of incidents where a specific object read turns out to matter. The trade-off is defensible as an engineering decision. It's the invisibility of the trade-off, not the trade-off itself, that causes the problem this piece is about.
There's a second layer to this that took me longer to notice: the same management/data split shows up, with different names, across almost every cloud provider's audit product, which means the gap isn't specific to ActionTrail. AWS CloudTrail draws essentially the same line between management events and data events, and defaults the same way. That consistency across vendors is worth sitting with for a second, because it means the underlying assumption ("teams will notice and configure the expensive tier where it matters") is being made industry-wide, not by one product team, and industry-wide assumptions about what users will notice are exactly the kind of thing worth testing rather than trusting.
The audit trail nobody revisits
Part of why the gap persists is organizational, not technical. ActionTrail configuration is typically touched once, during account provisioning, by whoever is standing up the account's baseline security posture, often before the workloads that will eventually run in that account even exist. The buckets and tables that will later hold sensitive data haven't been created yet, so there's nothing concrete to decide to enable logging for. By the time those resources exist and start holding data worth protecting, the person who provisioned the account has often moved to other work, and "did we turn on data-event logging for the bucket we created six months after initial setup" isn't a question with an obvious owner. The gap isn't a one-time oversight so much as a recurring one: every new sensitive resource is a new opportunity for the same gap to reopen, silently, unless something in the resource-provisioning process explicitly checks for it.
The experiment
I set up a trail with default settings, then simulated two scenarios that map to two different kinds of real incidents:
Scenario one: a RAM policy is changed to grant broader access. This is a management event by definition, so I expected it to appear clearly in the trail, with the old and new policy versions, the identity that made the change, and the timestamp.
Scenario two: that broadened identity then reads a batch of objects from a sensitive OSS bucket. This is a data event, so I expected, given the trail was still on default settings, that it would either not appear at all, or appear with substantially less detail than the first scenario.
Extending the test to a third, murkier case
Two clean scenarios make a tidy demonstration, but real incidents are rarely that clean, so I added a third case designed to sit closer to the boundary: an identity calling GetObject on a bucket where data-event logging had been enabled for some prefixes but not others, mimicking a common half-migrated state I've seen in accounts that started tightening their logging after a previous incident but hadn't finished the rollout. I wanted to know whether a partially configured bucket produced a partial record, a full record, or nothing, since "partial" is the state most real accounts are actually in, more often than either extreme.
# checking data-event logging status per resource, not per account
aliyun actiontrail DescribeTrails --NameList default-trail
aliyun actiontrail ListDataEventSelectors \
--Name default-trail \
--ResourceArn.1 acs:oss:*:*:sensitive-user-uploads
The proof
The two clean cases behaved exactly as predicted
Scenario one behaved exactly as expected: a complete, detailed record of the policy change, immediately queryable. Scenario two produced nothing. Not a truncated record, not a summary entry: nothing, because data-event logging for that bucket had never been explicitly enabled. From the trail's perspective, the object reads never happened.
{
"eventName": "AttachPolicy",
"eventType": "ApiCall",
"userIdentity": { "principalId": "acs:ram::123456789:user/build-pipeline" },
"requestParameters": { "policyName": "oss-broad-read", "principalType": "User" },
"eventTime": "2025-12-02T03:14:11Z"
}
That's what scenario one looked like in the trail: everything you'd want. Scenario two's corresponding OSS read events simply don't exist as trail entries at all under default configuration.
The partial case was the most dangerous one
The third case, the half-migrated bucket, was the one that surprised me, and it's the finding I think matters most for anyone auditing a real account rather than a lab setup. Reads against the prefix with data-event logging enabled produced complete records, indistinguishable in quality from the management-event records in scenario one. Reads against the unlogged prefix in the same bucket produced nothing, exactly as in scenario two. Querying the trail for "reads against sensitive-user-uploads" during the incident window returned some results and looked, at a glance, like a working, populated log: not the obvious blank page of scenario two, which at least prompts the question "why is this empty." A partially configured bucket produces a partially populated trail that reads, to someone unfamiliar with the per-prefix configuration, as a complete one. That's a worse failure mode than total silence, because total silence is at least legible as a gap. A trail that returns real, correctly formatted results for two-thirds of the traffic you're investigating gives no visual signal that the other third is missing.
# ListDataEventSelectors output for sensitive-user-uploads (abridged)
prefix: uploads/verified/ data-events: ENABLED
prefix: uploads/pending/ data-events: DISABLED
prefix: uploads/archive/ data-events: DISABLED
That's not a bug. It's the documented, intended behavior of a default trail extended to a partially configured one. But "documented and intended" and "understood by the person who checked the ActionTrail box eight months ago" are two very different states, and in every account I've reviewed that wasn't set up by someone who had already been burned by this gap, they diverge.
You might disagree
It's reasonable to argue that logging every object read across every bucket in an account would be prohibitively expensive and noisy at real scale, and that Alibaba Cloud is right to make data-event logging opt-in rather than forcing every account to pay that cost by default. I agree with that trade-off in the abstract, and the volume math from the "why the boundary exists" section above is real: most accounts would not thank you for a hundredfold increase in log volume and cost in exchange for visibility into reads nobody will ever query. My objection isn't to the existence of the opt-in boundary. It's to how invisible that boundary is at the moment someone is deciding whether their audit posture is adequate, and it's specifically to how the partial-configuration case makes that invisibility worse rather than better: a half-enabled bucket looks more complete, not less, than a bucket with logging fully off, which is exactly backwards from what would help someone doing an honest self-assessment. The fix isn't "log everything." It's making the boundary a decision someone makes on purpose, for the specific buckets and tables where it matters, with the per-prefix or per-resource state surfaced somewhere more prominent than a CLI call three levels deep, rather than a default nobody notices they inherited or a partial rollout nobody finished.
What I think now
I now treat "ActionTrail is on" as the start of a conversation, not the end of one. The follow-up question (which resources have data-event logging enabled, and does that list match the resources that would actually matter in an incident) has to be answered explicitly, resource by resource, for every OSS bucket, RDS instance, or PAI endpoint that would be worth investigating if something went wrong with it.
Concretely, that turned into a small script I now run against any account I'm asked to assess: enumerate every OSS bucket, RDS instance, and other data-event-eligible resource, check its data-event logging status against the trail configuration, and flag any resource where that status is "partial" (some prefixes or operations logged, others not) as higher priority to fix than a resource that's cleanly off, precisely because the partial state is the one that will mislead an investigator into thinking the coverage is better than it is. I'd rather find a bucket with logging fully disabled, which at least fails loudly, than one that's half-covered and quietly plausible.
The lesson learned
A default ActionTrail configuration gives you excellent visibility into what changed and almost none into what was read. For most incidents, the second question is the one that actually matters. Knowing which one you have (before you need it, not during the incident) is the entire difference between an audit trail and an audit trail you can trust, and knowing whether that coverage is complete or merely partial is the detail that separates a trustworthy audit from one that only looks that way at first query.