{"skill":{"slug":"intranet","displayName":"Intranet","summary":"Lightweight local HTTP file server with plugin support. Serves static files from a webroot, mounts plugin directories at URL prefixes via config, and runs in...","description":"---\nname: intranet\ndescription: \"Lightweight local HTTP file server with plugin support. Serves static files from a webroot, mounts plugin directories at URL prefixes via config, and runs index.py entry points as CGI.\"\nsummary: \"Local HTTP file server with config-based plugins and CGI support.\"\nversion: 3.2.7\nhomepage: https://github.com/odrobnik/intranet-skill\nmetadata:\n  openclaw:\n    emoji: \"🌐\"\n    acceptLicenseTerms: true\n    requires:\n      bins: [\"python3\"]\n---\n\n# Intranet\n\nLightweight local HTTP file server — no Apache/nginx needed, no root required. Serves static files, mounts plugin directories, and runs `index.py` entry points as CGI.\n\n**Entry point:** `{baseDir}/scripts/intranet.py`\n\n## Setup\n\nSee [SETUP.md](SETUP.md) for prerequisites and setup instructions.\n\n## Commands\n\n```bash\npython3 {baseDir}/scripts/intranet.py start                          # Start on default port 8080\npython3 {baseDir}/scripts/intranet.py start --port 9000              # Custom port\npython3 {baseDir}/scripts/intranet.py start --host 0.0.0.0            # LAN access (requires token + allowed_hosts)\npython3 {baseDir}/scripts/intranet.py start --token SECRET            # Enable bearer token auth\npython3 {baseDir}/scripts/intranet.py status                         # Check if running\npython3 {baseDir}/scripts/intranet.py stop                           # Stop server\n```\n\n## Directory Layout\n\n```\n{workspace}/intranet/\n├── config.json          # Server config (NOT served)\n└── www/                 # Webroot (served files go here)\n    ├── index.html\n    └── ...\n```\n\nConfig lives in `{workspace}/intranet/config.json`, webroot is `{workspace}/intranet/www/`. The config file is never exposed to HTTP.\n\n## Plugins\n\nPlugins mount external directories at URL prefixes. Configure in `config.json`:\n\n```json\n{\n  \"plugins\": {\n    \"banker\": \"{workspace}/skills/banker/web\",\n    \"deliveries\": \"{workspace}/skills/deliveries/web\"\n  }\n}\n```\n\nPlugin config supports simple (static only) or extended (with CGI hash) format:\n\n```json\n{\n  \"plugins\": {\n    \"static-only\": \"/path/to/dir\",\n    \"with-cgi\": {\n      \"dir\": \"/path/to/dir\",\n      \"hash\": \"sha256:abc123...\"\n    }\n  }\n}\n```\n\n- Plugin paths must be inside the workspace\n- If CGI is enabled and a plugin has a `hash`, `index.py` at the plugin root handles all sub-paths — but only if its SHA-256 matches\n- Plugins without a `hash` are static-only (CGI blocked even when globally enabled)\n- Generate a hash: `shasum -a 256 /path/to/index.py`\n\n## CGI Execution\n\n**Off by default.** Enable in `config.json`:\n\n```json\n{\n  \"cgi\": true\n}\n```\n\nWhen enabled, only files named `index.py` can execute as CGI:\n\n- **Webroot**: `index.py` in any subdirectory handles that directory's requests\n- **Plugins**: `index.py` at the plugin root handles all plugin sub-paths\n- **All other `.py` files** → 403 Forbidden (never served, never executed)\n- Scripts must have the executable bit set (`chmod +x`)\n\n## Security\n\n- **Webroot isolation** — config.json is outside the webroot (`www/`), never served\n- **CGI off by default** — must be explicitly enabled via `\"cgi\": true` in config.json\n- **Path containment** — all resolved paths must stay within their base directory. Symlinks are followed but the resolved target is checked for containment.\n- **Plugin allowlist** — only directories explicitly registered in `config.json` are served; must be inside workspace\n- **CGI restricted to `index.py`** — no arbitrary script execution; plugin CGI requires SHA-256 hash in config.json. Webroot CGI does not require a hash (webroot files are under your direct control)\n- **All `.py` files blocked** except `index.py` entry points (not served as text, not executed)\n- **Host allowlist** — optional `allowed_hosts` restricts which `Host` headers are accepted\n- **Token auth** — optional bearer token via `--token` flag or `config.json`. Browser clients visit `?token=SECRET` once → session cookie set → all subsequent navigation works. API clients use `Authorization: Bearer <token>` header.\n- **Path traversal protection** — all paths resolved and validated before serving\n- **Default bind: `127.0.0.1`** (loopback only). LAN access via `--host 0.0.0.0` requires both token auth and `allowed_hosts` in config.json.\n\n## Workspace Detection\n\nThe server auto-detects the workspace by walking up from `$PWD` (or the script location) looking for a `skills/` directory. The detected path is printed on startup so you can verify it.\n\nTo skip autodiscovery, set `INTRANET_WORKSPACE` to the workspace root:\n\n```bash\nINTRANET_WORKSPACE=/path/to/workspace python3 scripts/intranet.py start\n```\n\n## Notes\n- All state files are inside the workspace:\n  - Config: `{workspace}/intranet/config.json`\n  - PID: `{workspace}/intranet/.pid`\n  - Runtime: `{workspace}/intranet/.conf`\n  - Webroot: `{workspace}/intranet/www/`\n- No files are written outside the workspace\n- 30-second timeout on CGI execution (when enabled)\n","tags":{"latest":"3.2.7"},"stats":{"comments":0,"downloads":1892,"installsAllTime":71,"installsCurrent":2,"stars":0,"versions":28},"createdAt":1770966110611,"updatedAt":1778488088696},"latestVersion":{"version":"3.2.7","createdAt":1773306392500,"changelog":"Make session cookies deterministic so they survive server restarts","license":"MIT-0"},"metadata":{"setup":[],"os":null,"systems":null},"owner":{"handle":"odrobnik","userId":"s175mv7e1vzhser8sz79yq23fd83h54z","displayName":"Oliver Drobnik","image":"https://avatars.githubusercontent.com/u/333270?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1779973253386}}