skills/portless-ops/SKILL.md
Portless local-dev HTTPS proxy operations and integration. Use for: portless setup, named .localhost or custom-TLD URLs (axiom.lab, myapp.test), portless alias for externally-managed services, replacing Caddy/nginx for local dev, HTTP/2 dev servers, local CA generation and trust, portless service install (boot persistence on Windows/macOS/Linux), portless monorepo orchestration, Tailscale/Funnel dev sharing, git-worktree subdomain routing, portless.json configuration, agent-friendly URL discovery via portless get <name>, MCP-integration patterns, OAuth-with-portless TLD selection (.dev/.test for Google/Apple compliance), Vite/Next.js/Astro framework port injection, Windows openssl PATH gotcha, curl-vs-browser cert handling, custom TLD pitfalls (.local/.dev/.localhost), troubleshooting EADDRINUSE, /etc/hosts auto-sync, portless trust system store integration.
npx skillsauth add 0xDarkMatter/claude-mods portless-opsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Portless (Vercel Labs) is a local-dev HTTPS proxy that replaces port numbers with named URLs. Replacement for Caddy/nginx in the local-dev role; not for production.
Upstream: vercel-labs/portless (Apache-2.0). The portless repo ships canonical skills in its source tree (not in the npm package). Verbatim copies kept in references/:
references/upstream-portless.md — full CLI reference, integration patterns (zero-config, monorepo, turborepo, worktrees, Tailscale), HTTPS/LAN setup, troubleshootingreferences/upstream-oauth.md — OAuth provider compatibility (Google, Apple, Microsoft, Facebook, GitHub), TLD selection for OAuth, callback URI configurationThis SKILL.md adds operational patterns we've validated in production (Windows specifics, the static-alias-with-supervisor pattern, TLD-reset procedure, supply-chain hygiene). For canonical CLI usage, prefer the upstream reference files.
| Layer | Portless owns | Portless does NOT own |
|---|---|---|
| Routing | hostname → port mapping, HTTPS termination, HTTP/2, CA trust | process supervision (use Process Compose or PM2) |
| Naming | <name>.<tld> shape — one TLD per proxy | per-service distinct TLDs (not supported) |
| Process spawning | when invoked as portless myapp <cmd> | crash recovery, restart policy, health checks |
Key shape constraint: portless always renders <alias-name>.<tld>. You can't have 0x.axiom and axiom.lab in the same proxy because TLD is per-instance. Aliases like portless alias 0x.axiom 8108 get the TLD appended → 0x.axiom.lab.
# Pin a specific version (zero runtime deps, low supply-chain surface)
npm install -g [email protected]
# Verify
portless --version
Record the pinned version in your repo. Upgrades are explicit PRs.
# Proxy lifecycle
portless proxy start --tld lab --port 443 # HTTPS proxy on 443, *.lab routes
portless proxy start --tld test --port 1355 # Non-privileged port for testing
portless proxy stop
portless trust # Add CA to system trust store
# Aliases (for services portless didn't spawn — PM2, Process Compose, Docker, etc.)
portless alias axiom 8108 # https://axiom.lab → :8108
portless alias axiom 8108 --force # Overwrite existing
portless alias --remove axiom # Note: appends TLD! be careful
# Spawn-mode (portless manages the process)
portless myapp next dev # https://myapp.lab, auto port 4000-4999
portless run pnpm dev # Auto-infer name from package.json
# Discovery (agent-friendly)
portless list # Active routes
portless get axiom # Returns: https://axiom.lab
# Boot persistence
portless service install # OS-native startup task
portless service status
portless service uninstall
The common pattern: a process supervisor (Process Compose, PM2, Docker) runs your dev servers on fixed ports. Portless just routes named URLs to those ports.
# Started by Process Compose, listening on 8108
# Now make it reachable at https://axiom.lab
portless alias axiom 8108
Decoupling means:
pm2 restart axiom, process-compose process restart axiom) → portless keeps routing transparentlySource of truth pattern: keep alias registration in your supervisor config. Example scripts/install.ps1:
$services = (yq '.processes | keys | .[]' process-compose.yaml)
foreach ($svc in $services) {
$port = (yq ".processes.$svc.readiness_probe.http_get.port" process-compose.yaml)
if ($port -and $port -ne "null") {
portless alias $svc $port --force
}
}
| TLD | When to use | Caveats |
|---|---|---|
| .localhost (default) | Quickest start | Auto-resolves to 127.0.0.1 on most systems |
| .lab | Personal/distinctive | Not IANA-reserved (no DNS collision in practice for local) |
| .test | OAuth-friendly | IANA-reserved; safe |
| .dev | OAuth (Google, Apple) | Google-owned, forces HTTPS — portless handles this fine |
| .local | Avoid | mDNS/Bonjour conflict |
OAuth providers reject .localhost subdomains (not in Public Suffix List). Switch to --tld test or --tld dev for OAuth dev work. See references/upstream-oauth.md for full per-provider setup.
# Stop proxy
portless proxy stop
# Wipe all aliases (routes.json)
rm ~/.portless/routes.json # Linux/macOS
Remove-Item "$env:USERPROFILE\.portless\routes.json" # PowerShell
# Start fresh with desired TLD
portless proxy start --tld <tld> --port 443
# Re-register aliases from your supervisor config
This is the right pattern when you change TLD — portless alias --remove appends the active TLD which makes it fight you.
openssl required on PATHPortless uses OpenSSL to generate the local CA. Git for Windows ships it:
# Persistent: add to user PATH
$gitBin = "C:\Program Files\Git\usr\bin"
$current = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($current -notlike "*$gitBin*") {
[Environment]::SetEnvironmentVariable("PATH", "$gitBin;$current", "User")
}
Without it: Error: openssl failed: spawnSync openssl ENOENT
portless service install registers a Task Scheduler entry. Pair it with your supervisor's own boot task (e.g., for Process Compose, register a separate task via scripts/boot-task-install.ps1).
Verify both registered:
Get-ScheduledTask | Where-Object {
$_.TaskName -like "*ortless*" -or $_.TaskName -like "*ompose*"
}
curl on Windows uses its own bundled CA store, not the system one. So curl https://axiom.lab/ returns code 000 (cert untrusted) even after portless trust. Browsers work fine because they use the system store.
Test from curl with -k (skip verify), or --cacert ~/.portless/ca.pem:
curl -k https://axiom.lab/ # quick test
curl --cacert ~/.portless/ca.pem https://axiom.lab/ # proper
| Error | Cause | Fix |
|---|---|---|
| openssl failed: spawnSync openssl ENOENT | OpenSSL not on PATH | Add Git's usr/bin to PATH |
| Error: No alias found for "foo.lab" (you asked for foo) | --remove appends TLD; sometimes adds an extra | Wipe routes.json and re-register |
| Browser shows cert warning | CA not in system trust store | Re-run portless trust (may need admin) |
| https://name.lab shows "No app registered" | Alias not set or proxy stopped | portless list to confirm; re-register if needed |
| Safari can't resolve *.lab | Safari uses system DNS, not Node's resolver | portless hosts sync to write /etc/hosts |
| Port 443 conflict on portless proxy start | Another service bound (Caddy, IIS) | Stop the other service, or use --port 1355 for testing |
See ~/X/00_Orchestration/compose-portless/ for a worked migration from PM2+Caddy to Process Compose+portless. Key files:
process-compose.yaml — supervisor config with health-checked servicesscripts/cutover.ps1 — stops PM2/Caddy, starts portless+PC, registers aliasesdocs/MIGRATION-LOG.md — every issue hit during cutover and how it was solveddocs/SUPPLY-CHAIN.md — pinning + verification proceduresBAD: portless alias name 8000; portless alias name 8001 # second silently fails without --force
GOOD: portless alias name 8001 --force
BAD: use portless as production reverse proxy
GOOD: keep portless as dev-only; production = nginx/Caddy/cloud LB
BAD: rely on portless for crash recovery (it has none for spawned processes)
GOOD: pair portless with Process Compose / PM2 / supervisord for supervision
BAD: change TLD by stopping/starting with different --tld and hoping aliases update
GOOD: stop proxy, wipe routes.json, start with new TLD, re-register from supervisor config
references/upstream-portless.md — canonical portless SKILL.md verbatim (CLI ref, monorepo, turborepo, worktrees, LAN, Tailscale, HTTPS, troubleshooting)upstream-oauth.md — canonical OAuth setup for Google/Apple/Microsoft/Facebook/GitHubtld-selection.md — decision tree for picking the right TLD; trade-offs of .test/.dev/.localhost/custom-ownedwindows-specifics.md — openssl PATH, certutil quirks, curl-vs-browser cert handling, PS 5.1 gotchasintegration-patterns.md — combos with Process Compose / Docker / PM2 / Tailscale / git worktreesscripts/install-portless.ps1 — verified install: inspect tarball, scan for IOCs from recent attacks, install only if cleanreset-state.ps1 — clean state reset (used when changing TLD; --remove can't clear old-TLD aliases)sync-aliases-from-yaml.ps1 — derive portless aliases from a process-compose.yamlassets/portless.json.simple.json — single-app config templateportless.json.monorepo.json — workspace monorepo with name overridesportless.json.with-custom-tld.json — documents TLD choice in repopackage.json-portless-key.json — alternative: portless config inside package.jsonprocess-compose-ops — the supervisor we pair with portlessmcp-ops — agent-friendly tooling; portless get <name> provides URL discovery for agentscli-ops — general CLI tool patternstools
Behavioural-first software supply chain defense - catches poisoned npm/PyPI packages in the publish-to-advisory window that CVE tools miss. Use BEFORE every install or version bump (not only when an attack is suspected) - the 7-day cooldown gate + behavioural score catches freshly-published malware that CVE tools won't see for days. Socket.dev integration (free CLI + GitHub app + depscore MCP for Claude Code), stale-OIDC audit, dependency cooldown policy, publish-token rotation, VS Code extension audit, and a self-integrity scan that detects worm persistence hooks injected into Claude Code / VS Code settings. Triggers on: pip install, uv add, uv tool install, npm install, pnpm add, yarn add, cargo add, go get, composer require, gem install, upgrade dependency, dependency upgrade, version bump, bump version, bump package, adding dependency, new dependency, vetting a dependency, vet package, is this package safe, safe to install, should I install, before installing, pre-install check, preinstall scan, preinstall-check, PyPI cooldown, npm cooldown, release cooldown, minimumReleaseAge, score a package, package score, depscore, socket score, supply chain, supply chain attack, malicious package, poisoned dependency, npm worm, Shai-Hulud, behavioural scanning, Socket.dev, socket scan, dependency security, postinstall malware, OIDC token theft, compromised maintainer, typosquat, dependency confusion, package provenance, SLSA, persistence hook, malicious VS Code extension.
testing
GitHub remote operations — repo creation, metadata (description/homepage/topics), releases, README 'Recent Updates' enforcement, and issue / PR management with preview-before-send discipline. Companion to git-ops (local) and push-gate (pre-push safety). Three modes: new (first publish), update (subsequent release), audit (read-only checklist), plus atomic operations for issues and PRs. Triggers on: push to github, publish repo, ship release, cut release, gh release, set topics, repo description, github metadata, recent updates section, audit github repo, repo visibility, make repo public, gh repo create, gh issue, gh pr, create issue, comment on issue, close issue, triage issue, create PR, review PR, merge PR, pre-merge check, pr checks.
tools
Defend the agent's instruction surface against adversarial content - hidden-Unicode prompt injection (Trojan Source bidi reordering, U+E0000 tag-block ASCII smuggling, zero-width text), homoglyph confusables, and poisoned context that a human reviewer can't see but the model obeys. Scan CLAUDE.md / AGENTS.md / SKILL.md / .cursorrules and MCP tool descriptions; sanitize fetched web pages, issue/PR bodies, and dependency READMEs before they enter context. Triggers on: prompt injection, hidden unicode, invisible characters, zero-width space, bidi override, Trojan Source, ASCII smuggling, tag characters, homoglyph, confusable, unicode steganography, poisoned CLAUDE.md, malicious tool description, MCP tool poisoning, instruction injection, jailbreak in file, is this file safe, sanitize untrusted content, scan for hidden text.
tools
Set tool permissions for Claude Code. Configures allowed commands, rules, and preferences in .claude/ directory. Triggers on: setperms, init tools, configure permissions, setup project, set permissions, init claude.