mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
fix(server): sanitize repo name to prevent argument injection
This commit is contained in:
parent
98ee665889
commit
0895ffd17d
4 changed files with 47 additions and 6 deletions
|
|
@ -14,8 +14,24 @@ import { isIP } from 'net';
|
|||
/** Extract the repository name from a git URL (HTTPS or SSH). */
|
||||
export function extractRepoName(url: string): string {
|
||||
const cleaned = url.replace(/\/+$/, '');
|
||||
const lastSegment = cleaned.split(/[/:]/).pop() || 'unknown';
|
||||
return lastSegment.replace(/\.git$/, '');
|
||||
|
||||
// For SSH URLs like git@github.com:user/repo.git, we need to handle the colon.
|
||||
// For HTTPS URLs, the only colon should be in the protocol.
|
||||
let lastSegment: string;
|
||||
if (cleaned.includes('@') && cleaned.includes(':') && !cleaned.startsWith('http')) {
|
||||
// Likely an SSH URL
|
||||
lastSegment = cleaned.split(/[:/]/).pop() || 'unknown';
|
||||
} else {
|
||||
// Likely an HTTPS URL or local path
|
||||
lastSegment = cleaned.split('/').pop() || 'unknown';
|
||||
}
|
||||
|
||||
const name = lastSegment.replace(/\.git$/, '');
|
||||
|
||||
// Sanitize the name:
|
||||
// 1. Prevent argument injection by stripping leading dashes.
|
||||
// 2. Remove characters that are unsafe for directory names across platforms.
|
||||
return name.replace(/^-+/, '').replace(/[<>:"/\\|?*]/g, '_') || 'unknown';
|
||||
}
|
||||
|
||||
/** Get the clone target directory for a repo name. */
|
||||
|
|
|
|||
|
|
@ -151,9 +151,19 @@ export const parseRepoNameFromUrl = (url: string | null | undefined): string | n
|
|||
if (!trimmed) return null;
|
||||
// Strip `.git` suffix (case-insensitive) and any trailing slashes.
|
||||
const withoutSuffix = trimmed.replace(/\.git\/*$/i, '').replace(/\/+$/, '');
|
||||
// Last path segment, splitting on either `/` or `:` (covers SSH form).
|
||||
const m = withoutSuffix.match(/[/:]([^/:]+)$/);
|
||||
const candidate = m ? m[1] : withoutSuffix;
|
||||
|
||||
// Last path segment, handling colons for SSH URLs.
|
||||
// For HTTPS URLs, the only colon should be in the protocol.
|
||||
let candidate: string;
|
||||
if (withoutSuffix.includes('@') && withoutSuffix.includes(':') && !withoutSuffix.startsWith('http')) {
|
||||
// Likely an SSH URL
|
||||
const m = withoutSuffix.match(/[/:]([^/:]+)$/);
|
||||
candidate = m ? m[1] : withoutSuffix;
|
||||
} else {
|
||||
// Likely an HTTPS URL or local path
|
||||
candidate = withoutSuffix.split('/').pop() || withoutSuffix;
|
||||
}
|
||||
|
||||
return candidate || null;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,21 @@ describe('git-clone', () => {
|
|||
it('handles nested paths', () => {
|
||||
expect(extractRepoName('https://gitlab.com/group/subgroup/repo.git')).toBe('repo');
|
||||
});
|
||||
|
||||
it('strips leading dashes to prevent argument injection', () => {
|
||||
expect(extractRepoName('https://github.com/user/--upload-pack=payload.git')).toBe('upload-pack=payload');
|
||||
expect(extractRepoName('https://github.com/user/-repo')).toBe('repo');
|
||||
});
|
||||
|
||||
it('sanitizes unsafe directory characters', () => {
|
||||
expect(extractRepoName('https://github.com/user/repo<tag>.git')).toBe('repo_tag_');
|
||||
expect(extractRepoName('https://github.com/user/repo:name')).toBe('repo_name');
|
||||
});
|
||||
|
||||
it('handles colons in SSH URLs correctly', () => {
|
||||
expect(extractRepoName('git@github.com:user/my-repo.git')).toBe('my-repo');
|
||||
expect(extractRepoName('git@github.com:my-repo.git')).toBe('my-repo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCloneDir', () => {
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@
|
|||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
"include": ["src/**/*", "test/**/*"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue