[GH-ISSUE #6751] [Bug] ZIP symlinks without permission bits are reported as regular files #4113

Closed
opened 2026-10-31 00:46:54 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

ZipArtifactReader.entries() reports a ZIP member whose Unix mode is 0120000 (symbolic-link type with no permission bits) as kind: "file", with no limitation. The installed ZIP parser recognizes the same member as symlink: true. An otherwise identical 0120777 member correctly becomes kind: "symlink" with the usual limitation.

This was reproduced directly against the production reader API with a harmless local ZIP; CLI/MCP projection was not exercised.

Reproduction

From the source checkout with locked dependencies installed, generate a small stored ZIP:

python3 - <<'PY'
import stat, zipfile
with zipfile.ZipFile('/tmp/symlink-modes.zip', 'w') as z:
    z.writestr('target.txt', 'harmless local text')
    for name, mode in [('type-only-link', stat.S_IFLNK), ('permissions-link', stat.S_IFLNK | 0o777)]:
        i = zipfile.ZipInfo(name)
        i.create_system = 3
        i.external_attr = mode << 16
        z.writestr(i, 'target.txt')
PY

Create probe.mts in the checkout and run node ./node_modules/tsx/dist/cli.mjs probe.mts:

import { ZipArtifactReader } from './src/artifacts/ZipArtifactReader.ts';
const reader = new ZipArtifactReader('/tmp/symlink-modes.zip', 'zip');
try {
  for await (const e of reader.entries())
    console.log(e.path, e.kind, JSON.stringify(e.limitations));
} finally { await reader.close(); }

Observed in one completed fixture/probe run. No member extraction, real filesystem symlinks, or target execution was involved.

Expected behavior

Both declared symlinks should retain kind: "symlink". The adapter already distinguishes symlinks from files in its projection, and the installed zip.js parser reports both fixture members as symlinks.

Impact and scope

A valid ZIP type declaration loses its member kind and the corresponding limitation. Consumers of the reader receive ordinary-file facts for the affected link. The observed scope is a stored ZIP with Unix external attributes containing symlink type bits but no permissions; the 0777 control works. Real-world prevalence, other archive writers, and downstream CLI/MCP output remain untested.

REA version

6.7.0; source revision 2bcd7e6d13f0a8118d6619718254255e84f75d86.

Runtime and platform

Node.js v24.22.1; Linux x64; locked @zip.js/zip.js 2.15.0. Fixture generator: Python 3.14.8 standard-library zipfile.

Target and artifact context

Three-entry stored ZIP: one ordinary text member and two local link declarations containing the literal target.txt.

Interface

Other

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

The production-reader probe returned:

target.txt file []
type-only-link file []
permissions-link symlink ["Archive symlink target was not read."]

Independent observations from the same run:

Python stat.S_ISLNK(external_attr >> 16): true for both links
zip.js type-only-link:  symlink=true, unixMode=undefined, external mode=120000
zip.js permissions-link: symlink=true, unixMode=120777, external mode=120777

The reduced generator, probe, ZIP and full output are retained with this draft. These establish the reader classification failure only, not extraction or public-adapter behavior.

Analysis or suspected cause

Source-derived: the adapter's helper requires entry.unixMode. zip.js can leave that optional field unset while its separate symlink fact remains true, derived from the upper external-attribute bits. Requiring the optional mode loses the already-parsed type.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The local fixture demonstrates incorrect member-kind facts and omission of the symlink limitation for a bounded valid ZIP declaration.
  • Workaround status: unverified
  • Workaround basis: The permission-bearing control works, but no alternative REA request preserving the original archive's facts was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6751 ## 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 Evidence, artifacts, or reconstruction ## Observed behavior `ZipArtifactReader.entries()` reports a ZIP member whose Unix mode is `0120000` (symbolic-link type with no permission bits) as `kind: "file"`, with no limitation. The installed ZIP parser recognizes the same member as `symlink: true`. An otherwise identical `0120777` member correctly becomes `kind: "symlink"` with the usual limitation. This was reproduced directly against the production reader API with a harmless local ZIP; CLI/MCP projection was not exercised. ## Reproduction From the source checkout with locked dependencies installed, generate a small stored ZIP: ```sh python3 - <<'PY' import stat, zipfile with zipfile.ZipFile('/tmp/symlink-modes.zip', 'w') as z: z.writestr('target.txt', 'harmless local text') for name, mode in [('type-only-link', stat.S_IFLNK), ('permissions-link', stat.S_IFLNK | 0o777)]: i = zipfile.ZipInfo(name) i.create_system = 3 i.external_attr = mode << 16 z.writestr(i, 'target.txt') PY ``` Create `probe.mts` in the checkout and run `node ./node_modules/tsx/dist/cli.mjs probe.mts`: ```ts import { ZipArtifactReader } from './src/artifacts/ZipArtifactReader.ts'; const reader = new ZipArtifactReader('/tmp/symlink-modes.zip', 'zip'); try { for await (const e of reader.entries()) console.log(e.path, e.kind, JSON.stringify(e.limitations)); } finally { await reader.close(); } ``` Observed in one completed fixture/probe run. No member extraction, real filesystem symlinks, or target execution was involved. ## Expected behavior Both declared symlinks should retain `kind: "symlink"`. The adapter already distinguishes symlinks from files in [its projection](https://github.com/morluto/rea/blob/2bcd7e6d13f0a8118d6619718254255e84f75d86/src/artifacts/ZipArtifactReader.ts#L284-L309), and the installed zip.js parser reports both fixture members as symlinks. ## Impact and scope A valid ZIP type declaration loses its member kind and the corresponding limitation. Consumers of the reader receive ordinary-file facts for the affected link. The observed scope is a stored ZIP with Unix external attributes containing symlink type bits but no permissions; the `0777` control works. Real-world prevalence, other archive writers, and downstream CLI/MCP output remain untested. ## REA version 6.7.0; source revision `2bcd7e6d13f0a8118d6619718254255e84f75d86`. ## Runtime and platform Node.js v24.22.1; Linux x64; locked `@zip.js/zip.js` 2.15.0. Fixture generator: Python 3.14.8 standard-library `zipfile`. ## Target and artifact context Three-entry stored ZIP: one ordinary text member and two local link declarations containing the literal `target.txt`. ## Interface Other ## Earliest failure stage Artifact or evidence registration ## Relevant output or evidence The production-reader probe returned: ```text target.txt file [] type-only-link file [] permissions-link symlink ["Archive symlink target was not read."] ``` Independent observations from the same run: ```text Python stat.S_ISLNK(external_attr >> 16): true for both links zip.js type-only-link: symlink=true, unixMode=undefined, external mode=120000 zip.js permissions-link: symlink=true, unixMode=120777, external mode=120777 ``` The reduced generator, probe, ZIP and full output are retained with this draft. These establish the reader classification failure only, not extraction or public-adapter behavior. ## Analysis or suspected cause Source-derived: [the adapter's helper](https://github.com/morluto/rea/blob/2bcd7e6d13f0a8118d6619718254255e84f75d86/src/artifacts/ZipArtifactReader.ts#L475-L476) requires `entry.unixMode`. zip.js can leave that optional field unset while its separate `symlink` fact remains true, derived from the upper external-attribute bits. Requiring the optional mode loses the already-parsed type. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The local fixture demonstrates incorrect member-kind facts and omission of the symlink limitation for a bounded valid ZIP declaration. - Workaround status: unverified - Workaround basis: The permission-bearing control works, but no alternative REA request preserving the original archive's facts was tested.
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#4113
No description provided.