DevSpace is a single-owner bridge between ChatGPT web and approved local Projects. Its primary controls are narrow Project approval, OAuth capabilities, grant-bound execution references, path and version validation, effect replay protection, bounded Project handoffs, bounded output, and root locking.
It is not an operating-system security boundary.
The trusted operator is the person who:
- runs DevSpace under a local OS account;
- chooses the approved Project roots;
- controls the Owner password and persistent state;
- reviews and approves the ChatGPT OAuth grant;
- decides which commands ChatGPT may run.
Repository files, AGENTS.md, Skills, build scripts, command output, saved
handoff text, and model text are untrusted input. They cannot grant capabilities
or expand an approved root.
Configure the narrowest roots that contain the checkouts ChatGPT needs. Avoid approving:
/or a full home directory;- a cloud-drive root;
- a secrets directory;
- a parent containing unrelated work and personal data.
list_projects exposes only Projects selected for the active OAuth grant and
does not need to reveal absolute local paths. use_project validates the
returned Project reference, binds a logical context to that approved directory,
and returns an opaque executionRef. Git is not required.
File tools resolve Project-relative paths and verify that canonical targets
remain inside the approved root. Command workdir receives the same containment
check. These checks prevent accidental or model-supplied path traversal through
the DevSpace tool arguments.
They do not constrain what an already-started local process can access.
The OAuth bearer grant is the request identity. DevSpace does not consume or persist ChatGPT account or conversation identifiers.
Creating an execution requires a caller-chosen operationId and, when more than
one Project is approved, a projectRef. The result is an HMAC-authenticated
executionRef. Every Project-scoped tool requires that reference explicitly.
Before the tool handler runs, DevSpace verifies that the execution still belongs
to the active principal, OAuth client, grant, authorization epoch, approved
Project, and expected shared directory.
The reference is durable across MCP transports, ChatGPT conversations, and service restarts. It is not reusable under any different grant, including a second grant issued to the same OAuth client. Removing the source Project from authorization closes the execution before a new effect can start.
Each new operation creates a different logical context on the same approved
directory. Retrying the identical use_project request replays the same
execution, which makes a lost response safe without creating another context.
DevSpace never selects an execution by recency and has no process-global
“current Project.”
Logical contexts isolate references, instruction state, idempotency records, process handles, patch journals, and grant authorization—not files. Any two contexts or grants approved for the same Project see the same filesystem and can observe one another's writes. DevSpace root locks coordinate tracked DevSpace writers, but cannot serialize external programs or edits outside DevSpace.
A Project handoff is a bounded semantic progress snapshot for continuing work in a later ChatGPT conversation. It is not a ChatGPT account/session record, chat transcript, command log, diff, file snapshot, or source of authorization.
Handoffs are keyed by the approved Project's stable fingerprint rather than by
an OAuth grant. Any active grant that currently authorizes that same Project
may list and continue its resumable handoffs. Continuing one always creates a
new execution bound to the calling grant; it never transfers or reuses another
grant's executionRef, process handles, instruction acknowledgement, mutation
replay records, or non-Git patch journal. Knowing a handoffRef alone cannot
bypass Project authorization.
Each handoff has a title of at most 256 UTF-8 bytes and progress text of at most
8 KiB. Their JSON-serialized model text must also fit 12,000 bytes. A Project
may have at most 20 resumable handoffs and retains at most the newest 80
completed records; selection output is also bounded. save_progress uses a
caller-stable operationId and an integer ifMatch revision for replay-safe,
optimistic updates. Save responses and Project lists omit the progress body to
avoid duplicating it in model context.
Do not place secrets, credentials, hidden reasoning, complete file contents,
full diffs, transcripts, or raw logs in a handoff. Handoff text is durable state
and should be treated as sensitive. On resume, DevSpace returns it only on the
first Project-context page with explicit untrusted historical provenance and
mustRevalidate: true; the model must reread relevant files and Git state
before acting on it. A completed handoff is removed from resume selection but
its bounded record remains until it ages out of the per-Project completed
retention set. Pruning replaces obsolete execution links with a terminal marker
so those executions cannot create a new handoff; it removes metadata only,
never Project files.
DevSpace has one hidden local Owner. The Owner password gates the OAuth approval
page and is stored as a verifier rather than recoverable plaintext. Keep the
password and auth.json out of repositories, chats, screenshots, and logs.
Multiple OAuth grants may remain active concurrently, including grants sharing the same OAuth client ID. Each access token resolves an exact principal/client/grant/authorization-epoch boundary with its own scopes and approved Projects. Issuing a new grant does not invalidate another grant's tokens, executions, transports, or Project selection.
Revoking or expiring one grant affects only that grant. Its tracked executions
and processes are closed through the durable cleanup path, retained process
output and review state are retired, and Project files and Git state are left
untouched. Refresh-token replay revokes only the replayed grant.
Owner-password rotation and the authenticated revokeAll operation remain
intentional global emergency actions.
DevSpace does not claim to identify a ChatGPT account: it stores no ChatGPT account or conversation key. Independent OAuth grants are the multi-user security boundaries, even when the host reuses one OAuth client ID. Executions and processes remain grant-local. Project handoffs are the deliberate exception: they are shared only among grants that currently authorize the same Project, as described above.
The public OAuth scopes are fixed:
| Scope | Authority |
|---|---|
project:read |
Select approved Projects; load and save Project handoffs; load instructions and Skills; read, inspect, and review changes. |
project:write |
Apply file patches. |
process:execute |
Explicit high-trust opt-in for process I/O and, together with project:write, command creation. |
Every tool call rechecks the active grant and the capability required by that
tool. A projectRef is a selector, not a credential.
Access and refresh tokens are bearer credentials. Protect them in transit with HTTPS, never log them, and do not place them in shell history or repository content.
DEVSPACE_PUBLIC_BASE_URL is the public HTTPS origin, without /mcp. The MCP
endpoint is that origin plus /mcp.
The public listener serves the MCP and OAuth routes plus health endpoints. Administrative and internal control routes remain on loopback-only listeners. Tunnel or reverse-proxy only the public DevSpace service port. Never expose the admin/control listener.
Host-header and OAuth redirect-host validation reduce endpoint confusion and redirect abuse. They are configuration checks, not a replacement for TLS or OAuth.
Temporary tunnel URLs change. When one changes, update the public origin, restart the server, update the ChatGPT app endpoint, and authorize again.
DevSpace file writes preserve several invariants:
- Project-relative and canonical-path containment;
ifMatchfile-version preconditions;operationIdreplay protection where required by the tool contract;- per-root coordination for conflicting writes;
- bounded request, response, and diff data.
An ifMatch failure means the file changed after it was read. The caller must
reread and reconcile the edit.
Reusing the same operation identifier for the same request after a lost response replays the stored result rather than repeating the effect. A different intended effect requires a new identifier.
Root locks coordinate DevSpace writers that target the same checkout. A running
process may retain its root lease until the tracked process tree exits or is
cleaned up. Locks prevent cooperating DevSpace operations from racing; they do
not stop unrelated local programs from editing the same files.
save_progress updates only DevSpace metadata and does not acquire the Project
root lock, so a checkpoint can be recorded while a tracked command still holds
the filesystem lease.
Signed continuation cursors bind the grant, authorization epoch, execution,
resource, revision, query, and paging parameters. Continue with the same
executionRef plus the cursor and omit the initial paging fields. A cursor is
not Project authority and cannot be transferred to another grant or execution.
exec_command is intentionally a full local command facility. It accepts:
executionRef, operationId, cmd, workdir, env, yield_time_ms,
max_output_tokens, tty
DevSpace validates that workdir is inside the Project bound to the execution and bounds
returned output. write_stdin is mutation-only and requires operationId to
send input, close stdin, interrupt, or resize a terminal.
read_process_output performs live polling and retained-output reads without
mutating process input. Process count, retention, and cleanup limits prevent
unbounded server-side accumulation.
After process creation, the OS is the enforcement boundary. The command runs with the privileges of the OS user running DevSpace and can:
- access any file that OS user can access, including paths outside the Project;
- use the network according to the host OS and environment;
- spawn subprocesses and execute repository-provided scripts;
- make changes that DevSpace file-version checks cannot observe in advance.
DevSpace provides no process sandbox, command allow/deny list, risk
classification, child-process protected-path policy, or network egress policy.
Project and workdir validation must not be described as shell isolation.
Shutdown and interrupt cover only process groups that DevSpace started and still tracks, and termination is best effort. Detached, daemonized, re-parented, or otherwise untracked descendants may outlive DevSpace.
For stronger isolation, run DevSpace with a dedicated low-privilege OS user or
inside a suitably configured container or VM. Treat process:execute as
high-trust authority.
use_project returns a compact bounded root instruction delta without an eager
Skill catalog. read_files and inspect return newly applicable nested
instructionsDelta only when target paths require it. The skills tool
searches bounded metadata and lazily loads one selected Skill.
Default repository instruction discovery uses AGENTS.override.md and
AGENTS.md (including supported case variants). CLAUDE.md is not loaded
unless the operator explicitly configures it as a fallback filename.
These sources may describe how to work, but they cannot:
- alter OAuth capabilities or approved roots;
- authorize another local path;
- disable file-version or replay checks;
- disclose server credentials that were not already present in accessible content.
Review repository-provided instructions and Skills with the same care as build scripts.
The approved Project root is the mutable execution directory. use_project
accepts any existing approved directory and never creates, switches, removes,
or validates Git branches or worktrees. Users may ask the model to manage Git
through ordinary commands, subject to the full command-security boundary above.
When the approved Project root is exactly the Git top level, show_changes
reads the current staged, unstaged, and untracked repository diff without
writing the index, objects, or refs. A Project nested inside a larger repository
uses the non-Git source so review cannot expose paths above the approved root.
This includes repositories with no first commit. DevSpace disables Git
fsmonitor for review and rejects executable clean/process filters that apply to
Project files, because a project:read tool must not run repository-configured
programs.
The non-Git source is a bounded durable journal containing the exact successful
DevSpace apply_patch requests for the current logical execution. It is not a
filesystem monitor or net diff: command writes, external edits, failed or
unknown-outcome patches, and patches from another execution are excluded. A
full journal requires starting a new logical context; shared files are not
reset or copied.
Closing or revoking an authorization retires its logical and retained runtime state without deleting Project files or changing Git state. There is no worktree inventory or cleanup API.
Read results, diffs, command output, and retained process output are bounded. Truncation is expected for large data; callers should narrow their request.
Running and completed process records are subject to configured limits and cleanup. Do not use retained output as permanent storage. Project handoffs are also bounded but intentionally durable; they are continuity metadata, not a backup of Project files or chat history.
Keep the following out of logs and repositories:
- Owner passwords and OAuth tokens;
auth.jsonand master-key material;- internal control tokens;
- tunnel credentials;
- command text or output that contains secrets.
Use DEVSPACE_LOG_SHELL_COMMANDS=1 only when command previews are intentionally
acceptable. Audit and diagnostic output should remain bounded and sanitized,
but operators must still review it before sharing.
GET /healthz is a liveness check. GET /readyz is a readiness check and
should not expose credentials or local file contents.
The canonical public surface and examples are in ChatGPT Tool Contract.