[GH-ISSUE #6117] [Bug] Electron scenario path admission classifies host read denial as invalid_request #3749

Closed
opened 2026-10-31 00:43:38 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6117

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

A real filesystem search-permission denial while resolving capture-electron-scenario application_path returns invalid_request / invalid_input, an invalid_value input issue, and inspect_input_contract recovery. The issue text retains the selected path and EACCES, but typed callers receive schema-correction guidance instead of the existing filesystem-read/access-denied outcome.

Reproduction

Executed the built CLI with an inert application file beneath a directory temporarily set to mode 000 on a non-root POSIX host. The denial occurs before provider execution; the selected executable was Node solely to pass executable-path admission and was never launched by REA. The equivalent self-contained procedure below runs from an already-built checkout and restores permissions in finally.

import json, os, pathlib, subprocess, tempfile

with tempfile.TemporaryDirectory() as directory:
    root = pathlib.Path(directory)
    denied = root / 'denied'
    denied.mkdir()
    application = denied / 'main.js'
    application.write_text('module.exports = {};\n')
    executable = subprocess.check_output(
        ['node', '-p', 'process.execPath'], text=True).strip()
    request = root / 'request.json'
    request.write_text(json.dumps({
        'executable_path': executable,
        'application_path': str(application), 'actions': []}))
    env = {'PATH': os.environ['PATH'], 'REA_LOG_LEVEL': 'silent'}
    for name in ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME',
                 'XDG_CACHE_HOME', 'XDG_STATE_HOME']:
        path = root / name
        path.mkdir()
        env[name] = str(path)
    try:
        os.chmod(denied, 0)
        result = subprocess.run(
            ['node', 'scripts/rea.mjs', 'capture-electron-scenario',
             str(request), '--format', 'json'], env=env,
            capture_output=True, text=True, timeout=60)
        error = json.loads(result.stdout)
        assert result.returncode == 1
        assert error['code'] == 'invalid_request'
        assert error['details']['issues'][0]['path'] == ['application_path']
        assert 'EACCES' in error['details']['issues'][0]['message']
        print(json.dumps({key: error[key] for key in
                          ['code', 'category', 'recovery']}))
    finally:
        os.chmod(denied, 0o700)

Expected behavior

Classify an actual host read/search denial as the existing AnalysisAccessDeniedError, retaining its selected path and native code with read-access recovery. The domain contract explicitly states that a denied selected read is not malformed caller input. Missing inputs and deliberately rejected non-executable files can retain their existing input-error semantics.

Impact and scope

P2: structured consumers cannot distinguish a denied correct path from malformed request data without parsing free-form text, and the response selects the wrong recovery workflow. The observed denial is bounded and fails closed. A caller can interpret the retained EACCES text and restore access or choose a readable application. No process launch, data loss, invalid success or actual agent retry loop was observed. EPERM, other path fields and Windows were not exercised.

REA version

rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; rea-agents 6.7.0 source checkout with existing build and locked dependencies. The repository Node.js 24.18.0 pin was not separately exercised.

Target and artifact context

Generated inert JavaScript file beneath an owned POSIX directory with search permission removed. No Electron app or third-party artifact was executed.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The built command exited 1 with the following reduced result; the issue message is abbreviated to remove the generated fixture location:

{
  "code": "invalid_request",
  "category": "invalid_input",
  "recovery": {
    "kind": "inspect_input_contract",
    "operation": "capture_electron_scenario"
  },
  "details": {
    "operation": "capture_electron_scenario",
    "issues": [
      {
        "path": [
          "application_path"
        ],
        "reason": "invalid_value",
        "message": "Cannot resolve application_path <fixture>/denied/main.js: EACCES: permission denied, realpath ..."
      }
    ]
  }
}

The action tells the caller to inspect the declared input schema or CLI usage, correct fields, and avoid repeating the unchanged call. There is no structured system_code or filesystem-read boundary.

Typed outcome or recovery expected

code: access_denied, category unavailable, selected path, observed system_code: EACCES, and the existing filesystem-read guidance. Keep the denial effective.

Analysis or suspected cause

Source-derived explanation: resolveInputPath catches every realpath failure as an invalid-value input issue; canonicalizeInput aggregates those issues as AnalysisInputError. The public CLI and MCP registrations share this service. MCP behavior is source-derived, not separately executed.

Additional context

