@knowttl/checkpoint-axi 0.0.0 → 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +164 -3
  3. package/axi-credential.schema.json +35 -0
  4. package/bin/checkpoint-axi-hook.js +9 -0
  5. package/bin/checkpoint-axi.js +8 -0
  6. package/bin/version.js +3 -0
  7. package/dist/src/catalogue.d.ts +317 -0
  8. package/dist/src/catalogue.js +103 -0
  9. package/dist/src/cli.d.ts +9 -0
  10. package/dist/src/cli.js +22 -0
  11. package/dist/src/doctor.d.ts +3 -0
  12. package/dist/src/doctor.js +37 -0
  13. package/dist/src/downloads.d.ts +3 -0
  14. package/dist/src/downloads.js +52 -0
  15. package/dist/src/exceptions.d.ts +37 -0
  16. package/dist/src/exceptions.js +81 -0
  17. package/dist/src/local-commands.d.ts +3 -0
  18. package/dist/src/local-commands.js +123 -0
  19. package/dist/src/private-file.d.ts +10 -0
  20. package/dist/src/private-file.js +65 -0
  21. package/dist/src/profiles.d.ts +122 -0
  22. package/dist/src/profiles.js +137 -0
  23. package/dist/src/reads.d.ts +3 -0
  24. package/dist/src/reads.js +169 -0
  25. package/dist/src/redact.d.ts +6 -0
  26. package/dist/src/redact.js +25 -0
  27. package/dist/src/regions.d.ts +9 -0
  28. package/dist/src/regions.js +10 -0
  29. package/dist/src/transport.d.ts +86 -0
  30. package/dist/src/transport.js +306 -0
  31. package/dist/src/write-exceptions.d.ts +4 -0
  32. package/dist/src/write-exceptions.js +97 -0
  33. package/dist/src/write-sectools.d.ts +5 -0
  34. package/dist/src/write-sectools.js +107 -0
  35. package/dist/src/writes.d.ts +30 -0
  36. package/dist/src/writes.js +295 -0
  37. package/docs/agent-guide.md +108 -0
  38. package/docs/commands.md +489 -0
  39. package/docs/configuration.md +63 -0
  40. package/docs/design.md +165 -0
  41. package/docs/downloads.md +30 -0
  42. package/docs/reads.md +84 -0
  43. package/docs/writes.md +200 -0
  44. package/inventory/capabilities.json +217 -0
  45. package/inventory/specs.json +12 -0
  46. package/package.json +48 -5
  47. package/skills/checkpoint-axi/SKILL.md +102 -0
