CLI documentation

Exposed path and debug endpoint checks explained

The deployment files, debug endpoints and ports the OpenCloud Security Scanner checks are not publicly reachable.

OpenCloud does not normally publish directory indexes, deployment files, private keys or its identity database over HTTP. These checks look for a web server or reverse proxy that exposes those files, and for debug interfaces that should remain private.

Before any of these run, the scan first requests a path that cannot possibly exist and records the response. OpenCloud's web frontend is a single-page application, and unknown paths return the app shell with HTTP 200 rather than a 404 - a naive "does this path return 200?" check would flag every healthy instance. Only a response that actually differs from that catch-all baseline counts as a hit, on every check below.

1. Is a directory index being served: directoryListing

An Index of /-style page was returned. OpenCloud never generates one, so this can only come from a plain web server pointed directly at the deployment directory - the same misconfiguration that, left unnoticed, also serves everything the next check looks for by name.

Fix: stop serving the deployment directory as static files. Point the web server at OpenCloud's own address as a reverse proxy instead of at a filesystem path, and switch directory indexing off explicitly (Nginx autoindex off, Apache Options -Indexes) as a second layer - see Reverse proxies.

2. Is a specific deployment file readable: exposed:<path>

A fixed list of paths that must never answer over HTTP is requested by name:

Path Severity
/opencloud.yaml critical
/config/opencloud.yaml critical
/.opencloud/config/opencloud.yaml critical
/proxy/server.key critical
/idm/opencloud.boltdb critical
/.env critical
/docker-compose.yml high
/storage/users/ high
/.git/config high

These are configuration, key material and a database, in roughly that order of what reading them hands over: opencloud.yaml and .env carry secrets and settings, proxy/server.key is TLS private key material, and idm/opencloud.boltdb is the identity store. A hit on any of them means the deployment directory (or a git checkout of it) is reachable, exactly as with directoryListing above.

Fix: stop serving the deployment directory - proxy to OpenCloud's own address rather than exposing the filesystem it runs from - and confirm every reported path answers 404 afterwards. Treat anything that was readable as disclosed: rotate the TLS key, any credential in opencloud.yaml or .env, and review the identity store for accounts created while it was exposed.

3. Is a debug endpoint publicly readable: debugEndpoint:<path>

/metrics, /config and /debug/pprof/ are checked on the public address. These belong on OpenCloud's loopback-only debug listener, never in front of a reverse proxy: /metrics and /config hand an outsider the running configuration and internal state, and /debug/pprof/ - when enabled - lets the process be told to profile itself, which is both an information leak and a way to make it do expensive work on request.

Fix: do not proxy /debug paths to the public address, and leave the debug listeners bound to 127.0.0.1 as they are by default (OC_DEBUG_ADDR and the per-service *_DEBUG_ADDR variables). If a metrics scraper genuinely needs these, reach them over the internal network rather than routing them through the same address the public internet uses.

4. Is a service debug port reachable: debugPort:<port>

Beyond the HTTP paths above, each OpenCloud service also listens on its own debug port, bound to 127.0.0.1 by default. This check connects to the default ports directly (or the ports configured in scanner.debug_ports) - reaching one at all means it was published, almost always by a container port mapping rather than a deliberate OpenCloud setting.

Fix: remove the port mapping that publishes it and leave the debug listeners on 127.0.0.1. As with the HTTP debug endpoints above, reach them over the internal network if something needs to.

5. Is the backend reachable directly, bypassing the proxy: backendPortClosed

Port 9200 serves the same OpenCloud instance as the public address, but without whatever a reverse proxy adds in front of it. When a proxy fronts OpenCloud - for TLS termination, security headers, rate limiting, or all three - a client that reaches 9200 directly gets none of that: no TLS policy, no header hardening, none of the checks the rest of this scanner reports as passing actually apply to a request that arrives this way.

Fix: remove the public port mapping for 9200 and bind the backend to loopback or the private container network, so only the reverse proxy can reach it.

6. Who may read a response cross-origin: corsOriginRestricted

The other checks on this page ask whether something is reachable. This one asks who is allowed to read the answer once it is, which is a different question and, on a stock instance, the more alarming one.

