( gitDir: string, )
| 147 | * this by trimming after slicing past "ref:". |
| 148 | */ |
| 149 | export async function readGitHead( |
| 150 | gitDir: string, |
| 151 | ): Promise< |
| 152 | { type: 'branch'; name: string } | { type: 'detached'; sha: string } | null |
| 153 | > { |
| 154 | try { |
| 155 | const content = (await readFile(join(gitDir, 'HEAD'), 'utf-8')).trim() |
| 156 | if (content.startsWith('ref:')) { |
| 157 | const ref = content.slice('ref:'.length).trim() |
| 158 | if (ref.startsWith('refs/heads/')) { |
| 159 | const name = ref.slice('refs/heads/'.length) |
| 160 | // Reject path traversal and argument injection from a tampered HEAD. |
| 161 | if (!isSafeRefName(name)) { |
| 162 | return null |
| 163 | } |
| 164 | return { type: 'branch', name } |
| 165 | } |
| 166 | // Unusual symref (not a local branch) — resolve to SHA |
| 167 | if (!isSafeRefName(ref)) { |
| 168 | return null |
| 169 | } |
| 170 | const sha = await resolveRef(gitDir, ref) |
| 171 | return sha ? { type: 'detached', sha } : { type: 'detached', sha: '' } |
| 172 | } |
| 173 | // Raw SHA (detached HEAD). Validate: an attacker-controlled HEAD file |
| 174 | // could contain shell metacharacters that flow into downstream shell |
| 175 | // contexts. |
| 176 | if (!isValidGitSha(content)) { |
| 177 | return null |
| 178 | } |
| 179 | return { type: 'detached', sha: content } |
| 180 | } catch { |
| 181 | return null |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | // --------------------------------------------------------------------------- |
| 186 | // resolveRef — resolve loose/packed refs to SHAs |
no test coverage detected