@knowttl/falcon-axi 0.0.0-stage → 1.0.0-rc.1

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 (152) hide show
  1. package/.typescript-build/native/checksums.json +4 -0
  2. package/.typescript-build/src/auth.js +219 -0
  3. package/.typescript-build/src/axi-credential.schema.json +64 -0
  4. package/.typescript-build/src/bin.js +9 -0
  5. package/.typescript-build/src/cli.js +345 -0
  6. package/.typescript-build/src/core.js +31 -0
  7. package/.typescript-build/src/cql.js +159 -0
  8. package/.typescript-build/src/credentials.js +235 -0
  9. package/.typescript-build/src/cursor.js +65 -0
  10. package/.typescript-build/src/domain.js +119 -0
  11. package/.typescript-build/src/executable.js +62 -0
  12. package/.typescript-build/src/exposure.js +533 -0
  13. package/.typescript-build/src/help.json +91 -0
  14. package/.typescript-build/src/hook.js +19 -0
  15. package/.typescript-build/src/identity.js +313 -0
  16. package/.typescript-build/src/inspection-commands.json +539 -0
  17. package/.typescript-build/src/inspection.js +651 -0
  18. package/.typescript-build/src/inventory.js +356 -0
  19. package/.typescript-build/src/investigation.js +893 -0
  20. package/.typescript-build/src/malquery.js +145 -0
  21. package/.typescript-build/src/metadata.js +559 -0
  22. package/.typescript-build/src/origin.js +47 -0
  23. package/.typescript-build/src/parser.js +293 -0
  24. package/.typescript-build/src/render.js +92 -0
  25. package/.typescript-build/src/response.js +65 -0
  26. package/.typescript-build/src/rtr-filesystem.js +50 -0
  27. package/.typescript-build/src/rtr-policy.js +100 -0
  28. package/.typescript-build/src/rtr.js +409 -0
  29. package/.typescript-build/src/search-commands.json +184 -0
  30. package/.typescript-build/src/search-help.json +15 -0
  31. package/.typescript-build/src/search.js +237 -0
  32. package/.typescript-build/src/setup.js +173 -0
  33. package/.typescript-build/src/skill.js +87 -0
  34. package/.typescript-build/src/transport/descriptors.json +1598 -0
  35. package/.typescript-build/src/transport/dns-worker.js +8 -0
  36. package/.typescript-build/src/transport/documents.json +72 -0
  37. package/.typescript-build/src/transport/https.js +530 -0
  38. package/.typescript-build/src/transport/operations.js +55 -0
  39. package/.typescript-build/src/transport/resolver.js +89 -0
  40. package/.typescript-build/src/transport/search-authority.js +112 -0
  41. package/.typescript-build/src/version.js +1 -0
  42. package/.typescript-build/src/windows-acl.js +37 -0
  43. package/LICENSE +21 -0
  44. package/README.md +192 -2
  45. package/artium-invocations.json +1484 -0
  46. package/native/prebuilds/linux-x64.node +0 -0
  47. package/native/prebuilds/win32-x64.node +0 -0
  48. package/package.json +58 -4
  49. package/skills/falcon-axi/SKILL.md +104 -0
  50. package/skills/falcon-axi/references/account-list.md +25 -0
  51. package/skills/falcon-axi/references/account-show.md +14 -0
  52. package/skills/falcon-axi/references/actor-list.md +19 -0
  53. package/skills/falcon-axi/references/actor-show.md +18 -0
  54. package/skills/falcon-axi/references/agent-guide.md +93 -0
  55. package/skills/falcon-axi/references/application-list.md +30 -0
  56. package/skills/falcon-axi/references/asset-list.md +19 -0
  57. package/skills/falcon-axi/references/asset-show.md +12 -0
  58. package/skills/falcon-axi/references/auth-status.md +10 -0
  59. package/skills/falcon-axi/references/case-list.md +15 -0
  60. package/skills/falcon-axi/references/case-show.md +12 -0
  61. package/skills/falcon-axi/references/classification-list.md +18 -0
  62. package/skills/falcon-axi/references/classification-show.md +11 -0
  63. package/skills/falcon-axi/references/cloud-asset-list.md +19 -0
  64. package/skills/falcon-axi/references/cloud-asset-show.md +13 -0
  65. package/skills/falcon-axi/references/cloud-risk-list.md +19 -0
  66. package/skills/falcon-axi/references/config-path.md +9 -0
  67. package/skills/falcon-axi/references/container-list.md +18 -0
  68. package/skills/falcon-axi/references/correlation-rule-list.md +15 -0
  69. package/skills/falcon-axi/references/cve-list.md +23 -0
  70. package/skills/falcon-axi/references/cve-show.md +22 -0
  71. package/skills/falcon-axi/references/data-policy-list.md +20 -0
  72. package/skills/falcon-axi/references/data-policy-show.md +12 -0
  73. package/skills/falcon-axi/references/detection-list.md +29 -0
  74. package/skills/falcon-axi/references/detection-process.md +19 -0
  75. package/skills/falcon-axi/references/detection-show.md +13 -0
  76. package/skills/falcon-axi/references/detection-summary.md +21 -0
  77. package/skills/falcon-axi/references/exclusion-list.md +21 -0
  78. package/skills/falcon-axi/references/exclusion-show.md +13 -0
  79. package/skills/falcon-axi/references/file-change-list.md +18 -0
  80. package/skills/falcon-axi/references/file-change-show.md +14 -0
  81. package/skills/falcon-axi/references/firewall-group-list.md +15 -0
  82. package/skills/falcon-axi/references/firewall-rule-list.md +15 -0
  83. package/skills/falcon-axi/references/firewall-rule-show.md +12 -0
  84. package/skills/falcon-axi/references/host-group-list.md +17 -0
  85. package/skills/falcon-axi/references/host-group-members.md +17 -0
  86. package/skills/falcon-axi/references/host-list.md +27 -0
  87. package/skills/falcon-axi/references/host-logins.md +22 -0
  88. package/skills/falcon-axi/references/host-network.md +17 -0
  89. package/skills/falcon-axi/references/host-show.md +13 -0
  90. package/skills/falcon-axi/references/host-usb.md +22 -0
  91. package/skills/falcon-axi/references/identity-activity.md +21 -0
  92. package/skills/falcon-axi/references/identity-assessment.md +13 -0
  93. package/skills/falcon-axi/references/identity-list.md +24 -0
  94. package/skills/falcon-axi/references/identity-rule-list.md +18 -0
  95. package/skills/falcon-axi/references/identity-rule-show.md +12 -0
  96. package/skills/falcon-axi/references/identity-sensor-list.md +14 -0
  97. package/skills/falcon-axi/references/identity-sensor-show.md +11 -0
  98. package/skills/falcon-axi/references/identity-show.md +18 -0
  99. package/skills/falcon-axi/references/identity-timeline.md +21 -0
  100. package/skills/falcon-axi/references/image-list.md +18 -0
  101. package/skills/falcon-axi/references/image-vulnerabilities.md +18 -0
  102. package/skills/falcon-axi/references/indicator-list.md +20 -0
  103. package/skills/falcon-axi/references/indicator-show.md +13 -0
  104. package/skills/falcon-axi/references/ioa-group-list.md +15 -0
  105. package/skills/falcon-axi/references/ioc-hosts.md +15 -0
  106. package/skills/falcon-axi/references/ioc-list.md +17 -0
  107. package/skills/falcon-axi/references/ioc-processes.md +16 -0
  108. package/skills/falcon-axi/references/ioc-show.md +11 -0
  109. package/skills/falcon-axi/references/iom-list.md +19 -0
  110. package/skills/falcon-axi/references/iom-show.md +13 -0
  111. package/skills/falcon-axi/references/iot-list.md +17 -0
  112. package/skills/falcon-axi/references/login-list.md +18 -0
  113. package/skills/falcon-axi/references/malquery-quota.md +16 -0
  114. package/skills/falcon-axi/references/malquery-search.md +21 -0
  115. package/skills/falcon-axi/references/malquery-show.md +17 -0
  116. package/skills/falcon-axi/references/malquery-status.md +17 -0
  117. package/skills/falcon-axi/references/malware-list.md +17 -0
  118. package/skills/falcon-axi/references/malware-show.md +13 -0
  119. package/skills/falcon-axi/references/policy-list.md +19 -0
  120. package/skills/falcon-axi/references/policy-members.md +19 -0
  121. package/skills/falcon-axi/references/policy-show.md +16 -0
  122. package/skills/falcon-axi/references/process-show.md +19 -0
  123. package/skills/falcon-axi/references/process-tree.md +20 -0
  124. package/skills/falcon-axi/references/profile-list.md +9 -0
  125. package/skills/falcon-axi/references/quarantine-list.md +16 -0
  126. package/skills/falcon-axi/references/quarantine-show.md +11 -0
  127. package/skills/falcon-axi/references/recon-notification-list.md +16 -0
  128. package/skills/falcon-axi/references/recon-notification-show.md +12 -0
  129. package/skills/falcon-axi/references/recon-record-list.md +16 -0
  130. package/skills/falcon-axi/references/recon-record-show.md +12 -0
  131. package/skills/falcon-axi/references/recon-rule-list.md +16 -0
  132. package/skills/falcon-axi/references/recon-rule-show.md +12 -0
  133. package/skills/falcon-axi/references/report-list.md +19 -0
  134. package/skills/falcon-axi/references/report-show.md +17 -0
  135. package/skills/falcon-axi/references/rtr-audit-list.md +15 -0
  136. package/skills/falcon-axi/references/rtr-run.md +24 -0
  137. package/skills/falcon-axi/references/rtr-session-list.md +15 -0
  138. package/skills/falcon-axi/references/rtr-session-show.md +12 -0
  139. package/skills/falcon-axi/references/sandbox-list.md +21 -0
  140. package/skills/falcon-axi/references/sandbox-show.md +16 -0
  141. package/skills/falcon-axi/references/scopes.md +10 -0
  142. package/skills/falcon-axi/references/search-start.md +26 -0
  143. package/skills/falcon-axi/references/search-status.md +22 -0
  144. package/skills/falcon-axi/references/search-stop.md +16 -0
  145. package/skills/falcon-axi/references/sensor-usage-list.md +15 -0
  146. package/skills/falcon-axi/references/serverless-list.md +19 -0
  147. package/skills/falcon-axi/references/setup-hooks.md +14 -0
  148. package/skills/falcon-axi/references/setup.md +13 -0
  149. package/skills/falcon-axi/references/shield-list.md +19 -0
  150. package/skills/falcon-axi/references/shield-show.md +16 -0
  151. package/skills/falcon-axi/references/vuln-list.md +30 -0
  152. package/skills/falcon-axi/references/vuln-show.md +16 -0
