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