(name: string)
| 96 | * tabs, quotes, backslash) and path traversal (`..`). |
| 97 | */ |
| 98 | export function isSafeRefName(name: string): boolean { |
| 99 | if (!name || name.startsWith('-') || name.startsWith('/')) { |
| 100 | return false |
| 101 | } |
| 102 | if (name.includes('..')) { |
| 103 | return false |
| 104 | } |
| 105 | // Reject single-dot and empty path components (`.`, `foo/./bar`, `foo//bar`, |
| 106 | // `foo/`). Git-check-ref-format rejects these, and `.` normalizes away in |
| 107 | // path joins so a tampered HEAD of `refs/heads/.` would make us watch the |
| 108 | // refs/heads directory itself instead of a branch file. |
| 109 | if (name.split('/').some(c => c === '.' || c === '')) { |
| 110 | return false |
| 111 | } |
| 112 | // Allowlist-only: alphanumerics, /, ., _, +, -, @. Rejects all shell |
| 113 | // metacharacters, whitespace, NUL, and non-ASCII. Git's forbidden @{ |
| 114 | // sequence is blocked because { is not in the allowlist. |
| 115 | if (!/^[a-zA-Z0-9/._+@-]+$/.test(name)) { |
| 116 | return false |
| 117 | } |
| 118 | return true |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Validate that a string is a git SHA: 40 hex chars (SHA-1) or 64 hex chars |
no outgoing calls
no test coverage detected