Skip to content

Workspace, outbound-fetch, object-scope, and interpreter boundaries

Twenty records expose one reusable operator pattern: a weak early gate authorizes a route, path, object, destination, or structured value, then a stronger filesystem, credential, tenant, network, SQL, template, or process sink acts on it. Test the complete transition and deny the final effect.

Discovery and primary-source seeds:

Most records were unreviewed GitHub/NVD mirrors when this page was written. Treat them as validation seeds, confirm behavior against the linked upstream project and exact revision, and do not infer affected or fixed versions that a primary source does not state.

Synthetic fixtures and denied final sinks only

Use disposable workspaces, random marker files, owned DNS/HTTP/IMAP listeners, fake credentials, two-tenant objects, patched interpreters, and argv/SQL/DOM recorders. Never read host files, overwrite startup files, query cloud metadata or internal services, collect credentials, retrieve foreign records, forge signatures, run scripts or commands, or execute injected SQL/HTML.

1. Map developer-workspace HTTP authority before testing paths

The Theia records are strongest as one route-to-filesystem matrix. Browser deployments can protect WebSocket upgrades with a connection token while ordinary HTTP handlers continue after reissuing a cookie. Test authentication and path confinement independently; a valid workspace session does not authorize every backend-readable file, and a confined path does not repair a tokenless route.

Create a disposable backend root with:

  • workspace /tmp/theia-lab/workspace;
  • plugin root /tmp/theia-lab/plugins/publisher.name;
  • sibling files containing only random READ-<uuid> markers; and
  • a writable sibling target reserved for a denied upload recorder.

Instrument raw request target, route family, cookie/token input, middleware result, decoded route parameter, URI-to-path conversion, final canonical path, and patched read/move sink. Exercise:

Route family Input matrix Secure invariant
GET /file, GET /files/, PUT /files/ no cookie, invalid cookie, valid token; workspace and sibling URI reject before resolution without a valid HTTP credential; canonical path remains in the authorized root
/hostedPlugin/:pluginId/:path ordinary asset, encoded and double-encoded dot segments, mixed separators authorize the final decoded canonical path beneath the selected plugin root
POST /file-upload no credential and valid credential; relative and absolute multipart uri authenticate before staging and deny every target outside an explicit upload root

For upload testing, parse a locally generated multipart request and replace fs.move with a recorder that rejects the operation. Include a cross-origin multipart/form-data fixture to determine whether a browser can reach the handler without preflight, but do not host a public exploit page. A bounded positive is tokenless HTTP request reaches handler -> decoded/canonical target escapes the authorized root -> denied sink records the synthetic marker path. Never retrieve the marker body or write the target.

The art-template record extends the path fixture to loaders. Record configured template root, supplied include/extend name, path.resolve result, final real path, and denied readFileSync. Test relative in-root names, .., absolute names, symlinked parents, and platform separators. The decisive evidence is the out-of-root canary path reaching the loader, not rendered file content.

Treat remote artifact identifiers as paths until proven otherwise

Assemblyline adds a client/server variant: the service server returns a value represented as a SHA-256 digest, but the client joins that value directly beneath its tasking directory before writing a downloaded artifact. The finding requires a malicious or compromised service server, or an actor able to impersonate that server; normal deployments are expected to run the client in a constrained service container. Establish that trust precondition instead of presenting the client as an unauthenticated public file-write surface.

Build a disposable client fixture with a tasking root, one in-root marker target, and one sibling target that contains only a random name. Replace the final file open/write operation with a recorder that always denies the syscall. Have an owned mock server return:

  • a lowercase 64-hex digest and a matching inert body as the positive control;
  • short, long, uppercase, and non-hex identifiers;
  • relative dot segments, absolute paths, mixed separators, and encoded separators; and
  • a syntactically valid digest whose body hash does not match, to keep identifier validation separate from content verification.