Fetched main aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38 leaves these relevant lines unchanged; no newer runtime was built or tested. #5970 concerns the separate RecoveryFiles snapshot wrapper; #5991 concerns static JavaScript application acquisition. #5103 is lexical symlink-parent rewriting. PR #2279 addressed missing-path attribution and did not execute permission-denied cases. PR #2705 intentionally validates launchability with X_OK; this report does not ask to change non-executable-file rejection. No last known good version was established.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6117 Originally assigned to: @kaoru0822-kitauji on GitHub. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior A real filesystem search-permission denial while resolving `capture-electron-scenario` application_path returns `invalid_request` / `invalid_input`, an `invalid_value` input issue, and `inspect_input_contract` recovery. The issue text retains the selected path and `EACCES`, but typed callers receive schema-correction guidance instead of the existing filesystem-read/access-denied outcome. ### Reproduction Executed the built CLI with an inert application file beneath a directory temporarily set to mode 000 on a non-root POSIX host. The denial occurs before provider execution; the selected executable was Node solely to pass executable-path admission and was never launched by REA. The equivalent self-contained procedure below runs from an already-built checkout and restores permissions in `finally`. ```python import json, os, pathlib, subprocess, tempfile with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory) denied = root / 'denied' denied.mkdir() application = denied / 'main.js' application.write_text('module.exports = {};\n') executable = subprocess.check_output( ['node', '-p', 'process.execPath'], text=True).strip() request = root / 'request.json' request.write_text(json.dumps({ 'executable_path': executable, 'application_path': str(application), 'actions': []})) env = {'PATH': os.environ['PATH'], 'REA_LOG_LEVEL': 'silent'} for name in ['HOME', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_CACHE_HOME', 'XDG_STATE_HOME']: path = root / name path.mkdir() env[name] = str(path) try: os.chmod(denied, 0) result = subprocess.run( ['node', 'scripts/rea.mjs', 'capture-electron-scenario', str(request), '--format', 'json'], env=env, capture_output=True, text=True, timeout=60) error = json.loads(result.stdout) assert result.returncode == 1 assert error['code'] == 'invalid_request' assert error['details']['issues'][0]['path'] == ['application_path'] assert 'EACCES' in error['details']['issues'][0]['message'] print(json.dumps({key: error[key] for key in ['code', 'category', 'recovery']})) finally: os.chmod(denied, 0o700) ``` ### Expected behavior Classify an actual host read/search denial as the existing `AnalysisAccessDeniedError`, retaining its selected path and native code with read-access recovery. The [domain contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorCore.ts#L188-L202) explicitly states that a denied selected read is not malformed caller input. Missing inputs and deliberately rejected non-executable files can retain their existing input-error semantics. ### Impact and scope P2: structured consumers cannot distinguish a denied correct path from malformed request data without parsing free-form text, and the response selects the wrong recovery workflow. The observed denial is bounded and fails closed. A caller can interpret the retained EACCES text and restore access or choose a readable application. No process launch, data loss, invalid success or actual agent retry loop was observed. EPERM, other path fields and Windows were not exercised. ### REA version rea-agents 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; rea-agents 6.7.0 source checkout with existing build and locked dependencies. The repository Node.js 24.18.0 pin was not separately exercised. ### Target and artifact context Generated inert JavaScript file beneath an owned POSIX directory with search permission removed. No Electron app or third-party artifact was executed. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The built command exited 1 with the following reduced result; the issue message is abbreviated to remove the generated fixture location: ```json { "code": "invalid_request", "category": "invalid_input", "recovery": { "kind": "inspect_input_contract", "operation": "capture_electron_scenario" }, "details": { "operation": "capture_electron_scenario", "issues": [ { "path": [ "application_path" ], "reason": "invalid_value", "message": "Cannot resolve application_path <fixture>/denied/main.js: EACCES: permission denied, realpath ..." } ] } } ``` The action tells the caller to inspect the declared input schema or CLI usage, correct fields, and avoid repeating the unchanged call. There is no structured `system_code` or filesystem-read boundary. ### Typed outcome or recovery expected `code: access_denied`, category `unavailable`, selected path, observed `system_code: EACCES`, and the existing `filesystem-read` guidance. Keep the denial effective. ### Analysis or suspected cause Source-derived explanation: [resolveInputPath](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronActiveObservationService.ts#L24-L45) catches every realpath failure as an invalid-value input issue; [canonicalizeInput](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/javascript/ElectronActiveObservationService.ts#L71-L100) aggregates those issues as AnalysisInputError. The public CLI and MCP registrations share this service. MCP behavior is source-derived, not separately executed. ### Additional context Fetched main `aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38` leaves these relevant lines unchanged; no newer runtime was built or tested. #5970 concerns the separate RecoveryFiles snapshot wrapper; #5991 concerns static JavaScript application acquisition. #5103 is lexical symlink-parent rewriting. PR #2279 addressed missing-path attribution and did not execute permission-denied cases. PR #2705 intentionally validates launchability with X_OK; this report does not ask to change non-executable-file rejection. No last known good version was established.
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

确认,EACCES 是运行时主机文件系统拒绝,不是输入校验失败:路径本身合法,只是所在目录 mode 000 读不了。报成 invalid_request + schema 修正指引会把用户引向改路径/改 schema,实际要改的是目录权限——这跟 #6123/#6120 又是同一条线:把环境性失败硬塞进输入校验分类。建议 admission 阶段的路径可读性探测若被拒绝,直接复用已有 filesystem-read/access-denied outcome 分类,invalid_request 只留给真正的 schema/引用校验失败;issue 文本已经保留了路径和 errno,分类也一起保留住即可。

