Resource ACLs¶
Resource ACLs restrict who can read the content of specific sensors and Config Hive records inside an organization. Use them when part of an org holds sensitive data, such as a mail feed with PII, that only some users should see.
You restrict a resource by tagging it acl:<scope>, and you control who holds a scope with a record in the acl hive.
ACLs are opt-in. An organization with no acl: tags behaves exactly as before.
How it works¶
- Metadata stays visible, content is gated. Everyone with normal org permissions still sees that a sensor or record exists, along with its name, hostname, tags and online status. Only what is inside is restricted.
- ACLs only restrict. A caller needs the normal permission first (for example
sensor.getorsecret.get), then membership in the scope. Scope membership never grants a permission. - Multiple scopes are AND. A resource tagged
acl:hrandacl:financeis readable only by members of both. Adding a tag can only narrow access. - Missing scopes lock. An
acl:tag with no matching scope record, or with a disabled or expired one, locks the resource for everyone. Deleting a scope record does not unlock anything. - Current tags apply. Restriction follows the tags a sensor has now. Tagging a sensor hides its full history. Removing the tag exposes it again.
What is restricted¶
| Visible to anyone with org permissions | Restricted to scope members |
|---|---|
| Sensor list: hostname, platform, tags, online status, last seen | Sensor telemetry: timeline, historical events, Replay results |
Hive record name, usr_mtd (tags, enabled, expiry, comment), sys_mtd |
Hive record data |
| Artifact metadata | Artifact content and original logs |
| Output names | Tasking the sensor, output samples |
| Sensor counts, dashboards, tag search | Detections from restricted sensors (hidden, not redacted) |
A few details:
- A restricted hive record comes back with its metadata intact and
datareplaced by{"acl_restricted": true}. Itsetagis computed over that redacted view. Writing the marker back as data, or a conditional write using the redactedetag, is rejected, so a sync from a restricted view cannot erase the real content. - Fetching a restricted record for execution, such as a playbook or AI agent, is refused instead of redacted. A
hive://secret/...reference to a restricted secret is left unresolved for callers outside its scope. - Tasking a restricted sensor is refused for every command.
- A detection is hidden when its sensor, or any sensor that contributed to a stateful detection, is restricted for the caller.
- Restricted records are dropped from results rather than reported as an error, so searches, timelines and detection lists return fewer rows. Two analysts running the same query can legitimately get different totals.
- Enforcement on investigation expansion and on what an extension is told about a request only starts once the org has at least one sensor carrying an
acl:tag. Direct reads of a restricted hive record are gated either way. - Never tag a record that sensors or adapters fetch through the public (unauthenticated) endpoint, such as an external adapter config. That path has no caller to resolve, so it refuses the record and the affected adapters keep running on their last known-good configuration.
Permissions¶
| Permission | Allows |
|---|---|
acl.get |
Read scope records and list the resources tagged with a scope |
acl.set |
Create, change and delete scope records, and add or remove any acl: tag |
The Owner and Administrator roles include both. Permissions are stored when a role is assigned, so users who already had one of these roles, and the user who created the org, do not have acl.get or acl.set until the role is assigned again or the permissions are granted directly. Both can be granted individually to users and API keys.
acl.set does not grant read access. An administrator who can edit scopes still sees nothing restricted until they add themselves as a member. Since an acl.set holder can add themselves to any scope, treat acl.set as equivalent to seeing everything and grant it accordingly.
Setting up a scope¶
The steps below use the CLI. The same operations are available through the REST API and the SDKs.
1. Create the scope record¶
The record name is the scope name. This record creates scope mailsec:
# mailsec.yaml
data:
members:
- type: user
id: "<user UID>"
- type: api_key
id: "mail-pipeline"
- type: group
id: "<organization group ID>"
warn_only: true
Always pass --enabled. A disabled scope locks every resource tagged with it, including for its members.
Starting with warn_only: true is recommended. See Warn-only mode.
2. Tag the resources¶
Sensors:
limacharlie tag add --sid <SID> --tag acl:mailsec
limacharlie tag mass-add --selector 'hostname contains "mail"' --tag acl:mailsec
Installation keys, so every sensor or adapter enrolled with the key gets the tag:
Hive records, such as secrets and extension configs:
Each of these requires acl.set in addition to the usual permission (sensor.tag, ikey.set, or the hive's own set or set.mtd permission).
3. Check the result, then enforce¶
While the scope is warn-only, watch the organization errors for acl/mailsec entries. Each one names a user or key that would have been denied. Add the ones who should have access, then set warn_only: false (or remove it) and apply the record again.
To list the hive records carrying a scope:
This returns metadata only, grouped by hive, and requires acl.get. Use limacharlie tag find --tag acl:mailsec for sensors.
Scope records¶
| Field | Description |
|---|---|
members |
List of principals holding the scope. An empty list is valid and locks the scope's resources for everyone. |
members[].type |
user, api_key or group. |
members[].id |
For user, the user's UID or email. For api_key, the org API key's name. For group, the organization group ID. |
warn_only |
true to report instead of enforce. Defaults to false. |
Scope record names must be lowercase. acl: tags and acl_scopes entries are lowercased automatically, so acl:MailSec refers to the mailsec record. Names cannot be empty or contain ,, /, spaces, tabs, line breaks or control characters.
Record usr_mtd works as in any hive, with one difference in effect: disabling or expiring a scope record locks its resources rather than releasing them.
Notes on members:
- Prefer the UID for users. A member listed by email matches web app sessions and
limacharlie auth loginsessions, but not the user's personal API keys. A UID member covers both. - A
usermember never matches an org API key, even one named after the user. - Records are limited to 64 KiB.
Warn-only mode¶
With warn_only: true, a scope is not enforced on reads and tasking. Every caller is treated as a member there, so tagged resources behave as if the tag was absent. Every access the scope would have denied is reported as an organization error under the component acl/<scope>, naming the user or key and the resource.
Use it to roll out a scope without locking out analysts or cutting a SIEM feed by surprise.
- The org keeps one error entry per scope, holding the most recent violation. Reports are throttled to about one per scope every 15 minutes.
- Members of the scope are never reported.
- Warn-only does not relax tag writes.
acl.setis still required to add or removeacl:tags. - Warn-only does not relax D&R rules.
service request,extension requestandstart ai agentare still refused on tagged events unless the rule lists the scope inacl_scopes. - Setting
acl_scopeson an output or D&R rule still requires real membership oracl.set. - Warn-only does not apply to a disabled or expired record. Those still lock.
- Outputs are covered too. A record that would have been withheld from an output is normally delivered and reported. The first records after a platform restart may still be withheld for a short time.
Outputs¶
Events and detections from restricted sensors are excluded from outputs by default. This includes outputs that existed before the tag was added, so tagging a sensor removes its data from your SIEM feed unless you opt the output in.
To opt an output in, set acl_scopes to the list of scopes it may carry:
- A record reaches the output only if every
acl:tag on its sensor is listed. - To set
acl_scopes, the caller needsacl.setor membership in every listed scope. The check runs when the output is saved. Later membership changes do not affect an existing output. - Reading samples of an opted-in output requires membership in all of its scopes.
- Records with no sensor, such as billing or some deployment events, are not affected.
- Live streams, which the web app and
limacharlie streamopen, carry only the scopes the viewer holds.acl.setis not a bypass there, since opening a stream is reading content. - Exporting query or Replay results to an output drops every restricted record, whatever scopes you hold and whatever the output names. An export is a shared org-level archive, so it is built the same way for everyone.
- Long-term retention outputs keep everything, including restricted records, so that later searches still enforce correctly. Their samples are not readable through the sample view.
D&R rules¶
D&R rules cannot add or remove acl: tags. The add tag, remove tag, add hive tag and remove hive tag actions reject them, because rules run without a user identity to check acl.set against.
Three response actions send event content outside the platform and are refused on events from restricted sensors: service request, extension request and start ai agent. To allow them, list the scopes in the rule's acl_scopes field, next to detect and respond:
detect:
event: NEW_DOCUMENT
op: ends with
path: event/FILE_PATH
value: .eml
respond:
- action: extension request
extension name: ext-reliable-tasking
extension action: task
extension request:
sid: '{{ .routing.sid }}'
task: 'os_version'
acl_scopes:
- mailsec
- The action is allowed only when every
acl:scope on the event's sensor is inacl_scopes. - Changing
acl_scopesrequiresacl.setor membership in every scope in the new list. Removing all scopes requiresacl.set. - Editing other parts of the rule does not require membership, as long as
acl_scopesis unchanged. Includeacl_scopeswhenever you rewrite the whole rule.limacharlie dr set --detect ... --respond ...writes onlydetectandrespond, which counts as removing every scope. - The rule's scopes are forwarded to the extension, which uses them to decide what the request may reach.
reportandtaskactions are not affected. Their results go through channels that are already gated.
Extensions¶
An extension acts through its own org API key, named _<extension name>-<uuid>. To let an extension read or task restricted resources, add that key's name as an api_key member of the scope.
- A playbook tagged with a scope can only be run by the Playbook extension once its key is a member.
- Extensions also receive the scopes held by the user or rule that made the request, and are expected to honor them.
- Reliable Tasking checks both. A sensor is skipped with reason
creator_not_memberwhen the task's creator does not hold the sensor's scopes, and withextension_not_memberwhen the extension's key does not. Skipped tasks are not retried; re-create them after fixing membership.
Infrastructure as Code¶
Scope records are ordinary hive records in the acl hive, so limacharlie hive list|get|set --hive-name acl and the SDK hive clients work on them. Recent CLI releases also add --hive-acl to sync pull and sync push; run limacharlie sync pull --help to check yours.
The Infrastructure extension includes the acl hive only when its identity holds both acl.get and acl.set. Pushing any record or sensor change that adds or removes an acl: tag also requires acl.set.
Errors¶
| Code | Meaning |
|---|---|
ACL_CONTENT_RESTRICTED |
HTTP 403. The caller is not a member of every scope on the resource. |
UNAUTHORIZED_ACL_TAG |
HTTP 401. Adding or removing an acl: tag on a sensor or installation key without acl.set. |
UNAUTHORIZED |
HTTP 400. The same refusal for a hive record's acl: tags or a D&R rule's acl_scopes, and a refused fetch of a restricted record for execution. |
ACL_TAG_TTL_NOT_ALLOWED |
HTTP 400. acl: tags cannot have a TTL. |
INVALID |
HTTP 400. Writing a record whose data is the redaction marker, or writing with the etag of a redacted read. |
ACL_SCOPE_UNAVAILABLE |
HTTP 400, retriable. Membership could not be resolved while saving an output. |
ACL_SCOPES_UNAVAILABLE |
HTTP 400, retriable. Membership could not be resolved while saving a D&R rule's acl_scopes. |
Only ACL_CONTENT_RESTRICTED and UNAUTHORIZED_ACL_TAG come back in a machine-readable error_code field. The others carry the code in the error message.
When membership cannot be resolved on other paths, the API returns a generic retriable error, or a 403 for output samples. Restricted content stays locked and unrestricted content is not affected. On read paths this failure is silent: you get an empty result or a redacted record rather than an error.
Limits and behavior to know¶
- Propagation. Tag changes usually apply within seconds. Membership changes also usually apply within seconds. Both apply within about 6 minutes at most.
- Enrollment. Tags from an installation key are applied right after enrollment, not atomically with it, so a new sensor can be briefly unrestricted. Sensors enrolled before you tagged the key keep the tags they were given then. Tag those individually.
- Deleting is not gated. Deleting a sensor, a hive record or an installation key does not require
acl.setor membership. Deletion destroys content instead of exposing it. Deleting an installation key does not remove tags from sensors already enrolled. - Tag TTLs.
acl:tags cannot expire, so a restriction is never removed silently. - IOC search. Object and IOC searches still list the sensors that saw a value, including restricted ones. Only event content is gated.
- Audit logs are not restricted, and every ACL change is audited:
acl:tag writes on sensors and installation keys, and scope record writes as ordinary hive changes. - Billing and quota. Restricted telemetry counts like any other telemetry.
- LimaCharlie operations hold a separate org-wide permission that ACLs never gate, which is what makes support possible.
Troubleshooting¶
If a user reports that data disappeared:
- Check whether the org has any
acl:tags (limacharlie tag list). If not, ACLs are not involved. - Check that each
acl:tag in use has a matchingaclhive record that is enabled and not expired. A tag without one locks its resources. - Check that the user or key is a member of every scope on the missing resources. For personal API keys, the member must be listed by UID.
- Check the outputs. Existing outputs stop receiving a sensor's data once it is tagged, until they list the scope in
acl_scopes. - If you removed a member and they can still read, allow up to six minutes. Beyond that, remove their platform access and contact support.
To un-restrict a resource, remove the acl: tag from it. Deleting or disabling the scope record does the opposite and locks it.