Record the authenticated server identity, raw response field, decoded identifier, digest-schema result, joined path, canonical parent, write mode, and denied destination. A bounded positive is trusted server response -> non-digest identifier is interpreted as path syntax -> canonical destination leaves the tasking root -> denied writer records only the synthetic sibling path. Do not create the sibling file, target startup or service-manager paths, or infer host execution from destination control.

Replay the same matrix against the fixed revision. The upstream patch rejects identifiers that do not match the project's SHA-256 schema before path construction. Also verify full-string matching rather than prefix acceptance, and retain containment at the write sink as defense in depth. Apply this workflow to artifact caches, malware-analysis workers, CI agents, object downloaders, and any protocol where a remote peer supplies a value named digest, hash, key, or object_id that later becomes a filename.

Repository-controlled preferences

Use a separate Theia process for workspace-preference testing. Put inert nested keys, __proto__, constructor, and prototype shapes into synthetic .theia/settings.json and .vscode/settings.json files. Patch PreferenceUtils.merge or run it in an isolated subprocess, then inspect only canary properties on fresh empty objects before and after preference resolution.

A positive is opening the disposable workspace -> preference merge handles a prototype-related key -> a fresh object inherits the inert canary. Stop there. Do not seek a code-execution gadget, open an unknown repository in a privileged IDE, or claim process compromise from pollution alone.

2. Bind URL validation to the final socket peer

Use one disconnected network harness for Firefly III, Pixelfed, MLflow, and Mealie. Provide owned listeners representing a public validation peer and a synthetic denied peer. The second peer must serve only INTERNAL-CANARY-<uuid> and must not be a real internal service or metadata endpoint.

Capture this chain for every request:

  1. caller and required feature/role;
  2. raw and normalized URL;
  3. validation-time DNS answers and address classification;
  4. redirect hops;
  5. connection-time DNS answers and selected socket peer;
  6. forwarded headers or stored credentials; and
  7. patched response-return or logging sink.
Surface Boundary to vary Required proof
Firefly III webhook explicit loopback branch; validation lookup versus send-time lookup accepted URL and final owned peer differ; harmless webhook canary reaches the recorder
Pixelfed remote search literal-host block versus resolved private/mapped address; ActivityPub content type policy accepts a representation whose final owned peer is denied
MLflow gateway secret/proxy low-role secret creation, stored api_base, caller-selected suffix basic-auth user selects a destination outside their network authority and response recorder sees only the canary hash
Mealie recipe scrape/image validation resolution versus transport re-resolution public first answer passes, denied second peer is selected, and content-return sink records only canary presence

Test loopback, private, link-local, IPv4-mapped IPv6, alternate numeric forms, redirects, and rebinding only against owned fixtures. Preserve the final peer IP rather than reporting a hostname string as SSRF. For MLflow, patch the outbound request and response serializer; never request metadata credentials even in a cloud lab.

3. Separate destination editing from stored-credential use

Paperless-ngx provides a reusable confused-deputy test for every “test connection” or “verify credentials” endpoint. Seed a mail account with fake credentials such as IMAP-PASS-<uuid>, then expose the test action to a low-role user with only the documented object-level change permission.

Vary account ID, masked-password sentinel, account type, host, port, and transport mode. The owned IMAP recorder should accept a connection but retain only the credential field name and a one-way hash of the fake value. Capture which fields came from storage and which came from the request.

A bounded positive is stored password/token retained because the request uses a masked sentinel -> caller replaces the server destination -> connector would authenticate to the owned listener with the stored fake credential -> patched sender records the hash. Do not collect a real password or OAuth token. Generalize this matrix to SMTP tests, cloud-storage validators, datasource tests, webhook previews, and AI-provider health checks.

4. Bind route proof, role, and action to the exact object

Create two synthetic tenants and random marker objects. Patch downloads, updates, deletes, and signature writes to append-only decision recorders.

