T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/install.sh:401
- Finding
- Repository File Symlinks Can Disclose Local Files During Installation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install.sh:57-59` and `scripts/install.sh:401-422` **Vulnerability Type**: Symlink following and unintended local file disclosure **Risk Level**: Medium ### Vulnerable Code ```sh assert_real_dir_ancestors '.agents/skills/handoff' assert_real_dir_ancestors '.agents/tasks/archive' assert_real_dir_ancestors '.claude/skills' ``` ```sh write_agents_block() { _target=$TARGET/AGENTS.md if [ -f "$_target" ] && grep -qF "$BEGIN" "$_target"; then awk -v b="$BEGIN" -v e="$END" '$0==b{skip=1} !skip{print} $0==e{skip=0}' "$_target" > "$WORK/agents-body" elif [ -f "$_target" ]; then cp "$_target" "$WORK/agents-body" printf '\n' >> "$WORK/agents-body" else : > "$WORK/agents-body" fi { sed -n '1,$p' "$WORK/agents-body"; sed -n '1,$p' "$AGENTS_SOURCE"; } > "$WORK/AGENTS.md" mv "$WORK/AGENTS.md" "$_target" } write_import() { _target=$TARGET/CLAUDE.md _line=$(tr -d '\r\n' < "$IMPORT_SOURCE") if [ ! -f "$_target" ]; then printf '%s\n' "$_line" > "$_target" elif [ "$(head -1 "$_target")" != "$_line" ]; then { printf '%s\n' "$_line"; sed -n '1,$p' "$_target"; } > "$WORK/import" mv "$WORK/import" "$_target" fi } ``` ### Technical Analysis The installer explicitly rejects symbolic links along managed paths under `.agents` and `.claude`, but it does not perform an equivalent check for the repository-level `AGENTS.md` and `CLAUDE.md` files. The shell tests and utilities used by these functions follow symbolic links: - `[ -f "$_target" ]` succeeds when the link resolves to a regular file. - `grep`, `awk`, `cp`, `head`, and `sed` read the link target. - The resulting content is written into a temporary regular file. - `mv` then replaces the repository symlink itself with that regular file. Consequently, if an untrusted target repository contains `AGENTS.md` or `CLAUDE.md` as a symbolic link to a local file readable by the user running the installer, the linked file's ...[truncated 1956 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject symbolic links at both repository-level managed files before reading or replacing them: ```sh assert_regular_or_missing() { _path=$1 if [ -L "$_path" ]; then printf 'refusing to install: %s is a symlink\n' "${_path#"$TARGET"/}" >&2 return 1 fi if [ -e "$_path" ] && [ ! -f "$_path" ]; then printf 'refusing to install: %s is not a regular file\n' "${_path#"$TARGET"/}" >&2 return 1 fi } assert_regular_or_missing "$TARGET/AGENTS.md" assert_regular_or_missing "$TARGET/CLAUDE.md" ``` 2. Perform this validation before transaction hashes are calculated and repeat it immediately before each read and final replacement to reduce time-of-check/time-of-use exposure. 3. Where platform support permits, open existing files using a no-follow mechanism such as `O_NOFOLLOW` rather than relying exclusively on a separate shell check. 4. Ensure that replacement operations only target verified regular files or absent paths. Reject directories, devices, FIFOs, sockets, and other special file types. 5. Add regression tests covering: - `AGENTS.md` linked to a readable external file - `CLAUDE.md` linked to a readable external file - Dangling symbolic links - Links changed between validation and replacement - Ordinary regular files, to confirm legitimate updates still work 6. Verify after a rejected installation that no external content was copied into the repository and that the original symlink remains unchanged. ]]>
