
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
git-workspace-service
Advanced tools
Git workspace provisioning and credential management service with OAuth device flow authentication
Git workspace provisioning and credential management service. Handles cloning repositories, managing branches, credentials, and PR creation.
npm install git-workspace-service
# or
pnpm add git-workspace-service
For GitHub App support, also install:
npm install @octokit/rest @octokit/auth-app
import {
WorkspaceService,
CredentialService,
GitHubProvider,
} from 'git-workspace-service';
// Set up credential service with GitHub provider
const githubProvider = new GitHubProvider({
appId: process.env.GITHUB_APP_ID!,
privateKey: process.env.GITHUB_PRIVATE_KEY!,
});
await githubProvider.initialize();
const credentialService = new CredentialService({
defaultTtlSeconds: 3600,
maxTtlSeconds: 7200,
});
credentialService.registerProvider(githubProvider);
// Create workspace service
const workspaceService = new WorkspaceService({
config: {
baseDir: '/tmp/workspaces',
},
credentialService,
});
await workspaceService.initialize();
// Provision a workspace
const workspace = await workspaceService.provision({
repo: 'https://github.com/owner/repo',
branchStrategy: 'feature_branch',
baseBranch: 'main',
execution: {
id: 'exec-123',
patternName: 'code-review',
},
task: {
id: 'task-456',
role: 'engineer',
slug: 'auth-feature',
},
});
console.log(`Workspace created at: ${workspace.path}`);
console.log(`Branch: ${workspace.branch.name}`);
// Do work in the workspace...
// Finalize with PR
const pr = await workspaceService.finalize(workspace.id, {
push: true,
createPr: true,
pr: {
title: 'Add authentication feature',
body: 'Implements OAuth login',
targetBranch: 'main',
labels: ['enhancement'],
},
cleanup: true,
});
console.log(`PR created: ${pr?.url}`);
Instead of using GitHub App, users can provide their own PAT or OAuth token:
const workspace = await workspaceService.provision({
repo: 'https://github.com/owner/repo',
branchStrategy: 'feature_branch',
baseBranch: 'main',
execution: {
id: 'exec-123',
patternName: 'my-pattern',
},
task: {
id: 'task-456',
role: 'engineer',
},
userCredentials: {
type: 'pat',
token: 'ghp_xxxx...',
},
});
Or use SSH authentication (relies on system SSH agent):
const workspace = await workspaceService.provision({
repo: 'git@github.com:owner/repo.git',
// ...
userCredentials: {
type: 'ssh',
},
});
For CLI applications and AI agents, use the OAuth Device Code Flow for interactive authentication:
import {
CredentialService,
OAuthDeviceFlow,
FileTokenStore,
DEFAULT_AGENT_PERMISSIONS,
} from 'git-workspace-service';
// Set up token store for persistent caching (with encryption)
const tokenStore = new FileTokenStore({
directory: '~/.myapp/tokens',
encryptionKey: process.env.TOKEN_ENCRYPTION_KEY,
});
// Create credential service with OAuth support
const credentialService = new CredentialService({
tokenStore,
oauth: {
clientId: process.env.GITHUB_CLIENT_ID!,
permissions: DEFAULT_AGENT_PERMISSIONS,
promptEmitter: {
onAuthRequired(prompt) {
console.log(`Visit: ${prompt.verificationUri}`);
console.log(`Enter code: ${prompt.userCode}`);
},
onAuthComplete(result) {
if (result.success) {
console.log('Authenticated!');
}
},
},
},
});
// Request credentials - will use cached token or trigger device flow
const credential = await credentialService.getCredentials({
repo: 'https://github.com/owner/repo',
access: 'write',
context: {
executionId: 'exec-123',
taskId: 'task-456',
reason: 'Code review',
},
});
When requesting credentials, the service checks sources in this order:
Control what agents can do with AgentPermissions:
import { DEFAULT_AGENT_PERMISSIONS } from 'git-workspace-service';
const permissions = {
repositories: { type: 'selected', repos: ['owner/repo'] },
contents: 'write', // 'none' | 'read' | 'write'
pullRequests: 'write',
issues: 'read',
metadata: 'read',
canDeleteBranch: true,
canForcePush: false, // Dangerous operations off by default
canDeleteRepository: false,
canAdminister: false,
};
For parallel work on the same repository, use worktrees instead of clones. Worktrees share the .git directory, making them faster to create and using less disk space.
┌──────────────────────────┬────────────────────────────────────────┐
│ Clone │ Worktree │
├──────────────────────────┼────────────────────────────────────────┤
│ Full .git copy each time │ Shared .git directory │
│ Slower for same repo │ Fast - just checkout │
│ More disk space │ Minimal disk │
│ Good for different repos │ Perfect for parallel work on SAME repo │
└──────────────────────────┴────────────────────────────────────────┘
// First, create a clone workspace (the parent)
const parent = await workspaceService.provision({
repo: 'https://github.com/owner/repo',
strategy: 'clone', // explicit, but this is the default
branchStrategy: 'feature_branch',
baseBranch: 'main',
execution: { id: 'exec-123', patternName: 'review' },
task: { id: 'task-1', role: 'architect' },
});
// Then create worktrees from it for parallel agents
const reviewerWorkspace = await workspaceService.provision({
repo: 'https://github.com/owner/repo',
strategy: 'worktree',
parentWorkspace: parent.id, // Required for worktrees
branchStrategy: 'feature_branch',
baseBranch: 'main',
execution: { id: 'exec-123', patternName: 'review' },
task: { id: 'task-2', role: 'reviewer' },
});
// Or use the convenience method
const testerWorkspace = await workspaceService.addWorktree(parent.id, {
branch: 'main',
execution: { id: 'exec-123', patternName: 'review' },
task: { id: 'task-3', role: 'tester' },
});
// List all worktrees for a parent
const worktrees = workspaceService.listWorktrees(parent.id);
// Remove a specific worktree
await workspaceService.removeWorktree(reviewerWorkspace.id);
// Cleanup parent also cleans up all its worktrees
await workspaceService.cleanup(parent.id);
Subscribe to workspace lifecycle events:
const unsubscribe = workspaceService.onEvent((event) => {
switch (event.type) {
case 'workspace:provisioning':
console.log('Provisioning workspace...');
break;
case 'workspace:ready':
console.log('Workspace ready!');
break;
case 'credential:granted':
console.log(`Credential ${event.credentialId} granted`);
break;
case 'pr:created':
console.log(`PR created: ${event.data?.prUrl}`);
break;
case 'workspace:cleaned_up':
console.log('Workspace cleaned up');
break;
}
});
// Later, unsubscribe
unsubscribe();
Automatic branch naming with customizable prefix:
import { generateBranchName, parseBranchName } from 'git-workspace-service';
// Generate branch name
const branchName = generateBranchName({
executionId: 'exec-123',
role: 'engineer',
slug: 'auth-feature',
baseBranch: 'main',
});
// Result: 'parallax/exec-123/engineer-auth-feature'
// Parse branch name
const parsed = parseBranchName('parallax/exec-123/engineer-auth-feature');
// Result: { executionId: 'exec-123', role: 'engineer', slug: 'auth-feature' }
If you need a specific branch name (e.g. for deterministic naming or external conventions), pass branchName in the workspace config to bypass auto-generation:
const workspace = await workspaceService.provision({
repo: 'https://github.com/owner/repo',
branchStrategy: 'feature_branch',
baseBranch: 'main',
branchName: 'test/claude-nonce-abc123', // Used verbatim
execution: { id: 'exec-123', patternName: 'review' },
task: { id: 'task-456', role: 'engineer' },
});
console.log(workspace.branch.name); // 'test/claude-nonce-abc123'
When branchName is set, isManagedBranch() may return false for the resulting branch — this is expected since custom names intentionally bypass the naming convention. The branch is still created from baseBranch and works with both clone and worktree strategies.
class WorkspaceService {
constructor(options: WorkspaceServiceOptions);
// Initialize the service
initialize(): Promise<void>;
// Provision a new workspace
provision(config: WorkspaceConfig): Promise<Workspace>;
// Finalize workspace (push, create PR, cleanup)
finalize(workspaceId: string, options: WorkspaceFinalization): Promise<PullRequestInfo | void>;
// Get workspace by ID
get(workspaceId: string): Workspace | null;
// Get all workspaces for an execution
getForExecution(executionId: string): Workspace[];
// Clean up a workspace
cleanup(workspaceId: string): Promise<void>;
// Clean up all workspaces for an execution
cleanupForExecution(executionId: string): Promise<void>;
// Subscribe to events
onEvent(handler: WorkspaceEventHandler): () => void;
}
class CredentialService {
constructor(options?: CredentialServiceOptions);
// Register a provider adapter
registerProvider(provider: GitProviderAdapter): void;
// Get credentials for a repository
getCredentials(request: GitCredentialRequest): Promise<GitCredential>;
// Revoke a credential
revokeCredential(grantId: string): Promise<void>;
// Revoke all credentials for an execution
revokeForExecution(executionId: string): Promise<number>;
// Check if a credential is valid
isValid(grantId: string): boolean;
// Get grant info
getGrant(grantId: string): Promise<CredentialGrant | null>;
// Get all grants for an execution
getGrantsForExecution(executionId: string): Promise<CredentialGrant[]>;
}
class GitHubProvider implements GitProviderAdapter {
constructor(config: GitHubProviderConfig, logger?: GitHubProviderLogger);
// Initialize and fetch installations
initialize(): Promise<void>;
// Register an installation
registerInstallation(installationId: number): Promise<GitHubAppInstallation>;
// Get credentials for a repo
getCredentialsForRepo(owner: string, repo: string, access: 'read' | 'write', ttlSeconds?: number): Promise<GitCredential>;
// Create a PR
createPullRequestForRepo(owner: string, repo: string, options: { title, body, head, base, draft?, labels?, reviewers? }): Promise<PullRequestInfo>;
// Delete a branch
deleteBranch(owner: string, repo: string, branch: string): Promise<void>;
// List managed branches
listManagedBranches(owner: string, repo: string, prefix?: string): Promise<string[]>;
}
class OAuthDeviceFlow {
constructor(config: OAuthDeviceFlowConfig, logger?: OAuthDeviceFlowLogger);
// Start device flow and wait for authorization
authorize(): Promise<OAuthToken>;
// Request a device code (step 1)
requestDeviceCode(): Promise<DeviceCodeResponse>;
// Poll for token (step 2)
pollForToken(deviceCode: DeviceCodeResponse): Promise<OAuthToken>;
// Refresh an expired token
refreshToken(refreshToken: string): Promise<OAuthToken>;
}
// Abstract base class
abstract class TokenStore {
save(provider: GitProvider, token: OAuthToken): Promise<void>;
get(provider: GitProvider): Promise<OAuthToken | null>;
clear(provider?: GitProvider): Promise<void>;
list(): Promise<GitProvider[]>;
isExpired(token: OAuthToken): boolean;
needsRefresh(token: OAuthToken): boolean;
}
// In-memory store (for testing)
class MemoryTokenStore extends TokenStore { }
// File-based store with encryption
class FileTokenStore extends TokenStore {
constructor(options?: {
directory?: string; // Default: ~/.parallax/tokens
encryptionKey?: string; // AES-256-CBC encryption
});
}
| Event | Description |
|---|---|
workspace:provisioning | Workspace provisioning started |
workspace:ready | Workspace is ready for use |
workspace:error | Workspace provisioning failed |
workspace:finalizing | Workspace finalization started |
workspace:cleaned_up | Workspace has been cleaned up |
worktree:added | Git worktree added to parent |
worktree:removed | Git worktree removed |
credential:granted | Credential was granted |
credential:revoked | Credential was revoked |
pr:created | Pull request was created |
pr:merged | Pull request was merged |
MIT
FAQs
Git workspace provisioning and credential management service with OAuth device flow authentication
The npm package git-workspace-service receives a total of 1,975 weekly downloads. As such, git-workspace-service popularity was classified as popular.
We found that git-workspace-service demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.