Last updated

/iblai-vibe-virtual-machine

Mirrored from iblai/vibe · skills/organizations/iblai-vibe-virtual-machine/SKILL.md. This page is generated — edit it in the repository, not here.

Add the organization settings Virtual Machine tab -- "Manage the network policies and secrets available to agents' virtual machines." Agents on the Virtual Machine Shell sandbox kind run each chat in an isolated Linux VM with no network access by default. This surface holds the two organization-wide records that open it up, in two sub-tabs:

  • Network Policies — reusable named lists of the host:port destinations a VM may reach (up to 100 entries each).
  • Secrets — keys the VM can use but never read. Inside the machine the environment variable holds a placeholder; the real value is substituted only on TLS requests to the secret's allowed hosts.

Both are shared by every agent in the organization. Picking them for one agent happens in its Sandbox settings — see /iblai-vibe-agent-virtual-machine.

Virtual Machine — Network Policies
Virtual Machine — Network Policies
Virtual Machine — Secrets
Virtual Machine — Secrets
New Network Policy dialog
New Network Policy dialog
New VM Secret dialog — Value Source picks a stored value or an integration credential
New VM Secret dialog — Value Source picks a stored value or an integration credential
Network Policies — empty state
Network Policies — empty state
Secrets — empty state
Secrets — empty state

Common setup (brand, conventions, env files, verification): see docs/skill-setup.md.

  • /iblai-vibe-agent-virtual-machine — the agent's Sandbox → Network Access section: the egress profile, the policy picker and the secrets multi-select that consume these records. Its Manage Policies / Manage Secrets buttons open this exact surface in a popup.
  • /iblai-vibe-agent-sandbox — the Sandbox Type card that turns Virtual Machine Shell on in the first place.
  • /iblai-vibe-account — the Account settings page hosts this surface as its Virtual Machine tab (see Step 2).
  • /iblai-vibe-credential — the organization's integration credentials; a secret can read its value from one field of a saved credential instead of storing its own.

Prerequisites

  • Auth must be set up first (/iblai-vibe-auth)
  • MCP server + skills configured (@iblai/mcp in .mcp.json)
  • The viewer needs Ibl.Mentor/VirtualMachineNetworkPolicies/{list,write,delete} and Ibl.Mentor/VirtualMachineSecrets/{list,write,delete}. Organization admins hold them by default; they are not in the learner baseline. Each section hides on its own list 403, and when both lists are forbidden the tab renders a single permission notice.
  • Only agents on the Virtual Machine Shell kind consume these records. Nothing breaks without one, but nothing uses them either.

Step 1: Check Environment

Before proceeding, check for an iblai.env in the project root. Look for PLATFORM, DOMAIN, and TOKEN variables. If the file does not exist or is missing these variables, tell the user: "You need an iblai.env with your platform configuration. Download the template and fill in your values: curl -o iblai.env https://raw.githubusercontent.com/iblai/vibe/refs/heads/main/iblai.env"

Step 2: Mount it

Two ways, both supported.

As the Account page's Virtual Machine tab (how the SPAs ship it):

// app/(app)/settings/virtual-machine/page.tsx
"use client";

import { Account } from "@iblai/iblai-js/web-containers/next";

export default function OrgVirtualMachinePage() {
  return (
    <div className="flex h-full flex-col bg-white">
      {/* accountProps = your usual <Account> wiring */}
      <Account {...accountProps} targetTab="virtualMachine" />
    </div>
  );
}

See /iblai-vibe-account Step 2 for the full <Account> prop wiring.

Standalone — unlike the Memory admin tab, this one is exported:

"use client";

import { VirtualMachineAdminTab } from "@iblai/iblai-js/web-containers";

export default function VirtualMachinePage({
  tenantKey,
  username,
}: {
  tenantKey: string;
  username: string;
}) {
  return (
    <VirtualMachineAdminTab
      tenantKey={tenantKey}
      username={username}
      defaultTab="network-policies"
    />
  );
}

Step 3: Use MCP Tools for Customization

get_component_info("VirtualMachineAdminTab")
get_component_info("HostPortChipInput")
get_hook_info("useListVirtualMachineSecretsQuery")

Component Props

<VirtualMachineAdminTab> — @iblai/iblai-js/web-containers

PropTypeRequiredDescription
tenantKeystringYesOrganization key whose policies and secrets are managed
usernamestringNoSigned-in admin. When present, a policy delete refused because agents still use it lists those agents by name instead of by id
defaultTab'network-policies' | 'secrets'NoWhich sub-tab opens first; falls back to the other one when that list is forbidden

Component map (web-containers source)

Everything lives under packages/web-containers/src/components/ in the SDK. Only VirtualMachineAdminTab and HostPortChipInput are public exports; the rest are listed as the reference for custom builds.

