diff --git a/SECURITY_NOTES.md b/SECURITY_NOTES.md new file mode 100644 index 00000000000..75534145f38 --- /dev/null +++ b/SECURITY_NOTES.md @@ -0,0 +1,15 @@ + + + +# Security Notes + +## Upstream vulnerability snapshot + +This note records a review snapshot for OpenClaw@2026.4.11 transitive +dependencies (Lark SDK `@larksuiteoapi/node-sdk` and Discord `axios`/`tar` +deps). + +**Mitigation:** The tightened baseline sandbox policy blocks direct access to +Lark and Discord endpoints by default. + +**Action:** Revisit when OpenClaw ships `axios`/`tar` dependency bumps. diff --git a/nemoclaw-blueprint/policies/openclaw-sandbox.yaml b/nemoclaw-blueprint/policies/openclaw-sandbox.yaml index 85f342be0e2..8d7747396c0 100644 --- a/nemoclaw-blueprint/policies/openclaw-sandbox.yaml +++ b/nemoclaw-blueprint/policies/openclaw-sandbox.yaml @@ -146,42 +146,13 @@ network_policies: # discoverable preset (`presets/github.yaml`) so a sandbox only gets # GitHub access when the user explicitly selects the `github` preset # during onboard. See #1583. - - # ── OpenClaw "phone home" ──────────────────────────────────────────── - # Minimum viable set for OpenClaw to authenticate, discover plugins, - # and reach ClawHub. Restricted to openclaw and node (skill flows run on Node). - # Docs access is read-only (GET). ClawHub and openclaw.ai are - # restricted to GET+POST (auth flows, plugin discovery). - - clawhub: - name: clawhub - endpoints: - - host: clawhub.ai - port: 443 - protocol: rest - enforcement: enforce - tls: terminate - rules: - - allow: { method: GET, path: "/**" } - - allow: { method: POST, path: "/**" } - binaries: - - { path: /usr/local/bin/openclaw } - - { path: /usr/local/bin/node } - - openclaw_api: - name: openclaw_api - endpoints: - - host: openclaw.ai - port: 443 - protocol: rest - enforcement: enforce - tls: terminate - rules: - - allow: { method: GET, path: "/**" } - - allow: { method: POST, path: "/**" } - binaries: - - { path: /usr/local/bin/openclaw } - - { path: /usr/local/bin/node } + # + # SECURITY: The baseline policy intentionally omits optional hosted + # OpenClaw service-discovery endpoints such as clawhub.ai and + # openclaw.ai so the sandbox stays deny-by-default for plugin + # discovery and other "phone home" flows. Operators who need that + # access can widen egress explicitly with a different tier or policy + # override. openclaw_docs: name: openclaw_docs @@ -230,31 +201,3 @@ network_policies: - allow: { method: GET, path: "/file/bot*/**" } binaries: - { path: /usr/local/bin/node } - - discord: - name: discord - endpoints: - - host: discord.com - port: 443 - protocol: rest - enforcement: enforce - tls: terminate - rules: - - allow: { method: GET, path: "/**" } - - allow: { method: POST, path: "/**" } - # WebSocket gateway — must use access: full (CONNECT tunnel) instead - # of protocol: rest. The proxy's HTTP idle timeout (~2 min) kills - # long-lived WebSocket connections; a CONNECT tunnel avoids - # HTTP-level timeouts entirely. Matches presets/discord.yaml. See #409. - - host: gateway.discord.gg - port: 443 - access: full - - host: cdn.discordapp.com - port: 443 - protocol: rest - enforcement: enforce - tls: terminate - rules: - - allow: { method: GET, path: "/**" } - binaries: - - { path: /usr/local/bin/node } diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index 296948d13e8..695649ff29b 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -101,7 +101,7 @@ fi # at startup using capsh. The bounding set limits what caps any child process # (gateway, sandbox, agent) can ever acquire. # -# Kept: cap_chown, cap_setuid, cap_setgid, cap_fowner, cap_kill +# Kept: cap_chown, cap_setuid, cap_setgid, cap_kill # — required by the entrypoint for gosu privilege separation and chown. # Ref: https://github.com/NVIDIA/NemoClaw/issues/797 if [ "${NEMOCLAW_CAPS_DROPPED:-}" != "1" ] && command -v capsh >/dev/null 2>&1; then @@ -110,7 +110,7 @@ if [ "${NEMOCLAW_CAPS_DROPPED:-}" != "1" ] && command -v capsh >/dev/null 2>&1; if capsh --has-p=cap_setpcap 2>/dev/null; then export NEMOCLAW_CAPS_DROPPED=1 exec capsh \ - --drop=cap_net_raw,cap_dac_override,cap_sys_chroot,cap_fsetid,cap_setfcap,cap_mknod,cap_audit_write,cap_net_bind_service \ + --drop=cap_net_raw,cap_dac_override,cap_sys_chroot,cap_fsetid,cap_setfcap,cap_mknod,cap_audit_write,cap_net_bind_service,cap_fowner \ -- -c 'exec /usr/local/bin/nemoclaw-start "$@"' -- "$@" else echo "[SECURITY] CAP_SETPCAP not available — runtime already restricts capabilities" >&2 @@ -939,15 +939,27 @@ if [ ${#NEMOCLAW_CMD[@]} -gt 0 ]; then exec gosu sandbox "${NEMOCLAW_CMD[@]}" fi -# SECURITY: Protect gateway log from sandbox user tampering -touch /tmp/gateway.log -chown gateway:gateway /tmp/gateway.log -chmod 600 /tmp/gateway.log +# SECURITY: Protect gateway log from sandbox user tampering. +# Guard against symlink hijacking: if the path already exists as a symlink, +# an attacker could point it at a sensitive file and have the entrypoint +# overwrite it. Reject symlinks, then use mktemp+mv to avoid TOCTOU races. +for _log_path in /tmp/gateway.log /tmp/auto-pair.log; do + if [ -L "$_log_path" ]; then + echo "[SECURITY] $_log_path is a symlink — refusing to start (possible symlink hijack)" >&2 + exit 1 + fi + rm -f "$_log_path" +done + +_gw_log="$(mktemp /tmp/gateway.log.XXXXXX)" +chmod 600 "$_gw_log" +chown gateway:gateway "$_gw_log" +mv "$_gw_log" /tmp/gateway.log -# Separate log for auto-pair so sandbox user can write to it -touch /tmp/auto-pair.log -chown sandbox:sandbox /tmp/auto-pair.log -chmod 600 /tmp/auto-pair.log +_ap_log="$(mktemp /tmp/auto-pair.log.XXXXXX)" +chmod 600 "$_ap_log" +chown sandbox:sandbox "$_ap_log" +mv "$_ap_log" /tmp/auto-pair.log # Verify ALL symlinks in .openclaw point to expected .openclaw-data targets. # Dynamic scan so future OpenClaw symlinks are covered automatically.