Skip to content

Declaration files classify as executable, but excluding .d.cts/.d.mts by name is unsafe #585

Description

@MohammedAlkindi

.cts, .mts and .ts joined _EXECUTABLE_SUFFIXES in #548, so Path.suffix now classifies TypeScript declaration files as executable content. A skill whose only payload is types.d.cts scans as Executable=Yes.

The obvious fix is to exclude .d.cts/.d.mts by filename. I implemented exactly that in #480, measured it, and withdrew it as unsafe.

Node executes these files. On v24.18.0 node evil.d.cts runs, and node t.d.ts runs under type stripping. The .d. infix is a TypeScript convention, not a loader rule. So with a name-based exclusion, evil.d.cts containing execSync(...), no shebang and no exec bit, flips from flagged to unflagged. Renaming payload.cts to payload.d.cts would evade the scan.

That matters most at build_context.py:715 and :1319, which call is_executable_content(path, b"") with no mode. Suffix is the only live signal there, so neither the shebang nor the exec-bit arm can compensate.

Filing rather than patching, because the false positive is real but the cheap fix trades it for a false negative. Separating a declaration file from a runtime module needs content inspection, not the filename.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions