---
id: CVE-2026-47179
aliases:
  - GHSA-c3px-h233-h6fq
  - GO-2026-5308
title: >-
  Arcane Has an Authenticated Arbitrary Host File Read via Docker Compose
  Include Directives
summary: >-
  Arcane Has an Authenticated Arbitrary Host File Read via Docker Compose
  Include Directives
severity: high
cvss: 7.7
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N'
vendor: getarcaneapp
product: github.com/getarcaneapp/arcane/backend
ecosystem: go
affected:
  - github.com/getarcaneapp/arcane/backend < 1.19.4
patched:
  - github.com/getarcaneapp/arcane/backend 1.19.4
published: '2026-05-28'
updated: '2026-07-21'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-c3px-h233-h6fq'
references:
  - url: >-
      https://github.com/getarcaneapp/arcane/security/advisories/GHSA-c3px-h233-h6fq
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-47179'
  - url: >-
      https://github.com/getarcaneapp/arcane/commit/b6cbffabf61dbc3f12a28d3b5830e3c6b7e67daf
  - url: 'https://github.com/getarcaneapp/arcane'
tags:
  - osv
  - go
epss: 0.0046
epssPercentile: 0.37224
ingestedAt: '2026-07-21T19:04:58.653Z'
---

## Overview

## Summary

`ProjectService.GetProjectFileContent` returns the contents of any Docker Compose include directive declared in a project's compose file before any path-traversal validation runs. Because `ProjectService.CreateProject` writes attacker-supplied compose content to disk without validating include paths, an authenticated user can create a project whose compose file declares `include: ['../../../../etc/passwd']`, then read the include via the project file API. The result is arbitrary read of any file readable by the Arcane backend process, including `/app/data/arcane.db` (the SQLite database containing every user's password hash and API key), enabling escalation to admin and, via Arcane's Docker control plane, RCE on the host.

## Details

**Root cause #1 — `CreateProject` writes compose content without validation** (`backend/internal/services/project_service.go:1605-1644`):

```go
func (s *ProjectService) CreateProject(ctx context.Context, name, composeContent string, envContent *string, user models.User) (*models.Project, error) {
    // ... directory setup ...
    if err := projects.SaveOrUpdateProjectFiles(projectsDirectory, projectPath, composeContent, envContent); err != nil {
        _ = s.db.WithContext(ctx).Delete(proj).Error
        return nil, fmt.Errorf("failed to save project files: %w", err)
    }
    // ...
}
```

Compare with `UpdateProject` (project_service.go:2494, :2577), which calls `validateComposeContentForUpdate`. That validator (line 2599) loads the compose with `missingIncludeStubResourceLoaderInternal`, which calls `ValidateIncludePathForWrite` (includes.go:139) and rejects includes outside the project directory. `CreateProject` bypasses this entirely, so any malicious `include:` array survives to disk.

**Root cause #2 — `GetProjectFileContent` reads include files before path validation** (`backend/internal/services/project_service.go:831-872`):

```go
includes, parseErr := projects.ParseIncludes(composeFile, envMap, true)
if parseErr == nil {
    for _, inc := range includes {
        if inc.RelativePath == relativePath {
            return project.IncludeFile{
                Path:         inc.Path,
                RelativePath: inc.RelativePath,
                Content:      inc.Content,    // <-- arbitrary file content returned here
            }, nil
        }
    }
}

fullPath := filepath.Join(proj.Path, relativePath)
// ... IsSafeSubdirectory check at line 870 — never reached when include matches ...
```

**Root cause #3 — `ParseIncludes` reads include files from anywhere by design** (`backend/pkg/projects/includes.go:24-72`):

```go
// Security Model for Include Files:
// - READ: Docker Compose allows include files from anywhere (parent dirs, absolute paths, etc.)
//         We allow reading from any path to maintain compatibility with standard Docker Compose behavior
// - WRITE/DELETE: Restricted to files within the project directory only for security
```

`parseIncludeItemInternal` at includes.go:97-101 builds `fullPath = filepath.Clean(filepath.Join(baseDir, includePath))` and `os.ReadFile(fullPath)` at line 105 — no containment check. The returned `RelativePath` (line 124) is `filepath.ToSlash(filepath.Clean(includePath))`, which preserves `../../../../etc/passwd` verbatim for the equality match in `GetProjectFileContent`.

**Authorization surface**: The handler `GET /api/environments/{id}/projects/{projectId}/file` (`backend/internal/huma/handlers/projects.go:268-279`) and `POST /api/environments/{id}/projects` (line 242-253) only declare `BearerAuth`/`ApiKeyAuth`. There is no admin-role gate on either handler — `GetProjectFile` (line 582) and `CreateProject` (line 524) simply call `humamw.GetCurrentUserFromContext`. The default user role assigned in `users.go:223` is `"user"` (not admin), and that role is sufficient to exploit.

**Resulting primitive**: arbitrary read of any file readable by the Arcane backend process (uid/gid of the container). Sensitive targets include `/app/data/arcane.db` (SQLite containing argon2 password hashes and API keys for every user), `/app/data/secrets/*`, mounted host configuration, SSH keys (if mounted), and Docker socket-adjacent secrets.

## Impact

- **Arbitrary file read** as the Arcane backend process for any authenticated user, including users with the lowest-privilege `"user"` role.
- **Credential disclosure**: `arcane.db` contains argon2 password hashes for every account (including admins) and API key material — supports offline cracking and direct token exfiltration.
- **Privilege escalation**: a `"user"`-role attacker can recover or replay admin credentials, then exercise full Arcane functionality (Docker container/exec/volume control), which on a typical deployment with the host Docker socket mounted is host RCE.
- **Configuration / secret exposure**: any environment files, OIDC client secrets, registry credentials, or files mounted into the container are reachable.
- The scope crosses the security authority of other user accounts (S:C), since one authenticated user reads credentials belonging to other users and to the admin.

## Affected packages

- `github.com/getarcaneapp/arcane/backend < 1.19.4`

## Remediation

Upgrade to a patched release:

- `github.com/getarcaneapp/arcane/backend 1.19.4`
