Client or integration
OpenCodex dashboard
Area
Platform (Windows / macOS / Linux)
Summary
When OpenCodex and DeepSeek Harness (DSH) run under different Unix UIDs and share the DSH home through a writable Docker bind mount, an OpenCodex DSH integration write atomically replaces settings.yaml with a fresh 0600 inode owned by the OpenCodex UID. The DSH service then receives EACCES while reading its own configuration.
I expected an integration write either to leave the existing external configuration readable by its owner or to refuse the write before replacing the file when that cannot be guaranteed.
This was reproduced through the real POST /api/client-integrations/restore route on 2.49.0. The same write path is still present in the v2.50.0 source tag.
Reproduction
-
Run DSH as host UID/GID 987:981 with a valid /srv/dsh-data/settings.yaml owned by 987:981 and mode 0600.
-
Run OpenCodex in Docker as UID/GID 1000:1000 and bind mount /srv/dsh-data to /home/bun/.dsh with rw. Docker user namespace remapping is disabled.
-
Confirm that UID 987 can read the file:
sudo -u dsh sh -c 'cat /srv/dsh-data/settings.yaml >/dev/null'
stat -c 'inode=%i uid=%u gid=%g mode=%a' /srv/dsh-data/settings.yaml
Observed before the integration write:
inode=14686997 uid=987 gid=981 mode=600
DSH read exit status: 0
-
In the OpenCodex dashboard, enable/overwrite the DSH integration and then use its recorded restore operation, or call the equivalent authenticated route once:
POST /api/client-integrations/restore
Content-Type: application/json
{"opId":"<existing DSH operation id>","confirmDrift":false}
-
Inspect the same bind-mounted file and read it again as UID 987.
Observed immediately after the successful restore response:
inode=14687003 uid=1000 gid=1000 mode=600
cat: /srv/dsh-data/settings.yaml: Permission denied
DSH read exit status: 1
On this filesystem the directory has a default POSIX ACL. The replacement retained a named user:dsh:rw- entry but mask::--- made it ineffective. The ACL is not required for the failure: owner 1000 plus mode 0600 is already unreadable by UID 987.
The current source path is:
restoreIntegrationCoordinated
-> restoreIntegration
-> commit
-> IntegrationIO.writeText
-> atomicWriteFile
-> openSync(temp, "wx", 0o600)
-> fchmodSync(tempFd, 0o600)
-> rename(temp, settings.yaml)
Relevant v2.50.0 source:
The minimal safe behavior would be to reject an integration write when an existing external config has a different owner and OpenCodex cannot preserve its accessibility. A metadata-preserving atomic replacement could be supported where uid/gid/mode/ACL can all be applied to the temporary inode before rename.
Version
Reproduced on @bitkyc08/opencodex 2.49.0. Verified that the same IntegrationIO.writeText -> atomicWriteFile implementation remains in tag v2.50.0 (2d4d7a22381a2e497c2442902104619e25f937c7).
Operating system
fnOS host based on Debian GNU/Linux 12 (bookworm), Linux 6.18.18.c1032-trim x86_64; Docker bind mount, no user namespace remapping.
Provider and model
Not provider- or model-specific.
Logs or error output
Error: dsh: plugin tree failed to load: failed to apply loader entry settings (@deepseek-ai/dsh-settings-file): EACCES: permission denied, open '/srv/dsh-data/settings.yaml'
Error: EACCES: permission denied, open '/srv/dsh-data/settings.yaml'
at async readFile (node:internal/fs/promises:1287:14)
at async FileSettingsProvider.load (.../@deepseek-ai/dsh-settings-file/lib/index.js:137:11)
Screenshots and supporting files
No screenshots. The before/after metadata and DSH read result above came from a controlled, rollback-tested reproduction on the same inode-backed bind mount.
Redacted configuration
{
"opencodex": {
"version": "2.49.0",
"uid": 1000,
"gid": 1000,
"containerPath": "/home/bun/.dsh"
},
"dsh": {
"version": "0.1.5-rc.1",
"uid": 987,
"gid": 981,
"hostPath": "/srv/dsh-data"
},
"mount": {
"type": "bind",
"readWrite": true,
"userNamespaceRemapping": false
}
}
Checks
Client or integration
OpenCodex dashboard
Area
Platform (Windows / macOS / Linux)
Summary
When OpenCodex and DeepSeek Harness (DSH) run under different Unix UIDs and share the DSH home through a writable Docker bind mount, an OpenCodex DSH integration write atomically replaces
settings.yamlwith a fresh0600inode owned by the OpenCodex UID. The DSH service then receivesEACCESwhile reading its own configuration.I expected an integration write either to leave the existing external configuration readable by its owner or to refuse the write before replacing the file when that cannot be guaranteed.
This was reproduced through the real
POST /api/client-integrations/restoreroute on 2.49.0. The same write path is still present in the v2.50.0 source tag.Reproduction
Run DSH as host UID/GID
987:981with a valid/srv/dsh-data/settings.yamlowned by987:981and mode0600.Run OpenCodex in Docker as UID/GID
1000:1000and bind mount/srv/dsh-datato/home/bun/.dshwithrw. Docker user namespace remapping is disabled.Confirm that UID 987 can read the file:
Observed before the integration write:
In the OpenCodex dashboard, enable/overwrite the DSH integration and then use its recorded restore operation, or call the equivalent authenticated route once:
Inspect the same bind-mounted file and read it again as UID 987.
Observed immediately after the successful restore response:
On this filesystem the directory has a default POSIX ACL. The replacement retained a named
user:dsh:rw-entry butmask::---made it ineffective. The ACL is not required for the failure: owner1000plus mode0600is already unreadable by UID 987.The current source path is:
Relevant v2.50.0 source:
src/integrations/config-io.ts:writeText: (path, text) => atomicWriteFile(path, text)src/config/atomic-write.ts: the temporary file is created and hardened to0600, then renamed over the target; the existing uid/gid/mode/ACL are not captured or restored.The minimal safe behavior would be to reject an integration write when an existing external config has a different owner and OpenCodex cannot preserve its accessibility. A metadata-preserving atomic replacement could be supported where uid/gid/mode/ACL can all be applied to the temporary inode before rename.
Version
Reproduced on
@bitkyc08/opencodex2.49.0. Verified that the sameIntegrationIO.writeText -> atomicWriteFileimplementation remains in tagv2.50.0(2d4d7a22381a2e497c2442902104619e25f937c7).Operating system
fnOS host based on Debian GNU/Linux 12 (bookworm), Linux
6.18.18.c1032-trimx86_64; Docker bind mount, no user namespace remapping.Provider and model
Not provider- or model-specific.
Logs or error output
Screenshots and supporting files
No screenshots. The before/after metadata and DSH read result above came from a controlled, rollback-tested reproduction on the same inode-backed bind mount.
Redacted configuration
{ "opencodex": { "version": "2.49.0", "uid": 1000, "gid": 1000, "containerPath": "/home/bun/.dsh" }, "dsh": { "version": "0.1.5-rc.1", "uid": 987, "gid": 981, "hostPath": "/srv/dsh-data" }, "mount": { "type": "bind", "readWrite": true, "userNamespaceRemapping": false } }Checks