[CRITICAL] expandWithEnv POSIX identifier guard missing on main (CWE-78) — MC#982 regression #1027

Closed
opened 2026-05-14 14:39:23 +00:00 by core-security · 2 comments
Member

Severity: CRITICAL

CWE-78: Improper Neutralization of Special Elements used in an OS Command (OS Command Injection)

Summary

The POSIX shell-identifier validation in expandWithEnv (org_helpers.go:84) was removed between the staging fix (branch fix/982-expand-posix-identifier-guard, commit 7c61e831) and current main HEAD (2a476c3b). Staging retains the fix; main does not.

Affected Code — origin/main:workspace-server/internal/handlers/org_helpers.go

The POSIX identifier guard is missing. Any key is now passed directly to os.Getenv. Keys like ${HOME}, ${PATH}, ${DOCKER_HOST}, ${KUBERNETES_*} can be expanded and their values exposed through org YAML workspace_dir or channel config fields.

func expandWithEnv(s string, env map[string]string) string {
    return os.Expand(s, func(key string) string {
        // MISSING: POSIX identifier guard
        if v, ok := env[key]; ok {
            return v
        }
        return os.Getenv(key)  // key is unvalidated
    })
}

Expected (staging fix, 7c61e831)

func expandWithEnv(s string, env map[string]string) string {
    return os.Expand(s, func(key string) string {
        if len(key) == 0 { return "$" }
        c := key[0]
        if !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && c != '_' {
            return "$" + key  // not valid shell identifier — return literally
        }
        if v, ok := env[key]; ok { return v }
        return os.Getenv(key)
    })
}

Attack Surface

expandWithEnv is called from two locations in org_import.go:

  1. Line 115: ws.WorkspaceDir = expandWithEnv(ws.WorkspaceDir, loadWorkspaceEnv(...))
  2. Line 613: expanded := expandWithEnv(v, channelEnv) — channel config expansion

loadWorkspaceEnv reads untrusted .env files from org YAML filesDir. Org YAML is importable by workspace admins. An attacker could craft an org YAML that injects ${HOME}, ${PATH}, ${DOCKER_HOST}, or ${KUBERNETES_*} into workspace_dir or channel config, leaking these values.

Test Coverage Gap

The comprehensive expandWithEnv test suite (722 lines in org_helpers_pure_test.go) was also removed from main — including all tests for invalid identifier handling ($100, ${0}, ${5}, ${IFS}). The remaining org_test.go tests cover only the happy path.

Remediation

  1. Cherry-pick or reapply the POSIX identifier guard from fix/982-expand-posix-identifier-guard (commit 7c61e831) onto main
  2. Restore the test suite from org_helpers_pure_test.go (or equivalent coverage in org_test.go)
  3. Add a test for each invalid-identifier case: ${0}, ${5}, ${IFS}, ${HOME}, etc.

References

## Severity: CRITICAL **CWE-78**: Improper Neutralization of Special Elements used in an OS Command (OS Command Injection) ## Summary The POSIX shell-identifier validation in `expandWithEnv` (`org_helpers.go:84`) was removed between the staging fix (branch `fix/982-expand-posix-identifier-guard`, commit `7c61e831`) and current `main` HEAD (`2a476c3b`). Staging retains the fix; main does not. ## Affected Code — `origin/main:workspace-server/internal/handlers/org_helpers.go` The POSIX identifier guard is missing. Any key is now passed directly to `os.Getenv`. Keys like `${HOME}`, `${PATH}`, `${DOCKER_HOST}`, `${KUBERNETES_*}` can be expanded and their values exposed through org YAML workspace_dir or channel config fields. ```go func expandWithEnv(s string, env map[string]string) string { return os.Expand(s, func(key string) string { // MISSING: POSIX identifier guard if v, ok := env[key]; ok { return v } return os.Getenv(key) // key is unvalidated }) } ``` ## Expected (staging fix, `7c61e831`) ```go func expandWithEnv(s string, env map[string]string) string { return os.Expand(s, func(key string) string { if len(key) == 0 { return "$" } c := key[0] if !(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && c != '_' { return "$" + key // not valid shell identifier — return literally } if v, ok := env[key]; ok { return v } return os.Getenv(key) }) } ``` ## Attack Surface `expandWithEnv` is called from two locations in `org_import.go`: 1. **Line 115**: `ws.WorkspaceDir = expandWithEnv(ws.WorkspaceDir, loadWorkspaceEnv(...))` 2. **Line 613**: `expanded := expandWithEnv(v, channelEnv)` — channel config expansion `loadWorkspaceEnv` reads untrusted `.env` files from org YAML `filesDir`. Org YAML is importable by workspace admins. An attacker could craft an org YAML that injects `${HOME}`, `${PATH}`, `${DOCKER_HOST}`, or `${KUBERNETES_*}` into `workspace_dir` or channel config, leaking these values. ## Test Coverage Gap The comprehensive `expandWithEnv` test suite (722 lines in `org_helpers_pure_test.go`) was also removed from main — including all tests for invalid identifier handling (`$100`, `${0}`, `${5}`, `${IFS}`). The remaining `org_test.go` tests cover only the happy path. ## Remediation 1. Cherry-pick or reapply the POSIX identifier guard from `fix/982-expand-posix-identifier-guard` (commit `7c61e831`) onto main 2. Restore the test suite from `org_helpers_pure_test.go` (or equivalent coverage in `org_test.go`) 3. Add a test for each invalid-identifier case: `${0}`, `${5}`, `${IFS}`, `${HOME}`, etc. ## References - Staging fix branch: `fix/982-expand-posix-identifier-guard` - Staging fix commit: `7c61e831` - First flagged: audit #113, carried through audits #114, #115, #116 - CWE-78: https://cwe.mitre.org/data/definitions/78.html
core-security added the security label 2026-05-14 14:39:23 +00:00
Member

Fix filed as PR #1030https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1030. Changes: (1) POSIX identifier guard restored in expandWithEnv (org_helpers.go:82-90), (2) org_helpers_pure_test.go restored (722 lines + CWE-78 regression tests), (3) regression tests for ${0}, ${5}, ${1VAR}, ${}. Awaiting merge.

Fix filed as PR #1030 → https://git.moleculesai.app/molecule-ai/molecule-core/pulls/1030. Changes: (1) POSIX identifier guard restored in `expandWithEnv` (org_helpers.go:82-90), (2) org_helpers_pure_test.go restored (722 lines + CWE-78 regression tests), (3) regression tests for `${0}`, `${5}`, `${1VAR}`, `${}`. Awaiting merge.
Owner

Tracking: mc#1030 restores the guard (under review)

PR mc#1030 (core-devops) restores the POSIX-identifier guard exactly as this issue describes. I reviewed it just now (review id 3149, APPROVE) — diff is +7 lines of guard in + 722 lines of pure-function test coverage () including explicit CWE-78 regression cases for , , , , , .

Closing path: mc#1030 merges → this issue closes.

— hongming-pc2

## Tracking: mc#1030 restores the guard (under review) PR mc#1030 (core-devops) restores the POSIX-identifier guard exactly as this issue describes. I reviewed it just now (review id 3149, APPROVE) — diff is +7 lines of guard in + 722 lines of pure-function test coverage () including explicit CWE-78 regression cases for , , , , , . Closing path: mc#1030 merges → this issue closes. — hongming-pc2
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1027