Surface Weak early proof Exact binding to verify
Akaunting uploads/{id}/download authenticated portal customer media parent contact and company equal the caller's contact and company
Crater note detail/update/delete blanket view/manage notes ability target Note model is passed to policy and belongs to caller's company
Documize public attachment download non-empty secure parameter supplied proof is constant-time compared with the token bound to that attachment and organization
Documenso V1 sign-with-token valid recipient token plus assistant role/order actor owns the field and is permitted to perform the exact field type/action

Record caller tenant/recipient, route proof, object ID, parent owner, field type, sibling-order rule, policy result, and denied sink. Exercise list/detail, API version, public/authenticated, and read/write siblings because authorization often drifts between route families.

The positive is valid but insufficient generic proof -> foreign or stronger-action object resolves -> object/action predicate is absent -> denied sink records only the random target ID. For Documenso, never save a signature: use inert text fields as positive controls and a no-op signature-field recorder for the negative boundary. Do not retrieve attachments or note bodies.

5. Patch interpreters and process wrappers before supplying canaries

Magistrala illustrates the difference between an intentional scripting feature and authorization to expose host capabilities. In an isolated rules-engine process, replace Go/Lua file, database, environment, HTTP, and process functions with recorders. Test low-role rule creation and message-trigger execution with inert calls that identify only the requested capability.

Record rule owner, role, script language, accepted imports/libraries, validation decision, trigger tenant, and patched capability sink. A reportable boundary is low-role rule accepted -> server-side interpreter exposes a host/network/database capability outside the rule tenant's contract -> recorder receives the inert operation. The existence of scripting is not itself a vulnerability; establish the documented trust model and expected sandbox.

For command wrappers, capture argv and shell grammar without launching a child:

  • backmeup: vary name, source, destination, and filter with ordinary values plus inert metacharacter markers; distinguish local execution from an SSH-selected remote host. A positive requires a shell string whose parse tree contains an extra command node.
  • xidown: vary the URL position with ordinary URLs, a leading dash, --, and inert option-shaped values. Record the final yt-dlp argv and parse it with the same option grammar. A positive requires the URL slot to become an option; do not claim command execution unless a separate safe sink proves it.

Prefer direct process execution with a fixed executable, explicit argument array, end-of-options marker where supported, and a schema for every structured field. Path normalization does not quote shell syntax.

6. Record SQL grammar and render stages, not payload effects

Mautic's field selector is a reusable identifier-injection fixture. Seed a disposable database with one synthetic lead and patch query execution. Vary known field names, unknown identifiers, spaces, parentheses, comments, and delimiter canaries. Capture the request field, sanitizer output, constructed Doctrine/SQL identifier, parsed query shape, permission result, and denied executor.

A positive is ordinary authenticated route -> unlisted field crosses into identifier grammar -> parser shows structure beyond one allowed column -> executor remains denied. Bound parameters do not solve identifier selection; map the field to a fixed server-side column allowlist. Never extract rows or use delay/error amplification against a shared database.

Grocy demonstrates a sanitize-then-transform differential. Feed inert text through request parsing, HTMLPurifier, entity replacement, storage serialization, and a detached DOM parser with scripting disabled. Include raw tags, once-encoded text, double-encoded text, and benign ampersands. A positive is purifier neutralizes the marker -> later replacement reconstructs an active element in the detached parser -> render sink remains disabled. Do not execute JavaScript or collect a session. Report the exact stored and rendered forms; an API accepting angle brackets is not enough.

Reporting boundaries

  • Preserve raw, decoded, normalized, canonical, and final path forms; route reachability is not file read/write.
  • For SSRF, prove the final owned socket peer and response/log sink; never use metadata or production internal services.
  • Credential-relay evidence must use fake credentials and hashes, with request-supplied and stored fields recorded separately.
  • Cross-tenant findings require a random foreign marker reaching a denied sink, not merely an enumerable ID.
  • Prototype pollution is not code execution without an independently proven gadget and impact path.
  • Process, SQL, template, and DOM findings stop at argv, parser, loader, or detached-render evidence; do not execute the effect.