The browser's same-origin policy is what normally stops a page on attacker.example from reading a response your OpenCloud sent. Cross-Origin Resource Sharing is how a server switches that protection off for named origins. OpenCloud ships with it switched off for all of them: OC_CORS_ALLOW_ORIGINS defaults to * and OC_CORS_ALLOW_CREDENTIALS to true, and a middleware given both commonly reflects whatever Origin it was sent rather than the literal * - which is precisely the arrangement browsers refuse to allow when they can see it coming.

The scan sends a request to /graph/v1.0/me carrying an Origin that cannot belong to anybody (https://cors-probe.check-opencloud-security.invalid - .invalid is reserved by RFC 2606 and resolves nowhere) and reads what comes back:

What the instance answers Verdict
The probe origin reflected, with Access-Control-Allow-Credentials: true critical - any site can have a visitor's browser attach its OpenCloud session and hand the reply back
Access-Control-Allow-Origin: null, with credentials critical - null is what a sandboxed iframe sends, and any page can put itself in one
The probe origin reflected, without credentials medium - exposes what an unauthenticated caller could already fetch
A literal *, with or without credentials medium - browsers refuse the pair with credentials, so the request fails rather than succeeding dangerously
A different, specific origin pass - this is the configuration the check asks for
No Access-Control-Allow-Origin at all pass

Fix: set OC_CORS_ALLOW_ORIGINS to the exact origins that must reach the API - the web interface's own origin, plus any office or client application deliberately hosted elsewhere - and set OC_CORS_ALLOW_CREDENTIALS=false unless one of them genuinely needs to send the session. The per-service forms (GRAPH_CORS_ALLOW_ORIGINS, OCS_CORS_ALLOW_ORIGINS and so on) override the shared name where one service needs a wider list.

7. Is the request echoed back: traceMethodDisabled

TRACE asks the server to send the request back as the response body, headers included. Anything the browser attached on the way - the session cookie, an Authorization header, a header the reverse proxy added - then arrives as ordinary text, readable by a script that could never have read those headers directly.

OpenCloud does not implement TRACE, so an instance answering it has a reverse proxy or an application server in front that does. As with every check on this page, a 200 alone proves nothing on a single-page application: the answer only counts as an echo when the body actually looks like the request that was sent (Content-Type: message/http, or the echoed request line).

Probing for it is free in the sense that matters: TRACE is defined as a safe method by RFC 9110 - it echoes and changes nothing - which is why a plugin that may run every minute can ask.

Fix: refuse TRACE in whatever fronts the instance. Apache needs TraceEnable off; nginx already returns 405 unless a location was written to pass every method upstream; Traefik and Caddy need a rule limiting the methods forwarded.

8. Is a second service's console published beside the instance: companionAdminConsole

A document editor speaking WOPI - Collabora Online, OnlyOffice - is the usual second service in an OpenCloud deployment, and a reverse proxy that forwards /hosting and /browser to it publishes that editor on the instance's own origin. It is a second HTTP server, and its administration console lists every open document session and the users in them, reports the server's own configuration, and can terminate sessions. It is guarded by one shared password with no rate limiting in front of it.

The scan detects the backend by asking for /hosting/discovery and requiring the wopi-discovery root element the WOPI protocol specifies - a status code alone would find an editor on every instance, since OpenCloud answers unknown paths with its own HTML shell. Only once that document has answered does it ask for the console path, and only then can the finding be reported. A second finding, companionEditorHttps, reads the editor addresses that document advertises: an http:// one means the document and the token authorising the session travel unencrypted, and a browser on an HTTPS page blocks the frame outright.

Where no backend is published on this origin, neither finding appears at all - not as a pass. Most deployments serve the editor from a host of its own, and the scan deliberately does not follow the host named inside the discovery document, because that would let a scanned instance choose the next address the scanner connects to. Point a second scan at that host instead. See ADR 0036.

Fix: block the console path at the reverse proxy that publishes the backend, so only the editor's own paths reach the internet. Setting a console password instead is the weaker of the two, because that one credential protects every document session on the server.

Severity and rating impact

Every check in this group is an extraChecks entry, reported and rating-capped whenever the scan runs at all - critical findings cap the rating at D, high at C - see the extra-checks table in the main README. None of them are hardening flags, and none require --check-hardening: an exposed configuration file or an open debug port is a finding on every scan, not an opt-in one.