@@ -0,0 +1,4 @@
1
+ {
2
+ "linux-x64.node": "8d1b7e249a70165347e1c43c33ca234a0bed8f0bf44a688aa0f996be0e98afab",
3
+ "win32-x64.node": "ab2d7da816e0da97208e2fcd9f3b774a6591d5760e337b70bb2e1fd33cc4603d"
4
+ }
@@ -0,0 +1,219 @@
1
+ import { CliError, object } from "./core.js";
2
+ import { malformed } from "./response.js";
3
+ import { REGIONS } from "./origin.js";
4
+ import { registerSecret } from "./render.js";
5
+ import { requestToken } from "./transport/https.js";
6
+ import { operation } from "./transport/operations.js";
7
+ export function responseFailure(response, subject, token = false, member = false, operationId = "GetQueriesAlertsV2") {
8
+ const scopes = operation(operationId).scopes;
9
+ const envelope = object(response.body) ? response.body : {};
10
+ const meta = object(envelope.meta) ? envelope.meta : {};
11
+ const details = typeof meta.trace_id === "string" ? { trace_id: meta.trace_id } : {};
12
+ if (response.status === 400) {
13
+ if (operationId === "api_preempt_proxy_post_graphql")
14
+ throw new CliError("UPSTREAM_ERROR", `Falcon rejected falcon-axi's fixed Identity Protection query for ${subject}`, [
15
+ "The query does not match this tenant's Identity Protection GraphQL schema; this is a falcon-axi bug",
16
+ "Quote the trace_id and the command when reporting it",
17
+ ], details);
18
+ const remedy = {
19
+ query_accounts: ["account list", "username:'synthetic-user'"],
20
+ get_accounts: ["account list", "username:'synthetic-user'"],
21
+ GetSensorUsageWeekly: ["sensor-usage list", "period:'30'"],
22
+ combined_hosts: ["asset list", "entity_type:'managed'"],
23
+ get_hosts: ["asset list", "entity_type:'managed'"],
24
+ combined_applications: ["application list", "name:'Chrome'"],
25
+ query_logins: ["login list", "login_status:'Failed'"],
26
+ get_logins: ["login list", "login_status:'Failed'"],
27
+ queryCombinedHostGroups: ["host-group list", "group_type:'dynamic'"],
28
+ queryCombinedGroupMembers: [
29
+ "host-group members",
30
+ "platform_name:'Windows'",
31
+ ],
32
+ query_iot_hostsV2: ["iot list", "device_type:'Printer'"],
33
+ get_iot_hosts: ["iot list", "device_type:'Printer'"],
34
+ QuerySensorsByFilter: [
35
+ "identity sensor list",
36
+ "hostname:'SYNTH-DC-01'+status:'healthy'",
37
+ ],
38
+ GetSensorDetails: [
39
+ "identity sensor list",
40
+ "hostname:'SYNTH-DC-01'+status:'healthy'",
41
+ ],
42
+ cloud_security_assets_queries: [
43
+ "cloud-asset list",
44
+ "cloud_provider:'aws'",
45
+ ],
46
+ cloud_security_assets_entities_get: [
47
+ "cloud-asset list",
48
+ "cloud_provider:'aws'",
49
+ ],
50
+ cspm_evaluations_iom_queries: [
51
+ "iom list",
52
+ "severity:'critical'+status:'non-compliant'",
53
+ ],
54
+ cspm_evaluations_iom_entities: [
55
+ "iom list",
56
+ "severity:'critical'+status:'non-compliant'",
57
+ ],
58
+ combined_cloud_risks: [
59
+ "cloud-risk list",
60
+ "severity:'Critical'+status:'Open'",
61
+ ],
62
+ QueryVulnerabilities: ["cve list", "severity:'CRITICAL'"],
63
+ GetVulnerabilities: ["cve list", "severity:'CRITICAL'"],
64
+ getVulnerabilities: ["vuln list", "status:'open'"],
65
+ combinedQueryVulnerabilities: ["vuln list", "status:'open'"],
66
+ ReadContainerCombined: ["container list", "namespace:'production'"],
67
+ GetCombinedImages: ["image list", "repository:'example/service'"],
68
+ ReadCombinedVulnerabilities: ["image vulnerabilities", "cvss_score:>7"],
69
+ GetCombinedVulnerabilitiesSARIF: [
70
+ "serverless list",
71
+ "cloud_provider:'aws'",
72
+ ],
73
+ highVolumeQueryChanges: [
74
+ "file-change list",
75
+ "host.name:'SYNTHETIC-HOST'",
76
+ ],
77
+ getChanges: ["file-change list", "host.name:'SYNTHETIC-HOST'"],
78
+ RTR_ListAllSessions: ["rtr session list", "hostname:'SYNTHETIC-*'"],
79
+ RTR_ListSessions: ["rtr session list", "hostname:'SYNTHETIC-*'"],
80
+ RTRAuditSessions: ["rtr audit list", "created_at:>'now-7d'"],
81
+ indicator_search_v1: ["ioc list", "type:'domain'"],
82
+ indicator_get_v1: ["ioc list", "type:'domain'"],
83
+ };
84
+ const [command, example] = remedy[operationId] ??
85
+ (scopes.includes("Hosts:read")
86
+ ? ["host list", "hostname:'WIN-*'"]
87
+ : ["detection list", "severity_name:'High'+status:'new'"]);
88
+ const errors = Array.isArray(envelope.errors) ? envelope.errors : [];
89
+ if (errors.some((error) => object(error) &&
90
+ typeof error.message === "string" &&
91
+ /\b(fql|filter|query)\b/i.test(error.message)))
92
+ throw new CliError("FQL_INVALID", `the filter for ${subject} was rejected`, [
93
+ "FQL uses + for AND, `,` for OR, and values must be quoted",
94
+ `Example: \`--filter "${example}"\``,
95
+ `Run \`falcon-axi ${command} --help\` for the filterable fields`,
96
+ ]);
97
+ throw new CliError("UPSTREAM_ERROR", `Falcon rejected the request for ${subject}`, ["Check the flags for this command with `--help`"], details);
98
+ }
99
+ if (response.status === 401)
100
+ throw new CliError("AUTH_FAILED", "the Falcon credential was rejected", [
101
+ "The client ID or secret is wrong, or the API client was revoked",
102
+ ...(!token
103
+ ? ["Run `falcon-axi auth status` after updating the credential"]
104
+ : []),
105
+ ]);
106
+ if (response.status === 403) {
107
+ if (operation(operationId).effect === "malquery-search")
108
+ throw new CliError("SCOPE_DENIED", "MalQuery search is not licensed or not permitted for this API client", [
109
+ "Grant MalQuery:write in the Falcon console under Support and resources > API clients and keys",
110
+ "Falcon MalQuery licensing is required; if the scope is absent from the picker, the tenant may lack that subscription",
111
+ "Captain exception N4 admits only bounded exact and fuzzy pattern searches, never hunt or download",
112
+ "Run `falcon-axi scopes` for the matrix",
113
+ ], { required_scopes: [...scopes] });
114
+ if (operation(operationId).effect === "search-lifecycle")
115
+ throw new CliError("SCOPE_DENIED", `this API client is not permitted to run ${subject}`, [
116
+ `Grant ${scopes.join(" and ")} to the API client in the Falcon console under Support and resources > API clients and keys`,
117
+ "Run `falcon-axi scopes` for the matrix, including which commands need this write scope",
118
+ ], { required_scopes: [...scopes] });
119
+ if (token && member)
120
+ throw new CliError("TENANT_DENIED", "the selected member CID was refused for this credential", [
121
+ "Verify this is a valid child CID in the Falcon console and not the parent CID itself",
122
+ "Or retry with `--no-member-cid` to authenticate as the credential's own CID",
123
+ ]);
124
+ if (token)
125
+ throw new CliError("AUTH_FAILED", "the Falcon credential was refused", [
126
+ "The API client has no scopes granted, or is disabled",
127
+ "Grant `Alerts:read` (read only) in the Falcon console under Support and resources > API clients and keys",
128
+ ]);
129
+ const licences = {
130
+ "Assets:read": "Falcon Discover or Exposure Management",
131
+ "MalQuery:read": "Falcon MalQuery",
132
+ "Sandbox (Falcon Intelligence):read": "Falcon Intelligence (Sandbox)",
133
+ "Device Control Policies:read": "Falcon Device Control",
134
+ "Falcon FileVantage:read": "Falcon FileVantage",
135
+ "Threatgraph:read": "a Falcon subscription with ThreatGraph API access enabled",
136
+ "Real time response:read": "a Falcon subscription with Real Time Response enabled",
137
+ "Real time response audit:read": "a Falcon subscription with Real Time Response audit enabled",
138
+ };
139
+ throw new CliError("SCOPE_DENIED", `this API client is not permitted to read ${subject}`, [
140
+ `Grant ${scopes.join(" and ")} (read only) to the API client in the Falcon console under Support and resources > API clients and keys`,
141
+ ...scopes
142
+ .filter((scope) => licences[scope])
143
+ .map((scope) => `${scope} needs ${licences[scope]}; if the scope is absent from the picker, the tenant lacks that subscription`),
144
+ ...(operationId === "entities_processes"
145
+ ? [
146
+ "Process details may be unavailable under this tenant's subscription or API client permissions",
147
+ ]
148
+ : []),
149
+ "A 403 can also mean the API client is disabled",
150
+ ], { required_scopes: [...scopes] });
151
+ }
152
+ if (response.status === 429)
153
+ throw new CliError("RATE_LIMITED", "Falcon rate limited this API client", [
154
+ "Wait for the rate limit window to reset and retry",
155
+ "Narrow the FQL filter or lower `--limit` to issue fewer requests",
156
+ ]);
157
+ if ([502, 503, 504].includes(response.status))
158
+ throw new CliError("UPSTREAM_UNAVAILABLE", "Falcon is temporarily unavailable", [
159
+ "Retry shortly",
160
+ "Quote the trace_id when opening a CrowdStrike support case",
161
+ ], details);
162
+ throw new CliError("UPSTREAM_ERROR", `Falcon could not answer the request for ${subject}`, [
163
+ "Retry shortly",
164
+ "Quote the trace_id when opening a CrowdStrike support case",
165
+ ], details);
166
+ }
167
+ export function observeRate(session, response) {
168
+ for (const [header, field] of [
169
+ ["x-ratelimit-limit", "rateLimit"],
170
+ ["x-ratelimit-remaining", "remaining"],
171
+ ]) {
172
+ const value = response.headers[header];
173
+ if (value !== undefined &&
174
+ /^\d+$/.test(value) &&
175
+ Number.isSafeInteger(Number(value)))
176
+ session[field] = Number(value);
177
+ }
178
+ }
179
+ export async function authenticate(credential, base, allowUnknown, signal) {
180
+ let response = await requestToken(base, credential, allowUnknown, signal);
181
+ const current = Object.keys(REGIONS).find((key) => REGIONS[key] === base);
182
+ const observed = response.headers["x-cs-region"];
183
+ const redirect = () => {
184
+ throw new CliError("ORIGIN_NOT_ALLOWED", "the Falcon endpoint answered with a redirect and no credential was replayed", ["Supply a verified Falcon base URL with `--region <url>`"], { invalid_component: "redirect" });
185
+ };
186
+ if ((response.status >= 300 && response.status < 400) ||
187
+ ([200, 201].includes(response.status) &&
188
+ observed &&
189
+ current &&
190
+ observed !== current)) {
191
+ if (!observed)
192
+ redirect();
193
+ if (!Object.hasOwn(REGIONS, observed))
194
+ throw new CliError("REGION_MISMATCH", `this credential belongs to Falcon region ${observed}`, [
195
+ `falcon-axi has no verified base URL for ${observed}`,
196
+ "Supply a verified full Falcon base URL with `--region <url> --allow-unknown-origin`",
197
+ ], { observed_region: observed });
198
+ const target = REGIONS[observed];
199
+ if (target === base)
200
+ redirect();
201
+ base = target;
202
+ response = await requestToken(base, credential, allowUnknown, signal);
203
+ if (response.status >= 300 && response.status < 400)
204
+ redirect();
205
+ }
206
+ if (![200, 201].includes(response.status))
207
+ responseFailure(response, "the Falcon token endpoint", true, Boolean(credential.memberCid));
208
+ if (!object(response.body) ||
209
+ typeof response.body.access_token !== "string" ||
210
+ !response.body.access_token)
211
+ malformed(response, "the Falcon token response carried no access token", [
212
+ "Retry shortly",
213
+ ]);
214
+ const token = response.body.access_token;
215
+ registerSecret(token);
216
+ const session = { token, base };
217
+ observeRate(session, response);
218
+ return session;
219
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "falcon-axi credential profile",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["clientId"],
7
+ "properties": {
8
+ "rtr": {
9
+ "type": "object",
10
+ "additionalProperties": false,
11
+ "required": ["enabled"],
12
+ "properties": {
13
+ "enabled": { "type": "boolean", "default": false },
14
+ "commands": {
15
+ "type": "array",
16
+ "items": {
17
+ "enum": [
18
+ "cat",
19
+ "cd",
20
+ "csrutil",
21
+ "env",
22
+ "eventlog",
23
+ "filehash",
24
+ "getsid",
25
+ "ifconfig",
26
+ "ipconfig",
27
+ "ls",
28
+ "mount",
29
+ "netstat",
30
+ "ps",
31
+ "reg query",
32
+ "users"
33
+ ]
34
+ }
35
+ }
36
+ }
37
+ },
38
+ "region": { "type": "string", "minLength": 1 },
39
+ "memberCid": { "type": "string", "minLength": 1 },
40
+ "clientId": { "type": "string", "minLength": 1 },
41
+ "clientSecret": {
42
+ "x-secret": true,
43
+ "default": { "env": "FALCON_AXI_CLIENT_SECRET" },
44
+ "oneOf": [
45
+ {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "required": ["env"],
49
+ "properties": {
50
+ "env": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }
51
+ }
52
+ },
53
+ {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["file"],
57
+ "properties": {
58
+ "file": { "type": "string", "pattern": "^(?:/|[A-Za-z]:[\\\\/])" }
59
+ }
60
+ }
61
+ ]
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { tryFastPath } from "axi-sdk-js/fast-path";
3
+ import { VERSION } from "./version.js";
4
+ if (!tryFastPath(process.argv.slice(2), { version: VERSION })) {
5
+ const { main } = await import("./cli.js");
6
+ const exitCode = await main(process.argv.slice(2));
7
+ // Flush the safe result before terminating the one-shot CLI.
8
+ process.stdout.write("", () => process.exit(exitCode));
9
+ }
@@ -0,0 +1,345 @@
1
+ import { CliError } from "./core.js";
2
+ import { configPath, writeLogPath, loadProfiles, normalizeEnvironment, resolveCredential, } from "./credentials.js";
3
+ import { authenticate } from "./auth.js";
4
+ import { resolveOrigin, REGIONS } from "./origin.js";
5
+ import { render, Raw, registerMemberCid } from "./render.js";
6
+ import { commands, DESCRIPTION } from "./metadata.js";
7
+ import { operation } from "./transport/operations.js";
8
+ import { parse, filterOf } from "./parser.js";
9
+ import { investigate } from "./investigation.js";
10
+ import { Domain, quote } from "./domain.js";
11
+ import { inventory } from "./inventory.js";
12
+ import { exposure } from "./exposure.js";
13
+ import { inspection } from "./inspection.js";
14
+ import inspectionCommands from "./inspection-commands.json" with { type: "json" };
15
+ import { homedir } from "node:os";
16
+ import { executableCommand, shellArgument } from "./executable.js";
17
+ import helpText from "./help.json" with { type: "json" };
18
+ import { VERSION } from "./version.js";
19
+ import { search } from "./search.js";
20
+ import { identity } from "./identity.js";
21
+ import { malquery } from "./malquery.js";
22
+ import searchHelp from "./search-help.json" with { type: "json" };
23
+ import { document } from "./transport/search-authority.js";
24
+ import { rtrRun, validateRtrInvocation } from "./rtr.js";
25
+ import { installHooks } from "./setup.js";
26
+ import { HOME_GUIDANCE, skillHint } from "./skill.js";
27
+ import { readOnly } from "./rtr-policy.js";
28
+ const description = DESCRIPTION;
29
+ export async function main(argv, bin) {
30
+ if (argv.length === 1 && ["-v", "-V", "--version"].includes(argv[0])) {
31
+ process.stdout.write(VERSION + "\n");
32
+ return 0;
33
+ }
34
+ const started = performance.now();
35
+ const controller = new AbortController();
36
+ const cancel = () => controller.abort();
37
+ process.once("SIGTERM", cancel);
38
+ process.once("SIGINT", cancel);
39
+ let activeFlags = new Map();
40
+ let memberCid;
41
+ let home = false;
42
+ let hostnameDeadline;
43
+ const homeHead = () => {
44
+ const executable = executableCommand()
45
+ .map((argument) => process.platform !== "win32" && argument.startsWith(homedir() + "/")
46
+ ? "~/" + shellArgument(argument.slice(homedir().length + 1))
47
+ : shellArgument(argument))
48
+ .join(" ");
49
+ return {
50
+ bin: bin ?? executable,
51
+ description,
52
+ };
53
+ };
54
+ const contextualHelp = (help) => {
55
+ const suffix = ["region", "config", "profile"]
56
+ .flatMap((name) => activeFlags.has(`--${name}`)
57
+ ? [` --${name} ${quote(activeFlags.get(`--${name}`))}`]
58
+ : [])
59
+ .join("") +
60
+ ["allow-unknown-origin", "no-member-cid"]
61
+ .filter((name) => activeFlags.has(`--${name}`))
62
+ .map((name) => ` --${name}`)
63
+ .join("");
64
+ const result = help.map((item) => item.replace(/`(falcon-axi .*)`/s, (_, command) => `\`${command}${suffix}\``));
65
+ if (memberCid && help.some((item) => /`falcon-axi /.test(item)))
66
+ result.push("Supply the same tenant selection used for this invocation when following these suggestions");
67
+ return result;
68
+ };
69
+ try {
70
+ const invocation = parse(argv);
71
+ const { command, flags } = invocation;
72
+ activeFlags = flags;
73
+ home = command.name === "home";
74
+ memberCid = flags.get("--member-cid");
75
+ if (flags.has("--help")) {
76
+ if (command.name === "setup") {
77
+ process.stdout.write(commands
78
+ .filter((spec) => spec.name.startsWith("setup "))
79
+ .map((spec) => spec.summary)
80
+ .join("\n") + "\n");
81
+ return 0;
82
+ }
83
+ if (Object.hasOwn(searchHelp, command.name)) {
84
+ process.stdout.write(searchHelp[command.name] + "\n");
85
+ return 0;
86
+ }
87
+ if (Object.hasOwn(helpText, command.name)) {
88
+ process.stdout.write(helpText[command.name] + "\n");
89
+ return 0;
90
+ }
91
+ const lines = [
92
+ `falcon-axi ${command.name === "home" ? `${VERSION} - ${description}` : command.name}`,
93
+ "",
94
+ "Commands:",
95
+ ...commands
96
+ .filter((spec) => command.name === "home" ||
97
+ spec.name.startsWith(`${command.name} `))
98
+ .map((spec) => spec.summary),
99
+ "",
100
+ "Global flags:",
101
+ " --help, --region <name|url>, --allow-unknown-origin, --member-cid <cid>, --no-member-cid",
102
+ " --config <path>, --profile <name>",
103
+ "",
104
+ `Regions: ${Object.keys(REGIONS).join(", ")}`,
105
+ ...(command.name === "home"
106
+ ? [
107
+ "",
108
+ "RTR execution requires profile opt-in, --execute, and exact host:command --confirm (N3). It",
109
+ "requires Alerts:read, Hosts:read, Vulnerabilities:read, Vulnerabilities (Falcon Intelligence):read,",
110
+ "NGSIEM:read, Identity Protection Entities:read, Identity Protection Timeline:read, and",
111
+ "Identity Protection Assessment:read, plus three",
112
+ "write-labelled scopes: NGSIEM:write for `search start`, `search stop`, and `identity activity`, and",
113
+ "Identity Protection GraphQL:write for the fixed Identity Protection query commands,",
114
+ "which Falcon requires even for read-only queries, and MalQuery:write for bounded exact/fuzzy searches (N4).",
115
+ "The `account`, `asset`, `application`, and `login` commands need license-gated Assets:read.",
116
+ "The `indicator` commands require the license-gated Indicators (Falcon Intelligence):read scope.",
117
+ ]
118
+ : []),
119
+ ];
120
+ process.stdout.write(lines.join("\n") + "\n");
121
+ return 0;
122
+ }
123
+ const filter = filterOf(invocation);
124
+ if (flags.has("--region"))
125
+ resolveOrigin(flags.get("--region"), flags.has("--allow-unknown-origin"));
126
+ for (const warning of normalizeEnvironment())
127
+ process.stderr.write(warning + "\n");
128
+ if (command.name === "setup") {
129
+ process.stdout.write(render({
130
+ setup: "Install falcon-axi in a durable location before registering session integrations",
131
+ }, [
132
+ "Run `falcon-axi setup hooks` to install or repair Claude Code, Codex, and OpenCode integrations",
133
+ "Set FALCON_AXI_CLIENT_ID and FALCON_AXI_CLIENT_SECRET, or configure a profile with a SecretRef",
134
+ "Run `npx skills add knowttl/falcon-axi --skill falcon-axi` to install the agent skill separately",
135
+ ]));
136
+ return 0;
137
+ }
138
+ if (command.name === "setup hooks") {
139
+ process.stdout.write(render(await installHooks()));
140
+ return 0;
141
+ }
142
+ if (readOnly() &&
143
+ [
144
+ "search start",
145
+ "search stop",
146
+ "host usb",
147
+ "identity activity",
148
+ "malquery search",
149
+ ].includes(command.name))
150
+ throw new CliError("READ_ONLY_VIOLATION", "FALCON_AXI_READ_ONLY forbids search lifecycle changes");
151
+ if (command.name === "rtr run")
152
+ validateRtrInvocation(invocation);
153
+ if (command.name === "config path") {
154
+ process.stdout.write(render({
155
+ config: configPath(flags.get("--config")),
156
+ write_log: writeLogPath(),
157
+ }));
158
+ return 0;
159
+ }
160
+ if (command.name === "profile list") {
161
+ const document = await loadProfiles(flags.get("--config"));
162
+ process.stdout.write(render({
163
+ profiles: Object.keys(document.profiles),
164
+ default_profile: document.defaultProfile,
165
+ }));
166
+ return 0;
167
+ }
168
+ if (command.name === "scopes") {
169
+ const groups = new Map();
170
+ for (const spec of commands)
171
+ for (const scope of new Set([
172
+ ...spec.operations.flatMap((id) => operation(id).scopes),
173
+ ...([
174
+ "identity list",
175
+ "identity show",
176
+ "identity timeline",
177
+ "identity assessment",
178
+ ].includes(spec.name)
179
+ ? document(spec.name === "identity list"
180
+ ? "identity_list"
181
+ : spec.name.replace(" ", "_")).readScopes
182
+ : []),
183
+ ])) {
184
+ const names = groups.get(scope) ?? [];
185
+ names.push(spec.name);
186
+ groups.set(scope, names);
187
+ }
188
+ process.stdout.write(render({
189
+ posture: new Raw("read-only except three write-labelled scopes: NGSIEM:write for NG-SIEM search start and stop and identity activity (captain exception N1), and Identity Protection GraphQL:write for fixed read-only identity queries (captain exception N2); Real time response:read also permits guarded host execution with profile opt-in and exact confirmation (captain exception N3); MalQuery:write permits bounded exact/fuzzy pattern searches (captain exception N4)"),
190
+ scopes: [...groups].map(([scope, names]) => ({
191
+ scope,
192
+ access: scope.endsWith(":write") ? "write" : "read",
193
+ commands: names.join(", "),
194
+ required: names.every((name) => /^(policy|exclusion) /.test(name))
195
+ ? "selected --type"
196
+ : "yes",
197
+ })),
198
+ member_cid_token_scope: new Raw("unresolved (Flight Control read may be required; see design section 17.7)"),
199
+ }, [
200
+ "Grant these scopes in the Falcon console under Support and resources > API clients and keys",
201
+ "Grant read access only, apart from NGSIEM:write, Identity Protection GraphQL:write, and MalQuery:write; falcon-axi needs no other write scope",
202
+ "Identity Protection GraphQL:write is labelled write because Falcon requires it even for read-only queries; falcon-axi sends only its own fixed read queries, never caller-supplied GraphQL (captain exception N2)",
203
+ "Omit NGSIEM:write and `search start`, `search stop`, and `identity activity` fail with SCOPE_DENIED",
204
+ "Omit Identity Protection GraphQL:write and `identity list`, `identity show`, `identity timeline`, and `identity assessment` fail with SCOPE_DENIED",
205
+ "MalQuery:write needs Falcon MalQuery licensing and admits only bounded exact/fuzzy search under N4; hunt and sample downloads remain excluded",
206
+ "Assets:read is license-gated to Falcon Discover or Exposure Management; omit it and the `account`, `asset`, `application`, and `login` commands fail with SCOPE_DENIED",
207
+ ]));
208
+ return 0;
209
+ }
210
+ const credential = await resolveCredential(flags.get("--config"), flags.get("--profile"), command.name === "auth status");
211
+ if (flags.has("--no-member-cid"))
212
+ delete credential.memberCid;
213
+ else if (flags.has("--member-cid")) {
214
+ credential.memberCid = flags.get("--member-cid");
215
+ registerMemberCid(credential.memberCid);
216
+ }
217
+ memberCid = credential.memberCid;
218
+ const allowUnknown = flags.has("--allow-unknown-origin");
219
+ const base = resolveOrigin(flags.get("--region") ?? credential.region, allowUnknown);
220
+ if (command.name === "rtr run") {
221
+ process.removeListener("SIGTERM", cancel);
222
+ process.removeListener("SIGINT", cancel);
223
+ process.on("SIGTERM", cancel);
224
+ process.on("SIGINT", cancel);
225
+ await rtrRun(invocation, credential, base, controller.signal);
226
+ return 0;
227
+ }
228
+ if (flags.has("--hostname") &&
229
+ ["host logins", "host network"].includes(command.name))
230
+ hostnameDeadline = AbortSignal.timeout(Math.max(0, Math.floor(60000 - (performance.now() - started))));
231
+ const signal = flags.has("--wait")
232
+ ? AbortSignal.any([
233
+ controller.signal,
234
+ AbortSignal.timeout(Math.max(0, Math.floor(Number(flags.get("--wait")) * 1000 -
235
+ (performance.now() - started)))),
236
+ ])
237
+ : hostnameDeadline
238
+ ? AbortSignal.any([controller.signal, hostnameDeadline])
239
+ : controller.signal;
240
+ const session = await authenticate(credential, base, allowUnknown, signal);
241
+ const region = Object.keys(REGIONS).find((key) => REGIONS[key] === session.base) ??
242
+ "an unlisted Falcon cloud";
243
+ const tenant = new Raw(`${region}${base !== session.base ? ` (re-targeted from ${Object.keys(REGIONS).find((key) => REGIONS[key] === base)})` : ""}${credential.memberCid ? ` member CID …${credential.memberCid.length <= 4 ? "" : credential.memberCid.slice(-4)}` : " (own CID unavailable)"}`);
244
+ if (command.name === "auth status") {
245
+ process.stdout.write(render({
246
+ credential_resolved: true,
247
+ credential_channel: credential.channel,
248
+ profile: credential.profile,
249
+ endpoint: session.base,
250
+ client_secret_source: credential.secretSource,
251
+ credential_path: credential.path,
252
+ tenant,
253
+ scopes: new Raw("read scopes, plus NGSIEM:write for `search start`, `search stop`, and `identity activity`, Identity Protection GraphQL:write for the fixed Identity Protection query commands, and MalQuery:write for bounded exact/fuzzy searches (N4)"),
254
+ ...(session.rateLimit !== undefined &&
255
+ session.remaining !== undefined
256
+ ? {
257
+ rate_limit: new Raw(`${session.remaining} of ${session.rateLimit} requests remaining`),
258
+ }
259
+ : {}),
260
+ }, contextualHelp([
261
+ "Run `falcon-axi detection list` to read detections with this credential",
262
+ ])));
263
+ return 0;
264
+ }
265
+ const domain = new Domain(invocation, credential, session, allowUnknown, signal);
266
+ const result = command.name.startsWith("search ") ||
267
+ ["host usb", "identity activity"].includes(command.name)
268
+ ? await search(domain)
269
+ : [
270
+ "identity list",
271
+ "identity show",
272
+ "identity timeline",
273
+ "identity assessment",
274
+ ].includes(command.name)
275
+ ? await identity(domain)
276
+ : command.name.startsWith("malquery ")
277
+ ? await malquery(domain)
278
+ : inspectionCommands.some((entry) => entry.name === command.name)
279
+ ? await inspection(domain, filter)
280
+ : [
281
+ "account",
282
+ "asset",
283
+ "application",
284
+ "login",
285
+ "iot",
286
+ "host-group",
287
+ "sensor-usage",
288
+ ].includes(command.name.split(" ")[0]) ||
289
+ command.name.startsWith("identity sensor ")
290
+ ? await inventory(domain, filter)
291
+ : [
292
+ "vuln",
293
+ "cve",
294
+ "cloud-asset",
295
+ "iom",
296
+ "cloud-risk",
297
+ "container",
298
+ "image",
299
+ "serverless",
300
+ "shield",
301
+ ].includes(command.name.split(" ")[0])
302
+ ? await exposure(domain, filter)
303
+ : await investigate(invocation, credential, session, filter, allowUnknown, signal);
304
+ if (session.rateLimit &&
305
+ session.remaining !== undefined &&
306
+ session.remaining < session.rateLimit * 0.1) {
307
+ const note = `${session.remaining} of ${session.rateLimit} requests remaining in this rate limit window`;
308
+ if (command.name.startsWith("file-change "))
309
+ result.help.push(note);
310
+ else if (result.value.count || command.name === "process tree")
311
+ result.value.rate_limit = new Raw(note);
312
+ }
313
+ if (command.name === "home") {
314
+ result.value = {
315
+ ...homeHead(),
316
+ tenant,
317
+ ...result.value,
318
+ };
319
+ result.help.push(...HOME_GUIDANCE, ...(argv.length === 0 ? skillHint() : []));
320
+ }
321
+ process.stdout.write(render(result.value, contextualHelp(result.help)));
322
+ return 0;
323
+ }
324
+ catch (error) {
325
+ const safe = hostnameDeadline?.aborted && !controller.signal.aborted
326
+ ? new CliError("WAIT_TIMEOUT", "authentication, hostname lookup and history exceeded the combined 60-second time budget", ["Retry shortly"], error instanceof CliError ? error.details : {})
327
+ : error instanceof CliError
328
+ ? error
329
+ : new CliError("UNKNOWN", "falcon-axi could not complete this command", ["Retry shortly"]);
330
+ process.stdout.write(render({
331
+ ...(home ? homeHead() : {}),
332
+ error: safe.message,
333
+ code: safe.code,
334
+ ...safe.details,
335
+ }, contextualHelp([
336
+ ...safe.help,
337
+ ...(home && argv.length === 0 ? skillHint() : []),
338
+ ])));
339
+ return safe.exitCode;
340
+ }
341
+ finally {
342
+ process.removeListener("SIGTERM", cancel);
343
+ process.removeListener("SIGINT", cancel);
344
+ }
345
+ }