abhigyanpatwari/GitNexus · error · Error
Invalid group name "${name}". Names must start with a letter
Error message
Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-]. What it means
Thrown by validateGroupName (storage.ts) when the proposed group name fails GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/. Names must start with a letter or digit and contain only letters, digits, underscores, or hyphens. This runs inside getGroupDir and createGroupDir, so it guards every path that constructs a group directory — primarily to prevent path traversal and invalid filesystem characters, not merely aesthetics.
Source
Thrown at gitnexus/src/core/group/storage.ts:22
import * as os from 'node:os';
import type { ContractRegistry } from './types.js';
import { writeFileAtomic } from '../../storage/fs-atomic.js';
const CONTRACTS_FILE = 'contracts.json';
export function getDefaultGitnexusDir(): string {
return process.env.GITNEXUS_HOME || path.join(os.homedir(), '.gitnexus');
}
export function getGroupsBaseDir(gitnexusDir?: string): string {
return path.join(gitnexusDir || getDefaultGitnexusDir(), 'groups');
}
const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
export function validateGroupName(name: string): void {
if (!GROUP_NAME_RE.test(name)) {
throw new Error(
`Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-].`,
);
}
}
export function getGroupDir(gitnexusDir: string, groupName: string): string {
validateGroupName(groupName);
return path.join(gitnexusDir, 'groups', groupName);
}
export async function writeContractRegistry(
groupDir: string,
registry: ContractRegistry,
): Promise<void> {
await writeFileAtomic(path.join(groupDir, CONTRACTS_FILE), JSON.stringify(registry, null, 2));
}
export async function readContractRegistry(groupDir: string): Promise<ContractRegistry | null> {View on GitHub (pinned to d540b00184)
Solutions
- Use only [a-zA-Z0-9_-], starting with a letter or digit — e.g. 'team-project', not 'team.project'.
- If the name comes from user input, sanitize it (replace disallowed chars with '-' and strip leading non-alphanumerics) before calling validateGroupName.
- Avoid '.' especially: it would let a crafted name escape the groups/ base directory.
Example fix
// before — dot and slash trigger the guard await createGroupDir(home, 'team/sub.project', false); // after — sanitized, regex-valid name await createGroupDir(home, 'team-sub-project', false);
Defensive patterns
Strategy: validation
Validate before calling
const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
function isValidGroupName(name: string): boolean {
return GROUP_NAME_RE.test(name);
}
// sanitize untrusted input before calling createGroupDir:
function sanitizeGroupName(raw: string): string {
const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/^[^a-zA-Z0-9]+/, '');
return isValidGroupName(cleaned) ? cleaned : 'group';
} Type guard
function isValidGroupName(name: unknown): name is string {
return typeof name === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name);
} Prevention
- Validate names with the same regex before passing them to createGroupDir/getGroupDir.
- Sanitize untrusted input by replacing disallowed chars with '-' and stripping leading non-alphanumerics.
- Never allow '.', '/', or '..' segments — they enable path traversal out of the groups/ directory.
When it happens
Trigger: Calling createGroupDir or getGroupDir with a name containing '.', '/', spaces, leading hyphen/underscore, or any non-[a-zA-Z0-9_-] character; an empty string; a name starting with '-' (looks like a CLI flag and is regex-invalid).
Common situations: User passes a dotted name like 'team.project' (dot not allowed); a name with a slash implying a nested path (traversal risk); a name starting with '_' or '-'; a name derived from untrusted input that wasn't sanitized.
Related errors
- {label} contains an unsafe path component: {relative}
- unsafe results artifact path: {relative_value!r}
- overlay destination escapes repository: {target}
- extra read-only mount target must be absolute: {mount.target
- evidence names must be simple relative files: {name!r}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/a65359d36c198a3f.
Report an issue: GitHub.