@knowttl/mg-axi 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +149 -38
  2. package/dist/api.js +134 -8
  3. package/dist/bin/mg-axi-hook.js +31 -0
  4. package/dist/catalogue.js +390 -2
  5. package/dist/cli.js +116 -8
  6. package/dist/docs.js +45 -5
  7. package/dist/doctor.js +2 -2
  8. package/dist/entra-access-reviews.js +11 -4
  9. package/dist/entra-apps.js +13 -8
  10. package/dist/entra-audit-logs.js +8 -4
  11. package/dist/entra-auth-methods.js +8 -4
  12. package/dist/entra-certificate-auth.js +12 -8
  13. package/dist/entra-conditional-access.js +9 -4
  14. package/dist/entra-contacts.js +19 -12
  15. package/dist/entra-contracts.js +7 -4
  16. package/dist/entra-custom-security-attributes.js +19 -12
  17. package/dist/entra-data-policy-operations.js +7 -4
  18. package/dist/entra-delegated-admin.js +55 -6
  19. package/dist/entra-deleted-items.js +6 -4
  20. package/dist/entra-directory-objects.js +6 -4
  21. package/dist/entra-directory.js +19 -12
  22. package/dist/entra-domains.js +13 -8
  23. package/dist/entra-entitlement-management.js +12 -4
  24. package/dist/entra-group-pim.js +431 -0
  25. package/dist/entra-groups.js +13 -8
  26. package/dist/entra-lifecycle-workflows.js +136 -9
  27. package/dist/entra-organization.js +13 -8
  28. package/dist/entra-risk-prevention.js +436 -0
  29. package/dist/entra-risk.js +213 -5
  30. package/dist/entra-roles.js +10 -4
  31. package/dist/entra-subscriptions.js +6 -4
  32. package/dist/entra-users.js +7 -4
  33. package/dist/graph-session.js +29 -5
  34. package/dist/hook.js +84 -0
  35. package/dist/list-totals.js +52 -0
  36. package/dist/setup.js +5 -0
  37. package/docs/coverage.md +79 -13
  38. package/inventory/operations.json +2 -2
  39. package/package.json +4 -3
  40. package/skills/mg-axi/SKILL.md +56 -1
package/dist/api.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import { AxiError } from "axi-sdk-js";
2
2
  import { KNOWN_CONTACTED_REVIEWER_FIELDS, KNOWN_DECISION_FIELDS, KNOWN_DEFINITION_FIELDS, KNOWN_INSTANCE_FIELDS, KNOWN_STAGE_FIELDS } from "./entra-access-reviews.js";
3
3
  import { KNOWN_ACCESS_PACKAGE_FIELDS, KNOWN_ASSIGNMENT_POLICY_FIELDS, KNOWN_CATALOG_FIELDS, KNOWN_RESOURCE_ROLE_SCOPE_FIELDS } from "./entra-entitlement-management.js";
4
- import { KNOWN_LIFECYCLE_SETTINGS_FIELDS, KNOWN_RUN_FIELDS, KNOWN_SUBJECT_PROCESSING_FIELDS, KNOWN_TASK_DEFINITION_FIELDS, KNOWN_TASK_FIELDS, KNOWN_TASK_REPORT_FIELDS, KNOWN_USER_PROCESSING_FIELDS, KNOWN_WORKFLOW_FIELDS, KNOWN_WORKFLOW_TEMPLATE_FIELDS } from "./entra-lifecycle-workflows.js";
4
+ import { KNOWN_LIFECYCLE_SETTINGS_FIELDS, KNOWN_RUN_FIELDS, KNOWN_SUBJECT_PROCESSING_FIELDS, KNOWN_TASK_DEFINITION_FIELDS, KNOWN_TASK_FIELDS, KNOWN_TASK_PROCESSING_FIELDS, KNOWN_TASK_REPORT_FIELDS, KNOWN_USER_PROCESSING_FIELDS, KNOWN_WORKFLOW_FIELDS, KNOWN_WORKFLOW_TEMPLATE_FIELDS } from "./entra-lifecycle-workflows.js";
5
5
  import { KNOWN_BRANDING_FIELDS, KNOWN_ORGANIZATION_FIELDS } from "./entra-organization.js";
6
6
  import { KNOWN_CONTRACT_FIELDS } from "./entra-contracts.js";
