Compliance reference
Frameworks, the agreed comparison table, your own requirements, the 22 controls, finding families, finding to incident, risk decisions, attestations, the scanner pull, scores and audit.
Reference · Updated 2026-10-28 · Describes release 0.1.164
Exact facts about the compliance pillar. The procedures are in Turn on compliance and Security posture; who may approve a risk decision or release a compliance fix is owned by Approval policy and change tiers.
Frameworks and requirements
Two frameworks ship in full, as paraphrased requirement rows (never the standard's own text): 154 rows — 61 SOC 2 Trust Services Criteria and 93 ISO/IEC 27001:2022 Annex A controls. A requirement with no automated check mapped onto it is shown as a manual-attestation row, never a silent gap and never a fake pass.
| Framework key | Name | Grouping | In scope by default |
|---|---|---|---|
soc2 |
SOC 2 (Trust Services Criteria) | Categories security (Common Criteria), availability, confidentiality, processing_integrity, privacy |
security, availability, confidentiality — privacy and processing integrity stay visible in the catalogue but out of the score |
iso27001 |
ISO/IEC 27001:2022 | Themes organizational (A.5), people (A.6), physical (A.7), technological (A.8) |
All |
Scope lives in tenant.settings['compliance']:
| Key | Meaning |
|---|---|
enabled |
true or false, read only while the platform offers SOC 2 / ISO 27001 at all: they are phase 2, and the platform switch AIOPS_COMPLIANCE_FRAMEWORKS is off by default, which reads as false in every workspace — existing ones included — whatever enabled holds, hides Compliance frameworks (phase 2) in Guardrails and refuses compliance_enabled: true with 409. On the upgrade that turned it off, the control plane's first scheduler tick resolves the open control:<id> finding of every platform check, with the scope-change note below. Your platform administrator turns it on once the frameworks are agreed with you. Once it is on: a new workspace starts with false and is switched on at onboarding, once the customer has accepted the comparison table ([Agreed comparison table](#agreed-table)); a workspace created before this setting existed has no value, which reads as true. With falsethe workspace scores no framework: no control runs on a scan or after a captured change — EST-01 to EST-06 and the platform's own alarms (LOG-01, CRY-01, ACC-04, MON-01) included — no control finding opens, the posture lists no requirement and **Audit & coverage** has no **Frameworks** tab. Security Hub and Defender findings keep arriving. Set it with **Score this workspace against SOC 2 and ISO 27001** under **Compliance frameworks (phase 2)** in **Guardrails**, orcompliance_enabledonPATCH …/guardrails (members.manage, null= no value, read as on). Any change that takes a check out of the scan scope — this switch, a framework dropped fromframeworks, a control disabled — resolves that check's open control:<id>finding with the note *This check no longer runs in this workspace* (auditincident.resolved, by: scope-change), except one awaiting approval or with a fix in flight; the response carries closed_control_incidents`. |
platform_controls |
false (default) or true. The 13 deterministic checks of the platform's own settings (ACC-02 to ACC-04, LOG-01, DAT-01, DAT-02, CRY-01, CHG-01 to CHG-03, SEG-01, MON-01, MON-02) read nothing in your estate, so they stay out of the score unless this is true; their requirements fall back to attestation. They still run on every scan either way: a failure opens its control finding and a later pass closes it, so the platform's own alarms (an audit-trail break in LOG-01, plaintext secrets in CRY-01) keep working. ACC-01 reads your connector credentials and stays in. Set it with Count the checks of the platform's own settings or compliance_platform_controls on PATCH …/guardrails |
frameworks |
["soc2", "iso27001"] — a control is active when any of its mapped frameworks is in scope |
soc2_categories |
The SOC 2 categories the posture scores |
custom_requirements |
The workspace's own requirement rows (at most 100), written only through PUT …/compliance/custom-requirements/{ref} — see Your own requirements |
overrides |
`{control_id: {"disabled": true |
The mapping from requirement to check is this platform's interpretation; the interpretation sheet (POST …/compliance/interpretation-export, audit compliance.interpretation_exported) renders every control's stated basis for the customer's security or legal team to approve. Passing scans do not by themselves constitute SOC 2 or ISO 27001 compliance; every unmapped requirement needs human attestation and auditor-accepted evidence.
Agreed comparison table
The comparison table is the version of this mapping the customer accepts before onboarding: when the posture says compliant, it means compliant against that accepted version. It lists every requirement row, in scope or not, then the workspace's own rows. Under Agreed comparison table on the Frameworks page: Download table (CSV), Download table (Markdown) and Record acceptance.
| Column | Meaning |
|---|---|
| Framework, Ref, Requirement | The catalogue row (our paraphrase) or one of your own |
| In scope | yes / no under the workspace's frameworks and soc2_categories scope; the table is the same whether scoring is switched on or not |
| Judged by | What the posture scores the row with: platform rules, scanner controls, both, or attestation |
| Platform rules | The catalogue checks in scope that score the row (the platform's own checks only with platform_controls) |
| Scanner controls | The Security Hub controls those checks mirror, each with the AWS Config rule it runs (from the Security Hub CSPM controls reference); for your own row, the controls you named |
| AWS Config rules | The AWS Config managed rules (source identifiers) AWS Audit Manager's published data-source mapping puts behind the row: the SSAE-18 SOC 2 framework for SOC 2, the ISO/IEC 27001:2013 Annex A framework for ISO 27001, each 2013 control renumbered to its 2022 control by the ISO/IEC 27002:2022 Annex B correspondence (AWS Config basis names the 2013 controls) |
| Azure Policy | The group of the built-in Regulatory Compliance initiative: SOC 2 Type 2 (SOC_2_<ref>, every SOC 2 row) or ISO/IEC 27002 2022 (ISO_IEC_27002_2022_<n>, 23 ISO rows) |
| Note | What the platform reads for the row, and why the rest is evidenced by a person |
A cloud column is blank where no public mapping names a rule. AWS publishes no sample conformance pack for ISO 27001 or SOC 2 (a customer may author its own), and Security Hub has no ISO 27001 or SOC 2 standard; Audit Manager is closed to new customers, so its mapping is a reference. The scanner, AWS Config and Azure Policy columns of a catalogue row are not scored by the platform: they name the rules that evidence the same requirement in your cloud.
| Fact | Value |
|---|---|
| Export | GET …/compliance/agreed-table (report.view), fmt = json (rows, sources, sha256, the agreement), csv or md; the file name and the X-Table-SHA256 header carry the version |
| Version | sha256 over every column of every row and the scope: a scope switch, a platform rule override, your own row or a product upgrade that changes a row changes it |
| Record the acceptance | POST …/compliance/agreed-table/accept (pillars.manage) with sha256 (the version the customer saw), accepted_by (who accepted: name, role, organisation), accepted_on (default today, never in the future) and an optional note; 409 when the table changed since that version |
| What is kept | The table is frozen as a report artifact (source_id agreed-table, meta version, table_sha256, accepted_by, accepted_on) and an attestation under compliance:agreed-table cites it, for 1,095 days |
| Supersede, never edit | A new acceptance (v<n+1>) expires the previous one; the trail is append-only |
| Audit | compliance.table_agreed with version, table_sha256, accepted_by, accepted_on |
| On the posture | GET …/compliance/posture → agreement (version, accepted_by, accepted_on, current, lapsed, artifact_id); the page reads Scored against agreed table v2 (accepted by … on …), No agreed table yet without one, says the table has changed since when current is false, and that it lapsed after 1,095 days |
| On the Scores tab | GET …/compliance/reports → agreed (a current, unlapsed acceptance); without one, each SOC 2 / ISO 27001 row on Score per standard carries a not agreed badge: its score follows this platform's mapping, not a table the customer accepted. Turning Compliance frameworks (phase 2) on (once the platform offers it) does not wait for an agreement — the badge stays until one is recorded. |
Your own requirements
A workspace may add requirements of its own, such as "highly available: every applicable resource runs in at least two Availability Zones". Each row is judged by platform rules and/or scanner controls, or by an attestation when it names neither; it joins the comparison table (and changes its version) and the posture as the custom framework, shown as Workspace requirements. Add or remove them under Your own requirements in the Agreed comparison table card (Save requirement, Remove).
| Fact | Value |
|---|---|
| Endpoint | PUT …/compliance/custom-requirements/{ref} adds or replaces one, DELETE removes it (pillars.manage; audit compliance.custom_requirement_saved / compliance.custom_requirement_removed) |
ref |
Your own id: letters first, then letters or digits, parts joined by - or . (HA-01, POL.7); never a SOC 2 or ISO 27001 ref |
title, description |
What it asks (up to 200 characters) and how it is read (up to 1,000) |
checks |
Catalogue control ids (EST-01); DRIFT-01, an AI judgment, is refused. A check counts only while it is in scope |
scanner_controls |
Control ids a scanner reports: Security Hub (ELB.13, RDS.5) or a Defender assessment |
| Scanner control status | fail = an open finding no approved suppression covers; suppressed (accepted) = every open one is covered, never a pass; pass = the scanner reported it and cleared it; unknown = the scanner never reported it. The pull reads failing findings, so a control that never failed reads unknown |
| Row status | As a catalogue row: any fail fails it, all pass passes it, else unknown, else suppressed; a row with neither checks nor scanner controls takes an attestation (POST …/compliance/requirements/{ref}/attest) |
| Scope | Scored only while enabled is on; listed in the comparison table either way |
Automated controls
22 controls, each mapped many-to-many onto requirement refs. Check kinds in cost order — the order a scan evaluates them: deterministic (platform rows only, keyless), script (a direct read-only probe through the workspace's own connectors), lake (SQL over collected telemetry; no control uses it today), agent (one bounded run, the model residue — at most 5 per scan). Severity sets the compliance incident's severity on a fail: high → error, medium → warning, low → info.
| Id | Title | Kind | Severity | What the check does | SOC 2 | ISO 27001 | Mirrors |
|---|---|---|---|---|---|---|---|
| ACC-01 | Least-privilege connector credentials | script | high | Runs the permission audit over every enabled read-role connection; fails when any credential reports writes it could perform | CC6.1, CC6.3 | A.5.15, A.8.2 | — |
| ACC-02 | Platform role separation | deterministic | medium | Fails when more than one member exists and all hold the owner role | CC6.1, CC6.3 | A.5.15, A.5.18 | — |
| ACC-03 | Centralised identity (SSO) | deterministic | medium | Passes only when an enabled workspace IdP exists | CC6.1 | A.5.16, A.5.17 | — |
| ACC-04 | Support access is time-bound | deterministic | high | Fails when any active support grant has no expiry | CC6.1 | A.5.18, A.8.2 | — |
| LOG-01 | Tamper-evident audit trail | deterministic | medium | Newest audit event age; replays the last 500 rows of the sha256 audit chain; fails on a broken link or more than 5,000 unsealed rows; unknown with no event yet | CC7.2 | A.8.15 | CloudTrail.4 / CIS 3.2 (applied to the platform trail) |
| DAT-01 | Sensitive-data masking at capture | deterministic | medium | Evidences the always-on PII scrub floor plus the workspace's own patterns; always passes | CC6.7 | A.8.11 | — |
| DAT-02 | No unresolved sensitive-data exposure | deterministic | high | Fails while any pii:* compliance incident is active |
CC6.7, CC7.4 | A.5.34, A.8.11 | — |
| CRY-01 | Stored secrets encrypted at rest | deterministic | high | Passes only when the deployment sets the secret-store key (Fernet); fails on the plaintext fallback | CC6.1 | A.8.24 | — |
| CHG-01 | Changes require documented approval | deterministic | high | Evidences the intrinsic write gate and samples the 20 most recent approval decisions; always passes | CC8.1 | A.8.32 | — |
| CHG-02 | Executed changes recorded with outcome | deterministic | medium | Over the executed platform changes of the last 30 days (newest 500): counts those with a real before-state capture; method: none counts as missing; evidence carries {changes, with_prestate, pct} and the uncovered CHG ids |
CC8.1 | A.8.32 | — |
| CHG-03 | High-risk changes are window-gated | deterministic | low | Fails when an enabled live-infrastructure route classifies at L2/L3 without params.change_window |
CC8.1 | A.8.32 | — |
| SEG-01 | Read/write credential segregation | deterministic | high | Fails when an enabled live-infrastructure route's connection is not role: write |
CC6.1, CC8.1 | A.5.15, A.8.2 | — |
| MON-01 | Alert pipeline is live | deterministic | medium | Fails when no webhook token is set, no alert ever arrived, or the newest alert is older than 7 days | CC7.2 | A.8.16 | — |
| MON-02 | Monitoring coverage for enabled pillars | deterministic | low | Fails when an enabled pillar has a data category with no connected source | CC7.2 | A.8.16 | — |
| EST-01 | Backups enabled with retention | script | high | Managed databases have automated backups with retention > 0; every non-terminated VM is in a backup plan (AWS Backup / Recovery Services) | A1.2 | A.8.13 | RDS.11 |
| EST-02 | No publicly exposed storage | script | high | No bucket or container is world-readable or world-listable | CC6.6 | A.5.23, A.8.20 | S3.1, S3.8 / CIS 2.1.4 |
| EST-03 | No world-open security groups | script | high | No security-group / NSG rule opens a sensitive port (remote admin, database, cache, search, HDFS, Docker daemon, or all ports) to /0; a world-open 443 is out of scope |
CC6.6 | A.8.20, A.8.22 | EC2.13, EC2.14, EC2.19 / CIS 5.2 |
| EST-04 | Encryption at rest on data services | script | medium | At-rest encryption on managed databases and block volumes | CC6.1, CC6.7 | A.8.24 | RDS.3, EC2.3, EC2.7 / CIS 2.2.1 |
| EST-05 | Cloud audit logging enabled | script | medium | The account records control-plane activity (CloudTrail / activity log diagnostic setting) | CC7.2 | A.8.15 | CloudTrail.1 |
| EST-06 | Cloud IAM hygiene | script | high | AWS: IAM users with a console password and no MFA device, and active access keys older than 90 days — the first 50 IAM users only (more are noted and withhold a pass); Azure: more than 3 Owner assignments made directly at subscription scope. Root-account use, *:* policies and unused credentials are not read |
CC6.1, CC6.2, CC6.3 | A.5.16, A.5.18, A.8.2 | IAM.5, IAM.3 / CIS 1.10, 1.14 |
| GIT-01 | Protected default branches | script | medium | The connected repositories' default branches require reviews | CC8.1 | A.8.4, A.8.32 | — |
| DRIFT-01 | GitOps state matches the cluster | agent | medium | Out-of-sync or drifted applications, with the agent's interpretation. A model's verdict, not a deterministic rule, so it scores no requirement; it still runs while compliance is on | — | — | — |
EST-01 to EST-06 read AWS and Azure through the workspace's own connectors (GCP: probe pending — the control reports unknown with a connect hint). A control whose data category is not connected reports unknown, never a fail. The "Mirrors" column names the AWS Foundational Security Best Practices / CIS AWS Foundations control the basis prose cites; it is also the join between a scanner finding's control (S3.1 fails) and the platform control it evidences (EST-02).
How a scan runs
| Fact | Value |
|---|---|
| Triggers | manual (POST …/compliance/scan, incidents.act, 202; 409 while no check is in scope — SOC 2 / ISO 27001 off — with No compliance check runs in this workspace), schedule (the compliance cron), change (a captured change re-ran one control's probe — see Change-triggered checks) |
| Order | Controls in scope, cheapest kind first; an agent check runs only while the scan's agent budget (5) holds, unless an operator requested that control explicitly |
| Result statuses | pass, fail, unknown (not evaluable: nothing connected, a withheld read), not_applicable (operator override), accepted (every offender covered by a live exception) |
| Evidence | One artifact per control (the evidence spec in the catalogue) plus one scan report artifact; the scan's totals and cost_usd (agent residue only) |
| A fail | Opens or updates the control:<id> compliance incident with the control's severity; a later pass resolves it; an exception accepted after the scan shows on the posture immediately (effective_status) and moves the incident at the next probe |
| Concurrency | One running scan per workspace; a scan that stops heart-beating is swept as failed after 6 hours |
| Audit | compliance.scan_started, compliance.scan_finished (totals, cost_usd) |
A control:<id> incident (the platform's own probe) and a service=control:<id> incident (a scanner finding) are analysed on the incident pillar when it is enabled — only that pillar carries the wrap-up contract, the planning block and the write capabilities; the compliance pillar's own run ends as a generic wrap-up with no plan.
Schedule
| Fact | Value |
|---|---|
| Where | The compliance pillar's schedule_cron on the Agents page (5-field cron, UTC) |
| What fires | A ComplianceScan with trigger: schedule — the same scan as the Scan now button |
| Marker | One audit row pillar.scheduled per fire, target = the pillar agent; the next due time is computed from the last marker (or the row's update time), so a tick that was missed is not caught up in a burst |
| Skipped when | A scan is still running, or the previous scheduled run is still active. With no check in scope (SOC 2 / ISO 27001 off) the fire files no scan and no empty report: its marker carries skipped: no check in scope |
| Pulled findings | A separate cadence: aiops_scanner_sync_interval_s (see Pulling findings) |
Finding families
A scanner finding is classified offline and deterministically — the model is never asked what a control means, so the same finding always gets the same stance, which is what makes "may this auto-remediate?" auditable. Match order: an exact control id (or id prefix) a family lists; then an id contained by a listed sibling (a guessed family never hands out the auto stance); then the product token; then title / description patterns; then the scanner product; else other.
| Family | Stance | Tier | Method | Resource types | Listed controls (examples) | What escalates it |
|---|---|---|---|---|---|---|
logging-disabled |
auto | L1 | config-change | logging, s3, rds, container, network | CloudTrail.1/2/4/5, Config.1, CloudWatch.1, S3.9, RDS.9, EKS.8, ECS.12, ELB.5, APIGateway.1, WAFv2.1 | internet-facing or sensitive data → L2 |
missing-tags |
auto | L1 | config-change | ec2, s3, rds, container, other | Config.2, EKS.6/7, GuardDuty.2/3/4 | → L2 |
encryption-at-rest |
human | L2 | config-change | s3, ebs, rds, container, other | S3.4/5, EC2.7, RDS.3/4, EFS.1, DynamoDB.3, SQS.1, SNS.1, EKS.1 | → L3 |
public-exposure |
human | L3 | block | s3, network, rds, lambda, container, ebs, other | S3.1/2/3/8, EC2.2/13/14/15/18/19, RDS.2, Elasticsearch.1, Redshift.1, EMR.1, Lambda.1, EKS.2, ECS.2 | already L3 |
iam-hygiene |
human | L2 | policy | iam, container | IAM.1–9, IAM.21/22, ECS.8 | → L3 |
instance-metadata |
human | L2 | config-change | ec2 | EC2.8 | → L3 |
detective-service-disabled |
human | L2 | config-change | other | GuardDuty.1, GuardDuty.5–13, Inspector.1–4, Macie.1 | → L3 |
vulnerable-package |
human | L2 | patch | ec2, container, lambda | Inspector findings, ECR.1 | → L3; CVSS ≥ 9 on an internet-facing resource → severity critical |
unsupported-version |
human | L3 | patch | rds, container, lambda, other | RDS.13, EKS.3, ElastiCache.2, Lambda.2, ECS.10 | already L3 |
backup-missing |
auto | L2 | config-change | rds, ebs, other | RDS.11, DynamoDB.2, ElastiCache.1, Redshift.3, Backup.1, EC2.28; not capacity scaling (DynamoDB.1), deletion protection (Cognito.6, RDS.8) nor copying tags to backups (FSx.1/2) | → L3, and the stance becomes human |
threat-detected |
ticket | L3 | block | ec2, iam, container, other | GuardDuty | already L3; routed to the SecOps pillar |
certificate-hygiene |
human | L2 | policy | network | ACM.1/2 | → L3 |
policy-rule |
human | L2 | policy | other, container | ECR.2/3; any native AWS Config rule no other family recognises | → L3 |
other (default) |
human | L2 | accept-only | other | — | → L3 |
A vulnerable-package finding that lists its packages reaches the analysis with each package's installed and fixed version (the vulnerable_packages annotation, refreshed by every re-send). The analysis only drafts the fix: its steps are carried into the change request, where the remediation is reviewed, approved and released — nothing in the run changes the estate. The scanner clearing the finding is the plan's verification, never an extra step, and a package with no fixed version gets a compensating control instead of an upgrade.
What the analysis is told, per resource type — one section per type the findings name, each once, in this order:
| Resource | Steps | Rollback |
|---|---|---|
| Container image (ECR, any registry) | With a Git repository connection holding the Dockerfile or manifest: a repository edit that upgrades each package (its pull request is the change). Without one: upgrade in the image build, then rebuild and push under a new tag — never overwrite a tag or digest. Consumers (ECS, Kubernetes, Lambda) are listed for information only; no step redeploys or restarts them. The old digest is retired once nothing runs it, and its finding clears then. | The previous tag and digest stay in the registry. |
| EC2 instance (AWS) | AMI first; check what depends on each package; isolate with a quarantine security group that keeps only outbound 443 (and 80 where the package mirrors are plain HTTP); AWS-RunPatchBaseline with Operation=Install through Systems Manager (an operator command for now); put the security groups back; validate the package versions and the service endpoint. No hotfix, no blue/green. |
Restore the root volume from the AMI, then the security groups. |
| Lambda function (AWS) | Publish a version of the code in use; upgrade the package and publish the new version; validate a test invocation, Errors and Throttles; point the alias at the new version. |
Point the alias back — this covers only callers that use the alias. Systems Manager automation for Lambda is not assumed (to confirm). |
Any other resource type upgrades each package through its own package manager or build. When the image's repository, owner or consumers are not evidenced, the plan names the read permission or connection that would supply them instead of guessing.
Every family carries a prevention hint (the guardrail that keeps it closed) and an action sentence. method is the kind of fix that closes the family: patch, config-change, policy, block, or accept-only where the platform holds no fix path at all. Resource types are the coarse buckets coverage is reported on: ec2, container, lambda, s3, ebs, rds, network, iam, logging, other.
Typed fixes: a few exact controls map to one compiled posture operation — EC2.2/13/14/18/19 → aws.security_group.revoke_ingress; S3.1 → aws.s3control.put_public_access_block; IAM.7 and IAM.10–17 → aws.iam.update_account_password_policy; EC2.8 → aws.ec2.require_imdsv2; the Defender assessment "Storage account public access should be disallowed" → azure.storage_account.disallow_blob_public_access. A finding with a typed fix carries the remediation_operation label; the fix is still human-released (stance and tier stay the family's).
Remediation stance and escalation
remediation_stance |
Meaning |
|---|---|
auto |
Eligible for a pre-approved fix a person releases — low blast radius, reversible, no data path touched. Not an unattended fix: a finding is fixed only after a person approves it, never by a route or a certified step runbook on its own (route.match_skipped, reason finding_lane) |
human |
Propose a fix; a person releases it (the default) |
ticket |
No safe automated fix — raise it and let an owner plan |
Escalation is monotonic (rules only raise, never lower; a marker records that they ran once):
| Signal | Source | Effect |
|---|---|---|
exposure: internet |
Resource details and tags (public-access keys), or settings['risk_context'].internet_patterns (ARN globs) |
Tier up one step (L1→L2→L3) |
data_sensitivity: sensitive |
settings['risk_context'].sensitive_patterns |
Tier up one step |
| CVSS ≥ 9 and internet-facing | ASFF Vulnerabilities[] |
Severity raised to critical |
Tier lands L3 with stance auto |
— | Stance demoted to human (autonomy never widens on escalation) |
env |
Resource tags, else settings['risk_context'].env_map |
Carried on the finding; a production label raises the change tier at release |
settings['risk_context'] also has internal_patterns (fills exposure: internal only when nothing derived internet). A declaration can fill an unknown or raise exposure to internet; it can never talk a derived internet-facing resource back down. settings['workspace_scope'].tags (up to 10 tags, case-insensitive, all-of) limits a workspace to its own tag slice of a shared account; a scoped workspace treats an untagged resource as out of scope.
The deterministic risk block a person reads before deciding (risk): factors[] (internet-facing / internal-only, sensitive data, env, CVSS with fix availability, CVE count, the escalations), a one-line summary, exposure, data_sensitivity, env, cvss, epss, fix_available (YES / NO / PARTIAL), prevention, method, resource_types.
From a scanner finding to an incident
Security Hub (ASFF) and Defender for Cloud findings arrive pushed (/webhook/securityhub/<slug>/<token> and /webhook/defender/<slug>/<token>, usually behind SNS or an action group) or pulled by the scanner sync. Both go through the same normaliser and land as alerts:
| Alert label | Value |
|---|---|
alertname |
The control id (else the title) |
service |
control:<id> — the failure domain is the control, so twelve buckets failing S3.9 are one piece of work; cloud-posture when the finding names no control |
severity |
ASFF CRITICAL → critical (P1); HIGH → error (P2); MEDIUM / LOW → warning; INFORMATIONAL → info; Defender's properties.metadata.severity goes through the same map. In 0.1.143 and earlier HIGH was critical, so HIGH and CRITICAL were both P1; a register row takes the new severity the next time the scanner sends the finding; an incident already open keeps its priority (severity only ever rises on an open incident). A CVSS 9 or higher finding on an internet-facing resource is still raised to critical |
source |
securityhub or defender |
finding_family, remediation_stance, remediation_tier, rule_kind (detective) |
From the classification above |
account, region, resource, resource_type, product, fix_available, remediation_operation |
When known |
Pillar: threat-detected → SecOps; every other family → Compliance (an alert whose severity is literally security also goes to SecOps). Correlation key service=control:<id>, so a new resource failing the same control joins the open incident; the scanner's own RESOLVED / PASSED state is the clear. The CloudFinding register row (source, finding_key, control, family, stance, tier, CVE columns, context, risk, status open / cleared, seen_count, suppressed_count, incident_id, the raw ASFF kept scrubbed) is upserted on the same ingest.
Priority is derived from severity, never by a model: critical → P1, error → P2, warning → P3, info → P4; one level up (floor P1) when the blast radius reaches two services, one more for a mission-critical resource. Severity is the maximum of the correlated alerts and upgrades on join.
Pushed versus pulled:
| Arrival | What runs |
|---|---|
| Pushed (webhook), compliance pillar | The priority rule of pulled: P1/P2 is analysed automatically, P3/P4 waits for a person — also when a prepared fix exists. Nothing else of the pulled gate applies: aiops_compliance_auto_triage does not hold it, with the incident pillar disabled it runs on the compliance pillar, and a run the kill switch or budget refuses fails and escalates. No count folds findings together any more (0.1.147): a held finding's hold stays on its own alert and incident, and a group made only of held findings, split from a storm incident an earlier release left open, opens held |
Pushed (webhook), SecOps (threat-detected) |
The normal analysis, under the usual budget and pillar rules |
| Pulled (scanner sync) | The row opens either way; the paid run is held at P3/P4, a prepared fix or not; at P1/P2 it runs when the finding carries a typed fix (remediation_operation) or automatic P1/P2 analysis is on — and in both cases only when aiops_compliance_auto_triage is on (platform default on), the kill switch and budgets allow a run, and the incident pillar is enabled |
A platform check fails (control:<id>) |
A check of your estate or connections (not deterministic) at P1/P2 is analysed under the same switches; at P3/P4 (a medium or low check) it waits for a person, with the reason "its priority is below P2 (automatic analysis covers P1/P2)" |
Hold reasons shown on the incident: "no prepared fix for this finding type, and its priority is below P2 (automatic analysis covers P1/P2)"; "… and automatic P1/P2 analysis is off for this workspace"; "a prepared fix is available, but its priority is below P2 (automatic analysis covers P1/P2)" — the incident then reads A prepared fix is available — waiting for a person to start it, and Analyse starts the analysis that drafts it (no paid run for a P3/P4 until a person asks, 22-Oct); "automatic analysis is off, paused or over budget". A held finding is analysed with the Analyse button. The switches:
| Switch | Default | Scope |
|---|---|---|
aiops_auto_investigate_p1p2 |
on (platform default; a superadmin may change it, a workspace's own true / false wins) |
A P1/P2 finding — pulled, pushed to the compliance pillar, or a failing platform check — with no prepared fix gets its run when it opens |
aiops_auto_investigate_catchup |
off | The scheduler sweeps held service=control:<id> P1/P2 incidents that never got a run: oldest first, 3 per workspace per tick, at least 60 s old, never a second run |
aiops_compliance_auto_triage |
on | Whether a compliance finding may start a run at all |
A re-send the scanner observed before the fix recorded on the row (remediated_at) is stale and does not reopen; for Defender only within 48 hours of the fix.
Pulling findings from Security Hub and Defender
Security Hub and Defender findings can also be pulled instead of pushed: a workspace owner sets how often under Security posture → Posture → Scanner sync → Pull from scanners automatically (every 15 minutes by default; off, or every 5 minutes up to daily — through the API, scanner_sync_interval_s in seconds, 300 to 86400 or 0 = off, on PATCH /api/aiops/{tenant_id}/guardrails, stored as the workspace setting aiops_scanner_sync_interval_s, null = back to the platform default), the platform then reads them on that schedule through your read AWS / Azure connection, and Sync now on the same card pulls the last 24 hours on demand. A pull only reads the scanners: a finding that is already open stays on its incident, so re-reading it opens nothing and costs no model time, and a Security Hub or Defender re-send observed before a recorded fix is skipped (for Defender, an assessment whose verdict last changed before the fix, and only within 48 hours of the fix). Security Hub must be enabled in each region on the connection — "Security Hub is not enabled on <connection>" means it is not, not that the credential is wrong. On Azure the subscription needs Defender for Cloud, that is the Microsoft.Security resource provider registered: without it Azure lists no assessments at all, and the sync reads "Defender for Cloud is not enabled on <connection>" rather than 0 findings. Pull and webhook share the same deduplication, so using both is safe. With several AWS accounts, each read connection asks Security Hub for its own account's clears; a connection into the workspace's only AWS account — for example the Security Hub administrator account — asks for every account's. Only connections that run Security findings sync count: several of them into one account count once, and an access-key connection counts as an account of its own until a passing Test confirms which account it is in. A pull asks about up to 500 open findings at a time; with more open, each pull asks the next 500 and the line says so, for example 500 of 1240 open findings re-checked with Security Hub, the rest at the next syncs — or press Sync now again for the rest while the scheduled pull is off, since nothing else asks. Defender returns every assessment in one listing, so all its open findings are checked on each pull. A pull paces its reads to at most about three a second (Security Hub's limit for reading findings) and asks a throttled read again after 1, 2 and 4 seconds; the connection's line then says throttled, retried. A Security Hub region still throttled after that reads partial (rate limited) while the other regions land; when every region, or the Defender list, stays throttled, the line reads rate limited, retried at the next sync, or rate limited — press Sync now again in a minute while the scheduled pull is off. Sync now needs pillars.manage, so for anyone without it those two lines say a workspace owner can press Sync now again instead. A Sync now across many accounts can outlast the request: the card then says Still running on the server and shows the report when the sync finishes.
| Fact | Value |
|---|---|
| Connections | Every enabled read-role aws (Security Hub) and azure (Defender for Cloud) connection; per-connection opt-out sync_findings: false |
| Interval | aiops_scanner_sync_interval_s: platform default 900 (every 15 minutes); settings['aiops_scanner_sync_interval_s'] changes it per workspace (0 = off): minimum 300 s (5 minutes) through Scanner sync or PATCH /guardrails, and a smaller value written straight into the setting still runs at most every 60 s; the manual sync ignores it. The schedule does not follow the workspace's SOC 2 / ISO 27001 switch: the cloud's own scanners are pulled either way |
| Window | Since the connection's last clean tick minus a 20-minute overlap (the register dedupes by finding id), or one interval plus the overlap back |
| Cap | 500 failing findings per connection per sync; beyond it the report says truncated and the cursor does not move |
| Regions (AWS) | The connection's scope.regions, else its region, else us-east-1; a region where the hub is off is reported unavailable and skipped; a denied or throttled region keeps the cursor |
| Security Hub read 1 | GetFindings with RecordState = ACTIVE, ComplianceStatus ∈ {FAILED, WARNING}, WorkflowStatus ∈ {NEW, NOTIFIED}, UpdatedAt in the window |
| Security Hub read 2 | The register's open rows re-read by Id (20 per request); the ones the scanner now calls cleared land as clears, however old |
pull_vulnerabilities (on unless false) |
Adds a read between them: ProductName = Inspector under the same window with no compliance filter (an Inspector CVE has no Compliance block, so read 1 never sees it); shares the cap, deduped by id. On by default; false, 0, no or off on the connection stops it — every HIGH/CRITICAL CVE lands P1/P2 and may start a paid run, so set daily_cost_limit_usd before a large account's first pull |
finding_tag_scope: key=value (AWS) |
Read 1 adds Security Hub's ResourceTags filter (key EQUALS value): only findings on resources carrying that tag land — for an account shared with workloads the workspace must not analyse. The Inspector read is filtered after it is read, because an ECR image or Lambda finding carries no resource tags: a finding with tags of its own lands when they carry key=value; one without lands when the harvested inventory holds its resource (an image's repository, a Lambda version's function) with that tag; anything else is left out and counted as vulnerabilities_out_of_scope in the region's coverage entry (at most 5,000 Inspector findings are read per region and pull, then the region reads as partial). Read 2 stays unfiltered, so a finding already in the register still gets its clear. Two register clean-ups ride every pull, each through the same clear as the scanner's own, so their incidents resolve: an open finding read 2 asked in its own region that Security Hub answers in no region (a wiped account, a finding Security Hub deleted) clears as no longer reported by the scanner (not fixed) once it was last seen over 48 hours ago — only when every region was read in full, and never for a finding from a region the connection does not read; for the connection into the workspace's only AWS account, which asks about every account's findings, a finding of another account clears this way only when the same pull returned a finding of that account, otherwise only once it was last seen over 90 days ago (how long Security Hub keeps a finding), since a webhook-fed account the connection cannot read answers nothing either — with audit compliance.finding_gone; and while every AWS connection that pulls has a tag scope, an open finding whose resource the harvested inventory holds without any of their tags, and that no pull has re-sent for 48 hours, clears as out of scope (not fixed), with audit compliance.scanner_out_of_scope. A finding on a resource the inventory does not know stays open; a tag none of the harvested resources carries clears nothing. One key=value pair (key up to 128, value up to 256 characters); anything else fails the connection before any read with invalid_tag_scope, never a wider pull. Each region's coverage entry shows tag_scope, and the Scanner sync card's line for the region reads only resources tagged key=value. The findings the tag leaves out never reach the platform, so no count of them exists: the card says so, and scoped_out (in the response and the ingest.batch audit row) counts only the workspace tag scope — the audit row carries it only when that scope ran. Whether Security Hub carries the resource tags a finding needs depends on the source: if a scoped pull lands no finding you expect, check the finding's Resources[].Tags in Security Hub. The webhook (/webhook/securityhub/<slug>) is not filtered: scope it in the EventBridge rule that forwards the findings. The same tag (exact value) also scopes the platform's backup read — its findings and the Backup coverage figure (Score per standard, patching and backup) — the synthetic checks suggested from the connection's harvest and the resources Built-in detection watches |
| Defender | The assessments list per subscription ($expand=metadata): Unhealthy rows whose status changed in the window or that the register has never seen, plus Healthy / NotApplicable rows the register holds open (the clears); ids lower-cased; failures and clears each capped at 500; a subscription without the Microsoft.Security provider is reported as not enabled |
| Pace | 0.35 s between reads, 3 retries with back-off on throttling |
| Manual sync | POST …/findings/sync (pillars.manage), default the last 1,440 minutes; its result lands under its own marker |
| Markers | compliance.scanner_synced (the tick — drives the cursor), compliance.scanner_synced_manual (never moves it); GET …/findings/sync shows the reports |
Risk decisions
The agent prepares (finding + deterministic risk impact); a human decides; the register records it. A decision is a request first.
| Field | Values |
|---|---|
outcome |
accept — Suppress in the portal (register against the person, suppress re-sends until expiry, resolve the finding's alerts), avoid — Fix in the portal (the risk is not taken: a change request is opened and the change agent drafts the fix), mitigate — no longer offered in the portal; the API still takes it and older decisions keep it (the finding stands, a compensating control offsets it: a change request framed "do NOT fix the finding itself", suppression with expiry) |
scope |
finding or control (covers every resource failing that control) |
status |
requested → approved or rejected |
risk_statement |
Mandatory — the register records why, not just who |
expires_at |
Mandatory for accept and mitigate: default aiops_risk_accept_days (90) / aiops_risk_mitigate_days (180); must be in the future |
decided_by, decided_at |
The requester |
approved_by, approved_at |
The approver (also stamped on a rejection) |
revoked_at |
Set when a newer decision for the same scope supersedes it, or on an early revoke |
change_id, mitigation_landed_at |
The change request an avoid / mitigate opened and when its fix landed (risk.mitigation_landed) |
expired_at |
Stamped by the expiry sweep |
meta.source_receipt, meta.source_revoke_receipt |
The scanner-side suppression receipt |
Rules:
| Rule | Detail |
|---|---|
| Who requests | incidents.act or approvals.decide — POST …/findings/{id}/decision with {outcome, risk_statement, scope, expires_at, suppress_at_source} (201, Needs you risk_request) |
| Who approves, rejects, revokes | approvals.decide — POST …/risk-decisions/{id}/approve, …/reject (with a note), …/revoke |
| Self-approval | Refused while another member holds approvals.decide; a sole approver may, audited self_approved; aiops_risk_no_self_approval: true refuses it always |
| An expired request | Cannot be approved — send a new one |
| Supersede | A new approved decision for the same scope stamps revoked_at on the prior row (append-only, like the terms register) |
Effect of accept |
The finding's open alerts resolve (also an escalated or awaiting-approval incident — the acceptance is the human's decision); re-sends are swallowed and counted on suppressed_count, never silently; accept wins a tie with mitigate |
Effect of avoid / mitigate |
change.open_change(source: compliance, origin_type: finding) and the plan runner drafts the plan after commit; no suppression for avoid |
| Readers | See approved rows only |
| Expiry | By default no cron: an expired acceptance stops suppressing and the scanner's next re-send reopens the incident — that is the re-review. With aiops_risk_expiry_sweep: true the scheduler acts at expiry: stamps expired_at, reopens the finding's incident for a person, re-fires the quieted alerts, lifts a source-side suppression, records the posture drop (100 decisions per workspace per tick; audit risk.expired, actor system:risk-expiry) |
| Suppress at source | Opt-in settings['aiops_risk_suppress_at_source'] and suppress_at_source: true on an accept: after approval the platform writes Security Hub workflow SUPPRESSED (the decider's email in Note.UpdatedBy) or an Azure Policy exemption through the write-role connection of the same account; best effort, receipt in meta.source_receipt; revoke, supersede and expiry lift it; audit risk.suppressed_at_source / risk.unsuppressed_at_source |
| Reports | GET …/risk-register and POST …/risk-register/export (report.view; audit risk.register_exported) |
Control exceptions: for the estate controls EST-01 to EST-06 a person accepts the risk on some of a control's offenders (POST …/compliance/controls/{id}/exceptions, same requester rule): one anchor CloudFinding per (control, resource) with source: compliance, then an ordinary accept request — supersede, default expiry, audit and the register come with it. Only resources the platform showed in the latest evidence can be accepted; a control with no per-resource evidence is marked not applicable instead. An exception decided after the scan shows on the posture at once; the control's incident moves at the next probe.
Attestations and overrides
| Fact | Value |
|---|---|
| Endpoint | POST …/compliance/requirements/{ref}/attest (pillars.manage) |
status |
attested or not_applicable |
note |
Mandatory — say what evidences this (stored up to 2,000 characters) |
evidence_artifact_id |
Optional; must be an artifact of this workspace |
expires_in_days |
1–1,095; the attestation lapses after it |
| Supersede, never edit | A new attestation expires the previous live one for the same ref; the trail is append-only |
| Audit | compliance.attested with status, the note's first 200 characters, the artifact, expires_in_days |
| Control overrides | PATCH …/compliance/controls/{id} with disabled or not_applicable and a reason (pillars.manage; audit compliance.control_override) |
| Request a fix | POST …/compliance/controls/{id}/request-change (incidents.act): a change request through the change hub (audit compliance.change_requested); compliance holds no write path |
Mute list (legacy)
settings['aiops_compliance_muted'] — [{control_id, resource, note, by, at}], at most 200 entries — mutes a (control, resource) pair for change-triggered checks: a live match is skipped before the probe (no probe, no result, no incident) but never silently — one compliance.rule_muted_hit audit row per pair per tick. An entry stops muting aiops_risk_accept_days after its at (the acceptance clock); only POST …/incidents/muted-rules/unmute (approvals.decide) removes one (audit compliance.rule_unmuted). Nothing writes the list any more: an accepted exception replaced "Suppress this finding".
Change-triggered checks
Every harvested cloud change is judged, deterministic first:
| Step | What happens |
|---|---|
| 1. Rules | Pure predicates over the recorded event (provider, operation, request parameters or property diff) name a control — an opened security-group or NSG rule → EST-03, a public bucket or anonymous blob access → EST-02, audit trail or diagnostic setting removed → EST-05, encryption off → EST-04, an IAM / owner change → EST-06, a backup deleted → EST-01. A rule may be liberal: a false positive costs one keyless probe, never an incident |
| 2. Probe | The match is stamped on the change (plan.compliance, audit change.compliance_flagged); the control's own probe re-runs once and a fail lands exactly like a scan: ComplianceScan(trigger: change) + a change-triggered control result + the control:<id> incident with the triggering change ids on change_refs. The incident says what changed: the rule's sentence and one line of the request's key values (plan.compliance.values, already scrubbed), shown under Changes that triggered this check and on the change. Change scans never become "the latest scan" for the posture page |
| 3. Agent rung (off) | aiops_change_compliance_agent: true — one bounded turn per tick nominates violations among risky-looking events no rule matched (at most 20); a nomination walks the same probe-then-incident path and never opens an incident by itself |
| Re-break link | A captured write that puts back what a platform fix cleared within 14 days is linked both ways (change.reverts, change.undone_by_external; reverts / undone_by on the rows) |
| Mapped recheck | aiops_change_recheck_mapped: true — a change a rule mapped to a control also re-checks that control's open incident |
| Scanner link | Scope-free, no switch: the cloud's scanner is the re-check. An unmanaged captured write on a scanner control finding's resource (or, Azure, a child of it) in the 24 h before the finding started failing (CloudFinding.failing_since: when the scanner saw the failure — Defender's status change, Security Hub's observation, or for an account's first pull its first-observed time) and after the scanner last passed it lands on the finding incident's change_refs and on the change as plan.recheck.results.<control> with by (the scanner), audited incident.change_linked / change.finding_linked — from whichever side arrives second: the finding at ingest, or the write at the next harvest (findings_linked in the change.evaluated report). Webhook-reported, refused and platform writes never link, nor do vulnerability or threat findings, findings on a whole subscription, resource group or account, or a tag-only write outside a tagging control |
| Marker | change.evaluated once per workspace per harvest tick |
Posture scores
Two different numbers, two different questions.
| Score | Endpoint | Covers | How |
|---|---|---|---|
| Compliance posture | GET …/compliance/posture (report.view) |
The full requirement catalogue in scope, mapped from the latest completed scan (not a change scan), merged with live attestations and overrides | Per requirement, from its mapped controls' live statuses: fail if any fails; pass only if all pass; else unknown; else accepted_exception (never a pass); else not_applicable. A requirement with no scanned control takes its attestation (attested / not_applicable) or is manual_pending. The totals and the coverage statement ("Automated scans cover only the machine-checkable subset …") |
| Findings score | GET …/findings/score (incidents.view), the posture page's trend and cards |
Scanner findings (Security Hub, Defender) — optionally one account or source, optionally the platform checks per account |
score = cleared or accepted weight over total weight (100 when there are no findings); cleared_score = scanner clearance only (null without findings); weight = severity × fix tier: critical 8, high and error (a scanner HIGH) 5, medium 2, low / info / warning 1, times L1 ×1, L2 ×2, L3 ×3; counts total, open, cleared, accepted, actively_suppressed, awaiting_scanner, new, resolved |
The findings trend: the last 30 days by default (days, or an inclusive start_date / end_date range up to 366 days, or 12 ISO weeks / calendar months with interval). Each day comes from its saved daily snapshot when one exists (snapshot), else it is reconstructed from the current records (live for a day that has not closed); the first snapshot run reconstructs up to 366 days back. Neither score is a remediation rate: scan completeness is unknown, days without recorded findings do not prove a clean scan, a suppression is not remediation, and scanner clearance does not establish who performed a fix. POST …/findings/posture-export (report.view, audit risk.posture_exported) renders it.
GET …/findings/coverage (report.view) lists the observed findings by family, stance and resource type, with which families have a typed fix — the Coverage tab.
Score per standard, patching and backup
Security posture → Audit & coverage → Scores shows three reports read from your AWS accounts through the read connections the platform checks use (a connection with platform_checks: false is left out). The scheduler reads them once a day per workspace (AIOPS_COMPLIANCE_REPORT_INTERVAL_S, 86400; 0 = only the button); Refresh reports reads now (incidents.act). One row is saved per workspace per UTC day — a second read the same day replaces it — and the trend draws one point per saved day. A denied or failed AWS call is listed under the card; what was read still counts.
| Report | Read from | Number |
|---|---|---|
| Score per standard | Security Hub GetEnabledStandards and its control findings (GetFindings, product Security Hub, active records, at most 20,000 per region); each AWS Config conformance pack (DescribeConformancePacks, DescribeConformancePackCompliance); SOC 2 / ISO 27001 as this platform maps them (the posture score above — absent while Compliance frameworks (phase 2) is off) |
Per standard: passed ÷ (passed + failed + unknown) checks. A Security Hub control is failed when one of its findings failed, unknown on WARNING / NOT_AVAILABLE, passed when every finding passed; archived and suppressed findings are ignored and a control with no finding (no data) is left out — Security Hub's own rule, so its standards match the console. A conformance-pack rule is passed when COMPLIANT, failed when NON_COMPLIANT, left out on INSUFFICIENT_DATA (the Config console's pack score counts rule-resource pairs, so it can differ). A SOC 2 / ISO 27001 requirement is passed when it passes or is attested, failed on a fail or an accepted exception, unknown when unknown or waiting for an attestation |
| Total | All of the above | The same formula over every check counted once: a Security Hub control enabled in several standards (CIS and AWS Foundational Security Best Practices share controls through consolidated control findings) counts once, failed anywhere = failed; a Config rule deployed in several packs (the name without its -conformance-pack-… suffix) counts once; each SOC 2 / ISO 27001 requirement once. A Security Hub control and a conformance-pack rule built on the same managed rule still count as two checks. The card shows the plain mean of the standards beside it (every standard weighs the same, whatever its number of checks): the two differ when the standards differ in size — three standards at 60%, 90% and 80% average 76.7%, while the pooled total leans toward the standard with the most checks. |
| Patch compliance | Systems Manager DescribeInstanceInformation, DescribeInstancePatchStates; EC2 DescribeInstances |
Compliant nodes ÷ nodes with patch data. A node is non-compliant when Patch Manager reports a non-compliant patch (missing, failed, pending reboot or rejected, as its baseline decides). With aiops_patch_within_days set, a node whose last patch operation (scan or install) ended more than that many days ago is non-compliant too. Coverage = EC2 instances that report patch data ÷ all EC2 instances; the instances Systems Manager does not manage are listed apart. Target 100% |
| Backup | AWS Backup ListBackupJobs (created in the last 7 days); the EST-01 read (RDS backup retention, EC2 in a backup plan through ListProtectedResources) |
Jobs succeeded = COMPLETED ÷ finished (COMPLETED, FAILED, EXPIRED, ABORTED, PARTIAL), for 24 hours and 7 days; jobs still running are not counted. Coverage = resources EST-01 found protected ÷ resources it checked — only resources carrying the connection's finding_tag_scope tag and the workspace scope tags are checked, and the figure names those tags when every connection kept to the same ones (3 of 3 resources tagged aiops-managed=true). A scope none of the resources read carries is reported as a note and clears no backup finding. Target 100% |
Read permissions: the read role created by the guided AWS setup carries config:DescribeConformancePacks, config:DescribeConformancePackCompliance, ssm:DescribeInstanceInformation, ssm:DescribeInstancePatchStates and backup:ListBackupJobs since 0.1.144 (re-run the setup to add them); a role built on ReadOnlyAccess already has them. No call writes anything. Azure (Defender regulatory compliance, Update Manager, Azure Backup) is not read yet.
Audit actions
| Action | Emitted when |
|---|---|
compliance.scan_started, compliance.scan_finished |
A scan starts / completes (totals, cost_usd) |
pillar.scheduled |
The compliance cron fired (target = the pillar agent) |
compliance.scanner_synced, compliance.scanner_synced_manual |
A scheduled / manual findings pull (per-connection report in the payload) |
compliance.attested |
A requirement attested or marked not applicable |
compliance.control_override |
A control disabled or marked not applicable |
compliance.change_requested |
A fix requested for a control (or a discovery finding) |
compliance.interpretation_exported |
The interpretation sheet exported |
compliance.table_agreed |
The customer's acceptance of a version of the comparison table recorded |
compliance.custom_requirement_saved, compliance.custom_requirement_removed |
One of the workspace's own requirements added, replaced or removed |
compliance.rule_muted_hit, compliance.rule_unmuted |
A legacy mute skipped a probe / an entry was removed |
risk.requested, risk.decided, risk.rejected, risk.revoked, risk.expired |
The risk decision lifecycle (risk.decided carries requested_by, self_approved when applicable) |
risk.mitigation_landed |
The mitigate change's fix landed |
risk.suppressed_at_source, risk.unsuppressed_at_source |
The scanner-side suppression written / lifted (receipt in the payload) |
risk.register_exported, risk.posture_exported |
Exports |
change.compliance_flagged, change.evaluated |
Change-triggered checks |
incident.auto_remediate_blocked, route.match_skipped (reason finding_lane) |
A route would have run on a finding and did not |
Settings
| Setting | Default | Effect |
|---|---|---|
compliance.enabled, compliance.platform_controls |
off everywhere while AIOPS_COMPLIANCE_FRAMEWORKS is off (the default); once it is on, off for a new workspace (on when absent); off |
Score SOC 2 / ISO 27001 at all; count the checks of the platform's own settings |
compliance.frameworks, compliance.soc2_categories, compliance.overrides |
both frameworks; security, availability, confidentiality; {} |
Scope and overrides |
compliance.custom_requirements |
[] |
The workspace's own requirement rows |
aiops_compliance_auto_triage |
on (platform) | A compliance finding may start a run |
aiops_auto_investigate_p1p2 |
on (platform; superadmin-adjustable) | P1/P2 compliance findings with no prepared fix are analysed when they open |
aiops_auto_investigate_catchup |
off | The held P1/P2 backlog is swept, 3 per tick |
aiops_compliance_report_interval_s |
86400 (platform AIOPS_COMPLIANCE_REPORT_INTERVAL_S; 0 = only Refresh reports) |
How often the Scores tab's reports (score per standard, patch compliance, backup) are read again for a workspace with an AWS read connection or framework scoring on |
aiops_patch_within_days |
0 (no rule; platform AIOPS_PATCH_WITHIN_DAYS) |
Patch report: a node whose last patch operation is older than this many days counts as non-compliant. Unset, the report shows 40 days as an example (the nodes it would flag), never applied: the rule is optional and has no default |
aiops_patch_findings |
on (platform AIOPS_PATCH_FINDINGS) |
Each non-compliant managed node of the daily Patch Manager read opens a Patching finding (control PATCH.COMPLIANCE), unless Security Hub's SSM.2 already holds it open; off clears every open one |
aiops_scanner_sync_interval_s |
900 (platform) | Scheduled Security Hub / Defender pull; ≥ 60 s; 0 = off |
sync_findings: false, pull_vulnerabilities: false, finding_tag_scope (per connection) |
— | Opt a connection out of the pull / out of the Inspector CVEs / pull only findings on resources with one tag |
risk_context |
{} |
internet_patterns, internal_patterns, sensitive_patterns, env_map (ARN globs) |
workspace_scope.tags |
{} |
Tag slice of a shared account (up to 10 tags) |
aiops_risk_accept_days, aiops_risk_mitigate_days |
90, 180 | Default expiry of an accept / mitigate |
aiops_risk_no_self_approval |
off | The requester may never approve their own request |
aiops_risk_expiry_sweep |
off | The scheduler acts at expiry instead of waiting for a re-send |
aiops_risk_suppress_at_source |
off | Accepted findings are suppressed at the scanner |
aiops_compliance_muted |
[] |
Legacy mute list (read only) |
aiops_change_compliance_agent |
off (platform) | The agent rung of change-triggered checks |
aiops_change_recheck_mapped |
on | A mapped change re-checks the control's open incident |
API summary
| Method and path | Permission | Purpose |
|---|---|---|
GET …/compliance/posture, GET …/compliance/catalog, GET …/compliance/scans, GET …/compliance/scans/{id}, GET …/compliance/controls/{id} |
report.view |
Posture, catalogue, scans, one control's evidence |
POST …/compliance/scan |
incidents.act |
Scan now (202; 409 while no check is in scope) |
GET …/compliance/reports (days, default 30) |
report.view |
The score per standard and total, patching and backup, with the trend |
POST …/compliance/reports/refresh |
incidents.act |
Read the reports from AWS now (inline up to 55 s, then refreshing: true) |
PATCH …/compliance/controls/{id} |
pillars.manage |
Override a control |
POST …/compliance/requirements/{ref}/attest |
pillars.manage |
Attest a requirement |
POST …/compliance/controls/{id}/request-change |
incidents.act |
Open a change request for a control |
POST …/compliance/controls/{id}/exceptions |
incidents.act or approvals.decide |
Request exceptions on a control's offenders |
GET …/compliance/discovery-findings, POST …/compliance/discovery-findings/{index}/request-change |
report.view + datasources.view / incidents.act + datasources.view |
Compliance notes an estate x-ray recorded, with the scan's related draft routes and knowledge — API and MCP only; the portal leaves them to the x-ray report, since no control or framework maps them. datasources.view is the read permission of the x-ray scan itself |
POST …/compliance/interpretation-export |
report.view |
The interpretation sheet |
GET …/compliance/agreed-table |
report.view |
The comparison table, as json, csv or md |
POST …/compliance/agreed-table/accept |
pillars.manage |
Record the customer's acceptance of a version |
PUT …/compliance/custom-requirements/{ref}, DELETE …/compliance/custom-requirements/{ref} |
pillars.manage |
Add, replace or remove one of your own requirements |
GET …/findings, GET …/findings/accounts, GET …/findings/score, GET …/findings/{id} |
incidents.view |
The register, accounts, the score, one finding |
GET …/findings/coverage, GET …/findings/sync, POST …/findings/posture-export |
report.view |
Coverage, pull reports, export |
POST …/findings/sync |
pillars.manage |
Pull findings now |
POST …/findings/{id}/decision |
incidents.act or approvals.decide |
Request a risk decision |
POST …/risk-decisions/{id}/approve, …/reject, …/revoke |
approvals.decide |
Decide / withdraw |
GET …/risk-register, POST …/risk-register/export |
report.view |
The register |
POST …/incidents/muted-rules/unmute |
approvals.decide |
Remove a legacy mute entry |