How unauthorized changes are detected
Per change source (cloud audit log, Kubernetes audit, GitOps, ITSM), what counts as an authorized change, what raises an unauthorized change, and a sandbox demo.
Concept · Updated 2026-10-26 · Describes release 0.1.164
An unauthorized change is a write the platform saw applied in your estate that nothing accounts for: no approved change request, no approved ITSM change ticket, no automation identity you trust. It is different from unauthorized access, a write the cloud refused: nothing changed, so there is nothing to roll back, and it goes to SecOps as a report.
The rule is the same for every source that can see who made a write. What differs is what each source can see. The field-by-field facts (notes, audit actions, caps) are in the Changes reference.
The switches
| Setting | Default | What it does |
|---|---|---|
aiops_unauthorized_change_incidents (Guardrails → Unmanaged changes, Open an incident for every change with no reference) |
off | An applied write from the cloud audit log or the Kubernetes audit log needs a reference; one without opens an incident that waits for a person. Off by default because a busy account records thousands of outside writes a day: fill the allowlist first. |
aiops_denied_access_to_secops |
on | A refused write opens a SecOps report (one per principal, account and hour), with the SecOps agent enabled. |
aiops_change_principal_check |
on | A write made with the platform's own cloud credential that no platform change, approved action or source-side suppression explains is flagged. |
change_freeze |
none | While the declared freeze window matches, any observed change with no platform change in flight for its service is flagged. |
change_authorized_principals |
[] |
The automation identities (globs) whose writes are authorized: CI/CD roles, pipeline service accounts. |
itsm_change_match |
per ticket system | The ticket fields read for state, CI and planned window. |
With the first switch off, the platform still records every captured write with its principal, and the freeze window, the principal check and denied attempts still flag rows on the Changes page; only the reference rule and its incidents are off.
What counts as authorized
A captured, applied write is authorized when one of these accounts for it, checked in this order. The first match is stored on the change as its reference.
- The platform made it. The platform's own credential, with a receipt: a typed platform step on the same resource within 10 minutes, a route change naming the resource or its service, an approved action inside an analysis, or a source-side suppression.
- An allowlisted principal. The AWS role or user, the Azure caller or the Kubernetes user matches a glob in
change_authorized_principals(case-insensitive). Use it for deployment pipelines and for in-cluster CI/CD service accounts, for examplesystem:serviceaccount:flux-system:*. - An approved ITSM change ticket. A ServiceNow change request or a Jira change issue the platform has received, in an approved state, whose CI names the resource or a service the write is linked to, and whose planned window covers the time of the write. A ticket without a planned start and end never matches.
- A change request a person approved on the platform. Its typed steps, reviewed action or operator commands name the resource itself (a whole service is not enough), it was opened before the write, and it moved within 24 hours of it. One the platform executed covers writes only until its execution ended.
A write with none of these is flagged "no reference found (no ticket, approval or signed change request)". A freeze window or the principal check can still flag a write that has a reference.
Method per source
Cloud audit log: AWS CloudTrail and the Azure activity log
- What is read. Completed write events of the read connection's account, in its regions plus
us-east-1for the global services (CloudTrail), and write operations per subscription (activity log). The harvest runs every 15 minutes by default. - What is never judged. Reads, failed events, session plumbing and every STS call, and writes an AWS service makes for itself (
AWSService). A write made on the platform's behalf and seen back in the log becomes the receipt of its change, not a second row. - Authorized by the four references above, in that order.
- Raises an unauthorized change when none matches and the switch is on: one incident per action (the same principal and account within 10 minutes), one alert row per resource, priority from the resources' criticality (P3 when none is declared), no automatic analysis, a ticket through your on-open ticket route, and a revert drafted per change from its recorded before-state.
- Raises unauthorized access when the cloud refused the write (
AccessDenied,UnauthorizedOperation,AuthorizationFailed): a denied attempt, reported to SecOps, never rolled back.
Kubernetes audit log
- What is read. On EKS, the control-plane audit log in CloudWatch Logs when the AWS connection carries
k8s_audit: true; on other clusters, the API server's audit webhook. Completed write verbs (create,update,patch,delete) by people, and by the service accounts of the CI/CD namespaces ink8s_ci_namespaces(Flux, Jenkins, GitLab Runner and Tekton by default). - What is never judged. Other
system:identities (nodes, controllers, the GitOps controller itself, whose changes arrive as its sync events), EKS's own service identities, server-side dry runs, and the pods and runs a pipeline spawns. - Authorized by the same references. A pipeline service account is authorized only when its glob is on the allowlist; a person's
kubectledit needs an ITSM ticket or an approved change request naming the object. - Raises an unauthorized change per action, as for the cloud audit log (the account is the cluster); a 401 or 403 is a denied attempt.
GitOps: repositories, Argo CD and the change webhook
- What is read. Pushes and merged pull requests of the watched repositories (GitHub, GitLab, Gitea, Azure DevOps), Argo CD sync and drift (out of sync) events, and anything you post to the change webhook.
- Authorized by the repository's own controls: a merged pull request went through the reviews and branch protection the repository enforces, and Argo CD applies only what is in Git. The platform does not judge these events by the reference rule, and they never open an unauthorized-change incident.
- Raises only inside a declared freeze window: an event with no platform change in flight for its service is flagged "external change during a declared freeze window with no in-flight platform change for this service", audited and listed under Unauthorized on the Changes page. A direct push that skipped review is recorded, not raised: require reviews with branch protection in the repository.
- What the cluster sees. A
kubectledit that bypasses Git shows as Argo CD drift and, where the Kubernetes audit log is read, as a person's write judged by the rule above.
ITSM: ServiceNow and Jira change tickets
- What is read. ServiceNow change requests (
change_request, numbers startingCHG) and Jira issues whose type is a change, posted to their source webhooks. Each becomes a ticket row in the change ledger with its state, CI and planned window, refreshed by every re-send: a ticket approved after it was raised counts from then on. - Its role. The ticket is the authorization, never a change to judge: it is reference 3 above for every write the audit logs capture.
- What to set. The approved values and field names in
itsm_change_matchwhen yours differ from the defaults (ServiceNowapproval,cmdb_ci,start_date,end_date). Jira has no standard planned-window fields: until you name your start and end custom fields, no Jira ticket can match. - Raises nothing by itself. A write whose ticket is not approved, names another CI, or falls outside the planned window has no ITSM reference.
What a person does
An unauthorized-change incident never fixes or rolls back on its own, whatever runbook is certified. On its Unauthorized change card, a person with the approver permission either:
- chooses Approve rollback to open the drafted revert as a change request, approve and release it (a revert with no typed step shows the command to run by hand instead), or
- chooses Keep this change (legitimate) with a note: the reverts are rejected, the changes are marked authorized by that person, and the incident closes as expected.
Run the sandbox demo
scripts/demo-unauthorized-change.py shows the cloud audit log path end to end in a sandbox account. The script is in the platform's source repository, not on this installation: ask your platform administrator or the vendor for it. It turns aiops_unauthorized_change_incidents on for the demo window only and puts the workspace's own value back on the way out, also when the run fails or is stopped with Ctrl+C, SIGTERM or a closed terminal. If putting it back fails, the script says so in a line that starts !!! and names the value to set by hand; a SIGKILL skips the restore.
Before you start: a workspace whose AWS read connection covers the sandbox account and region; a security group there, tagged aiops-managed=true so the platform's write connection can run the drafted rollback; a personal access token with members.manage, pillars.manage and incidents.view; the AWS CLI signed in to the sandbox account with an identity that is not on change_authorized_principals (the script refuses to start otherwise).
AIOPS_BASE=https://aiops.example.com AIOPS_TOKEN=<token> TENANT_ID=<workspace id> \
SG_ID=sg-0123456789abcdef0 AWS_PROFILE=<sandbox profile> AWS_REGION=ap-southeast-1 \
python3 scripts/demo-unauthorized-change.py
- The script adds one ingress rule to the security group: TCP 8443 from
203.0.113.0/24, a documentation range that is never routed on the internet. - It pulls the audit log through the platform every minute until the write is flagged; CloudTrail takes about 5 to 15 minutes to deliver an event. You should see the note "no reference found (no ticket, approval or signed change request)" and the incident's address.
- Open the incident and answer on its Unauthorized change card. The script waits up to an hour (
DECIDE_WAIT_S, 3600 seconds) for the incident to close: a rollback is released by a person and then validated in rounds. - The script restores the switch, then removes the rule if no rollback removed it (
CLEANUP=0keeps it). While the incident's rollback is requested, approved or running, it leaves the rule to the rollback and prints the command that removes it later.
To show an authorized write instead, raise an approved change ticket naming the security group with a planned window around the demo, or add your identity to change_authorized_principals (the script then refuses, because nothing would be flagged).