ComponentPathRole
VirtualMachineAdminTabprofile/virtual-machine-admin/index.tsxThe surface: info box, permission gate, Network Policies / Secrets sub-tabs
NetworkPoliciesSectionprofile/virtual-machine-admin/network-policies-section.tsxPolicies table + New / Edit / Delete, including the "still in use" refusal
VmSecretsSectionprofile/virtual-machine-admin/vm-secrets-section.tsxSecrets table + New / Edit / Delete, and the Source column
NetworkPolicyDialogvirtual-machine/network-policy-dialog.tsxCreate / edit a policy. Also opened straight from the agent's Create Policy button
VmSecretDialogvirtual-machine/vm-secret-dialog.tsxCreate / edit a secret, including the Value Source choice
HostPortChipInputvirtual-machine/host-port-chip-input.tsxThe host:port chip input both dialogs use (Enter / comma / space / paste, per-entry validation, keyboard-removable chips)
toIntegrationCredentialOptionsvirtual-machine/credential-options.tsNormalises the integration-credential list into { id, name, fields } — only ids, names and the keys of the masked value

What each sub-tab renders

Network Policies

  • New Policy opens the dialog; each row has pencil (edit) and trash (delete) actions.
  • Columns: Name, Allowed Hosts (first 3 as chips, then "+N more"), Description (— when blank), Updated (time-ago), Actions.
  • Empty: "No network policies yet. Create one to let an agent's VM reach specific hosts."
  • New / Edit Network Policy dialog: Name (unique in the organization, ≤ 255 chars), Allowed Hosts (1–100 host:port entries, IP addresses allowed), Description (optional). Editing sends only the fields that changed, and removing a host raises an amber warning that a bound secret needing it will stop working — the backend allows the edit anyway.
  • Delete asks to confirm. If an agent still uses the policy the backend refuses: the dialog then lists the blocking agents (by name when username lets it resolve them, else by id) and hides the Delete button until you move those agents off it.

Secrets

  • New Secret opens the dialog; pencil and trash per row.
  • Columns: Name, Variable (the env var as a code chip), Allowed Hosts, Source (Stored Value, or Credential {name} · {field}), Updated, Actions.
  • Empty: "No VM secrets yet. Create one to let an agent call an API with your organization's key without ever seeing it."
  • New / Edit VM Secret dialog:
    • Name — display name, ≤ 255 chars.
    • Environment Variable — e.g. ACME_KEY; letters, digits and underscores, cannot start with a digit. Read-only once created — delete and recreate to rename.
    • Allowed Hosts — hostnames only, no IPs. These are the only hosts the real value is ever sent to, so keep them narrow.
    • Value Source — Enter a Value (stored encrypted, never shown again, at least 8 characters) or Use an Integration Credential (pick a saved credential and one of its fields; the secret follows rotations automatically).
    • On edit the value field is never prefilled — blank keeps the current value, and typing a new one replaces it (dropping any credential link).
  • Delete warns that the secret is also removed from every agent that uses it, then deletes.

REST API

For custom UI. Both records live under ${dmUrl}/api/ai-account/orgs/{org}/ where dmUrl is NEXT_PUBLIC_API_BASE_URL. Auth: Authorization: Token <token>. Records are keyed in the body, never in the URL.

Network policies — virtual-machine-network-policy/

MethodBody / notes
GET[{ id, name, allowed_hosts, description, created_at, updated_at }]
POST{ "name": "Acme APIs", "allowed_hosts": ["api.acme.com:443"], "description": "" } → 201
PATCH{ "id": 12, … } — any of name, allowed_hosts (replaces the whole list), description
DELETE{ "id": 12 } → 204; 400 while an agent still uses it, and the message names the agents

Secrets — virtual-machine-secret/

MethodBody / notes
GET[{ id, name, env_var, allow_hosts, source_credential_id, source_field, created_at, updated_at }] — never a value
POST{ "name", "env_var": "ACME_KEY", "allow_hosts": ["api.acme.com:443"], "value": "…" } or { …, "source_credential_id": 7, "source_field": "api_key" } — exactly one form
PATCHkeyed by { "env_var": "ACME_KEY" }; sending value or a source_credential_id switches the form. env_var itself cannot change
DELETE{ "env_var": "ACME_KEY" } → 204; also unbinds it from every agent

Integration credentials for the second form come from GET /api/ai-account/orgs/{org}/integration-credential/, which returns [{ id, name, value: { api_key: "sk-****", … } }]. Only the ids, names and the keys of value are used — masked values are never shown.

Validation the UI mirrors

