security: validate revision parameter to prevent Nix injection

The revision parameter was interpolated directly into a Nix expression,
allowing potential injection of arbitrary Nix code. An attacker could
craft a revision string like:
  "; builtins.readFile /etc/passwd; "

This adds ValidateRevision() which ensures revisions only contain safe
characters (alphanumeric, hyphens, underscores, dots) and are at most
64 characters long.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 19:10:31 +01:00
parent 5f0445e749
commit be1ff4839b
2 changed files with 76 additions and 0 deletions

View File

@@ -11,12 +11,18 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"git.t-juice.club/torjus/labmcp/internal/database"
)
// revisionPattern validates revision strings to prevent injection attacks.
// Allows: alphanumeric, hyphens, underscores, dots (for channel names like "nixos-24.11"
// and git hashes). Must be 1-64 characters.
var revisionPattern = regexp.MustCompile(`^[a-zA-Z0-9._-]{1,64}$`)
// Indexer handles indexing of nixpkgs revisions.
type Indexer struct {
store database.Store
@@ -42,10 +48,24 @@ type IndexResult struct {
AlreadyIndexed bool // True if revision was already indexed (skipped)
}
// ValidateRevision checks if a revision string is safe to use.
// Returns an error if the revision contains potentially dangerous characters.
func ValidateRevision(revision string) error {
if !revisionPattern.MatchString(revision) {
return fmt.Errorf("invalid revision format: must be 1-64 alphanumeric characters, hyphens, underscores, or dots")
}
return nil
}
// IndexRevision indexes a nixpkgs revision by git hash or channel name.
func (idx *Indexer) IndexRevision(ctx context.Context, revision string) (*IndexResult, error) {
start := time.Now()
// Validate revision to prevent injection attacks
if err := ValidateRevision(revision); err != nil {
return nil, err
}
// Resolve channel names to git refs
ref := resolveRevision(revision)
@@ -116,6 +136,11 @@ func (idx *Indexer) IndexRevision(ctx context.Context, revision string) (*IndexR
// ReindexRevision forces re-indexing of a revision, deleting existing data first.
func (idx *Indexer) ReindexRevision(ctx context.Context, revision string) (*IndexResult, error) {
// Validate revision to prevent injection attacks
if err := ValidateRevision(revision); err != nil {
return nil, err
}
ref := resolveRevision(revision)
// Delete existing revision if present