@@ -0,0 +1,122 @@
1
+ import { z } from "zod";
2
+ import { type PrivateFileOptions } from "./private-file.js";
3
+ import { SecretRedactor } from "./redact.js";
4
+ export type CredentialContext = PrivateFileOptions & {
5
+ env?: NodeJS.ProcessEnv;
6
+ home?: string;
7
+ };
8
+ declare function schema(platform: NodeJS.Platform): z.ZodObject<{
9
+ version: z.ZodLiteral<1>;
10
+ defaultProfile: z.ZodOptional<z.ZodString>;
11
+ profiles: z.ZodRecord<z.ZodString, z.ZodObject<{
12
+ region: z.ZodEnum<{
13
+ us: "us";
14
+ eu: "eu";
15
+ ca: "ca";
16
+ au: "au";
17
+ uk: "uk";
18
+ uae: "uae";
19
+ in: "in";
20
+ }>;
21
+ clientId: z.ZodString;
22
+ clientSecret: z.ZodDefault<z.ZodUnion<readonly [z.ZodObject<{
23
+ env: z.ZodString;
24
+ }, z.core.$strict>, z.ZodObject<{
25
+ file: z.ZodString;
26
+ }, z.core.$strict>]>>;
27
+ saas: z.ZodDefault<z.ZodString>;
28
+ scope: z.ZodOptional<z.ZodString>;
29
+ downloads: z.ZodDefault<z.ZodObject<{
30
+ allow: z.ZodBoolean;
31
+ }, z.core.$strict>>;
32
+ writes: z.ZodDefault<z.ZodObject<{
33
+ allowWrites: z.ZodBoolean;
34
+ operations: z.ZodArray<z.ZodString>;
35
+ }, z.core.$strict>>;
36
+ }, z.core.$strict>>;
37
+ }, z.core.$strict>;
38
+ export type Profile = z.infer<ReturnType<typeof schema>>["profiles"][string];
39
+ export type SelectedProfile = Profile & {
40
+ name: string;
41
+ source: "flag" | "env" | "env-profile" | "config-default" | "sole";
42
+ };
43
+ export declare function credentialStore(options?: CredentialContext, redactor?: SecretRedactor): {
44
+ configPath: (explicit?: string) => string;
45
+ statePath: () => string;
46
+ writeLogPath: () => string;
47
+ readOnly: () => boolean;
48
+ load: (explicit?: string, verifyAcl?: boolean) => {
49
+ path: string;
50
+ config: {
51
+ version: 1;
52
+ profiles: Record<string, {
53
+ region: "us" | "eu" | "ca" | "au" | "uk" | "uae" | "in";
54
+ clientId: string;
55
+ clientSecret: {
56
+ env: string;
57
+ } | {
58
+ file: string;
59
+ };
60
+ saas: string;
61
+ downloads: {
62
+ allow: boolean;
63
+ };
64
+ writes: {
65
+ allowWrites: boolean;
66
+ operations: string[];
67
+ };
68
+ scope?: string | undefined;
69
+ }>;
70
+ defaultProfile?: string | undefined;
71
+ };
72
+ };
73
+ select: (config: ReturnType<(explicit?: string, verifyAcl?: boolean) => {
74
+ path: string;
75
+ config: {
76
+ version: 1;
77
+ profiles: Record<string, {
78
+ region: "us" | "eu" | "ca" | "au" | "uk" | "uae" | "in";
79
+ clientId: string;
80
+ clientSecret: {
81
+ env: string;
82
+ } | {
83
+ file: string;
84
+ };
85
+ saas: string;
86
+ downloads: {
87
+ allow: boolean;
88
+ };
89
+ writes: {
90
+ allowWrites: boolean;
91
+ operations: string[];
92
+ };
93
+ scope?: string | undefined;
94
+ }>;
95
+ defaultProfile?: string | undefined;
96
+ };
97
+ }>["config"], flag?: string) => SelectedProfile;
98
+ credentials: (profile: SelectedProfile, verifyAcl?: boolean) => {
99
+ clientId: string;
100
+ clientSecret: string;
101
+ };
102
+ status: (explicit?: string, flag?: string) => {
103
+ profile: string;
104
+ source: "env" | "flag" | "env-profile" | "config-default" | "sole";
105
+ region: "us" | "eu" | "ca" | "au" | "uk" | "uae" | "in";
106
+ origin: "https://cloudinfra-gw-us.portal.checkpoint.com" | "https://cloudinfra-gw.portal.checkpoint.com" | "https://cloudinfra-gw.ca.portal.checkpoint.com" | "https://cloudinfra-gw.ap.portal.checkpoint.com" | "https://cloudinfra-gw.uk.portal.checkpoint.com" | "https://cloudinfra-gw.me.portal.checkpoint.com" | "https://cloudinfra-gw.in.portal.checkpoint.com";
107
+ saas: string;
108
+ clientSecret: string;
109
+ readOnly: boolean;
110
+ };
111
+ list: (explicit?: string) => {
112
+ profiles: {
113
+ name: string;
114
+ region: "us" | "eu" | "ca" | "au" | "uk" | "uae" | "in";
115
+ default: boolean;
116
+ }[];
117
+ count: number;
118
+ config: string;
119
+ };
120
+ redactor: SecretRedactor;
121
+ };
122
+ export {};
@@ -0,0 +1,137 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { z } from "zod";
3
+ import { homedir } from "node:os";
4
+ import { isAbsolute, join, resolve, win32 } from "node:path";
5
+ import { readPrivateFile, toolDirectory } from "./private-file.js";
6
+ import { REGIONS } from "./regions.js";
7
+ import { SecretRedactor } from "./redact.js";
8
+ const profileName = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/);
9
+ const region = z.enum(Object.keys(REGIONS));
10
+ const nonempty = z.string().trim().min(1);
11
+ function schema(platform) {
12
+ const secretRef = z.union([
13
+ z.strictObject({ env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/) }),
14
+ z.strictObject({ file: z.string().refine((path) => platform === "win32" ? /^[A-Za-z]:[\\/]/.test(path) : isAbsolute(path)) }),
15
+ ]);
16
+ const profile = z.strictObject({
17
+ region,
18
+ clientId: nonempty,
19
+ clientSecret: secretRef.default({ env: "CHECKPOINT_AXI_CLIENT_SECRET" }),
20
+ saas: nonempty.default("office365_emails"),
21
+ scope: nonempty.optional(),
22
+ downloads: z.strictObject({ allow: z.boolean() }).default({ allow: false }),
23
+ writes: z.strictObject({ allowWrites: z.boolean(), operations: z.array(z.string().regex(/^[a-z][A-Za-z0-9_.-]*$/)) })
24
+ .default({ allowWrites: false, operations: [] }),
25
+ });
26
+ return z.strictObject({ version: z.literal(1), defaultProfile: profileName.optional(), profiles: z.record(profileName, profile) })
27
+ .refine((config) => config.defaultProfile === undefined || Object.hasOwn(config.profiles, config.defaultProfile));
28
+ }
29
+ export function credentialStore(options = {}, redactor = new SecretRedactor()) {
30
+ const env = options.env ?? process.env;
31
+ const platform = options.platform ?? process.platform;
32
+ const home = options.home ?? homedir();
33
+ const invalid = (message) => {
34
+ throw new AxiError(message, "CONFIG_INVALID", [
35
+ "Use version: 1, named profiles, a supported region, clientId and a clientSecret env/file SecretRef; run checkpoint-axi auth status --help",
36
+ ]);
37
+ };
38
+ redactor.add(env.CHECKPOINT_AXI_CLIENT_SECRET);
39
+ function configPath(explicit) {
40
+ const selected = explicit ?? env.CHECKPOINT_AXI_CONFIG;
41
+ if (selected !== undefined && !selected.trim())
42
+ invalid("Config path must not be blank");
43
+ return selected !== undefined ? resolve(selected) : (platform === "win32" ? win32.join : join)(toolDirectory("config", env, platform, home), "config.json");
44
+ }
45
+ function readOnly() {
46
+ const value = env.CHECKPOINT_AXI_READ_ONLY?.toLowerCase();
47
+ if (value === undefined || value === "0" || value === "false")
48
+ return false;
49
+ if (value === "1" || value === "true")
50
+ return true;
51
+ return invalid("Read-only override must be 1, true, 0 or false");
52
+ }
53
+ function load(explicit, verifyAcl = false) {
54
+ const path = configPath(explicit);
55
+ let raw;
56
+ try {
57
+ raw = JSON.parse(readPrivateFile(path, false, verifyAcl, options));
58
+ }
59
+ catch (error) {
60
+ if (error instanceof AxiError)
61
+ throw error;
62
+ if (explicit === undefined && env.CHECKPOINT_AXI_CONFIG === undefined && error.code === "ENOENT") {
63
+ return { path, config: { version: 1, profiles: {}, defaultProfile: undefined } };
64
+ }
65
+ return invalid("Cannot read profile configuration as JSON; check its path and syntax");
66
+ }
67
+ // Register malformed inline secrets too, before any validation result is rendered.
68
+ if (raw && typeof raw === "object" && "profiles" in raw && raw.profiles && typeof raw.profiles === "object") {
69
+ for (const profile of Object.values(raw.profiles)) {
70
+ if (!profile || typeof profile !== "object" || !("clientSecret" in profile))
71
+ continue;
72
+ const ref = profile.clientSecret;
73
+ if (typeof ref === "string")
74
+ redactor.add(ref);
75
+ if (ref && typeof ref === "object" && "env" in ref && typeof ref.env === "string")
76
+ redactor.add(env[ref.env]);
77
+ }
78
+ }
79
+ const parsed = schema(platform).safeParse(raw);
80
+ if (!parsed.success)
81
+ return invalid("Invalid configuration: check profile names, region, defaultProfile, unknown fields and clientSecret SecretRef");
82
+ return { path, config: parsed.data };
83
+ }
84
+ function select(config, flag) {
85
+ const selected = flag ?? env.CHECKPOINT_AXI_PROFILE;
86
+ if (selected !== undefined && !profileName.safeParse(selected).success)
87
+ invalid("Invalid selected profile name");
88
+ const envFields = ["REGION", "CLIENT_ID", "SAAS", "SCOPE"];
89
+ if (selected === undefined && envFields.some((field) => env[`CHECKPOINT_AXI_${field}`] !== undefined)) {
90
+ if (!region.safeParse(env.CHECKPOINT_AXI_REGION).success || !env.CHECKPOINT_AXI_CLIENT_ID?.trim() || !env.CHECKPOINT_AXI_CLIENT_SECRET?.trim()
91
+ || (env.CHECKPOINT_AXI_SAAS !== undefined && !env.CHECKPOINT_AXI_SAAS.trim()) || (env.CHECKPOINT_AXI_SCOPE !== undefined && !env.CHECKPOINT_AXI_SCOPE.trim())) {
92
+ return invalid("Incomplete or invalid env profile; set REGION, CLIENT_ID and CLIENT_SECRET together");
93
+ }
94
+ return { region: env.CHECKPOINT_AXI_REGION, clientId: env.CHECKPOINT_AXI_CLIENT_ID.trim(),
95
+ clientSecret: { env: "CHECKPOINT_AXI_CLIENT_SECRET" }, saas: env.CHECKPOINT_AXI_SAAS?.trim() ?? "office365_emails", scope: env.CHECKPOINT_AXI_SCOPE?.trim(),
96
+ downloads: { allow: false }, writes: { allowWrites: false, operations: [] }, name: "env", source: "env-profile" };
97
+ }
98
+ const names = Object.keys(config.profiles);
99
+ const name = selected ?? config.defaultProfile ?? (names.length === 1 ? names[0] : undefined);
100
+ if (name === undefined)
101
+ throw new AxiError(names.length ? "Select one of the configured profiles" : "No profiles are configured", names.length ? "PROFILE_AMBIGUOUS" : "PROFILE_REQUIRED", ["Pass --profile <name> or configure defaultProfile; run checkpoint-axi config path"]);
102
+ if (!Object.hasOwn(config.profiles, name))
103
+ throw new AxiError("Selected profile is not configured", "PROFILE_NOT_FOUND", ["Run checkpoint-axi profile list"]);
104
+ return { ...config.profiles[name], name, source: flag !== undefined ? "flag" : env.CHECKPOINT_AXI_PROFILE !== undefined ? "env" : config.defaultProfile !== undefined ? "config-default" : "sole" };
105
+ }
106
+ function credentials(profile, verifyAcl = false) {
107
+ const ref = profile.clientSecret;
108
+ let value;
109
+ try {
110
+ value = "env" in ref ? env[ref.env] : readPrivateFile(ref.file, true, verifyAcl, options).replace(/^\uFEFF/, "").trimEnd();
111
+ }
112
+ catch (error) {
113
+ if (error instanceof AxiError)
114
+ throw error;
115
+ throw new AxiError("Cannot read clientSecret SecretRef file", "AUTH_REQUIRED", ["Check the SecretRef file exists and is readable"]);
116
+ }
117
+ redactor.add(value);
118
+ if (!value?.trim())
119
+ throw new AxiError("clientSecret is required", "AUTH_REQUIRED", ["Set the selected clientSecret SecretRef source; run checkpoint-axi auth status --help"]);
120
+ return { clientId: profile.clientId, clientSecret: value };
121
+ }
122
+ function status(explicit, flag) {
123
+ const forced = readOnly();
124
+ const loaded = load(explicit, true);
125
+ const profile = select(loaded.config, flag);
126
+ credentials(profile, true);
127
+ return { profile: profile.name, source: profile.source, region: profile.region, origin: REGIONS[profile.region], saas: profile.saas,
128
+ clientSecret: "env" in profile.clientSecret ? "env" : "file", readOnly: forced || !profile.writes.allowWrites || profile.writes.operations.length === 0 };
129
+ }
130
+ function list(explicit) {
131
+ readOnly();
132
+ const loaded = load(explicit);
133
+ return { profiles: Object.entries(loaded.config.profiles).map(([name, profile]) => ({ name, region: profile.region, default: name === loaded.config.defaultProfile })),
134
+ count: Object.keys(loaded.config.profiles).length, config: loaded.path };
135
+ }
136
+ return { configPath, statePath: () => toolDirectory("state", env, platform, home), writeLogPath: () => env.CHECKPOINT_AXI_WRITE_LOG?.trim() || join(toolDirectory("state", env, platform, home), "writes.log"), readOnly, load, select, credentials, status, list, redactor };
137
+ }
@@ -0,0 +1,3 @@
1
+ import type { credentialStore } from "./profiles.js";
2
+ import { gateway } from "./transport.js";
3
+ export declare function readCommand(name: "email search" | "email show" | "event list" | "event show" | "task show", id: string | undefined, flags: Map<string, string>, store: ReturnType<typeof credentialStore>, transport?: Parameters<typeof gateway>[3]): Promise<Record<string, unknown>>;
@@ -0,0 +1,169 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { createHash } from "node:crypto";
3
+ import { z } from "zod";
4
+ import { REGIONS } from "./regions.js";
5
+ import { gateway, READ_LIMITS } from "./transport.js";
6
+ const day = 86_400_000;
7
+ const timestamp = z.iso.datetime({ offset: true });
8
+ const filters = z.array(z.strictObject({ saasAttrName: z.string().min(1),
9
+ saasAttrOp: z.enum(["is", "contains", "startsWith", "isEmpty", "isNot", "notContains", "isNotEmpty", "greaterThan", "lessThan"]),
10
+ saasAttrValue: z.string() }));
11
+ const continuation = z.strictObject({ version: z.literal(1), binding: z.string(), start: timestamp, end: timestamp,
12
+ scroll: z.string().optional(), offset: z.number().int().nonnegative(), replayRisk: z.literal(true).optional() });
13
+ // Entity schemas and SaaS-specific payloads: guide pages 11-15 and 21-24.
14
+ const entity = z.object({ entityInfo: z.object({ entityId: z.string().min(1), entityActionState: z.string().optional() }).passthrough(),
15
+ entityPayload: z.object({ subject: z.string().optional() }).passthrough(),
16
+ entitySecurityResults: z.object({ combinedVerdict: z.unknown().optional() }).passthrough().optional(),
17
+ }).passthrough();
18
+ // Event and task fields: guide pages 31-32, 35-36 and 40-41.
19
+ const event = z.object({ eventId: z.string().min(1), type: z.string(), state: z.string(), severity: z.string() }).passthrough();
20
+ const task = z.object({ id: z.union([z.string().min(1), z.number().int().nonnegative()]),
21
+ status: z.enum(["init", "inprogress", "completed", "failed", "stopped", "paused"]),
22
+ actions: z.array(z.object({ action_status: z.string() }).passthrough()),
23
+ progress: z.number().nonnegative(), total: z.number().nonnegative(),
24
+ }).passthrough();
25
+ const invalid = (message) => { throw new AxiError(message, "VALIDATION_ERROR", ["Run checkpoint-axi <email search|email show|event list|event show|task show> --help"]); };
26
+ export async function readCommand(name, id, flags, store, transport) {
27
+ const isEvent = name.startsWith("event ");
28
+ if (name.endsWith(" show") && (!id || !/^[A-Za-z0-9_-]+$/.test(id)))
29
+ invalid(`${name} requires one ID containing letters, digits, underscores or hyphens`);
30
+ const limitText = flags.get("--limit") ?? "50";
31
+ if (!/^[1-9][0-9]*$/.test(limitText) || Number(limitText) > READ_LIMITS.rows)
32
+ invalid("--limit must be an integer from 1 to 1000");
33
+ if (flags.has("--all") && flags.has("--limit"))
34
+ invalid("Use either --all or --limit");
35
+ const limit = flags.has("--all") ? READ_LIMITS.rows : Number(limitText);
36
+ let extended;
37
+ if (flags.has("--filter")) {
38
+ let raw;
39
+ try {
40
+ raw = JSON.parse(flags.get("--filter"));
41
+ }
42
+ catch {
43
+ invalid("--filter must be a JSON array of attribute filters");
44
+ }
45
+ const parsed = filters.safeParse(raw);
46
+ if (!parsed.success)
47
+ invalid("--filter must contain saasAttrName, a documented saasAttrOp and saasAttrValue");
48
+ extended = parsed.data;
49
+ }
50
+ let cursor;
51
+ if (flags.has("--cursor")) {
52
+ try {
53
+ const encoded = flags.get("--cursor");
54
+ if (!/^[A-Za-z0-9_-]+$/.test(encoded))
55
+ invalid("Invalid continuation cursor");
56
+ cursor = continuation.parse(JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")));
57
+ }
58
+ catch {
59
+ invalid("Invalid continuation cursor");
60
+ }
61
+ }
62
+ const end = flags.get("--end-date") ?? cursor?.end ?? new Date().toISOString();
63
+ if (!timestamp.safeParse(end).success)
64
+ invalid("--end-date must be an ISO timestamp with timezone");
65
+ const start = flags.get("--start-date") ?? cursor?.start ?? new Date(Date.parse(end) - day).toISOString();
66
+ if (!timestamp.safeParse(start).success)
67
+ invalid("--start-date must be an ISO timestamp with timezone");
68
+ const window = Date.parse(end) - Date.parse(start);
69
+ if (window <= 0 || window > 31 * day)
70
+ invalid("Search window must be positive and at most 31 days");
71
+ for (const flag of ["--saas", "--saas-entity"])
72
+ if (flags.has(flag) && !flags.get(flag).trim())
73
+ invalid(`${flag} must not be blank`);
74
+ const eventSelectors = {};
75
+ for (const [flag, key] of [["event-types", "eventTypes"], ["event-states", "eventStates"], ["severities", "severities"], ["event-ids", "eventIds"], ["saas", "saas"]]) {
76
+ if (!isEvent || !flags.has(`--${flag}`))
77
+ continue;
78
+ const values = flags.get(`--${flag}`).split(",").map((value) => value.trim());
79
+ if (values.some((value) => !value))
80
+ invalid(`--${flag} must contain nonempty comma-separated values`);
81
+ eventSelectors[key] = values;
82
+ }
83
+ for (const [flag, key] of [["confidence-indicator", "confidenceIndicator"], ["description", "description"]]) {
84
+ if (!flags.has(`--${flag}`))
85
+ continue;
86
+ const value = flags.get(`--${flag}`).trim();
87
+ if (!value)
88
+ invalid(`--${flag} must not be blank`);
89
+ eventSelectors[key] = value;
90
+ }
91
+ store.readOnly();
92
+ const profile = store.select(store.load(flags.get("--config"), true).config, flags.get("--profile"));
93
+ const saas = flags.get("--saas")?.trim() ?? profile.saas;
94
+ const entityFilter = { saas, ...(flags.has("--saas-entity") ? { saasEntity: flags.get("--saas-entity").trim() } : {}), startDate: start, endDate: end };
95
+ const selectors = { ...eventSelectors, saas: eventSelectors.saas ?? [saas], startDate: start, endDate: end };
96
+ const binding = createHash("sha256").update(JSON.stringify({ profile: profile.name, client: profile.clientId,
97
+ origin: REGIONS[profile.region], operation: name, ...(isEvent ? { selectors } : { entityFilter, extended }) })).digest("hex");
98
+ if (cursor && cursor.binding !== binding)
99
+ invalid("Cursor does not match this profile, region or search selectors; repeat the original selectors");
100
+ const api = gateway(profile, store.credentials(profile, true), store.redactor, transport);
101
+ if (name.endsWith(" show")) {
102
+ const kind = name.split(" ")[0];
103
+ const page = await api.read({ path: name === "task show" ? `/task/${id}` : isEvent ? `/event/${id}` : `/search/entity/${id}` });
104
+ if (page.records.length === 0)
105
+ throw new AxiError(`${kind} was not found`, "NOT_FOUND", []);
106
+ const parsed = (name === "task show" ? task : isEvent ? event : entity).safeParse(page.records[0]);
107
+ if (page.records.length !== 1 || !parsed.success)
108
+ throw new AxiError(`Invalid ${kind} detail response`, "RESPONSE_INVALID", []);
109
+ const record = page.records[0];
110
+ const returnedId = name === "task show" ? String(record.id) : isEvent ? record.eventId : record.entityInfo?.entityId;
111
+ if (returnedId !== id)
112
+ throw new AxiError(`Invalid ${kind} detail response`, "RESPONSE_INVALID", []);
113
+ return { [kind]: parsed.data };
114
+ }
115
+ const results = [];
116
+ let scroll = cursor?.scroll;
117
+ let offset = cursor?.offset ?? 0;
118
+ let replayRisk = cursor?.replayRisk ?? offset > 0;
119
+ let total;
120
+ let exhausted = false;
121
+ while (results.length < limit) {
122
+ let page;
123
+ try {
124
+ page = await api.read(isEvent ? { path: "/event/query", body: { ...selectors, ...(scroll === undefined ? {} : { scrollId: scroll }) } }
125
+ : { path: "/search/query", body: { entityFilter,
126
+ ...(extended === undefined ? {} : { entityExtendedFilter: extended }), ...(scroll === undefined ? {} : { scrollId: scroll }) } });
127
+ }
128
+ catch (error) {
129
+ if (results.length && error instanceof AxiError && ["REQUEST_BUDGET", "TIME_BUDGET", "RESPONSE_TOO_LARGE"].includes(error.code))
130
+ break;
131
+ throw error;
132
+ }
133
+ const parsed = z.array(isEvent ? event : entity).safeParse(page.records);
134
+ if (!parsed.success || offset > page.records.length)
135
+ throw new AxiError("Invalid search response", "RESPONSE_INVALID", []);
136
+ // Never wrap an echoed credential in a reversible continuation cursor.
137
+ if (page.scrollId && store.redactor.text(page.scrollId) !== page.scrollId)
138
+ throw new AxiError("Unsafe API scrollId", "RESPONSE_INVALID", []);
139
+ total = page.total ?? total;
140
+ const rows = parsed.data.slice(offset, offset + limit - results.length);
141
+ results.push(...rows.map((record) => {
142
+ if (isEvent) {
143
+ const value = event.parse(record);
144
+ return { id: value.eventId, type: value.type, state: value.state, severity: value.severity };
145
+ }
146
+ const value = entity.parse(record);
147
+ return { id: value.entityInfo.entityId, subject: value.entityPayload.subject ?? "",
148
+ state: value.entityInfo.entityActionState ?? "", verdict: value.entitySecurityResults?.combinedVerdict ?? null };
149
+ }));
150
+ offset += rows.length;
151
+ if (offset < page.records.length) {
152
+ replayRisk = true;
153
+ break;
154
+ }
155
+ if (!page.scrollId) {
156
+ exhausted = true;
157
+ break;
158
+ }
159
+ scroll = page.scrollId;
160
+ offset = 0;
161
+ }
162
+ const next = exhausted ? undefined : Buffer.from(JSON.stringify({ version: 1, binding, start, end, scroll, offset,
163
+ ...(replayRisk ? { replayRisk: true } : {}) })).toString("base64url");
164
+ return { [isEvent ? "events" : "emails"]: results, count: results.length, ...(total === undefined ? {} : { total }), complete: exhausted && !replayRisk,
165
+ ...(replayRisk ? { reason: "replay-may-skip-or-repeat" } : {}), ...(next ? { cursor: next } : {}),
166
+ help: [...(results.length ? [isEvent ? "Run checkpoint-axi event show <id> for event details and available actions" : "Run checkpoint-axi email show <id> for verdicts, actions and available actions"] : [`No ${isEvent ? "events" : "emails"} matched this search window and selectors`]),
167
+ ...(next ? ["Repeat this search with --cursor <cursor> and the original selectors to continue"] : []),
168
+ ...(replayRisk ? ["Results changed between calls may be skipped or repeated during page replay"] : [])] };
169
+ }
@@ -0,0 +1,6 @@
1
+ export declare class SecretRedactor {
2
+ private readonly secrets;
3
+ add(secret: string | undefined): void;
4
+ text(value: string): string;
5
+ value(value: unknown): unknown;
6
+ }
@@ -0,0 +1,25 @@
1
+ export class SecretRedactor {
2
+ secrets = new Set();
3
+ add(secret) {
4
+ if (!secret)
5
+ return;
6
+ this.secrets.add(secret);
7
+ this.secrets.add(JSON.stringify(secret).slice(1, -1));
8
+ if (secret.isWellFormed())
9
+ this.secrets.add(encodeURIComponent(secret));
10
+ }
11
+ text(value) {
12
+ for (const secret of [...this.secrets].sort((a, b) => b.length - a.length))
13
+ value = value.split(secret).join("***redacted***");
14
+ return value;
15
+ }
16
+ value(value) {
17
+ if (typeof value === "string")
18
+ return this.text(value);
19
+ if (Array.isArray(value))
20
+ return value.map((child) => this.value(child));
21
+ if (value && typeof value === "object")
22
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [this.text(key), this.value(child)]));
23
+ return value;
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ export declare const REGIONS: {
2
+ readonly us: "https://cloudinfra-gw-us.portal.checkpoint.com";
3
+ readonly eu: "https://cloudinfra-gw.portal.checkpoint.com";
4
+ readonly ca: "https://cloudinfra-gw.ca.portal.checkpoint.com";
5
+ readonly au: "https://cloudinfra-gw.ap.portal.checkpoint.com";
6
+ readonly uk: "https://cloudinfra-gw.uk.portal.checkpoint.com";
7
+ readonly uae: "https://cloudinfra-gw.me.portal.checkpoint.com";
8
+ readonly in: "https://cloudinfra-gw.in.portal.checkpoint.com";
9
+ };
@@ -0,0 +1,10 @@
1
+ // Email Security API Reference Guide, 23 October 2026, page 8.
2
+ export const REGIONS = {
3
+ us: "https://cloudinfra-gw-us.portal.checkpoint.com",
4
+ eu: "https://cloudinfra-gw.portal.checkpoint.com",
5
+ ca: "https://cloudinfra-gw.ca.portal.checkpoint.com",
6
+ au: "https://cloudinfra-gw.ap.portal.checkpoint.com",
7
+ uk: "https://cloudinfra-gw.uk.portal.checkpoint.com",
8
+ uae: "https://cloudinfra-gw.me.portal.checkpoint.com",
9
+ in: "https://cloudinfra-gw.in.portal.checkpoint.com",
10
+ };
@@ -0,0 +1,86 @@
1
+ import { type Agent } from "node:https";
2
+ import type { SelectedProfile } from "./profiles.js";
3
+ import type { SecretRedactor } from "./redact.js";
4
+ import { type WriteAuthorization } from "./writes.js";
5
+ export declare const READ_LIMITS: {
6
+ readonly requests: 10;
7
+ readonly rows: 1000;
8
+ readonly milliseconds: 60000;
9
+ readonly bytes: number;
10
+ };
11
+ export declare const DOWNLOAD_LIMIT: number;
12
+ export type DownloadRequest = {
13
+ entityId: string;
14
+ attachmentId?: string;
15
+ all?: boolean;
16
+ original?: boolean;
17
+ scope?: string;
18
+ };
19
+ export type ExceptionPath = `/exceptions/${"whitelist" | "blacklist" | "spam_whitelist"}${"" | `/${string}`}` | `/sectools/${"anomaly" | "click_time_protection"}/exceptions${"" | `/${string}`}` | `/sectool-exceptions/${"checkpoint2" | "avanan_url" | "avanan_dlp"}/exceptions/${string}`;
20
+ export type ReadRequest = {
21
+ path: "/search/query";
22
+ body: {
23
+ entityFilter: {
24
+ saas: string;
25
+ saasEntity?: string;
26
+ startDate: string;
27
+ endDate: string;
28
+ };
29
+ entityExtendedFilter?: {
30
+ saasAttrName: string;
31
+ saasAttrOp: string;
32
+ saasAttrValue: string;
33
+ }[];
34
+ scrollId?: string;
35
+ };
36
+ } | {
37
+ path: `/search/entity/${string}`;
38
+ body?: never;
39
+ } | {
40
+ path: "/event/query";
41
+ body: {
42
+ startDate?: string;
43
+ endDate?: string;
44
+ saas?: string[];
45
+ eventTypes?: string[];
46
+ eventStates?: string[];
47
+ severities?: string[];
48
+ eventIds?: string[];
49
+ confidenceIndicator?: string;
50
+ description?: string;
51
+ scrollId?: string;
52
+ };
53
+ } | {
54
+ path: `/event/${string}` | `/task/${string}`;
55
+ body?: never;
56
+ } | {
57
+ path: ExceptionPath;
58
+ body?: never;
59
+ };
60
+ export type Page = {
61
+ records: unknown[];
62
+ scrollId?: string;
63
+ total?: number;
64
+ };
65
+ type Dependencies = {
66
+ agent?: Agent;
67
+ now?: () => number;
68
+ wait?: (milliseconds: number) => Promise<unknown>;
69
+ };
70
+ export declare function decodePage(value: unknown, unverified?: boolean): Page;
71
+ export declare function gateway(profile: SelectedProfile, credentials: {
72
+ clientId: string;
73
+ clientSecret: string;
74
+ }, redactor: SecretRedactor, dependencies?: Dependencies, mutation?: boolean): {
75
+ authenticate: () => Promise<void>;
76
+ read: (query: ReadRequest) => Promise<Page>;
77
+ download: (query: DownloadRequest) => Promise<{
78
+ content: Buffer;
79
+ sha256: string;
80
+ }>;
81
+ emailAction: (authorization: WriteAuthorization, action: "quarantine" | "restore", targets: readonly string[]) => Promise<Page>;
82
+ eventAction: (authorization: WriteAuthorization, action: string, targets: readonly string[]) => Promise<Page>;
83
+ exceptionWrite: (authorization: WriteAuthorization, kind: "phishing-allow" | "phishing-block" | "spam-allow", action: "create" | "update" | "remove", target: string, checkScopeBeforeSend: () => void, data?: Record<string, unknown>) => Promise<void>;
84
+ sectoolsWrite: (authorization: WriteAuthorization, kind: "anomaly" | "click-time" | "malware" | "url" | "dlp", action: "create" | "update" | "remove" | "remove-items", targets: readonly string[], checkScopeBeforeSend: () => void, data?: Record<string, unknown>, specific?: boolean) => Promise<unknown>;
85
+ };
86
+ export {};