Port Forwarding and Mapping
Users, developers, and agents sometimes need to debug websites located in sandboxes. Due to the isolation nature of sandbox environments, traditional debugging methods may not apply directly. In such cases, you may need to use port forwarding and other techniques to provide remote access capabilities to the outside (to you).
When you need to map a website's service port (HTTP/WebSocket) to the external network for debugging purposes, you can use Lybic's web port mapping feature.
Creating a Mapping
Creating a mapping will forward a specified Web port (HTTP-based, supporting WebSocket Upgrade protocol) that is listening on the localhost (LISTEN localhost:*) within the sandbox to an externally accessible URL such as https://random-strs.port.lybic.cn.
You can create a mapping using the following API:
POST /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings
{
"targetEndpoint": "" // Target TCP endpoint, e.g., 127.0.0.1:3000
}Upon successful creation, the API will return the mapping URL and authentication information:
{
"domain": "string", // Access domain
"accessToken": "string", // Authentication token required when accessing this URL
"targetEndpoint": "" // Target TCP endpoint
}Accessing the Mapped Service
To ensure service security, based on our content policy, users must authenticate when accessing mapped services. Depending on your needs, you can choose either one of the following two authentication schemes.
Header-based Authentication
Simply include the X-Gateway-Access-Token field in the request header, with its value set to the accessToken returned when the mapping was created.
Example:
curl -H "X-Gateway-Access-Token: <accessToken>" \
https://<domain>URL Parameter Authentication
When accessing the mapped URL, include the x-gateway-access-token parameter, with its value set to the accessToken returned when the mapping was created.
URL parameter authentication is subject to browser access policy restrictions. Please refer to the following section for more details.
Example:
curl "https://<domain>?x-gateway-access-token=<accessToken>"Accessing via Browser
Based on our content policy, users cannot directly access port mapping content through the browser address bar; however, accessing via the fetch or XHR API is not subject to this restriction.
Technically, if you use URL parameter authentication, we will check the User-Agent request header. If the User-Agent contains Mozilla/5.0 and URL parameter authentication is used, we will deny the access and return an HTTP 403 response.
If you need to directly access the port-mapped service through the browser address bar, we recommend that you set up your own reverse proxy service, or host a webpage under your own domain, and use a Service Worker to add request headers during requests. Alternatively, you can contact our business team to obtain custom domain support.
Host Header
When forwarding requests, we rewrite the Host header to localhost and move the original header value to X-Gateway-Host.
This is because common tools and software (such as CDP and Vite) validate the HTTP Host header to ensure it matches localhost.
Since Lybic's gateway service implements its own Access Token security checks, these tool-specific security measures are redundant.
You can use the X-Forwarded-Host header, which the gateway will automatically rewrite as the Host header.
This allows you to rewrite the Host freely. For example:
curl "https://<domain>" \
-H "X-Gateway-Access-Token: $TOKEN" \
-H "X-Forwarded-Host: example.com"In this case, the HTTP request received by the forwarding target will be as follows:
GET / HTTP/1.1
Host: example.com
User-Agent: curl/x.y.z
X-Forwarded-Host: example.com
X-Gateway-Host: <domain>
X-Gateway-Access-Token: <token>Deleting a Mapping
Deleting a mapping will remove the specified mapping.
You can delete a mapping using the following API:
DELETE /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings/<targetEndpoint>
Resp: 200 No Content
Getting the Mapping List
You can retrieve a list of all port mappings in a sandbox using the following API:
GET /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings
Resp:
[
{
"domain": "string",
"targetEndpoint": "127.0.0.1:3000",
"accessToken": "string"
}
]Getting a Single Mapping Information
You can retrieve information about a single port mapping using the following API:
GET /api/orgs/<orgId>/sandboxes/<sandboxId>/mappings/<targetEndpoint>
Resp:
{
"domain": "string",
"targetEndpoint": "127.0.0.1:3000",
"accessToken": "string"
}Examples
1. Playwright Launches Chromium, Exposing and Mapping CDP Port URL and Playwright Control Port
Refer to Running Long-Running Asynchronous Tasks, run the following script to launch Chromium:
import { chromium } from 'playwright';
(async () => {
const server = await chromium.launchServer({
headless: true,
args: [
'--remote-debugging-port=9222', // Expose CDP port
'--no-sandbox'
]
});
const wsEndpoint = server.wsEndpoint();
console.log('Playwright Ws endpoint:', wsEndpoint);
const cdpEndpoint = fetch('http://localhost:9222/json/version')
.then(res => res.json())
.then(data => data.webSocketDebuggerUrl);
console.log('CDP WebSocket URL:', await cdpEndpoint);
})();Playwright Ws endpoint is a WebSocket URL specific to Playwright, in the form of ws://localhost:port/<id>;
CDP WebSocket URL is a WebSocket protocol URL for use with tools like Chrome DevTools, Puppeteer, Selenium, etc., in the form of ws://localhost:9222/devtools/browser/<id>;
Use the API to create a CDP port mapping:
curl -X POST "https://api.lybic.cn/api/orgs/<orgId>/sandboxes/<sandboxId>/mappings" \
-H "Content-Type: application/json" \
-d '{"targetEndpoint": "localhost:9222"}'Response:
{
"domain": "random-strs.port.lybic.cn",
"accessToken": "link_auth_token_here",
"targetEndpoint": "127.0.0.1:3000"
}You can connect to this mapped URL on your local machine using another Node.js process via CDP:
import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP(
'wss://random-strs.port.lybic.cn',
{
headers: {
'X-Gateway-Access-Token': 'link_auth_token_here'
}
}
);
const context = browser.contexts()[0];
const page = await context.newPage();Similarly, if you want to use Playwright to connect to Playwright's WebSocket URL, you can create another port mapping with the target endpoint as localhost:<port>, and after creating the mapping, use the chromium.connect(wsEndpoint+'?x-gateway-access-token=<accessToken>') method to connect.
2. Running a Node.js Frontend Project in a Sandbox
Suppose you have a simple Node.js frontend project using express as the server, located in the /home/agent/frontend-app directory.
You can start the project using npm start, which listens on the local 3000 port:
import { express } from 'express';
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello from sandboxed frontend app!');
});
app.listen(port, 'localhost', () => {
console.log(`Frontend app listening at http://localhost:${port}`);
});After starting the project, use the API to create a port mapping:
curl -X POST "https://api.lybic.cn/api/orgs/<orgId>/sandboxes/<sandboxId>/mappings" \
-H "Content-Type: application/json" \
-d '{"targetEndpoint": "127.0.0.1:3000"}'Response:
{
"domain": "random-strs.port.lybic.cn",
"accessToken": "link_auth_token_here",
"targetEndpoint": "127.0.0.1:3000"
}You can access this mapped URL on your local machine using curl:
curl -H "X-Gateway-Access-Token: link_auth_token_here" https://random-strs.port.lybic.cnSDK Usage
In addition to using the API directly, you can also use the Lybic SDK to manage HTTP port mappings.
Creating a Port Mapping
import asyncio
from lybic import LybicClient
async def main():
async with LybicClient() as client:
# Create a port mapping
mapping = await client.sandbox.create_http_port_mapping(
sandbox_id="SBX-xxxx",
target_endpoint="127.0.0.1:3000"
)
print(f"Mapping domain: {mapping.domain}")
print(f"Access token: {mapping.accessToken}")
if __name__ == '__main__':
asyncio.run(main())import (
"context"
"fmt"
"github.com/lybic/lybic-sdk-go"
)
func main() {
ctx := context.Background()
client, err := lybic.NewClient(nil)
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
return
}
// Create a port mapping
mapping, err := client.CreateHttpPortMapping(ctx, "SBX-xxxx", "127.0.0.1:3000")
if err != nil {
fmt.Printf("Error creating port mapping: %v\n", err)
return
}
fmt.Printf("Mapping domain: %s\n", mapping.Domain)
fmt.Printf("Access token: %s\n", mapping.AccessToken)
}import { LybicClient } from '@lybic/core'
const client = new LybicClient({
baseUrl: 'https://api.lybic.cn',
orgId: 'ORG-xxxx',
apiKey: 'lysk-your-api-key-here',
})
// Create a port mapping
const mapping = await client.createHttpPortMapping('SBX-xxxx', {
targetEndpoint: '127.0.0.1:3000'
})
console.log('Mapping domain:', mapping.data?.domain)
console.log('Access token:', mapping.data?.accessToken)Listing Port Mappings
import asyncio
from lybic import LybicClient
async def main():
async with LybicClient() as client:
# List all port mappings
mappings = await client.sandbox.list_http_port_mappings(
sandbox_id="SBX-xxxx"
)
for mapping in mappings:
print(f"Domain: {mapping.domain}")
print(f"Target endpoint: {mapping.targetEndpoint}")
print("---")
if __name__ == '__main__':
asyncio.run(main())import (
"context"
"fmt"
"github.com/lybic/lybic-sdk-go"
)
func main() {
ctx := context.Background()
client, err := lybic.NewClient(nil)
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
return
}
// List all port mappings
mappings, err := client.ListHttpPortMappings(ctx, "SBX-xxxx")
if err != nil {
fmt.Printf("Error listing port mappings: %v\n", err)
return
}
for _, mapping := range mappings {
fmt.Printf("Domain: %s\n", mapping.Domain)
fmt.Printf("Target endpoint: %s\n", mapping.TargetEndpoint)
fmt.Println("---")
}
}import { LybicClient } from '@lybic/core'
const client = new LybicClient({
baseUrl: 'https://api.lybic.cn',
orgId: 'ORG-xxxx',
apiKey: 'lysk-your-api-key-here',
})
// List all port mappings
const mappings = await client.listHttpPortMappings('SBX-xxxx')
for (const mapping of mappings.data || []) {
console.log('Domain:', mapping.domain)
console.log('Target endpoint:', mapping.targetEndpoint)
console.log('---')
}Deleting a Port Mapping
import asyncio
from lybic import LybicClient
async def main():
async with LybicClient() as client:
# Delete a port mapping
await client.sandbox.delete_http_port_mapping(
sandbox_id="SBX-xxxx",
target_endpoint="127.0.0.1:3000"
)
print("Mapping deleted")
if __name__ == '__main__':
asyncio.run(main())import (
"context"
"fmt"
"github.com/lybic/lybic-sdk-go"
)
func main() {
ctx := context.Background()
client, err := lybic.NewClient(nil)
if err != nil {
fmt.Printf("Error creating client: %v\n", err)
return
}
// Delete a port mapping
err = client.DeleteHttpPortMapping(ctx, "SBX-xxxx", "127.0.0.1:3000")
if err != nil {
fmt.Printf("Error deleting port mapping: %v\n", err)
return
}
fmt.Println("Mapping deleted successfully.")
}import { LybicClient } from '@lybic/core'
const client = new LybicClient({
baseUrl: 'https://api.lybic.cn',
orgId: 'ORG-xxxx',
apiKey: 'lysk-your-api-key-here',
})
// Delete a port mapping
await client.deleteHttpPortMapping('SBX-xxxx', '127.0.0.1:3000')
console.log('Mapping deleted successfully.')