<!-- gh-comment-id:6330495593 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认,EACCES 是运行时主机文件系统拒绝,不是输入校验失败:路径本身合法,只是所在目录 mode 000 读不了。报成 invalid_request + schema 修正指引会把用户引向改路径/改 schema,实际要改的是目录权限——这跟 #6123/#6120 又是同一条线:把环境性失败硬塞进输入校验分类。建议 admission 阶段的路径可读性探测若被拒绝,直接复用已有 filesystem-read/access-denied outcome 分类,invalid_request 只留给真正的 schema/引用校验失败;issue 文本已经保留了路径和 errno,分类也一起保留住即可。
Author
Owner

@kaoru0822-kitauji commented on GitHub (Oct 22, 2026):

Reproduced on latest main (origin/main at the time of this comment, built CLI, macOS arm64, non-root user, Node.js v26.5.0; the 24.18.0 pin was not separately exercised). An inert file sat beneath a directory set to mode 000; permissions were restored in finally. No Electron or browser was launched: every case below fails at path admission.

Observed (code / category / recovery):

  • capture-electron-scenario, application_path under the search-denied directory: invalid_request / invalid_input / inspect_input_contract, issue ["application_path"] invalid_value "Cannot resolve application_path …: EACCES …".
  • Same result for application_root and for executable_path placed under the search-denied directory.
  • Sibling admission with the same shape: capture-process with working_directory under a search-denied directory, and with an explicit executable path under it, also returns invalid_request with an invalid_value issue carrying EACCES.

Counterexamples that already give invalid_request and should keep it: missing application_path (ENOENT), a regular file without execute permission as executable_path / executable, a missing working_directory.

I'm taking this one; the fix will classify EACCES/EPERM at these admission points as the existing access_denied (filesystem-read, selected path, system_code).

<!-- gh-comment-id:6331915531 --> @kaoru0822-kitauji commented on GitHub (Oct 22, 2026): Reproduced on latest main (`origin/main` at the time of this comment, built CLI, macOS arm64, non-root user, Node.js v26.5.0; the 24.18.0 pin was not separately exercised). An inert file sat beneath a directory set to mode 000; permissions were restored in `finally`. No Electron or browser was launched: every case below fails at path admission. Observed (code / category / recovery): - `capture-electron-scenario`, `application_path` under the search-denied directory: `invalid_request` / `invalid_input` / `inspect_input_contract`, issue `["application_path"] invalid_value "Cannot resolve application_path …: EACCES …"`. - Same result for `application_root` and for `executable_path` placed under the search-denied directory. - Sibling admission with the same shape: `capture-process` with `working_directory` under a search-denied directory, and with an explicit `executable` path under it, also returns `invalid_request` with an `invalid_value` issue carrying `EACCES`. Counterexamples that already give `invalid_request` and should keep it: missing `application_path` (ENOENT), a regular file without execute permission as `executable_path` / `executable`, a missing `working_directory`. I'm taking this one; the fix will classify EACCES/EPERM at these admission points as the existing `access_denied` (`filesystem-read`, selected path, `system_code`).
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

确认这是环境性失败被错误塞进输入校验分类的问题。除了 application_path,复现里 application_root 和 executable_path 走的是同一 admission 路径,建议直接在 resolveInputPath/canonicalizeInput 这层把 EACCES/EPERM 统一归类为 access_denied(filesystem-read、selected path、system_code),一次修复即可覆盖全部路径字段,也让结构化消费者不用再解析 free-form 文本。EPERM 和 Windows 虽未复现,但在共用层分类能一并兜住。

<!-- gh-comment-id:6332378802 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认这是环境性失败被错误塞进输入校验分类的问题。除了 application_path,复现里 application_root 和 executable_path 走的是同一 admission 路径,建议直接在 resolveInputPath/canonicalizeInput 这层把 EACCES/EPERM 统一归类为 access_denied(filesystem-read、selected path、system_code),一次修复即可覆盖全部路径字段,也让结构化消费者不用再解析 free-form 文本。EPERM 和 Windows 虽未复现,但在共用层分类能一并兜住。
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

同意 kaoru0822-kitauji 的修复方案:在 admission 点将 EACCES/EPERM 归类为 access_denied(filesystem-read、selected path、system_code),同时保留 ENOENT、非可执行文件等真正输入错误的 invalid_request 语义。这样结构化调用方能直接按 recovery 修复权限,而不必解析自由文本。

<!-- gh-comment-id:6332383860 --> @XinwangTao commented on GitHub (Oct 22, 2026): 同意 kaoru0822-kitauji 的修复方案:在 admission 点将 EACCES/EPERM 归类为 access_denied(filesystem-read、selected path、system_code),同时保留 ENOENT、非可执行文件等真正输入错误的 invalid_request 语义。这样结构化调用方能直接按 recovery 修复权限,而不必解析自由文本。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3749
No description provided.