7
7
  import { KNOWN_DELEGATED_ADMIN_CUSTOMER_FIELDS, KNOWN_DELEGATED_ADMIN_RELATIONSHIP_FIELDS, KNOWN_DELEGATED_ADMIN_ACCESS_ASSIGNMENT_FIELDS, KNOWN_DELEGATED_ADMIN_OPERATION_FIELDS, KNOWN_DELEGATED_ADMIN_REQUEST_FIELDS } from "./entra-delegated-admin.js";
8
8
  import { KNOWN_TENANT_INFORMATION_FIELDS } from "./entra-tenant-information.js";
9
9
  import { KNOWN_MTO_FIELDS, KNOWN_MTO_JOIN_REQUEST_FIELDS, KNOWN_MTO_TENANT_FIELDS } from "./entra-multi-tenant-organization.js";
10
10
  import { KNOWN_DATA_POLICY_FIELDS } from "./entra-data-policy-operations.js";
11
+ import { KNOWN_FRAUD_FIELDS, KNOWN_WAF_FIELDS, KNOWN_VERIFICATION_FIELDS } from "./entra-risk-prevention.js";
11
12
  import { KNOWN_FEDERATION_FIELDS } from "./entra-federation-configurations.js";
12
13
  import { KNOWN_CA_FIELDS, KNOWN_PKI_FIELDS } from "./entra-certificate-auth.js";
13
14
  import { KNOWN_SUBSCRIPTION_FIELDS } from "./entra-subscriptions.js";
15
+ import { KNOWN_ASSIGNMENT_INSTANCE_FIELDS, KNOWN_ASSIGNMENT_SCHEDULE_FIELDS, KNOWN_ELIGIBILITY_INSTANCE_FIELDS, KNOWN_ELIGIBILITY_REQUEST_FIELDS, KNOWN_ELIGIBILITY_SCHEDULE_FIELDS } from "./entra-group-pim.js";
14
16
  import { KNOWN_SYNC_FIELDS } from "./entra-on-premises-synchronization.js";
15
17
  import { KNOWN_ACCEPTANCE_FIELDS, KNOWN_AGREEMENT_FIELDS } from "./entra-terms-of-use.js";
16
18
  import { KNOWN_DIRECTORY_OBJECT_FIELDS } from "./entra-directory-objects.js";