RuleConstant
host:port, no scheme, path or wildcard; port 1–65535VIRTUAL_MACHINE_HOST_PORT_PATTERN
localhost, metadata*, instance-data* and .local / .internal / .localhost suffixes refused; reserved IP ranges refusedVIRTUAL_MACHINE_REJECTED_HOSTS, VIRTUAL_MACHINE_REJECTED_HOST_SUFFIXES
Policies accept IP addresses, secrets do notHostPortChipInput's allowIpAddresses
≤ 100 hosts per policyVIRTUAL_MACHINE_MAX_POLICY_HOSTS
≤ 20 secrets per agentVIRTUAL_MACHINE_MAX_SECRETS_PER_MENTOR
Stored value ≥ 8 characters, and a masked-looking value (sk-****) is refusedVIRTUAL_MACHINE_SECRET_VALUE_MIN_LENGTH
env_var matches ^[A-Za-z_][A-Za-z0-9_]*$VIRTUAL_MACHINE_ENV_VAR_PATTERN

Every 400 comes back as { error } — either a plain message or { field: ["…"] }.

From @iblai/iblai-js/web-containers:

  • VirtualMachineAdminTab, VirtualMachineAdminTabProps — this surface.
  • HostPortChipInput, HostPortChipInputProps — the host:port chip input, reusable for any allowlist field.
  • VirtualMachineNetworkSection — the per-agent binding section (/iblai-vibe-agent-virtual-machine).

From @iblai/iblai-js/web-containers/next:

  • Account — the settings page hosting this as its Virtual Machine tab (targetTab="virtualMachine").

From @iblai/iblai-js/data-layer:

  • Policies: useListVirtualMachineNetworkPoliciesQuery, useLazyListVirtualMachineNetworkPoliciesQuery, useCreateVirtualMachineNetworkPolicyMutation, useUpdateVirtualMachineNetworkPolicyMutation, useDeleteVirtualMachineNetworkPolicyMutation
  • Secrets: useListVirtualMachineSecretsQuery, useLazyListVirtualMachineSecretsQuery, useCreateVirtualMachineSecretMutation, useUpdateVirtualMachineSecretMutation, useDeleteVirtualMachineSecretMutation
  • Credentials for the Value Source picker: useGetIntegrationCredentialsQuery
  • Helpers: validateHostPort, isValidHostPort, splitHostPortInput, normalizeHostPort, getVirtualMachineApiError, getVirtualMachineErrorStatus, extractBoundMentorIds
  • Constants: VIRTUAL_MACHINE_ENDPOINTS, VIRTUAL_MACHINE_PERMISSIONS, VIRTUAL_MACHINE_EGRESS_PROFILES, VIRTUAL_MACHINE_MAX_POLICY_HOSTS, VIRTUAL_MACHINE_MAX_SECRETS_PER_MENTOR, VIRTUAL_MACHINE_SECRET_VALUE_MIN_LENGTH, VIRTUAL_MACHINE_ENV_VAR_PATTERN, VIRTUAL_MACHINE_HOST_PORT_PATTERN, VIRTUAL_MACHINE_REJECTED_HOSTS, VIRTUAL_MACHINE_REJECTED_HOST_SUFFIXES
  • Types: VirtualMachineNetworkPolicy, VirtualMachineSecret, VirtualMachineApiError, VirtualMachineHostPortIssue

SDK source: packages/web-containers/src/components/profile/virtual-machine-admin/, components/virtual-machine/, and packages/data-layer/src/features/virtual-machine/.

Step 4: Verify

Run /iblai-vibe-ops-test before telling the user the work is ready:

  1. pnpm build -- must pass with zero errors
  2. pnpm test -- vitest must pass
  3. Touch test /settings/virtual-machine in the browser as /iblai-vibe-ops-test says: open it, use every control this skill added, read the console, check the phone width.

Important Notes

  • Redux store: Must include mentorReducer and mentorMiddleware — they register virtualMachineApiSlice, which these hooks live on.
  • initializeDataLayer(): 5 args (v1.2+)
  • @reduxjs/toolkit: Deduplicated via webpack aliases in next.config.ts
  • Peer deps: sonner and @iblai/iblai-web-mentor must be installed (pnpm add sonner @iblai/iblai-web-mentor)
  • Records are keyed in the body. PATCH and DELETE take id (policies) or env_var (secrets) in the request body, not in the path. These endpoints are newer than the pinned @iblai/iblai-api release, so the slice is hand-written.
  • A value is never returned. No endpoint hands back a secret's value, the table only shows its source, and the edit form starts blank. Never build UI that tries to display one.
  • env_var is immutable. Deleting and recreating is the rename path — and the delete unbinds the secret from every agent first.
  • Per-section 403. The two lists gate independently: an admin may see policies but not secrets. Both forbidden → one permission notice for the whole tab.
  • Deleting a policy an agent uses is refused, deliberately, so an agent on the Custom profile never points at a policy that vanished. Move those agents to another policy or profile first.
  • Scope is one organization. Policies and secrets are never shared across organizations.
  • Billing: VM lifetime is charged to the chatting user's credits — $1 per 10 minutes by default, prorated per second and overridable per organization. The agent-side section states this next to the controls.
  • Brand guidelines: BRAND.md

Copyright © ibl.ai | support@iblai.zendesk.com