First off, thanks for taking the time to contribute! 🎉
The following is a set of guidelines for contributing to pentlog. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
- Go: Version 1.24.0 or higher.
- Git: For version control.
- golangci-lint: For linting the code.
-
Clone the repository:
git clone https://github.com/aancw/pentlog.git cd pentlog -
Download dependencies:
go mod download
A high-level overview of the codebase:
cmd/: Contains the main application entry points and Cobra command definitions. Each command (e.g.,pentlog create) typically has its own file here (e.g.,cmd/create.go).pkg/: Contains the core library code.metadata/: Handles engagement context (client, phase, etc.).vulns/: Logic for managing vulnerabilities and findings.tui/: User interface components.utils/: Helper functions.
pentlog uses Cobra for its CLI interface. To add a new command:
-
Create a new file in
cmd/(e.g.,cmd/mycommand.go). -
Define the Command:
package cmd import ( "fmt" "github.com/spf13/cobra" ) // Define flags if needed var myFlag string var myCmd = &cobra.Command{ Use: "mycommand", Short: "A brief description of what mycommand does", Run: func(cmd *cobra.Command, args []string) { // Your implementation logic here fmt.Println("Hello from mycommand!") }, } func init() { // Register flags myCmd.Flags().StringVarP(&myFlag, "flag", "f", "", "Description for flag") // Add to root command rootCmd.AddCommand(myCmd) }
Most commands need to know the current engagement details (Client, Phase, etc.). You can load this from the centralized config manager:
import (
"pentlog/pkg/config"
"fmt"
)
func runMyCommand(cmd *cobra.Command, args []string) {
mgr := config.Manager()
ctx, err := mgr.LoadContext()
if err != nil {
fmt.Println("Error: Not in an active engagement. Run 'pentlog create' first.")
return
}
fmt.Printf("Current Client: %s\n", ctx.Client)
}All error messages should use the pkg/errors package for consistency and user guidance. This transforms generic errors into actionable messages with reasons and solutions.
The package provides 18 error types covering common failures:
- Context:
NoActiveContext,ContextNotFound,InvalidContext - Sessions:
SessionNotFound,SessionCrashed,AlreadyInSession - Dependencies:
DependencyMissing,DependencyVersionMismatch - Database:
DatabaseError,DatabaseLocked - Files:
FileNotFound,DirectoryNotFound,PermissionDenied - Configuration:
AIConfigMissing,AIConfigInvalid - Archives:
ArchiveNotFound,ArchiveCorrupted,ArchiveEncrypted - Generic:
Generic(for other errors)
Example 1: Missing Context (Fatal)
import "pentlog/pkg/errors"
func runMyCommand(cmd *cobra.Command, args []string) {
mgr := config.Manager()
ctx, err := mgr.LoadContext()
if err != nil {
errors.NoContext().Fatal()
}
// ...
}Example 2: Database Error (Non-Fatal)
import "pentlog/pkg/errors"
sessions, err := logs.ListSessions()
if err != nil {
errors.DatabaseErr("list_sessions", err).Print()
return
}Example 3: File Operation Error
import "pentlog/pkg/errors"
data, err := os.ReadFile(path)
if err != nil {
errors.FileErr(path, err).Print()
return
}Example 4: Custom Error with Details
import "pentlog/pkg/errors"
errors.NewError(errors.SessionNotFound, "Session 123 not found").
AddReason("Session file was deleted").
AddSolution("Run: pentlog recover").
Print()For common scenarios, use pre-built helpers:
errors.NoContext() // Missing engagement context
errors.ContextMissing() // Context file not found
errors.SessionMissing(sessionID) // Session not found
errors.SessionCrashedError(sessionID) // Session crashed
errors.AlreadyInShell() // Already in pentlog shell
errors.MissingDependency(tool, cmd) // Missing dependency
errors.DatabaseErr(op, err) // Database error
errors.DatabaseLockedErr() // Database locked
errors.FileErr(path, err) // File error (auto-detects type)
errors.DirErr(path, err) // Directory error (auto-detects type)
errors.AIConfigErr() // AI config missing
errors.ArchiveErr(path, err) // Archive operation error
errors.ArchivePasswordErr(path) // Archive encrypted❌ Error: No active engagement context found.
This can happen when:
1. You haven't started an engagement yet
2. The context was reset with 'pentlog reset'
3. Running pentlog from a different user account
To fix:
$ pentlog create # Start a new engagement
$ pentlog switch # Switch to an existing context
See ERROR_HANDLING_GUIDE.md for detailed documentation.
To build the pentlog binary:
go build -o pentlog main.goTo run the test suite:
go test ./... -vEnsure your code follows the project's style guidelines:
golangci-lint run- Fork the repo and create your branch from
main. - If you've added code that should be tested, add tests.
- Use enhanced error messages with
pkg/errorsfor any user-facing errors (no generic error strings). - Ensure the test suite passes (
go test ./...). - Make sure your code lints (
golangci-lint run). - Test error messages manually to ensure they're helpful:
go build -o pentlog main.go # Test commands that trigger errors ./pentlog <command> # Verify error output is clear and actionable
- Issue that pull request!
When handling errors in commands:
- ✅ DO use
pkg/errorshelpers for user-facing errors - ✅ DO provide context in error messages (why it happened)
- ✅ DO include actionable solutions (how to fix)
- ❌ DON'T use generic
fmt.Fprintf(os.Stderr, "Error: %v")for user-visible errors - ❌ DON'T expose raw Go error messages without context
Example of good error handling:
ctx, err := mgr.LoadContext()
if err != nil {
errors.NoContext().Fatal() // ✅ Clear, actionable, helpful
}
// Instead of:
// fmt.Fprintf(os.Stderr, "Error: %v\n", err) // ❌ Generic