openpencil/tools/check-collab-deployment-boundaries.sh
Kayshen-X 79beee711f feat(mcp): capability profile and scope enforcement for the online /mcp surface
A public bearer client previously reached the full 160-tool catalog,
including save_document (fs::write at a caller-chosen path), the
process-global codegen plan store, and host diagnostics. Online mode
now classifies every tool by surface in one reviewable table and denies
the 17 that touch the daemon host (filesystem, outbound fetch,
unscoped globals, diagnostics, future process spawn) in both tools/list
and tools/call — refusal happens before argument parsing, so traversal
payloads never reach a handler. mcp:read/mcp:write scopes gate write
tools ahead of dispatch (deny-then-scope, so a filesystem tool reports
denial rather than inviting a bigger token); session cookies keep full
scope and unclassified static tools fail the build. Local and managed
catalogs are unchanged. The deployment-boundaries script now refuses
direct invocation instead of green-exiting with every helper missing.
2026-08-08 14:34:03 +08:00

446 lines
20 KiB
Bash

# Deployment-specific collaboration security boundaries.
# Sourced by check-collab-security-boundaries.sh after its shared assertions
# and failure accumulator have been initialized.
#
# This file is a FRAGMENT, not a runnable gate. Every assertion below is a
# function the parent defines, so running this file directly used to print
# "require_file: command not found" once per assertion and then exit 0 — a
# green result from a script that checked nothing. Anyone who ran it by hand,
# or wired it into CI as its own step, got that silent pass.
#
# The guard below makes the mistake loud. It also refuses to run when the
# parent has not initialized the failure accumulator, so a future reordering
# of the parent cannot reintroduce the same hole.
for _boundary_helper in \
require_file \
require_executable \
require_literal \
require_literal_count \
record_failure; do
if ! command -v "$_boundary_helper" >/dev/null 2>&1; then
printf '%s\n' \
"check-collab-deployment-boundaries.sh is not a standalone gate." \
"It must be sourced by check-collab-security-boundaries.sh, which" \
"defines the assertions it uses (missing: $_boundary_helper)." \
"Run instead: bash tools/check-collab-security-boundaries.sh" >&2
exit 2
fi
done
unset _boundary_helper
for required in \
.dockerignore \
.gitignore \
Cargo.toml \
crates/op-collab/Cargo.toml \
crates/op-collab/LICENSE \
crates/op-collab/src/ticket_json.rs \
crates/op-collab/tests/credential_ownership.rs \
crates/op-collab-transport/Cargo.toml \
crates/op-collab-transport/LICENSE \
crates/op-collab-relay-protocol/Cargo.toml \
crates/op-collab-relay-protocol/LICENSE \
crates/op-collab-relay-client/Cargo.toml \
crates/op-collab-relay-client/LICENSE \
crates/op-collab-relay-server/Cargo.toml \
crates/op-collab-relay-server/LICENSE \
crates/op-collab-relay-control-plane/Cargo.toml \
crates/op-collab-relay-control-plane/LICENSE \
crates/op-collab-policy-file/Cargo.toml \
crates/op-collab-policy-file/LICENSE \
crates/op-collab-relay-locator-server/Cargo.toml \
crates/op-collab-relay-locator-server/LICENSE \
crates/op-collab-smoke/LICENSE \
crates/op-auth-bridge/Cargo.toml \
crates/op-auth-bridge/LICENSE \
crates/op-auth-bridge/prebuilt_provenance.rs \
deploy/collab-relay/Dockerfile \
deploy/collab-relay/README.md \
deploy/collab-relay/compose.yaml \
deploy/collab-relay/compose.production.yaml \
deploy/collab-relay/compose.reduced-assurance.yaml \
deploy/collab-relay/nginx.conf \
deploy/collab-relay-edge/README.md \
deploy/collab-relay-edge/global-nginx.conf \
deploy/collab-relay-edge/cn-federation-nginx.conf \
deploy/collab-relay-edge/compose.global.yaml \
deploy/collab-relay-edge/compose.cn.yaml \
deploy/collab-relay-edge/global-new-connection-rate.nft \
deploy/collab-relay-edge/install-global-new-connection-rate.sh \
deploy/collab-relay-edge/verify-global-new-connection-rate.sh \
deploy/collab-relay-edge/verify-rate-rules.py \
deploy/collab-relay-edge/deploy-global.sh \
deploy/collab-relay-edge/openpencil-collab-relay-global.service.example \
deploy/collab-relay-edge/rotate-cn-crl.sh \
deploy/collab-relay-edge/validate.sh \
deploy/collab-relay-locator/Dockerfile \
deploy/collab-relay-locator/README.md \
deploy/collab-relay-locator/compose.yaml \
deploy/collab-relay-locator/nginx-http-limits.conf \
deploy/collab-relay-locator/nginx-location.conf \
deploy/collab-relay-locator/validate.sh \
deploy/collab-relay-locator-edge/README.md \
deploy/collab-relay-locator-edge/global-nginx.conf \
deploy/collab-relay-locator-edge/cn-federation-nginx.conf \
deploy/collab-relay-locator-edge/cn-locator-https-nginx.conf \
deploy/collab-relay-locator-edge/compose.global.yaml \
deploy/collab-relay-locator-edge/compose.cn.yaml \
deploy/collab-relay-locator-edge/compose.cn-https.yaml \
deploy/collab-relay-locator-edge/global-new-connection-rate.nft \
deploy/collab-relay-locator-edge/install-global-new-connection-rate.sh \
deploy/collab-relay-locator-edge/verify-global-new-connection-rate.sh \
deploy/collab-relay-locator-edge/deploy-global.sh \
deploy/collab-relay-locator-edge/openpencil-collab-locator-global.service.example \
deploy/collab-relay-locator-edge/rotate-cn-crl.sh \
deploy/collab-relay-locator-edge/validate.sh \
tools/check-collab-security-boundaries-cases.sh \
tools/check-collab-deployment-boundaries.sh \
.github/workflows/collab-security.yml \
docs/security/p2p-collaboration-threat-model.md; do
require_file "$required"
done
for executable_deploy_file in \
deploy/collab-relay-edge/install-global-new-connection-rate.sh \
deploy/collab-relay-edge/verify-global-new-connection-rate.sh \
deploy/collab-relay-edge/verify-rate-rules.py \
deploy/collab-relay-edge/deploy-global.sh \
deploy/collab-relay-edge/rotate-cn-crl.sh \
deploy/collab-relay-edge/validate.sh \
deploy/collab-relay-locator-edge/install-global-new-connection-rate.sh \
deploy/collab-relay-locator-edge/verify-global-new-connection-rate.sh \
deploy/collab-relay-locator-edge/deploy-global.sh \
deploy/collab-relay-locator-edge/rotate-cn-crl.sh \
deploy/collab-relay-locator-edge/validate.sh; do
require_executable "$executable_deploy_file" "deployment gate executable boundary"
done
for auth_tool_path in \
"tools/check-op-auth-prebuilt.sh" \
"tools/check-op-auth-prebuilt.test.sh" \
"tools/package-op-auth-prebuilt.sh"; do
require_file "$auth_tool_path"
require_literal_count .github/workflows/collab-security.yml \
"$auth_tool_path" 3 "authentication artifact workflow gate"
done
for workflow_path in \
".dockerignore" \
".gitignore" \
"crates/op-collab-smoke/**" \
"crates/op-collab-relay-protocol/**" \
"crates/op-collab-relay-client/**" \
"crates/op-collab-relay-server/**" \
"crates/op-collab-relay-control-plane/**" \
"crates/op-collab-policy-file/**" \
"crates/op-collab-relay-locator-server/**" \
"crates/op-util/**" \
"crates/op-editor-core/**" \
"crates/op-editor-host-core/**" \
"crates/op-editor-ui/**" \
"crates/op-host-native/**" \
"crates/op-host-desktop/**" \
"crates/op-host-services/**" \
"crates/op-i18n/**" \
"deploy/collab-relay/**" \
"deploy/collab-relay-edge/**" \
"deploy/collab-relay-locator/**" \
"deploy/collab-relay-locator-edge/**"; do
require_literal_count .github/workflows/collab-security.yml \
"$workflow_path" 2 "collaboration security workflow path trigger"
done
for edge_anchor in \
"listen 8443;" \
"limit_conn global_clients 32;" \
"proxy_ssl on;" \
"proxy_ssl_verify on;" \
"proxy_ssl_certificate /run/secrets/global-edge-client-cert.pem;" \
"proxy_ssl_certificate_key /run/secrets/global-edge-client-key.pem;" \
"proxy_ssl_trusted_certificate /run/secrets/cn-federation-ca.pem;" \
"proxy_ssl_session_reuse off;" \
"proxy_next_upstream off;" \
"access_log off;"; do
require_literal deploy/collab-relay-edge/global-nginx.conf \
"$edge_anchor" "Global-to-CN inner-TLS passthrough boundary"
done
for cn_federation_anchor in \
"listen 9443 ssl;" \
"ssl_verify_client on;" \
"ssl_client_certificate /run/secrets/global-edge-client-ca.pem;" \
"ssl_crl /run/secrets/global-edge-client-crl.pem;" \
"ssl_session_cache off;" \
"proxy_next_upstream off;" \
"access_log off;"; do
require_literal deploy/collab-relay-edge/cn-federation-nginx.conf \
"$cn_federation_anchor" "CN outer-mTLS federation boundary"
done
require_literal .github/workflows/collab-security.yml \
"bash deploy/collab-relay-edge/validate.sh" \
"nested-TLS relay edge workflow validation"
for relay_edge_rate_anchor in \
"ct state new" \
"meter_name=relay_edge_new_v4" \
'meter $meter_name' \
"ip saddr timeout 2m" \
"limit rate over 60/minute burst 20 packets" \
"counter drop"; do
require_literal deploy/collab-relay-edge/install-global-new-connection-rate.sh \
"$relay_edge_rate_anchor" "overseas relay per-source connection-rate boundary"
done
for relay_edge_supervision_anchor in \
'published: 443' \
'restart: "no"'; do
require_literal deploy/collab-relay-edge/compose.global.yaml \
"$relay_edge_supervision_anchor" "overseas relay supervised fixed-port boundary"
done
for relay_edge_deploy_anchor in \
'"$script_dir/install-global-new-connection-rate.sh"' \
'OPENPENCIL_RELAY_EDGE_VALIDATION_MODE=production' \
'--abort-on-container-exit --exit-code-from global-edge'; do
require_literal deploy/collab-relay-edge/deploy-global.sh \
"$relay_edge_deploy_anchor" "overseas relay supervised startup boundary"
done
for relay_edge_service_anchor in \
'Requires=docker.service nftables.service' \
'After=network-online.target nftables.service docker.service' \
'ExecStart=/opt/openpencil/deploy/collab-relay-edge/deploy-global.sh'; do
require_literal deploy/collab-relay-edge/openpencil-collab-relay-global.service.example \
"$relay_edge_service_anchor" "overseas relay boot-order boundary"
done
for relay_edge_rotation_anchor in \
"candidate CRLNumber must be strictly greater" \
"candidate CRL drops an existing revoked certificate serial" \
"CRL activation requires root" \
"CRL/CA files must be root:101 mode 0440" \
"OPENPENCIL_RELAY_EDGE_VALIDATION_MODE=production" \
"Recheck the final staged inode after ownership and mode changes." \
"up -d --no-deps --force-recreate cn-federation" \
"exec -T cn-federation nginx -t"; do
require_literal deploy/collab-relay-edge/rotate-cn-crl.sh \
"$relay_edge_rotation_anchor" "relay federation CRL activation boundary"
done
for exact_rate_verifier_anchor in \
'not address.is_global' \
'len(expressions) != 7' \
'"rate": 60' \
'"burst": 20' \
'"per": "minute"' \
'expect_equal(expressions[6], {"drop": None}'; do
require_literal deploy/collab-relay-edge/verify-rate-rules.py \
"$exact_rate_verifier_anchor" "kernel nftables semantic verification boundary"
done
require_literal_count deploy/collab-relay/nginx.conf \
"client_header_buffer_size 64k;" 2 \
"48 KiB relay bearer ingress header boundary"
require_literal_count deploy/collab-relay/nginx.conf \
"large_client_header_buffers 2 64k;" 2 \
"48 KiB relay bearer ingress header boundary"
for docker_secret_pattern in \
'**/relay-x25519-keys*.json' \
'**/*private-keys*.json' \
'**/*private_keys*.json' \
'**/locator-signing-key*.json'; do
require_literal .dockerignore "$docker_secret_pattern" \
"Docker build-context private-key exclusion"
done
for git_secret_pattern in \
'**/relay-x25519-keys*.json' \
'**/*private-keys*.json' \
'**/*private_keys*.json' \
'**/locator-signing-key*.json'; do
require_literal .gitignore "$git_secret_pattern" \
"Git private-key exclusion"
done
for cn_wss_anchor in \
"location = /v1/tunnel {" \
'proxy_set_header Authorization $http_authorization;' \
"proxy_pass_header OpenPencil-Relay-Challenge;" \
"listen 8444 ssl;" \
"limit_req zone=relay_federation_handshakes burst=500 nodelay;" \
"limit_conn relay_federation_connections 512;"; do
require_literal deploy/collab-relay/nginx.conf \
"$cn_wss_anchor" "CN WSS/federation ingress boundary"
done
for locator_ingress_anchor in \
"location = /v1/locator {" \
'if ($request_uri != "/v1/locator") {' \
"location = /v1/pairing-code {" \
'if ($request_uri != "/v1/pairing-code") {' \
"client_max_body_size 624;" \
"client_body_buffer_size 624;" \
'application/vnd.openpencil.relay-pairing-publish-v1' \
"proxy_pass http://locator:8092/v1/pairing-code;" \
"location = /v1/pairing-code/claim {" \
'if ($request_uri != "/v1/pairing-code/claim") {' \
"client_max_body_size 49;" \
"client_body_buffer_size 49;" \
'if ($content_length != "49") {' \
'application/vnd.openpencil.relay-pairing-claim-v1' \
'application/vnd.openpencil.relay-sealed-invite-v1' \
"proxy_pass http://locator:8092/v1/pairing-code/claim;" \
'if ($http_host = "") {' \
"client_max_body_size 191;" \
'proxy_set_header Authorization $http_authorization;' \
"location / {" \
"return 404;"; do
require_literal deploy/collab-relay-locator/nginx-location.conf \
"$locator_ingress_anchor" "locator exact-route ingress boundary"
done
require_literal_count deploy/collab-relay-locator/nginx-location.conf \
'limit_except POST {' 2 \
"locator pairing ingress POST-only boundary"
require_literal_count deploy/collab-relay-locator/nginx-location.conf \
'proxy_pass_request_headers off;' 2 \
"locator pairing ingress header isolation boundary"
for locator_limit_anchor in \
'limit_req_zone $binary_remote_addr zone=openpencil_locator_per_source:10m rate=10r/s;' \
'limit_conn_zone $binary_remote_addr zone=openpencil_locator_connections:10m;' \
'large_client_header_buffers 2 64k;'; do
require_literal deploy/collab-relay-locator/nginx-http-limits.conf \
"$locator_limit_anchor" "locator per-source ingress boundary"
done
for locator_location_limit_anchor in \
"limit_req zone=openpencil_locator_per_source burst=20 nodelay;" \
"limit_conn openpencil_locator_connections 16;"; do
require_literal deploy/collab-relay-locator/nginx-location.conf \
"$locator_location_limit_anchor" "locator per-source ingress boundary"
done
for locator_container_anchor in \
"OPENPENCIL_COLLAB_LOCATOR_HSM_SOCKET: /run/openpencil-hsm/signer.sock" \
"read_only: true" \
"cap_drop:" \
"no-new-privileges:true"; do
require_literal deploy/collab-relay-locator/compose.yaml \
"$locator_container_anchor" "locator production container boundary"
done
require_literal deploy/collab-relay-locator/Dockerfile \
'ENTRYPOINT ["/usr/local/bin/op-collab-relay-locator-server", "--production"]' \
"locator production-only image entrypoint"
for relay_dockerfile in \
deploy/collab-relay/Dockerfile \
deploy/collab-relay-locator/Dockerfile; do
if ! grep -Eq \
'^FROM rust:[^[:space:]@]+@sha256:[0-9a-f]{64}([[:space:]]+AS[[:space:]]+build)?$' \
"$relay_dockerfile" ||
! grep -Eq \
'^FROM gcr\.io/distroless/cc-debian12:nonroot@sha256:[0-9a-f]{64}$' \
"$relay_dockerfile"
then
record_failure \
"relay container base images must use reviewed immutable SHA-256 digests: $relay_dockerfile"
fi
done
require_literal .github/workflows/collab-security.yml \
"bash deploy/collab-relay-locator/validate.sh" \
"locator deployment workflow validation"
for locator_edge_global_anchor in \
"listen 8443;" \
"limit_conn locator_global_clients 32;" \
"proxy_ssl on;" \
"proxy_ssl_verify on;" \
"proxy_ssl_certificate /run/secrets/global-locator-edge-client-cert.pem;" \
"proxy_ssl_certificate_key /run/secrets/global-locator-edge-client-key.pem;" \
"proxy_ssl_trusted_certificate /run/secrets/cn-locator-federation-ca.pem;" \
"proxy_ssl_session_reuse off;" \
"proxy_next_upstream off;" \
"access_log off;"; do
require_literal deploy/collab-relay-locator-edge/global-nginx.conf \
"$locator_edge_global_anchor" "overseas locator inner-TLS passthrough boundary"
done
for locator_edge_cn_anchor in \
"listen 9543 ssl;" \
"ssl_verify_client on;" \
"ssl_client_certificate /run/secrets/global-locator-edge-client-ca.pem;" \
"ssl_crl /run/secrets/global-locator-edge-client-crl.pem;" \
"ssl_session_cache off;" \
"proxy_next_upstream off;" \
"access_log off;"; do
require_literal deploy/collab-relay-locator-edge/cn-federation-nginx.conf \
"$locator_edge_cn_anchor" "CN locator outer-mTLS federation boundary"
done
for locator_edge_https_anchor in \
'if ($ssl_server_name != locator.example.cn) {' \
'if ($http_host != locator.example.cn) {' \
'if ($request_uri != "/v1/locator") {' \
'if ($request_uri != "/v1/pairing-code") {' \
'if ($request_uri != "/v1/pairing-code/claim") {' \
'location = /v1/pairing-code {' \
'location = /v1/pairing-code/claim {' \
'client_max_body_size 624;' \
'client_max_body_size 49;' \
'client_body_buffer_size 624;' \
'client_body_buffer_size 49;' \
'if ($content_length != "49") {' \
'application/vnd.openpencil.relay-pairing-publish-v1' \
'application/vnd.openpencil.relay-pairing-claim-v1' \
'application/vnd.openpencil.relay-sealed-invite-v1' \
'proxy_pass http://openpencil_locator/v1/pairing-code;' \
'proxy_pass http://openpencil_locator/v1/pairing-code/claim;' \
'keepalive_requests 1;' \
'keepalive_timeout 0;' \
'client_header_buffer_size 64k;' \
'large_client_header_buffers 2 64k;' \
'proxy_pass_request_headers off;' \
'proxy_set_header Authorization $http_authorization;' \
'proxy_set_header Transfer-Encoding "";' \
'proxy_set_header Content-Encoding "";'; do
require_literal deploy/collab-relay-locator-edge/cn-locator-https-nginx.conf \
"$locator_edge_https_anchor" "CN locator exact inner-HTTPS boundary"
done
require_literal_count deploy/collab-relay-locator-edge/cn-locator-https-nginx.conf \
'limit_except POST {' 3 \
"CN locator exact inner-HTTPS POST-only boundary"
require_literal_count deploy/collab-relay-locator-edge/cn-locator-https-nginx.conf \
'proxy_pass_request_headers off;' 3 \
"CN locator exact inner-HTTPS header isolation boundary"
for locator_edge_rate_anchor in \
"ct state new" \
"meter locator_edge_new_v4" \
"ip saddr timeout 2m" \
"limit rate over 60/minute burst 20 packets" \
"counter drop"; do
require_literal deploy/collab-relay-locator-edge/install-global-new-connection-rate.sh \
"$locator_edge_rate_anchor" "overseas locator per-source connection-rate boundary"
done
for locator_edge_supervision_anchor in \
'published: 443' \
'restart: "no"'; do
require_literal deploy/collab-relay-locator-edge/compose.global.yaml \
"$locator_edge_supervision_anchor" "overseas locator supervised fixed-port boundary"
done
for locator_edge_deploy_anchor in \
'"$script_dir/install-global-new-connection-rate.sh"' \
'OPENPENCIL_LOCATOR_EDGE_VALIDATION_MODE=production' \
'--abort-on-container-exit --exit-code-from global-locator-edge'; do
require_literal deploy/collab-relay-locator-edge/deploy-global.sh \
"$locator_edge_deploy_anchor" "overseas locator supervised startup boundary"
done
for locator_edge_service_anchor in \
'Requires=docker.service nftables.service' \
'After=network-online.target nftables.service docker.service' \
'ExecStart=/opt/openpencil/deploy/collab-relay-locator-edge/deploy-global.sh'; do
require_literal \
deploy/collab-relay-locator-edge/openpencil-collab-locator-global.service.example \
"$locator_edge_service_anchor" "overseas locator boot-order boundary"
done
for locator_edge_rotation_anchor in \
"candidate CRLNumber must be strictly greater" \
"candidate CRL drops an existing revoked certificate serial" \
"CRL activation requires root" \
"CRL/CA files must be root:101 mode 0440" \
"OPENPENCIL_LOCATOR_EDGE_VALIDATION_MODE=production" \
"Revalidate the exact staged inode after its final ownership/mode changes." \
"up -d --no-deps --force-recreate" \
"exec -T locator-cn-federation nginx -t"; do
require_literal deploy/collab-relay-locator-edge/rotate-cn-crl.sh \
"$locator_edge_rotation_anchor" "locator federation CRL activation boundary"
done
require_literal .github/workflows/collab-security.yml \
"bash deploy/collab-relay-locator-edge/validate.sh" \
"nested-TLS locator edge workflow validation"