You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit da254e7
Browse filesBrowse the repository at this point in the historyBrowse files
fix(workspace): harden the pin against the review findings
Addresses the consensus round-up and the three automated reviews on #1320.
Security:
- `withinRoot` delegates to `Filesystem.containsReal` instead of comparing
`realpathSync` output with a LEXICAL fallback. The fallback was a real
bypass: a not-yet-created path under a symlinked ancestor failed to resolve,
fell back to the raw string, and passed the prefix test — enough to attribute
an outside project's skills and memory to the pinned workspace, via the
caller-supplied `x-opencode-directory` on an unsecured server.
- `readPin` distinguishes missing keys from empty values, so three
present-but-empty variables fail closed instead of reading as "no pin".
- The validation memo is keyed on a digest of the API key as well as the
tenant, so a credential switch inside one tenant no longer inherits the
previous principal's authorization for the TTL.
- API failures are classified by ERROR TYPE, not by a `status` property.
`api-client` throws `ForbiddenError`/`NotFoundError`/`NotConfiguredError` as
plain named errors carrying no status, so the first attempt at this sorted a
real 403 into the transient bucket and granted it the offline grace — the
opposite of the intent. An unclassifiable error now counts as NOT transient:
for a check gating authorization, the default has to be fail-closed.
- The offline grace is bounded (30 min), so an endpoint that fails forever
cannot hand out an unbounded licence.
- `tool/bash.ts` strips the three pin variables from spawned children
alongside `ALTIMATE_CODE_SERVE`, so a nested `serve` cannot inherit a pin its
session was never given.
Correctness and cost:
- The offline fallback keeps the server-confirmed name instead of regressing to
the environment's, which can be stale after a rename.
- `resolveProjectIdentifier` is cached per directory. It runs
`spawnSync("git", …)` with a 3s timeout, and `resolveBindingOutcome` is
reached per turn AND per memory write, so it was a synchronous subprocess on
both hot paths.
- One credentials read rather than two; two paths previously logged an
identical line, so the message could not say which had refused.
Tests:
- Symlink containment: an existing symlinked escape, a not-yet-existing
descendant under one, a genuine not-yet-existing descendant (which must still
be allowed), and a `..` escape.
- An injectable clock, so the stale-on-error branches are actually crossed. The
previous test re-resolved while the memo was still fresh and would have
passed with that branch deleted.
- Real `ForbiddenError` / `WorkspaceApiError` instead of synthetic errors with
a hand-set `status`, which tested the mock rather than the client.
- Pin environment variables are snapshotted and restored, not deleted.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
0 commit comments