@miosa/sdk 1.0.0 → 1.2.0
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.
- package/dist/index.d.ts +968 -6
- package/dist/index.js +1341 -236
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +39 -0
- package/src/index.ts +90 -0
- package/src/resources/admin.ts +11 -0
- package/src/resources/api-keys.ts +16 -0
- package/src/resources/computer.ts +16 -0
- package/src/resources/egress.test.ts +318 -0
- package/src/resources/egressAudit.ts +245 -0
- package/src/resources/egressNetwork.ts +450 -0
- package/src/resources/egressSecrets.ts +577 -0
- package/src/resources/governance.test.ts +355 -0
- package/src/resources/governance.ts +528 -0
- package/src/resources/org-invites.ts +189 -0
- package/src/resources/phase1.test.ts +187 -0
- package/src/resources/quotas.ts +77 -0
- package/src/resources/sandbox-processes.ts +112 -0
- package/src/resources/sandbox-shares.ts +83 -0
- package/src/resources/sandboxes.ts +239 -10
- package/src/resources/tenant-events.ts +32 -0
- package/src/resources/workspace-invites.ts +188 -0
- package/src/resources/workspace-members.test.ts +121 -0
- package/src/resources/workspace-members.ts +143 -0
- package/src/resources/workspaces.ts +285 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Egress audit log — paginated query + live tail.
|
|
3
|
+
*
|
|
4
|
+
* Backed by:
|
|
5
|
+
* GET /api/v1/egress/audit
|
|
6
|
+
* GET /api/v1/egress/audit/:id
|
|
7
|
+
*
|
|
8
|
+
* `client.audit.tail()` long-polls the REST endpoint and yields new
|
|
9
|
+
* events as they arrive. The sandbox-scoped variant
|
|
10
|
+
* (`sandbox.audit.tail()`) upgrades to a live SSE connection backed by
|
|
11
|
+
* `GET /sandboxes/:id/audit/stream` so the tail latency is
|
|
12
|
+
* sub-second.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { HttpClient } from "../http.js";
|
|
16
|
+
|
|
17
|
+
// ── Resource shapes ──────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export interface EgressAuditEvent {
|
|
20
|
+
id: string;
|
|
21
|
+
action?: string;
|
|
22
|
+
effect?: string;
|
|
23
|
+
host?: string;
|
|
24
|
+
method?: string;
|
|
25
|
+
path?: string;
|
|
26
|
+
status_code?: number;
|
|
27
|
+
actor_id?: string;
|
|
28
|
+
resource_id?: string;
|
|
29
|
+
resource_type?: string;
|
|
30
|
+
policy_id?: string;
|
|
31
|
+
rule_id?: string;
|
|
32
|
+
external_user_id?: string;
|
|
33
|
+
external_workspace_id?: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
inserted_at?: string;
|
|
36
|
+
timestamp?: string;
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Request payloads ─────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export interface AuditListParams {
|
|
43
|
+
resourceId?: string;
|
|
44
|
+
resource_id?: string;
|
|
45
|
+
resourceType?: string;
|
|
46
|
+
resource_type?: string;
|
|
47
|
+
host?: string;
|
|
48
|
+
action?: string;
|
|
49
|
+
since?: string;
|
|
50
|
+
until?: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
cursor?: string;
|
|
53
|
+
externalUserId?: string;
|
|
54
|
+
external_user_id?: string;
|
|
55
|
+
externalWorkspaceId?: string;
|
|
56
|
+
external_workspace_id?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AuditTailParams extends AuditListParams {
|
|
60
|
+
pollIntervalMs?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
interface WireEnvelope<T> {
|
|
66
|
+
data?: T;
|
|
67
|
+
event?: T;
|
|
68
|
+
items?: T;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function unwrap<T>(payload: unknown): T {
|
|
72
|
+
if (payload && typeof payload === "object") {
|
|
73
|
+
const p = payload as Record<string, unknown>;
|
|
74
|
+
for (const k of ["data", "event", "items"]) {
|
|
75
|
+
if (k in p) return p[k] as T;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return payload as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function unwrapList<T>(payload: unknown): T[] {
|
|
82
|
+
if (Array.isArray(payload)) return payload as T[];
|
|
83
|
+
if (payload && typeof payload === "object") {
|
|
84
|
+
const p = payload as Record<string, unknown>;
|
|
85
|
+
for (const k of ["data", "events", "audit", "items"]) {
|
|
86
|
+
if (Array.isArray(p[k])) return p[k] as T[];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function stripUndefined(
|
|
93
|
+
input: Record<string, unknown>,
|
|
94
|
+
): Record<string, unknown> {
|
|
95
|
+
return Object.fromEntries(
|
|
96
|
+
Object.entries(input).filter(([, v]) => v !== undefined),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function pickFirst<T>(...values: Array<T | undefined>): T | undefined {
|
|
101
|
+
for (const v of values) if (v !== undefined) return v;
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function listQuery(
|
|
106
|
+
params: AuditListParams,
|
|
107
|
+
): Record<string, string | number | boolean | undefined> {
|
|
108
|
+
return stripUndefined({
|
|
109
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
110
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
111
|
+
host: params.host,
|
|
112
|
+
action: params.action,
|
|
113
|
+
since: params.since,
|
|
114
|
+
until: params.until,
|
|
115
|
+
limit: params.limit,
|
|
116
|
+
cursor: params.cursor,
|
|
117
|
+
external_user_id: pickFirst(params.externalUserId, params.external_user_id),
|
|
118
|
+
external_workspace_id: pickFirst(
|
|
119
|
+
params.externalWorkspaceId,
|
|
120
|
+
params.external_workspace_id,
|
|
121
|
+
),
|
|
122
|
+
}) as Record<string, string | number | boolean | undefined>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function sleep(ms: number): Promise<void> {
|
|
126
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Main resource ─────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export class EgressAudit {
|
|
132
|
+
constructor(protected readonly http: HttpClient) {}
|
|
133
|
+
|
|
134
|
+
/** List audit events with optional filters. */
|
|
135
|
+
async list(params: AuditListParams = {}): Promise<EgressAuditEvent[]> {
|
|
136
|
+
const data = await this.http.get<unknown>(
|
|
137
|
+
"/egress/audit",
|
|
138
|
+
listQuery(params),
|
|
139
|
+
);
|
|
140
|
+
return unwrapList<EgressAuditEvent>(data);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Get a single audit event by id. */
|
|
144
|
+
async get(id: string): Promise<EgressAuditEvent> {
|
|
145
|
+
const data = await this.http.get<WireEnvelope<EgressAuditEvent>>(
|
|
146
|
+
`/egress/audit/${id}`,
|
|
147
|
+
);
|
|
148
|
+
return unwrap<EgressAuditEvent>(data);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Long-poll the audit endpoint and yield new events as they appear.
|
|
153
|
+
*
|
|
154
|
+
* Tenant-wide `client.audit.tail()` is REST-based long polling. A
|
|
155
|
+
* live WebSocket / SSE tail is only available for the sandbox-scoped
|
|
156
|
+
* variant — see {@link SandboxAudit.tail}.
|
|
157
|
+
*/
|
|
158
|
+
async *tail(
|
|
159
|
+
params: AuditTailParams = {},
|
|
160
|
+
): AsyncIterableIterator<EgressAuditEvent> {
|
|
161
|
+
const pollMs = params.pollIntervalMs ?? 2000;
|
|
162
|
+
let since: string | undefined = params.since;
|
|
163
|
+
const seen = new Set<string>();
|
|
164
|
+
while (true) {
|
|
165
|
+
const queryParams: AuditListParams = { ...params };
|
|
166
|
+
if (since !== undefined) queryParams.since = since;
|
|
167
|
+
const data = await this.http.get<unknown>(
|
|
168
|
+
"/egress/audit",
|
|
169
|
+
listQuery(queryParams),
|
|
170
|
+
);
|
|
171
|
+
const events = unwrapList<EgressAuditEvent>(data);
|
|
172
|
+
for (const event of events) {
|
|
173
|
+
if (event.id && seen.has(event.id)) continue;
|
|
174
|
+
if (event.id) seen.add(event.id);
|
|
175
|
+
yield event;
|
|
176
|
+
const ts = event.inserted_at ?? event.timestamp;
|
|
177
|
+
if (typeof ts === "string") since = ts;
|
|
178
|
+
}
|
|
179
|
+
await sleep(pollMs);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Resource-scoped wrappers ─────────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Sandbox-bound view of {@link EgressAudit}. `list()` pre-scopes
|
|
188
|
+
* `resource_id` + `resource_type="sandbox"`. `tail()` upgrades to the
|
|
189
|
+
* per-sandbox SSE stream for sub-second tail latency.
|
|
190
|
+
*/
|
|
191
|
+
export class SandboxAudit {
|
|
192
|
+
protected readonly resourceType: string = "sandbox";
|
|
193
|
+
private readonly delegate: EgressAudit;
|
|
194
|
+
|
|
195
|
+
constructor(
|
|
196
|
+
protected readonly http: HttpClient,
|
|
197
|
+
protected readonly resourceId: string,
|
|
198
|
+
) {
|
|
199
|
+
this.delegate = new EgressAudit(http);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
list(params: AuditListParams = {}): Promise<EgressAuditEvent[]> {
|
|
203
|
+
return this.delegate.list({
|
|
204
|
+
...params,
|
|
205
|
+
resource_id: params.resourceId ?? params.resource_id ?? this.resourceId,
|
|
206
|
+
resource_type:
|
|
207
|
+
params.resourceType ?? params.resource_type ?? this.resourceType,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
get(id: string): Promise<EgressAuditEvent> {
|
|
212
|
+
return this.delegate.get(id);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** SSE tail of the sandbox-scoped audit stream. */
|
|
216
|
+
async *tail(
|
|
217
|
+
params: AuditTailParams = {},
|
|
218
|
+
): AsyncIterableIterator<EgressAuditEvent> {
|
|
219
|
+
const streamPath =
|
|
220
|
+
this.resourceType === "sandbox"
|
|
221
|
+
? `/sandboxes/${this.resourceId}/audit/stream`
|
|
222
|
+
: `/computers/${this.resourceId}/audit/stream`;
|
|
223
|
+
try {
|
|
224
|
+
const stream = this.http.stream<EgressAuditEvent>(streamPath, {
|
|
225
|
+
method: "GET",
|
|
226
|
+
});
|
|
227
|
+
for await (const event of stream) {
|
|
228
|
+
yield event;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
// SSE endpoint unavailable — fall back to long-poll on the
|
|
232
|
+
// tenant-wide endpoint with this resource pre-filtered.
|
|
233
|
+
yield* this.delegate.tail({
|
|
234
|
+
...params,
|
|
235
|
+
resource_id: this.resourceId,
|
|
236
|
+
resource_type: this.resourceType,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Computer-bound audit — same surface, `resource_type="computer"`. */
|
|
243
|
+
export class ComputerAudit extends SandboxAudit {
|
|
244
|
+
protected readonly resourceType: string = "computer";
|
|
245
|
+
}
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Egress network — policies, allowlist, suggestions.
|
|
3
|
+
*
|
|
4
|
+
* Backed by:
|
|
5
|
+
* GET /api/v1/egress/policies
|
|
6
|
+
* POST /api/v1/egress/policies
|
|
7
|
+
* PATCH /api/v1/egress/policies/:id (or no id for tenant default)
|
|
8
|
+
*
|
|
9
|
+
* GET /api/v1/egress/allowlist
|
|
10
|
+
* POST /api/v1/egress/allowlist
|
|
11
|
+
* DELETE /api/v1/egress/allowlist/:id
|
|
12
|
+
*
|
|
13
|
+
* GET /api/v1/egress/audit/suggestions
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { HttpClient } from "../http.js";
|
|
17
|
+
|
|
18
|
+
// ── Resource shapes ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
export type EgressPolicyMode = "enforce" | "audit_only";
|
|
21
|
+
export type EgressRuleEffect = "allow" | "deny";
|
|
22
|
+
|
|
23
|
+
export interface EgressAllowlistRule {
|
|
24
|
+
id: string;
|
|
25
|
+
host: string;
|
|
26
|
+
effect: EgressRuleEffect | string;
|
|
27
|
+
methods?: string[];
|
|
28
|
+
path_glob?: string | null;
|
|
29
|
+
policy_id?: string | null;
|
|
30
|
+
resource_id?: string | null;
|
|
31
|
+
resource_type?: string | null;
|
|
32
|
+
note?: string | null;
|
|
33
|
+
created_at?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EgressPolicyData {
|
|
38
|
+
id: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
mode: EgressPolicyMode | string;
|
|
41
|
+
default_effect: EgressRuleEffect | string;
|
|
42
|
+
description?: string | null;
|
|
43
|
+
resource_id?: string | null;
|
|
44
|
+
resource_type?: string | null;
|
|
45
|
+
rules?: EgressAllowlistRule[];
|
|
46
|
+
created_at?: string;
|
|
47
|
+
updated_at?: string;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface EgressSuggestion {
|
|
52
|
+
host: string;
|
|
53
|
+
methods?: string[];
|
|
54
|
+
path_glob?: string | null;
|
|
55
|
+
count?: number;
|
|
56
|
+
first_seen?: string;
|
|
57
|
+
last_seen?: string;
|
|
58
|
+
resource_id?: string | null;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Request payloads ─────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
export interface AllowParams {
|
|
65
|
+
methods?: string[];
|
|
66
|
+
pathGlob?: string;
|
|
67
|
+
path_glob?: string;
|
|
68
|
+
policyId?: string;
|
|
69
|
+
policy_id?: string;
|
|
70
|
+
resourceId?: string;
|
|
71
|
+
resource_id?: string;
|
|
72
|
+
resourceType?: string;
|
|
73
|
+
resource_type?: string;
|
|
74
|
+
note?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PolicyCreateParams {
|
|
78
|
+
name: string;
|
|
79
|
+
mode?: EgressPolicyMode | string;
|
|
80
|
+
defaultEffect?: EgressRuleEffect | string;
|
|
81
|
+
default_effect?: EgressRuleEffect | string;
|
|
82
|
+
resourceId?: string;
|
|
83
|
+
resource_id?: string;
|
|
84
|
+
resourceType?: string;
|
|
85
|
+
resource_type?: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface PolicyUpdateParams {
|
|
90
|
+
mode?: EgressPolicyMode | string;
|
|
91
|
+
defaultEffect?: EgressRuleEffect | string;
|
|
92
|
+
default_effect?: EgressRuleEffect | string;
|
|
93
|
+
name?: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ModeParams {
|
|
98
|
+
policyId?: string;
|
|
99
|
+
policy_id?: string;
|
|
100
|
+
resourceId?: string;
|
|
101
|
+
resource_id?: string;
|
|
102
|
+
resourceType?: string;
|
|
103
|
+
resource_type?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SuggestionsParams {
|
|
107
|
+
resourceId?: string;
|
|
108
|
+
resource_id?: string;
|
|
109
|
+
resourceType?: string;
|
|
110
|
+
resource_type?: string;
|
|
111
|
+
since?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface PolicyListParams {
|
|
115
|
+
resourceId?: string;
|
|
116
|
+
resource_id?: string;
|
|
117
|
+
resourceType?: string;
|
|
118
|
+
resource_type?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface RulesListParams {
|
|
122
|
+
policyId?: string;
|
|
123
|
+
policy_id?: string;
|
|
124
|
+
resourceId?: string;
|
|
125
|
+
resource_id?: string;
|
|
126
|
+
resourceType?: string;
|
|
127
|
+
resource_type?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
interface WireEnvelope<T> {
|
|
133
|
+
data?: T;
|
|
134
|
+
policy?: T;
|
|
135
|
+
rule?: T;
|
|
136
|
+
items?: T;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function unwrap<T>(payload: unknown): T {
|
|
140
|
+
if (payload && typeof payload === "object") {
|
|
141
|
+
const p = payload as Record<string, unknown>;
|
|
142
|
+
for (const k of ["data", "policy", "rule", "items"]) {
|
|
143
|
+
if (k in p) return p[k] as T;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return payload as T;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function unwrapList<T>(payload: unknown): T[] {
|
|
150
|
+
if (Array.isArray(payload)) return payload as T[];
|
|
151
|
+
if (payload && typeof payload === "object") {
|
|
152
|
+
const p = payload as Record<string, unknown>;
|
|
153
|
+
for (const k of [
|
|
154
|
+
"data",
|
|
155
|
+
"policies",
|
|
156
|
+
"rules",
|
|
157
|
+
"allowlist",
|
|
158
|
+
"suggestions",
|
|
159
|
+
"items",
|
|
160
|
+
]) {
|
|
161
|
+
if (Array.isArray(p[k])) return p[k] as T[];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function stripUndefined(
|
|
168
|
+
input: Record<string, unknown>,
|
|
169
|
+
): Record<string, unknown> {
|
|
170
|
+
return Object.fromEntries(
|
|
171
|
+
Object.entries(input).filter(([, v]) => v !== undefined),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function pickFirst<T>(...values: Array<T | undefined>): T | undefined {
|
|
176
|
+
for (const v of values) if (v !== undefined) return v;
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function ruleBody(
|
|
181
|
+
host: string,
|
|
182
|
+
params: AllowParams,
|
|
183
|
+
effect: EgressRuleEffect,
|
|
184
|
+
): Record<string, unknown> {
|
|
185
|
+
return stripUndefined({
|
|
186
|
+
host,
|
|
187
|
+
effect,
|
|
188
|
+
methods: params.methods,
|
|
189
|
+
path_glob: pickFirst(params.pathGlob, params.path_glob),
|
|
190
|
+
policy_id: pickFirst(params.policyId, params.policy_id),
|
|
191
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
192
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
193
|
+
note: params.note,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Main resource ─────────────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
export class EgressNetwork {
|
|
200
|
+
constructor(protected readonly http: HttpClient) {}
|
|
201
|
+
|
|
202
|
+
// ── allowlist ─────────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
/** Add an `allow` rule for `host` to the allowlist. */
|
|
205
|
+
async allow(
|
|
206
|
+
host: string,
|
|
207
|
+
params: AllowParams = {},
|
|
208
|
+
): Promise<EgressAllowlistRule> {
|
|
209
|
+
const data = await this.http.post<WireEnvelope<EgressAllowlistRule>>(
|
|
210
|
+
"/egress/allowlist",
|
|
211
|
+
ruleBody(host, params, "allow"),
|
|
212
|
+
);
|
|
213
|
+
return unwrap<EgressAllowlistRule>(data);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Add a `deny` rule for `host` to the allowlist. */
|
|
217
|
+
async deny(
|
|
218
|
+
host: string,
|
|
219
|
+
params: AllowParams = {},
|
|
220
|
+
): Promise<EgressAllowlistRule> {
|
|
221
|
+
const data = await this.http.post<WireEnvelope<EgressAllowlistRule>>(
|
|
222
|
+
"/egress/allowlist",
|
|
223
|
+
ruleBody(host, params, "deny"),
|
|
224
|
+
);
|
|
225
|
+
return unwrap<EgressAllowlistRule>(data);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** List allowlist rules. */
|
|
229
|
+
async rules(params: RulesListParams = {}): Promise<EgressAllowlistRule[]> {
|
|
230
|
+
const query = stripUndefined({
|
|
231
|
+
policy_id: pickFirst(params.policyId, params.policy_id),
|
|
232
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
233
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
234
|
+
}) as Record<string, string | number | boolean | undefined>;
|
|
235
|
+
const data = await this.http.get<unknown>("/egress/allowlist", query);
|
|
236
|
+
return unwrapList<EgressAllowlistRule>(data);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Delete an allowlist rule by id. */
|
|
240
|
+
async removeRule(ruleId: string): Promise<void> {
|
|
241
|
+
await this.http.delete<unknown>(`/egress/allowlist/${ruleId}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── policies ──────────────────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
/** List egress policies. */
|
|
247
|
+
async policies(params: PolicyListParams = {}): Promise<EgressPolicyData[]> {
|
|
248
|
+
const query = stripUndefined({
|
|
249
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
250
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
251
|
+
}) as Record<string, string | number | boolean | undefined>;
|
|
252
|
+
const data = await this.http.get<unknown>("/egress/policies", query);
|
|
253
|
+
return unwrapList<EgressPolicyData>(data);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Create an egress policy. */
|
|
257
|
+
async createPolicy(params: PolicyCreateParams): Promise<EgressPolicyData> {
|
|
258
|
+
const body = stripUndefined({
|
|
259
|
+
name: params.name,
|
|
260
|
+
mode: params.mode ?? "enforce",
|
|
261
|
+
default_effect: pickFirst(
|
|
262
|
+
params.defaultEffect,
|
|
263
|
+
params.default_effect,
|
|
264
|
+
"deny" as EgressRuleEffect,
|
|
265
|
+
),
|
|
266
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
267
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
268
|
+
description: params.description,
|
|
269
|
+
});
|
|
270
|
+
const data = await this.http.post<WireEnvelope<EgressPolicyData>>(
|
|
271
|
+
"/egress/policies",
|
|
272
|
+
body,
|
|
273
|
+
);
|
|
274
|
+
return unwrap<EgressPolicyData>(data);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Update an egress policy by id. */
|
|
278
|
+
async updatePolicy(
|
|
279
|
+
policyId: string,
|
|
280
|
+
params: PolicyUpdateParams,
|
|
281
|
+
): Promise<EgressPolicyData> {
|
|
282
|
+
const body = stripUndefined({
|
|
283
|
+
mode: params.mode,
|
|
284
|
+
default_effect: pickFirst(params.defaultEffect, params.default_effect),
|
|
285
|
+
name: params.name,
|
|
286
|
+
description: params.description,
|
|
287
|
+
});
|
|
288
|
+
const data = await this.http.patch<WireEnvelope<EgressPolicyData>>(
|
|
289
|
+
`/egress/policies/${policyId}`,
|
|
290
|
+
body,
|
|
291
|
+
);
|
|
292
|
+
return unwrap<EgressPolicyData>(data);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── mode helpers ──────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
/** Set the policy to `mode="enforce"` — denied egress is blocked. */
|
|
298
|
+
async lockdown(params: ModeParams = {}): Promise<EgressPolicyData> {
|
|
299
|
+
return this.setMode("enforce", params);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Set the policy to `mode="audit_only"` — log but do not block. */
|
|
303
|
+
async observe(params: ModeParams = {}): Promise<EgressPolicyData> {
|
|
304
|
+
return this.setMode("audit_only", params);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private async setMode(
|
|
308
|
+
mode: EgressPolicyMode,
|
|
309
|
+
params: ModeParams,
|
|
310
|
+
): Promise<EgressPolicyData> {
|
|
311
|
+
const policyId = pickFirst(params.policyId, params.policy_id);
|
|
312
|
+
const resourceId = pickFirst(params.resourceId, params.resource_id);
|
|
313
|
+
const resourceType = pickFirst(params.resourceType, params.resource_type);
|
|
314
|
+
|
|
315
|
+
if (policyId) {
|
|
316
|
+
return this.updatePolicy(policyId, { mode });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const body =
|
|
320
|
+
resourceId !== undefined && resourceType !== undefined
|
|
321
|
+
? stripUndefined({
|
|
322
|
+
mode,
|
|
323
|
+
resource_id: resourceId,
|
|
324
|
+
resource_type: resourceType,
|
|
325
|
+
})
|
|
326
|
+
: { mode };
|
|
327
|
+
const data = await this.http.patch<WireEnvelope<EgressPolicyData>>(
|
|
328
|
+
"/egress/policies",
|
|
329
|
+
body,
|
|
330
|
+
);
|
|
331
|
+
return unwrap<EgressPolicyData>(data);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── suggestions ───────────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
/** AI-generated allowlist suggestions from recent denied egress. */
|
|
337
|
+
async suggestions(
|
|
338
|
+
params: SuggestionsParams = {},
|
|
339
|
+
): Promise<EgressSuggestion[]> {
|
|
340
|
+
const query = stripUndefined({
|
|
341
|
+
resource_id: pickFirst(params.resourceId, params.resource_id),
|
|
342
|
+
resource_type: pickFirst(params.resourceType, params.resource_type),
|
|
343
|
+
since: params.since ?? "7d",
|
|
344
|
+
}) as Record<string, string | number | boolean | undefined>;
|
|
345
|
+
const data = await this.http.get<unknown>(
|
|
346
|
+
"/egress/audit/suggestions",
|
|
347
|
+
query,
|
|
348
|
+
);
|
|
349
|
+
return unwrapList<EgressSuggestion>(data);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ── Resource-scoped wrappers ─────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Sandbox-bound view of {@link EgressNetwork}. Pre-scopes
|
|
357
|
+
* `resource_id` + `resource_type="sandbox"` on every call.
|
|
358
|
+
*/
|
|
359
|
+
export class SandboxNetwork {
|
|
360
|
+
protected readonly resourceType: string = "sandbox";
|
|
361
|
+
private readonly delegate: EgressNetwork;
|
|
362
|
+
|
|
363
|
+
constructor(
|
|
364
|
+
http: HttpClient,
|
|
365
|
+
protected readonly resourceId: string,
|
|
366
|
+
) {
|
|
367
|
+
this.delegate = new EgressNetwork(http);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private resolvedResourceId(params: {
|
|
371
|
+
resourceId?: string;
|
|
372
|
+
resource_id?: string;
|
|
373
|
+
}): string {
|
|
374
|
+
return params.resourceId ?? params.resource_id ?? this.resourceId;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private resolvedResourceType(params: {
|
|
378
|
+
resourceType?: string;
|
|
379
|
+
resource_type?: string;
|
|
380
|
+
}): string {
|
|
381
|
+
return params.resourceType ?? params.resource_type ?? this.resourceType;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
allow(host: string, params: AllowParams = {}): Promise<EgressAllowlistRule> {
|
|
385
|
+
return this.delegate.allow(host, {
|
|
386
|
+
...params,
|
|
387
|
+
resource_id: this.resolvedResourceId(params),
|
|
388
|
+
resource_type: this.resolvedResourceType(params),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
deny(host: string, params: AllowParams = {}): Promise<EgressAllowlistRule> {
|
|
393
|
+
return this.delegate.deny(host, {
|
|
394
|
+
...params,
|
|
395
|
+
resource_id: this.resolvedResourceId(params),
|
|
396
|
+
resource_type: this.resolvedResourceType(params),
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
rules(params: RulesListParams = {}): Promise<EgressAllowlistRule[]> {
|
|
401
|
+
return this.delegate.rules({
|
|
402
|
+
...params,
|
|
403
|
+
resource_id: this.resolvedResourceId(params),
|
|
404
|
+
resource_type: this.resolvedResourceType(params),
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
removeRule(ruleId: string): Promise<void> {
|
|
409
|
+
return this.delegate.removeRule(ruleId);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
lockdown(params: { policyId?: string } = {}): Promise<EgressPolicyData> {
|
|
413
|
+
const body: ModeParams = {
|
|
414
|
+
resource_id: this.resourceId,
|
|
415
|
+
resource_type: this.resourceType,
|
|
416
|
+
};
|
|
417
|
+
if (params.policyId !== undefined) body.policyId = params.policyId;
|
|
418
|
+
return this.delegate.lockdown(body);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
observe(params: { policyId?: string } = {}): Promise<EgressPolicyData> {
|
|
422
|
+
const body: ModeParams = {
|
|
423
|
+
resource_id: this.resourceId,
|
|
424
|
+
resource_type: this.resourceType,
|
|
425
|
+
};
|
|
426
|
+
if (params.policyId !== undefined) body.policyId = params.policyId;
|
|
427
|
+
return this.delegate.observe(body);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
suggestions(params: { since?: string } = {}): Promise<EgressSuggestion[]> {
|
|
431
|
+
const body: SuggestionsParams = {
|
|
432
|
+
resource_id: this.resourceId,
|
|
433
|
+
resource_type: this.resourceType,
|
|
434
|
+
};
|
|
435
|
+
if (params.since !== undefined) body.since = params.since;
|
|
436
|
+
return this.delegate.suggestions(body);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
policies(): Promise<EgressPolicyData[]> {
|
|
440
|
+
return this.delegate.policies({
|
|
441
|
+
resource_id: this.resourceId,
|
|
442
|
+
resource_type: this.resourceType,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Computer-bound network — same surface, `resource_type="computer"`. */
|
|
448
|
+
export class ComputerNetwork extends SandboxNetwork {
|
|
449
|
+
protected readonly resourceType: string = "computer";
|
|
450
|
+
}
|