@intentic/sandbox-contract 1.151.0 → 1.152.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/contracts/capabilities.contract.d.ts +36 -3
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/vpn.contract.d.ts +63 -0
- package/dist/contracts/vpn.contract.d.ts.map +1 -0
- package/dist/contracts/vpn.contract.js +10 -0
- package/dist/contracts/vpn.contract.js.map +1 -0
- package/dist/index.d.ts +103 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +224 -5
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +68 -2
- package/dist/schemas.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/vpn.contract.ts +32 -0
- package/src/index.ts +3 -0
- package/src/schemas.ts +135 -7
package/src/schemas.ts
CHANGED
|
@@ -940,12 +940,61 @@ export const SshConfigSchema = z.discriminatedUnion("auth", [
|
|
|
940
940
|
password: z.string().min(1),
|
|
941
941
|
}),
|
|
942
942
|
]);
|
|
943
|
-
//
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
|
|
943
|
+
// ---- vpn ----
|
|
944
|
+
// A VPN the agent's traffic rides. One capability = one tunnel, discriminated by `provider` so a new protocol
|
|
945
|
+
// is a new arm (plus a driver in the daemon's vpn/), never a reinterpretation of an existing field:
|
|
946
|
+
// wireguard — a pasted .conf, brought up with wg-quick.
|
|
947
|
+
// fortinet — a FortiGate SSL-VPN (what FortiClient's <sslvpn> connections speak), dialled with openconnect
|
|
948
|
+
// --protocol=fortinet. openconnect is the client rather than openfortivpn because it routes over
|
|
949
|
+
// tun instead of pppd: it needs exactly the tun + NET_ADMIN grant this kind already carries, and
|
|
950
|
+
// no /dev/ppp device (which the runtime allowlist does not — and should not — include).
|
|
951
|
+
// ipsec — an IKEv1/IKEv2 tunnel with a pre-shared key and optional XAuth (FortiClient's <ipsecvpn>
|
|
952
|
+
// connections), run by strongSwan. `aggressive` mirrors FortiClient's dial-up default.
|
|
953
|
+
// Connecting is NOT a config field: connect/disconnect are live operations (see vpn.contract.ts) that both the
|
|
954
|
+
// user and the agent drive, so a stored tunnel's up/down state is read from the OS, never from the manifest.
|
|
955
|
+
// `autoConnect` is the only persisted intent — whether the daemon dials this tunnel again on boot.
|
|
956
|
+
export const VpnProviderSchema = z.enum(["wireguard", "fortinet", "ipsec"]);
|
|
957
|
+
export type VpnProvider = z.infer<typeof VpnProviderSchema>;
|
|
958
|
+
|
|
959
|
+
const autoConnect = z.enum(["on", "off"]).default("on");
|
|
960
|
+
|
|
961
|
+
export const WireguardVpnConfigSchema = z.object({
|
|
962
|
+
provider: z.literal("wireguard"),
|
|
963
|
+
// The pasted .conf ([Interface] + [Peer]) — it holds the private key, so it's this arm's secret field.
|
|
964
|
+
config: z.string().min(1),
|
|
965
|
+
autoConnect,
|
|
966
|
+
});
|
|
967
|
+
export const FortinetVpnConfigSchema = z.object({
|
|
968
|
+
provider: z.literal("fortinet"),
|
|
969
|
+
// Gateway host only; the port is its own field so a pasted "host:port" can be split on import.
|
|
970
|
+
server: z.string().min(1),
|
|
971
|
+
port: z.coerce.number().int().min(1).max(65535).default(443),
|
|
972
|
+
username: z.string().min(1),
|
|
973
|
+
password: z.string().min(1),
|
|
974
|
+
// A FortiGate on a self-signed/private-CA certificate: openconnect pins this digest
|
|
975
|
+
// ("sha256:…", copied from its own refusal message) instead of trusting a CA. Absent ⇒ normal CA validation.
|
|
976
|
+
trustedCert: z.string().min(1).optional(),
|
|
977
|
+
// Some gateways scope a login to a realm/group (openconnect --usergroup, FortiClient's tunnel realm).
|
|
978
|
+
realm: z.string().min(1).optional(),
|
|
979
|
+
autoConnect,
|
|
980
|
+
});
|
|
981
|
+
export const IpsecVpnConfigSchema = z.object({
|
|
982
|
+
provider: z.literal("ipsec"),
|
|
983
|
+
server: z.string().min(1),
|
|
984
|
+
presharedKey: z.string().min(1),
|
|
985
|
+
// The local IKE identity (FortiClient's <localid>) — dial-up FortiGates key their phase-1 selection off it.
|
|
986
|
+
localId: z.string().min(1).optional(),
|
|
987
|
+
remoteId: z.string().min(1).optional(),
|
|
988
|
+
// XAuth (FortiClient's <xauth>) — absent for PSK-only tunnels.
|
|
989
|
+
username: z.string().min(1).optional(),
|
|
990
|
+
password: z.string().min(1).optional(),
|
|
991
|
+
ikeVersion: z.enum(["1", "2"]).default("1"),
|
|
992
|
+
// IKEv1 aggressive mode: insecure by construction, and exactly what FortiGate dial-up with a group PSK
|
|
993
|
+
// requires — hence opt-in per connection rather than a global strongSwan setting.
|
|
994
|
+
aggressive: z.enum(["on", "off"]).default("on"),
|
|
995
|
+
autoConnect,
|
|
996
|
+
});
|
|
997
|
+
export const VpnConfigSchema = z.discriminatedUnion("provider", [WireguardVpnConfigSchema, FortinetVpnConfigSchema, IpsecVpnConfigSchema]);
|
|
949
998
|
// A logged-in browser session the AGENT drives via Playwright MCP tools — for social platforms whose APIs can't
|
|
950
999
|
// cover "all the actions" (X reads are paywalled; X community-join and YouTube community-posts have no API). No
|
|
951
1000
|
// secret in the manifest: the session lives in a persisted Chromium profile under .intentic/browser/<platform>,
|
|
@@ -973,6 +1022,9 @@ export type CliConfig = z.infer<typeof CliConfigSchema>;
|
|
|
973
1022
|
export type PluginConfig = z.infer<typeof PluginConfigSchema>;
|
|
974
1023
|
export type ExtensionConfig = z.infer<typeof ExtensionConfigSchema>;
|
|
975
1024
|
export type SshConfig = z.infer<typeof SshConfigSchema>;
|
|
1025
|
+
export type WireguardVpnConfig = z.infer<typeof WireguardVpnConfigSchema>;
|
|
1026
|
+
export type FortinetVpnConfig = z.infer<typeof FortinetVpnConfigSchema>;
|
|
1027
|
+
export type IpsecVpnConfig = z.infer<typeof IpsecVpnConfigSchema>;
|
|
976
1028
|
export type VpnConfig = z.infer<typeof VpnConfigSchema>;
|
|
977
1029
|
export type BrowserPlatform = z.infer<typeof BrowserPlatformSchema>;
|
|
978
1030
|
export type BrowserConfig = z.infer<typeof BrowserConfigSchema>;
|
|
@@ -990,7 +1042,9 @@ export const CapabilitySchema = z.discriminatedUnion("kind", [
|
|
|
990
1042
|
z.object({ id: entryId, kind: z.literal("plugin"), config: PluginConfigSchema }),
|
|
991
1043
|
z.object({ id: entryId, kind: z.literal("extension"), config: ExtensionConfigSchema }),
|
|
992
1044
|
z.object({ id: entryId, kind: z.literal("ssh"), config: SshConfigSchema }),
|
|
993
|
-
|
|
1045
|
+
// No IFNAMSIZ cap on the id: the tunnel's interface name is DERIVED (see the daemon's vpn/vpn-paths.ts
|
|
1046
|
+
// interfaceName) rather than being the id itself, so a descriptive name is free.
|
|
1047
|
+
z.object({ id: entryId, kind: z.literal("vpn"), config: VpnConfigSchema }),
|
|
994
1048
|
// The in-sandbox Docker Engine (baked into the base image, dormant by default). No config: the capability's
|
|
995
1049
|
// whole effect is its fragment's `--privileged` runtime directive + running dockerd. No remove — the engine's
|
|
996
1050
|
// state (/var/lib/docker) and whatever runs on it make a silent de-privilege more destructive than useful.
|
|
@@ -1018,6 +1072,80 @@ export const CapabilitySecretInputSchema = z.object({ id: z.string(), value: z.s
|
|
|
1018
1072
|
// which the web surfaces in the terminal panel for the user to complete the sign-in.
|
|
1019
1073
|
export const CapabilityLoginSchema = z.object({ session: z.string() });
|
|
1020
1074
|
|
|
1075
|
+
// ---- vpn: live tunnel state + connect/disconnect ----
|
|
1076
|
+
// The manifest says which VPNs EXIST; this says which are UP right now. Every field is read back from the OS
|
|
1077
|
+
// (wg show / ip / openconnect's pidfile / swanctl), never remembered by the daemon — so a tunnel the agent
|
|
1078
|
+
// dropped from a shell and one the UI dropped read identically, and a daemon restart loses nothing.
|
|
1079
|
+
|
|
1080
|
+
export const VpnStateSchema = z.enum([
|
|
1081
|
+
// The tunnel is up and carrying traffic.
|
|
1082
|
+
"connected",
|
|
1083
|
+
// Dialling: openconnect authenticated but the interface has no address yet, or strongSwan is negotiating.
|
|
1084
|
+
"connecting",
|
|
1085
|
+
// Configured and idle — the normal resting state for a tunnel nobody asked for.
|
|
1086
|
+
"disconnected",
|
|
1087
|
+
// The tunnel's client isn't installed yet: the capability's image fragment needs an owner-run rebuild.
|
|
1088
|
+
"unavailable",
|
|
1089
|
+
// The last dial failed; `detail` carries the client's own message.
|
|
1090
|
+
"failed",
|
|
1091
|
+
]);
|
|
1092
|
+
export type VpnState = z.infer<typeof VpnStateSchema>;
|
|
1093
|
+
|
|
1094
|
+
export const VpnLinkSchema = z.object({
|
|
1095
|
+
id: z.string(),
|
|
1096
|
+
provider: VpnProviderSchema,
|
|
1097
|
+
state: VpnStateSchema,
|
|
1098
|
+
// The gateway this tunnel dials — host:port for fortinet, the [Peer] endpoint for wireguard, the IKE peer
|
|
1099
|
+
// for ipsec. Display only; never a secret.
|
|
1100
|
+
gateway: z.string().optional(),
|
|
1101
|
+
// The tun/wg interface carrying the tunnel, once it exists.
|
|
1102
|
+
interface: z.string().optional(),
|
|
1103
|
+
// The address the gateway assigned this sandbox — the single most useful "am I on the VPN?" fact.
|
|
1104
|
+
address: z.string().optional(),
|
|
1105
|
+
// The CIDRs routed into the tunnel ("0.0.0.0/0" = full tunnel). Empty until the link is up.
|
|
1106
|
+
routes: z.array(z.string()).default([]),
|
|
1107
|
+
// DNS servers the tunnel pushed, when it pushed any.
|
|
1108
|
+
dns: z.array(z.string()).default([]),
|
|
1109
|
+
// Epoch ms the link came up — the UI renders "connected 14m ago". Absent unless connected.
|
|
1110
|
+
since: z.number().optional(),
|
|
1111
|
+
// Whether the daemon re-dials this tunnel on boot (the manifest's autoConnect).
|
|
1112
|
+
autoConnect: z.boolean(),
|
|
1113
|
+
// Why it is failed/unavailable, or an extra note on a healthy link. Never carries credentials.
|
|
1114
|
+
detail: z.string().optional(),
|
|
1115
|
+
});
|
|
1116
|
+
export type VpnLink = z.infer<typeof VpnLinkSchema>;
|
|
1117
|
+
export const VpnListSchema = z.object({ links: z.array(VpnLinkSchema) });
|
|
1118
|
+
|
|
1119
|
+
// POST /vpn/{id}/connect body. `otp` is a one-time 2FA code, supplied per dial and NEVER stored — a FortiGate
|
|
1120
|
+
// with token auth rejects the dial without it, and the daemon surfaces that as a retry-with-a-code error.
|
|
1121
|
+
export const VpnConnectInputSchema = z.object({ id: z.string(), otp: z.string().min(1).optional() });
|
|
1122
|
+
export const VpnIdParamSchema = z.object({ id: z.string() });
|
|
1123
|
+
|
|
1124
|
+
// POST /vpn/import-forticlient: parse an exported FortiClient configuration (the XML FortiClient writes from
|
|
1125
|
+
// File → Settings → Backup) into addable connections. Credentials in that file are wrapped in FortiClient's
|
|
1126
|
+
// proprietary "EncX …" encryption, which is NOT reversible here — so a parsed connection carries the endpoint
|
|
1127
|
+
// and, when it was stored in the clear, the username; the password is always typed by the user afterwards.
|
|
1128
|
+
export const ForticlientImportInputSchema = z.object({ xml: z.string().min(1) });
|
|
1129
|
+
export const ForticlientConnectionSchema = z.object({
|
|
1130
|
+
// FortiClient's connection name, slugged into a legal capability id.
|
|
1131
|
+
id: z.string(),
|
|
1132
|
+
// The original <name>, shown so the user recognises the connection they picked.
|
|
1133
|
+
label: z.string(),
|
|
1134
|
+
provider: VpnProviderSchema,
|
|
1135
|
+
server: z.string(),
|
|
1136
|
+
port: z.number(),
|
|
1137
|
+
// Present only when FortiClient stored it unencrypted; an EncX-wrapped username is dropped, not guessed.
|
|
1138
|
+
username: z.string().optional(),
|
|
1139
|
+
description: z.string().optional(),
|
|
1140
|
+
// ipsec-only, and only when the file stored them in the clear.
|
|
1141
|
+
localId: z.string().optional(),
|
|
1142
|
+
aggressive: z.boolean().optional(),
|
|
1143
|
+
// What the user still has to supply for this connection to dial (always at least the password).
|
|
1144
|
+
needs: z.array(z.string()),
|
|
1145
|
+
});
|
|
1146
|
+
export type ForticlientConnection = z.infer<typeof ForticlientConnectionSchema>;
|
|
1147
|
+
export const ForticlientImportSchema = z.object({ connections: z.array(ForticlientConnectionSchema) });
|
|
1148
|
+
|
|
1021
1149
|
// Browse a Claude Code plugin marketplace (a git repo with .claude-plugin/marketplace.json). POST so the
|
|
1022
1150
|
// optional token for a private marketplace never rides a URL or an access log.
|
|
1023
1151
|
export const MarketplaceRequestSchema = z.object({ url: z.string().url(), token: z.string().min(1).optional() });
|