@@ -41,7 +43,7 @@ import { encodeGraphPathSegment, functionBindingFor, GraphSession, resolveSessio
41
43
  // preview, /me and sensitive-area policy still runs inside the shared session,
42
44
  // so raw reads cannot bypass named-command gating.
43
45
  export const REVIEWED_ON = "2026-10-04";
44
- const TRUNCATE_AT = 4000;
46
+ const TRUNCATE_AT = 500;
45
47
  // Collections support server filtering, ordering and paging hints. Advanced
46
48
  // queries ($search, $count=true) need per-endpoint ConsistencyLevel review and
47
49
  // stay unsupported here; $skiptoken is server paging state, paged via --all.
@@ -59,6 +61,9 @@ const SIGNIN_FIELDS = ["id", "createdDateTime", "userId", "userPrincipalName", "
59
61
  const AUDIT_FIELDS = ["id", "activityDateTime", "activityDisplayName", "category", "loggedByService", "operationType", "result", "resultReason", "correlationId", "initiatedBy", "targetResources"];
60
62
  const RISKY_USER_FIELDS = ["id", "userPrincipalName", "userDisplayName", "riskDetail", "riskLastUpdatedDateTime", "riskLevel", "riskState"];
61
63
  const RISK_DETECTION_FIELDS = ["id", "detectedDateTime", "activityDateTime", "userId", "userPrincipalName", "userDisplayName", "ipAddress", "location", "riskDetail", "riskLevel", "riskState", "riskEventType", "detectionTimingType", "lastUpdatedDateTime", "source"];
64
+ const RISKY_SP_FIELDS = ["id", "displayName", "appId", "servicePrincipalType", "riskDetail", "riskLastUpdatedDateTime", "riskLevel", "riskState", "isEnabled", "isProcessing"];
65
+ const RISKY_SP_HISTORY_FIELDS = ["id", "displayName", "appId", "servicePrincipalType", "servicePrincipalId", "riskDetail", "riskLastUpdatedDateTime", "riskLevel", "riskState", "isEnabled", "isProcessing", "initiatedBy", "activity"];
66
+ const SP_DETECTION_FIELDS = ["id", "detectedDateTime", "activityDateTime", "servicePrincipalId", "servicePrincipalDisplayName", "appId", "keyIds", "ipAddress", "location", "riskDetail", "riskLevel", "riskState", "riskEventType", "detectionTimingType", "lastUpdatedDateTime", "source", "additionalInfo"];
62
67
  const APP_FIELDS = ["id", "appId", "displayName", "createdDateTime", "signInAudience", "publisherDomain", "keyCredentials", "passwordCredentials", "requiredResourceAccess", "web", "spa", "publicClient"];
63
68
  const SP_FIELDS = ["id", "appId", "displayName", "servicePrincipalType", "accountEnabled", "appOwnerOrganizationId", "appRoleAssignmentRequired", "preferredSingleSignOnMode", "loginUrl"];
64
69
  const GRANT_FIELDS = ["id", "clientId", "consentType", "principalId", "resourceId", "scope", "startTime", "expiryTime"];
@@ -136,6 +141,28 @@ export const REVIEWED_ROUTES = [
136
141
  { id: "v1.0:GET:/identityProtection/riskDetections/{riskDetection-id}", kind: "single", query: SINGLE_QUERY, fields: RISK_DETECTION_FIELDS,
137
142
  access: "D/A IdentityRiskEvent.Read.All. Delegated callers pass it as --scopes.",
138
143
  sources: ["https://learn.microsoft.com/graph/api/riskdetection-get?view=graph-rest-1.0"] },
144
+ { id: "v1.0:GET:/identityProtection/riskyServicePrincipals", kind: "collection", query: COLLECTION_QUERY, fields: RISKY_SP_FIELDS,
145
+ access: "D/A IdentityRiskyServicePrincipal.Read.All. Delegated callers pass it as --scopes.",
146
+ note: "The riskyServicePrincipals API requires a Microsoft Entra Workload Identities Premium licence; limited results stay limited, never reinterpreted as empty.",
147
+ sources: ["https://learn.microsoft.com/graph/api/identityprotectionroot-list-riskyserviceprincipals?view=graph-rest-1.0"] },
148
+ { id: "v1.0:GET:/identityProtection/riskyServicePrincipals/{riskyServicePrincipal-id}", kind: "single", query: SINGLE_QUERY, fields: RISKY_SP_FIELDS,
149
+ access: "D/A IdentityRiskyServicePrincipal.Read.All. Delegated callers pass it as --scopes.",
150
+ sources: ["https://learn.microsoft.com/graph/api/riskyserviceprincipal-get?view=graph-rest-1.0"] },
151
+ { id: "v1.0:GET:/identityProtection/riskyServicePrincipals/{riskyServicePrincipal-id}/history", kind: "collection", query: COLLECTION_QUERY, fields: RISKY_SP_HISTORY_FIELDS,
152
+ access: "D/A IdentityRiskyServicePrincipal.Read.All. Delegated callers pass it as --scopes.",
153
+ note: "Per-principal risk history; history items inherit the principal properties and add the service-principal identifier with the actor/activity of the change.",
154
+ sources: ["https://learn.microsoft.com/graph/api/riskyserviceprincipal-list-history?view=graph-rest-1.0"] },
155
+ { id: "v1.0:GET:/identityProtection/riskyServicePrincipals/{riskyServicePrincipal-id}/history/{riskyServicePrincipalHistoryItem-id}", kind: "single", query: SINGLE_QUERY, fields: RISKY_SP_HISTORY_FIELDS,
156
+ access: "D/A IdentityRiskyServicePrincipal.Read.All. Delegated callers pass it as --scopes.",
157
+ note: "No operation-level documentation page; access follows the parent history-list contract and the history-item resource reference.",
158
+ sources: ["https://learn.microsoft.com/graph/api/riskyserviceprincipal-list-history?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/riskyserviceprincipalhistoryitem?view=graph-rest-1.0"] },
159
+ { id: "v1.0:GET:/identityProtection/servicePrincipalRiskDetections", kind: "collection", query: COLLECTION_QUERY, fields: SP_DETECTION_FIELDS,
160
+ access: "D/A IdentityRiskEvent.Read.All. Delegated callers pass it as --scopes.",
161
+ note: "The servicePrincipalRiskDetection API requires a Microsoft Entra Workload Identities Premium licence; riskDetail and riskLevel report hidden without it.",
162
+ sources: ["https://learn.microsoft.com/graph/api/identityprotectionroot-list-serviceprincipalriskdetections?view=graph-rest-1.0"] },
163
+ { id: "v1.0:GET:/identityProtection/servicePrincipalRiskDetections/{servicePrincipalRiskDetection-id}", kind: "single", query: SINGLE_QUERY, fields: SP_DETECTION_FIELDS,
164
+ access: "D/A IdentityRiskEvent.Read.All. Delegated callers pass it as --scopes.",
165
+ sources: ["https://learn.microsoft.com/graph/api/serviceprincipalriskdetection-get?view=graph-rest-1.0"] },
139
166
  { id: "v1.0:GET:/applications", kind: "collection", query: COLLECTION_QUERY, fields: APP_FIELDS,
140
167
  access: "D/A Application.Read.All. Delegated callers pass it as --scopes.",
141
168
  note: "appId (client ID) is distinct from the object id. Credential fields carry expiry metadata only; GET never returns secret values and success redaction still applies.",
@@ -175,6 +202,46 @@ export const REVIEWED_ROUTES = [
175
202
  access: "D/A RoleAssignmentSchedule.Read.Directory. Delegated callers pass it as --scopes.",
176
203
  note: "Active (including activated eligible) assignments; PIM needs P2 or ID Governance.",
177
204
  sources: ["https://learn.microsoft.com/graph/api/rbacapplication-list-roleassignmentscheduleinstances?view=graph-rest-1.0"] },
205
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/assignmentSchedules", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_ASSIGNMENT_SCHEDULE_FIELDS,
206
+ access: "D/A PrivilegedAssignmentSchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
207
+ note: "Assignment schedules govern group membership or ownership over time; PIM for Groups needs P2 or ID Governance; lists require $filter on groupId or principalId. AssignmentScheduleRequests stay out: their least privilege is the write scope PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup.",
208
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroup-list-assignmentschedules?view=graph-rest-1.0"] },
209
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/assignmentSchedules/{privilegedAccessGroupAssignmentSchedule-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_ASSIGNMENT_SCHEDULE_FIELDS,
210
+ access: "D/A PrivilegedAssignmentSchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
211
+ note: "PIM for Groups needs P2 or ID Governance.",
212
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroupassignmentschedule-get?view=graph-rest-1.0"] },
213
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/assignmentScheduleInstances", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_ASSIGNMENT_INSTANCE_FIELDS,
214
+ access: "D/A PrivilegedAssignmentSchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
215
+ note: "Provisioned assignment windows; PIM for Groups needs P2 or ID Governance; lists require $filter on groupId or principalId.",
216
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroup-list-assignmentscheduleinstances?view=graph-rest-1.0"] },
217
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/assignmentScheduleInstances/{privilegedAccessGroupAssignmentScheduleInstance-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_ASSIGNMENT_INSTANCE_FIELDS,
218
+ access: "D/A PrivilegedAssignmentSchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
219
+ note: "PIM for Groups needs P2 or ID Governance.",
220
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroupassignmentscheduleinstance-get?view=graph-rest-1.0"] },
221
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilitySchedules", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_ELIGIBILITY_SCHEDULE_FIELDS,
222
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
223
+ note: "Eligibility schedules govern who may activate; PIM for Groups needs P2 or ID Governance; lists require $filter on groupId or principalId.",
224
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroup-list-eligibilityschedules?view=graph-rest-1.0"] },
225
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilitySchedules/{privilegedAccessGroupEligibilitySchedule-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_ELIGIBILITY_SCHEDULE_FIELDS,
226
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
227
+ note: "PIM for Groups needs P2 or ID Governance.",
228
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroupeligibilityschedule-get?view=graph-rest-1.0"] },
229
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_ELIGIBILITY_INSTANCE_FIELDS,
230
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
231
+ note: "Provisioned eligibility windows; PIM for Groups needs P2 or ID Governance; lists require $filter on groupId or principalId.",
232
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroup-list-eligibilityscheduleinstances?view=graph-rest-1.0"] },
233
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances/{privilegedAccessGroupEligibilityScheduleInstance-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_ELIGIBILITY_INSTANCE_FIELDS,
234
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
235
+ note: "PIM for Groups needs P2 or ID Governance.",
236
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroupeligibilityscheduleinstance-get?view=graph-rest-1.0"] },
237
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_ELIGIBILITY_REQUEST_FIELDS,
238
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
239
+ note: "Eligibility asks plus outcomes; justification text rides only behind an explicit $select; PIM for Groups needs P2 or ID Governance; lists require $filter on groupId or principalId.",
240
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroup-list-eligibilityschedulerequests?view=graph-rest-1.0"] },
241
+ { id: "v1.0:GET:/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests/{privilegedAccessGroupEligibilityScheduleRequest-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_ELIGIBILITY_REQUEST_FIELDS,
242
+ access: "D/A PrivilegedEligibilitySchedule.Read.AzureADGroup. Delegated callers pass it as --scopes.",
243
+ note: "PIM for Groups needs P2 or ID Governance.",
244
+ sources: ["https://learn.microsoft.com/graph/api/privilegedaccessgroupeligibilityschedulerequest-get?view=graph-rest-1.0"] },
178
245
  { id: "v1.0:GET:/devices", kind: "collection", query: COLLECTION_QUERY, fields: DEVICE_FIELDS,
179
246
  access: "D/A Device.Read.All. Delegated callers pass it as --scopes.",
180
247
  note: "Directory-device scope only; Intune device management is a separately authorized surface.",
@@ -551,6 +618,41 @@ export const REVIEWED_ROUTES = [
551
618
  access: "D/A User.Export.All plus User.Read.All. Delegated callers pass both as --scopes; delegated access additionally needs Company Administrator, the privileged role documented for export reads; personal Microsoft accounts are not supported. No P1/P2 prerequisite is stated for this read.",
552
619
  note: "storageLocation always renders as the redaction marker: export blob URLs and signed links never reach output. Workforce context only; export submission belongs to no read slice.",
553
620
  sources: ["https://learn.microsoft.com/graph/api/datapolicyoperation-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/datapolicyoperation?view=graph-rest-1.0"] },
621
+ { id: "v1.0:GET:/identity/riskPrevention/fraudProtectionProviders", kind: "collection", query: ["$select"], fields: KNOWN_FRAUD_FIELDS, keepODataType: true,
622
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for these reads.",
623
+ delegatedOnly: true,
624
+ note: "Graph documents $select only on the fraud-protection-provider list; $filter is not reviewed here. Arkose privateKey/publicKey and HUMAN serverToken are never projected: the first two are key material and the server token authenticates HUMAN API calls. Workforce context only; external-customer user flows and provisioning stay scheduled.",
625
+ sources: ["https://learn.microsoft.com/graph/api/riskpreventioncontainer-list-fraudprotectionproviders?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/fraudprotectionprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/arkosefraudprotectionprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/humansecurityfraudprotectionprovider?view=graph-rest-1.0"] },
626
+ { id: "v1.0:GET:/identity/riskPrevention/fraudProtectionProviders/{fraudProtectionProvider-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_FRAUD_FIELDS, keepODataType: true,
627
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for this read.",
628
+ delegatedOnly: true,
629
+ note: "Arkose privateKey/publicKey and HUMAN serverToken are never projected: the first two are key material and the server token authenticates HUMAN API calls. Workforce context only; external-customer user flows and provisioning stay scheduled.",
630
+ sources: ["https://learn.microsoft.com/graph/api/fraudprotectionprovider-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/fraudprotectionprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/arkosefraudprotectionprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/humansecurityfraudprotectionprovider?view=graph-rest-1.0"] },
631
+ { id: "v1.0:GET:/identity/riskPrevention/webApplicationFirewallProviders", kind: "collection", query: ["$select", "$filter"], fields: KNOWN_WAF_FIELDS, keepODataType: true,
632
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for these reads.",
633
+ delegatedOnly: true,
634
+ note: "Filtering passes through as plain $filter with no $count or ConsistencyLevel contract. Akamai clientSecret/clientToken/accessToken and Cloudflare apiToken are never projected: all four authenticate provider APIs. Workforce context only; verification submission and external-customer user flows stay scheduled.",
635
+ sources: ["https://learn.microsoft.com/graph/api/riskpreventioncontainer-list-webapplicationfirewallproviders?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/akamaiwebapplicationfirewallprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/cloudflarewebapplicationfirewallprovider?view=graph-rest-1.0"] },
636
+ { id: "v1.0:GET:/identity/riskPrevention/webApplicationFirewallProviders/{webApplicationFirewallProvider-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_WAF_FIELDS, keepODataType: true,
637
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for this read.",
638
+ delegatedOnly: true,
639
+ note: "Akamai clientSecret/clientToken/accessToken and Cloudflare apiToken are never projected: all four authenticate provider APIs. Workforce context only; verification submission and external-customer user flows stay scheduled.",
640
+ sources: ["https://learn.microsoft.com/graph/api/webapplicationfirewallprovider-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/akamaiwebapplicationfirewallprovider?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/cloudflarewebapplicationfirewallprovider?view=graph-rest-1.0"] },
641
+ { id: "v1.0:GET:/identity/riskPrevention/webApplicationFirewallVerifications", kind: "collection", query: ["$select", "$filter"], fields: KNOWN_VERIFICATION_FIELDS,
642
+ access: "D RiskPreventionProviders.Read.All. Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for these reads.",
643
+ delegatedOnly: true,
644
+ note: "Filtering passes through as plain $filter with no $count or ConsistencyLevel contract ($expand stays unreviewed). verificationResult and verifiedDetails ride whole as complex objects. Rows never carry provider detail: the provider navigation has its own sub-read. Workforce context only; verification submission stays scheduled.",
645
+ sources: ["https://learn.microsoft.com/graph/api/riskpreventioncontainer-list-webapplicationfirewallverifications?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallverificationmodel?view=graph-rest-1.0"] },
646
+ { id: "v1.0:GET:/identity/riskPrevention/webApplicationFirewallVerifications/{webApplicationFirewallVerificationModel-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_VERIFICATION_FIELDS,
647
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for this read.",
648
+ delegatedOnly: true,
649
+ note: "The get documents $expand only; $select is reviewed here as baseline single-resource support and $expand stays unreviewed. verificationResult and verifiedDetails ride whole as complex objects. The row never carries provider detail: the provider navigation has its own sub-read. Workforce context only; verification submission stays scheduled.",
650
+ sources: ["https://learn.microsoft.com/graph/api/webapplicationfirewallverificationmodel-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallverificationmodel?view=graph-rest-1.0"] },
651
+ { id: "v1.0:GET:/identity/riskPrevention/webApplicationFirewallVerifications/{webApplicationFirewallVerificationModel-id}/provider", kind: "single", query: SINGLE_QUERY, fields: KNOWN_WAF_FIELDS, keepODataType: true,
652
+ access: "D RiskPreventionProviders.Read.All (RiskPreventionProviders.ReadWrite.All is the documented higher-privileged alternative). Delegated callers pass it as --scopes; delegated access additionally needs a directory role that can read risk-prevention configuration (Security Reader is the least-privileged read-only role); personal Microsoft accounts are not supported and Graph documents no supported application permission, so application profiles are refused before credentials. No P1/P2 prerequisite is stated for this read.",
653
+ delegatedOnly: true,
654
+ note: "No provider-navigation operation page exists; access follows the WAF provider-get contract and the verificationModel resource reference. Akamai clientSecret/clientToken/accessToken and Cloudflare apiToken are never projected: all four authenticate provider APIs. Workforce context only.",
655
+ sources: ["https://learn.microsoft.com/graph/api/webapplicationfirewallprovider-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallverificationmodel?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/webapplicationfirewallprovider?view=graph-rest-1.0"] },
554
656
  { id: "v1.0:GET:/identityGovernance/accessReviews/definitions", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_DEFINITION_FIELDS,
555
657
  access: "D/A AccessReview.Read.All. Delegated callers pass it as --scopes; delegated access additionally needs a supported Entra role (group/app reviews: review creator, Global Reader, Security Reader, User Administrator, Identity Governance Administrator or Security Administrator; Entra-role reviews: Security Reader, Identity Governance Administrator, Privileged Role Administrator or Security Administrator); personal Microsoft accounts are not supported.",
556
658
  note: "Definitions are review schedules (a series), never their occurrences; access reviews need P2 or ID Governance depending on capability.",
@@ -690,6 +792,30 @@ export const REVIEWED_ROUTES = [
690
792
  access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
691
793
  note: "No taskReport-nested operation page exists; access follows the taskReports list contract with the top-level taskdefinition-get contract (which documents LifecycleWorkflows.Read.All for its own route) and the taskDefinition resource reference. No query parameters are reviewed here, so whole rows arrive and are projected locally. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
692
794
  sources: ["https://learn.microsoft.com/graph/api/identitygovernance-workflow-list-taskreports?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/identitygovernance-taskdefinition-get?view=graph-rest-1.0"] },
795
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/userProcessingResults", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_USER_PROCESSING_FIELDS,
796
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
797
+ note: "Filtering passes through as plain $filter with no $count or ConsistencyLevel contract ($search/$orderby/$expand stay unreviewed). Run user processing results are personal data: defaults carry status and counts only and the subject link needs $expand and stays out. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
798
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-run-list-userprocessingresults?view=graph-rest-1.0"] },
799
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/userProcessingResults/{userProcessingResult-id}", kind: "single", query: SINGLE_QUERY, fields: KNOWN_USER_PROCESSING_FIELDS,
800
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
801
+ note: "The userprocessingresult-get page covers this run-nested single directly. Run user processing results are personal data: defaults carry status and counts only and the subject link stays out. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
802
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-userprocessingresult-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/identitygovernance-userprocessingresult?view=graph-rest-1.0"] },
803
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/subjectProcessingResults", kind: "collection", query: ["$filter"], fields: KNOWN_SUBJECT_PROCESSING_FIELDS,
804
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
805
+ note: "Graph documents $count/$filter/$orderby/$expand but not $select here, so only $filter is reviewed and rows arrive whole. Run subject processing results are personal data: defaults carry subject type, status and counts only and the subject navigation stays out. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
806
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-run-list-subjectprocessingresults?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/identitygovernance-subjectprocessingresult?view=graph-rest-1.0"] },
807
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/subjectProcessingResults/{subjectProcessingResult-id}", kind: "single", query: [], fields: KNOWN_SUBJECT_PROCESSING_FIELDS,
808
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
809
+ note: "The subjectprocessingresult-get page covers this run-nested single directly and documents $expand only, so whole rows are reviewed here and the subject navigation stays out. Run subject processing results are personal data: defaults carry subject type, status and counts only. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
810
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-subjectprocessingresult-get?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/identitygovernance-subjectprocessingresult?view=graph-rest-1.0"] },
811
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/taskProcessingResults", kind: "collection", query: COLLECTION_QUERY, fields: KNOWN_TASK_PROCESSING_FIELDS,
812
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
813
+ note: "Filtering passes through as plain $filter with no $count or ConsistencyLevel contract ($search/$orderby/$expand stay unreviewed). Run task processing results carry one task's outcome with failure detail; the subject, task and workflowSubject navigations need $expand and stay out. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
814
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-run-list-taskprocessingresults?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/identitygovernance-taskprocessingresult?view=graph-rest-1.0"] },
815
+ { id: "v1.0:GET:/identityGovernance/lifecycleWorkflows/workflows/{workflow-id}/runs/{run-id}/taskProcessingResults/{taskProcessingResult-id}", kind: "single", query: [], fields: KNOWN_TASK_PROCESSING_FIELDS,
816
+ access: "D/A LifecycleWorkflows-Reports.Read.All (LifecycleWorkflows.Read.All or LifecycleWorkflows.ReadWrite.All for richer detail). Delegated callers pass one as --scopes; delegated access additionally needs Global Reader or Lifecycle Workflows Administrator; personal Microsoft accounts are not supported.",
817
+ note: "No operation-level get page exists; access follows the run task-list contract and the taskProcessingResult resource reference. No query parameters are reviewed here, so whole rows arrive and are projected locally; the subject, task and workflowSubject navigations stay out. Lifecycle workflows need Microsoft Entra ID Governance or Microsoft Entra Suite.",
818
+ sources: ["https://learn.microsoft.com/graph/api/identitygovernance-run-list-taskprocessingresults?view=graph-rest-1.0", "https://learn.microsoft.com/graph/api/resources/identitygovernance-taskprocessingresult?view=graph-rest-1.0"] },
693
819
  ];
694
820
  function splitPath(path) {
695
821
  return path.split("/").filter(segment => segment.length > 0);
@@ -814,9 +940,9 @@ function reviewedFields(route, value) {
814
940
  kept["@odata.type"] = value["@odata.type"];
815
941
  return kept;
816
942
  }
817
- // String truncation at the output boundary, mirroring az-axi's 4000-char
818
- // convention. --full disables truncation; redaction already ran in the
819
- // session and row caps are unaffected. Plain objects and arrays only.
943
+ // String truncation at the output boundary, matching the 500-char rule used
944
+ // by every Entra command. --full disables truncation; redaction already ran
945
+ // in the session and row caps are unaffected. Plain objects and arrays only.
820
946
  function truncateForOutput(value, full) {
821
947
  if (typeof value === "string") {
822
948
  if (full || value.length <= TRUNCATE_AT)
@@ -924,7 +1050,7 @@ export async function runApiGet(args, deps) {
924
1050
  const body = await session.execute({ profile: args.profile, operation, params, query, scopes });
925
1051
  const shaped = truncateForOutput(reviewedFields(outputRoute, body), full);
926
1052
  const record = isRecord(shaped.value) ? shaped.value : { value: shaped.value };
927
- return shaped.truncated ? { ...record, help: ["Strings truncated at 4000 chars; re-run with --full"] } : record;
1053
+ return shaped.truncated ? { ...record, help: ["Strings truncated at 500 chars; re-run with --full"] } : record;
928
1054
  }
929
1055
  const collected = await session.collect({ profile: args.profile, operation, params, query, scopes, limit: args.limit, cursor: args.cursor });
930
1056
  const shaped = truncateForOutput(collected.value.map(row => reviewedFields(outputRoute, row)), full);
@@ -932,11 +1058,11 @@ export async function runApiGet(args, deps) {
932
1058
  const warnings = route.warning ? { warnings: [route.warning] } : {};
933
1059
  if (collected.complete) {
934
1060
  return shaped.truncated
935
- ? { returned: value.length, complete: true, value, ...warnings, help: ["Strings truncated at 4000 chars; re-run with --full"] }
1061
+ ? { returned: value.length, complete: true, value, ...warnings, help: ["Strings truncated at 500 chars; re-run with --full"] }
936
1062
  : { returned: value.length, complete: true, value, ...warnings };
937
1063
  }
938
1064
  const hint = "Resume the same path, profile and scopes with --cursor - and supply the cursor token on stdin; use --limit <rows> or --all";
939
- const help = shaped.truncated ? [hint, "Strings truncated at 4000 chars; re-run with --full"] : [hint];
1065
+ const help = shaped.truncated ? [hint, "Strings truncated at 500 chars; re-run with --full"] : [hint];
940
1066
  return { returned: value.length, complete: false, reason: collected.reason, value, ...warnings, cursor: collected.cursor, help };
941
1067
  }
942
1068
  // Minimal HTTPS transport: sends only what the session authorized (GET on the
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // Session-start hook entry point: print the local-only ambient summary and
3
+ // always exit 0 on config state, never loudly, so agent startup never breaks.
4
+ // Unknown argv fails loud with exit 2 instead of a silent wrong summary.
5
+ // Static imports are avoided so a broken install still reaches the fallback.
6
+ const args = process.argv.slice(2);
7
+ if (args.length === 1 && args[0] === "--help") {
8
+ process.stdout.write("mg-axi-hook: print the local-only mg-axi session-start summary (configured profiles, auth-cache presence, version)\nusage: mg-axi-hook [--help]\n");
9
+ }
10
+ else if (args.length > 0) {
11
+ try {
12
+ const { encode } = await import("@toon-format/toon");
13
+ process.stdout.write(`${encode({ error: `unexpected argument \`${args[0]}\` for \`mg-axi-hook\``, code: "VALIDATION_ERROR", help: ["Run `mg-axi-hook` with no arguments for the session summary"] })}\n`);
14
+ }
15
+ catch {
16
+ process.stdout.write(`error: unexpected argument \`${args[0]}\` for \`mg-axi-hook\`\n`);
17
+ }
18
+ process.exitCode = 2;
19
+ }
20
+ else {
21
+ try {
22
+ const { VERSION } = await import("../version.js").catch(() => ({ VERSION: "unknown" }));
23
+ const { hookSummary } = await import("../hook.js");
24
+ process.stdout.write(await hookSummary({ version: VERSION }));
25
+ }
26
+ catch {
27
+ process.stdout.write("mg: status unavailable\n");
28
+ }
29
+ process.exitCode = 0;
30
+ }
31
+ export {};