Profile Generation Guide for AI
This document is written for AI assistants, not human readers. It defines how to generate a valid Surfboard profile (
.conf) from user requirements. Read it together with Profile Syntax Reference before generating any profile. The reference contains the complete parameter tables; this guide contains the rules, semantics and recipes.
Human-oriented documentation lives in Profile Format and Profile Example.
Role
You are generating a Surfboard profile: a Surge-style INI configuration file consumed by the Surfboard Android VPN app. The user describes a goal (e.g. "block ads", "add my Hysteria2 node", "route only browsers through the proxy"); you output a complete, importable .conf file.
Output Contract
These are hard rules. Violating any of them produces a broken or misleading profile.
- ALWAYS output a complete, self-contained file. Include every section the configuration depends on. Never output fragments with comments like
# ... keep your existing rules ...unless the user explicitly asked for a patch. - ALWAYS resolve syntax against reference.md. Never invent keys, parameters, protocols, rule types or group types. If a capability is not listed there, it does not exist.
- NEVER emit dangling policy references. Every name used as a rule target or group member MUST be defined in
[Proxy], defined in[Proxy Group], or be a built-in policy (DIRECT,REJECT,REJECT-TINYGIF,REJECT-DROP,REJECT-NO-DROP). References are validated at import; unknown names fail. - ALWAYS end
[Rule]with aFINAL,<policy>rule. Without it, unmatched traffic has undefined behavior. Nothing may appear afterFINAL. - NEVER fabricate server credentials or addresses. If the user has not provided node details, use obvious placeholders (
<SERVER>,<PORT>,<PASSWORD>,<UUID>) and add a# TODOcomment on that line telling the user exactly what to fill in. - NEVER emit real third-party domains or IP addresses as examples. Use
example.com,*.example.com, or reserved documentation ranges (192.0.2.0/24,198.51.100.0/24,203.0.113.0/24,2001:db8::/32). Well-known public infrastructure domains (e.g.google.com,cloudflare.com) are acceptable in test URLs and rule examples only. - ALWAYS ask before guessing. If the user's requirement is ambiguous (which apps? which region? which node?), ask a targeted question instead of silently picking one.
- VERIFY reachability of user-supplied external resources before referencing them. When the user provides a URL intended for
DOMAIN-SET,RULE-SET,policy-path, or a#!MANAGED-CONFIGsubscription header, fetch it first and confirm it is reachable (HTTP 200, non-empty body). If the fetch fails, times out, or returns an error page, do NOT silently embed the URL — report the failure to the user and ask whether to keep it anyway or drop the feature. Also sanity-check the content format (DOMAIN-SET: one domain per line,.prefix = suffix; RULE-SET: rules without the policy column; policy-path:[Proxy]-style lines). Skip this check only for placeholder URLs (example.com) the user is expected to replace, and say so explicitly.
Profile Anatomy
A profile is INI text with three comment styles (#, ;, //), key = value pairs, and comma-separated parameters. Recognized sections, in canonical order:
#!MANAGED-CONFIG https://example.com/profile.conf interval=86400 strict=false # optional, only for subscribed profiles
[General] # global behavior: DNS, listeners, test URLs
[Host] # custom DNS resolution (optional)
[Proxy] # outbound proxy definitions
[Proxy Group] # policy groups referencing proxies (optional)
[Rule] # routing rules, first match wins, ends with FINAL
[Panel] # UI panels (optional)
[WireGuard name] # one section per WireGuard interface, when used
Minimal Viable Skeleton
Every generated profile should start from this shape:
[General]
dns-server = system
proxy-test-url = http://www.gstatic.com/generate_204
[Proxy]
MyNode = ss, <SERVER>, <PORT>, encrypt-method=chacha20-ietf-poly1305, password=<PASSWORD> # TODO: fill in node details
[Proxy Group]
Proxy = select, MyNode, DIRECT
[Rule]
FINAL,Proxy
Grow it by adding nodes to [Proxy], adding members/groups to [Proxy Group], and inserting rules above FINAL.
Core Semantics
Internalize these before writing any [Rule] section:
- First match wins. Rules are evaluated top to bottom. Specific rules (exact domains, process names) MUST come before broad ones (domain keywords, GEOIP, IP-CIDR).
- Rule order is part of correctness.
GEOIP,CN,DIRECTplaced beforeDOMAIN-SUFFIX,example.com,Proxywould break the intent for CN-hosted domains. - Policies must exist before use. A rule or group referencing
MyNodeis invalid unlessMyNodeis defined. Define proxies before groups, groups before rules. - A
Global Proxyselect group is generated automatically containing all non-hidden proxies; you do not define it, and you MUST NOT redefine this name. - Domain rules may trigger DNS resolution for the connection's host. Append
no-resolveto IP-based rules (IP-CIDR,GEOIP) when the rule must not force resolution of domain-based connections. udp-policy-not-supported-behaviour(defaultREJECT) decides what happens to UDP traffic routed to a proxy withoutudp-relay=true. If the user wants UDP (games, calls) to fall back instead of being dropped, set it toDIRECT.- SSID/BSSID rules and subnet/ssid groups require location permission on the device; mention this to the user when you generate them.
Common Recipes
Map user intents to these patterns. Adapt names; keep the structure.
1. "Add my proxy node"
Add one line to [Proxy] using the correct protocol row from reference.md. If the user pastes a share link (ss://, vmess://, trojan://, anytls://, hysteria2:///hy2://), decode it into a [Proxy] line (see Share Links). Then add the node to the relevant groups.
User: "add a trojan node, server 203.0.113.10 port 443 password abc, sni cdn.example.com"
[Proxy]
MyTrojan = trojan, 203.0.113.10, 443, password=abc, sni=cdn.example.com
[Proxy Group]
Proxy = select, MyTrojan, DIRECT
2. "Block ads"
Use REJECT rules above the traffic rules. For a handful of domains write them inline; for large lists prefer a remote DOMAIN-SET or RULE-SET. See full example.
[Rule]
DOMAIN-SUFFIX,ads.example.com,REJECT
DOMAIN-KEYWORD,advertising,REJECT
DOMAIN-SET,https://example.com/adblock-domains.txt,REJECT
# ... traffic rules ...
FINAL,Proxy
REJECT resets TCP and drops UDP/ICMP. REJECT-TINYGIF behaves like REJECT. Use REJECT-DROP when the user wants silent drops.
3. "Route specific apps through the proxy"
Use PROCESS-NAME with Android package names (wildcards supported). Note: on Android 11+, app visibility is restricted; Surfboard's per-app proxy settings control which apps enter the VPN at all — PROCESS-NAME only routes traffic that reaches the VPN. See PROCESS-NAME docs.
[Rule]
PROCESS-NAME,com.example.app,Proxy
PROCESS-NAME,com.example.bank,DIRECT
FINAL,DIRECT
4. "Bypass proxy for domestic sites" (GEOIP split)
The classic layout: CN domains/IPs direct, everything else proxied. Local/private ranges first.
[Rule]
DOMAIN-SUFFIX,example.cn,DIRECT
IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
GEOIP,CN,DIRECT
FINAL,Proxy
For a production-scale version, see gfw example.
5. "Auto-select the fastest node"
Use url-test. The test url MUST be http:// (https is rejected at parse time).
[Proxy Group]
Auto = url-test, NodeA, NodeB, NodeC, url=http://www.gstatic.com/generate_204, interval=600, tolerance=100, timeout=5
Proxy = select, Auto, NodeA, NodeB, NodeC, DIRECT
To auto-include nodes from a subscription instead of listing them: policy-path=https://example.com/nodes.txt, policy-regex-filter=HK filters members by name regex.
6. "Chain proxies" (landing node via relay)
[Proxy]
Relay = ss, 198.51.100.1, 8000, encrypt-method=aes-256-gcm, password=<PASSWORD> # TODO
Landing = trojan, 203.0.113.20, 443, password=<PASSWORD>, underlying-proxy=Relay # TODO
underlying-proxy works on every protocol; cycles are rejected at import.
7. "Block QUIC for some nodes"
QUIC (UDP/443) bypasses TCP-based filtering expectations. Per node: append block-quic=on (or auto). Globally: PROTOCOL,QUIC,REJECT rule.
Pitfalls
Frequent generation mistakes. Check every output against this list.
proxy-test-url,internet-test-url, groupurlMUST behttp://, nothttps://— parse-time failure otherwise.ws-headersformat isKey:Value|Key2:Value2(pipe-separated, colon inside). Not JSON, not commas.- Quote values containing commas, semicolons or spaces:
port-hopping="1234;5000-6000",include-other-group="GroupA, GroupB". - Hysteria2/TUIC
port-hoppinguses;between ranges in conf files, but share links encode multiple ports with,. Do not mix them up when converting links. - VMess
usernameand TUICuuidMUST be valid UUIDs — parse-time failure otherwise. - SS2022 passwords MUST be base64 of exactly 16 bytes (
2022-blake3-aes-128-gcm) or 32 bytes (2022-blake3-aes-256-gcm). sni=offexplicitly disables SNI; omittingsniis different from disabling it.- Snell
versionabove 4 is silently clamped to 4;udp-relayonly works withversion>=3. - Listen ports (
http-listen,socks5-listen) must be in 1025-65535. RULE-SET,SYSTEMandRULE-SET,LANare NOT supported.RULE-SET/DOMAIN-SETaccept remotehttp(s)URLs only; entries inside aRULE-SETfile have no policy column.- Logical rules (
AND/OR/NOT): sub-rules carry no policy; max nesting depth 16;NOTtakes exactly one sub-rule. If any sub-rule is invalid, the whole rule is dropped (fail-closed). hysteria2://share links supportobfs=salamander/obfs=geckowithobfs-password; any otherobfsvalue, orobfswithoutobfs-password, is rejected. In conf lines use the equivalentsalamander-password/gecko-passwordparameters.- Legacy SS stream ciphers (
rc4,aes-*-cfb,chacha20, …) only work in debug builds. Prefer AEAD methods in generated profiles. evaluate-before-useandicon-urlgroup parameters are silently ignored — do not rely on them.
Self-Validation Checklist
Before presenting the generated profile, walk this checklist mentally and fix any failure:
- Every section header spelled exactly:
[General][Host][Proxy][Proxy Group][Rule][Panel][WireGuard <name>]— others are silently ignored. - Every policy referenced in
[Rule]and[Proxy Group]is defined or built-in. [Rule]ends withFINAL,<defined policy>; nothing follows it.- Specific rules precede broad rules;
no-resolvepresent on IP rules where DNS resolution is undesirable. - All test URLs are
http://. - All UUIDs are UUID-shaped; SS2022 passwords base64 of correct length.
- Values with
,/;/spaces are quoted. - Every protocol parameter used exists in reference.md for that protocol.
- Placeholders are marked with
# TODOcomments; no invented credentials. - Sections follow dependency order: proxies → groups → rules.
- Every external resource URL (DOMAIN-SET / RULE-SET / policy-path / subscription header) has been fetched and confirmed reachable, or the user has explicitly acknowledged keeping an unverified/placeholder URL.
References
- Complete syntax tables: Profile Syntax Reference — read this first.
- Human-oriented per-topic docs: Profile Format · Proxy · Proxy Group · Rule · General · Host
- Full annotated examples: global · adblock · gfw split routing · local server
- Authoritative sample (source repository):
surfboard-core/profile/src/test/resources/test_profile.confcovers nearly every syntax variant and edge case.