Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, registry management, package management, project management, user information, token management, Git integration, and Julia integration.
This is a Go-based CLI tool for interacting with JuliaHub, a platform for Julia computing. The CLI provides commands for authentication, dataset management, registry management, package management, project management, user information, token management, vulnerability scanning, Git integration, and Julia integration.

## Architecture

Expand All @@ -19,6 +18,7 @@ The application follows a command-line interface pattern using the Cobra library
- **user.go**: User information retrieval using GraphQL API and REST API for listing users
- **tokens.go**: Token management operations (list) with REST API integration
- **landing.go**: Landing page management (show, update, remove) with REST API integration
- **vuln.go**: Vulnerability scanning for Julia packages via REST API
- **git.go**: Git integration (clone, push, fetch, pull) with JuliaHub authentication
- **julia.go**: Julia installation and management
- **run.go**: Julia execution with JuliaHub configuration
Expand All @@ -33,7 +33,7 @@ The application follows a command-line interface pattern using the Cobra library
- Stores tokens securely in `~/.juliahub` with 0600 permissions

2. **API Integration**:
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`), registry operations (`/api/v1/registry/registries/descriptions`, `/api/v1/registry/config/registry/{name}`), package search/info primary path (`/packages/info`), token management (`/app/token/activelist`), user management (`/app/config/features/manage`), and landing page management (`/app/homepage` GET, `/app/config/homepage` POST/DELETE)
- **REST API**: Used for dataset operations (`/api/v1/datasets`, `/datasets/{uuid}/url/{version}`), registry operations (`/api/v1/registry/registries/descriptions`, `/api/v1/registry/config/registry/{name}`), package search/info primary path (`/packages/info`), token management (`/app/token/activelist`), user management (`/app/config/features/manage`), landing page management (`/app/homepage` GET, `/app/config/homepage` POST/DELETE), and vulnerability scanning (`/api/v1/ui/vulnerabilities/packages/{name}`)
- **GraphQL API**: Used for projects, user info, and package search/info fallback (`/v1/graphql`)
- **Headers**: All GraphQL requests require `X-Hasura-Role: jhuser` header
- **Authentication**: Uses ID tokens (`token.IDToken`) for API calls
Expand All @@ -50,6 +50,7 @@ The application follows a command-line interface pattern using the Cobra library
- `jh admin user`: User management (list all users with REST API, supports verbose mode)
- `jh admin token`: Token management (list all tokens with REST API, supports verbose mode)
- `jh admin landing-page`: Landing page management (show/update/remove custom markdown landing page with REST API)
- `jh vuln`: Vulnerability scanning for Julia packages (REST API; defaults to latest stable version via `GET /docs/<registry>/<package>/versions.json`; supports `--version` for a specific version, `--registry` to specify the registry for version lookup (default: General), `--advisory` to filter to a specific advisory ID, `--all` to show all advisories regardless of affected status, and `--verbose` for additional details)
- `jh clone`: Git clone with JuliaHub authentication and project name resolution
- `jh push/fetch/pull`: Git operations with JuliaHub authentication
- `jh git-credential`: Git credential helper for seamless authentication
Expand Down Expand Up @@ -163,6 +164,18 @@ cat landing.md | go run . admin landing-page update
go run . admin landing-page remove
```

### Test vulnerability scan operations
```bash
go run . vuln MbedTLS_jll
go run . vuln MbedTLS_jll --version 2.28.1010+0
go run . vuln MbedTLS_jll --all
go run . vuln MbedTLS_jll --advisory JLSEC-2025-232
go run . vuln MbedTLS_jll --advisory JLSEC-2025-232 --verbose
go run . vuln MbedTLS_jll --verbose
go run . vuln MyPkg --registry MyRegistry
go run . vuln SomePackage -s nightly.juliahub.dev
```

### Test Git operations
```bash
go run . clone john/my-project # Clone from another user
Expand Down Expand Up @@ -412,6 +425,14 @@ jh run setup
- `executeGraphQL(server, token, req)` in `packages.go` is a shared helper for GraphQL POST requests (sets Authorization, Content-Type, Accept, X-Hasura-Role headers)
- `getPackageInfo` in `packages.go` implements exact name-match lookup using REST-first (`getPackageInfoREST`), GraphQL fallback (`getPackageInfoGraphQL`); `packageInfoCmd` in `main.go` resolves registries via `fetchRegistries`
- `getPackageDependencies` uses GraphQL (`fetchGraphQLPackages`) to locate the package, then fetches `/docs/{registry}/{package}/stable/pkg.json` for dependency data; no REST fallback (docs endpoint is authoritative)
- `jh vuln` uses two REST endpoints: vulnerabilities at `/api/v1/ui/vulnerabilities/packages/{name}?version=<ver>` and latest version at `/docs/<registry>/<package>/versions.json` (first entry is latest); no GraphQL fallback
- When no `--version` is given, `fetchLatestVersion` calls the versions.json endpoint (registry defaults to `General`, overridable with `--registry`)
- By default only advisories where `is_affected == true` are shown; `--all` overrides this
- Each advisory is printed as: Advisory ID (clickable OSC8 hyperlink to JuliaLang SecurityAdvisories), Affected (Yes/No), Severity scores (all, comma-separated), full Summary, Affected versions (one line), Version ranges (one line, with ranges type)
- `--advisory <id>` filters to a single advisory (case-insensitive match); same output format
- `--verbose` adds: Aliases, Published date, Modified date, References
- `advisoryLink` in `vuln.go` builds the OSC8 terminal hyperlink to `https://github.com/JuliaLang/SecurityAdvisories.jl/blob/main/advisories/published/<year>/<id>.md`
- `topSeverity` in `vuln.go` prefers CVSS_V3 scores; falls back to first available score; returns "N/A" if none

## Implementation Details

Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A command-line interface for interacting with JuliaHub, a platform for Julia com
- **Git Integration**: Clone, push, fetch, and pull with automatic JuliaHub authentication
- **Julia Integration**: Install Julia and run with JuliaHub package server configuration
- **User Management**: Display user information and view profile details
- **Vulnerability Scanning**: Scan Julia packages for known security vulnerabilities
- **Administrative Commands**: Manage users, tokens, and system resources (requires admin permissions)

## Installation
Expand Down Expand Up @@ -223,6 +224,18 @@ go build -o jh .
- `cat landing.md | jh admin landing-page update` - Read content from stdin
- `jh admin landing-page remove` - Remove the custom landing page and revert to the default

### Vulnerability Scanning (`jh vuln`)

- `jh vuln <package-name>` - Show known vulnerabilities for a Julia package
- Defaults to the latest stable version (fetched from the registry); only shows advisories where that version is affected
- `--version <ver>` - Check a specific version instead of the latest
- `--registry <name>` - Registry to use for version lookup (default: `General`)
- `--advisory <id>` - Filter to a specific advisory ID (e.g. a CVE or GHSA identifier)
- `--all` - Show all advisories regardless of whether the queried version is affected
- `--verbose` / `-v` - Show additional details: aliases, published/modified dates, and references
- Advisory IDs are clickable links to the JuliaLang SecurityAdvisories repository
- Each advisory shows: severity scores, affected status, full summary, affected versions, and version ranges

### Update (`jh update`)

- `jh update` - Check for updates and automatically install the latest version
Expand Down Expand Up @@ -378,6 +391,31 @@ cat landing.md | jh admin landing-page update
jh admin landing-page remove
```

### Vulnerability Scanning

```bash
# Scan latest stable version (only shows advisories where it is affected)
jh vuln MbedTLS_jll

# Scan a specific version
jh vuln MbedTLS_jll --version 2.28.1010+0

# Show all advisories regardless of affected status
jh vuln MbedTLS_jll --all

# Filter to a specific advisory
jh vuln MbedTLS_jll --advisory JLSEC-2025-232

# Show extra details (aliases, dates, references)
jh vuln MbedTLS_jll --verbose

# Use a non-General registry for version lookup
jh vuln MyPkg --registry MyRegistry

# Scan against a custom server
jh vuln SomePackage -s nightly.juliahub.dev
```

### Git Workflow

```bash
Expand Down
99 changes: 98 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ Available command categories:
pull - Pull changes with authentication
julia - Julia installation and management
run - Run Julia with JuliaHub configuration
scan - Scan a package for known vulnerabilities

Use 'jh <command> --help' for more information about a specific command.`,
}
Expand Down Expand Up @@ -643,6 +644,96 @@ PROVIDER TYPES
}`
}

var vulnCmd = &cobra.Command{
Use: "vuln <package-name>",
Short: "Show known vulnerabilities for a package",
Long: `Show known security vulnerabilities for a Julia package.

Defaults to checking the latest stable version of the package. Use --version to
check a specific version. Only advisories that affect the queried version are shown
by default; use --all to list all advisories regardless of affected status.

Use --advisory to look up a specific advisory by ID.`,
Example: " jh scan MbedTLS_jll\n jh scan MbedTLS_jll --version 2.28.1010+0\n jh scan MbedTLS_jll --all\n jh scan MbedTLS_jll --advisory GHSA-xxx-yyy-zzz",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
server, err := getServerFromFlagOrConfig(cmd)
if err != nil {
fmt.Printf("Failed to get server config: %v\n", err)
os.Exit(1)
}

packageName := args[0]
version, _ := cmd.Flags().GetString("version")
advisory, _ := cmd.Flags().GetString("advisory")
registry, _ := cmd.Flags().GetString("registry")
all, _ := cmd.Flags().GetBool("all")
verbose, _ := cmd.Flags().GetBool("verbose")

if version == "" {
latest, err := fetchLatestVersion(server, registry, packageName)
if err != nil {
fmt.Printf("Failed to fetch latest version: %v\n", err)
os.Exit(1)
}
version = latest
}

vulns, err := fetchVulnerabilities(server, packageName, version)
if err != nil {
fmt.Printf("Failed to fetch vulnerabilities: %v\n", err)
os.Exit(1)
}

fmt.Printf("Package: %s", packageName)
if version != "" {
fmt.Printf(" (%s)", version)
}
fmt.Println()
fmt.Println()

if advisory != "" {
for _, v := range vulns {
if strings.EqualFold(v.AdvisoryID, advisory) {
printAdvisory(&v, true, verbose)
return
}
}
fmt.Printf("Advisory %q not found for package %s.\n", advisory, packageName)
os.Exit(1)
}

var toShow []PackageVulnerability
for _, v := range vulns {
if all || (v.IsAffected != nil && *v.IsAffected) {
toShow = append(toShow, v)
}
}

if len(toShow) == 0 {
if all {
fmt.Println("No vulnerabilities found.")
} else {
fmt.Println("No known vulnerabilities affecting this version.")
}
return
}

suffix := "ies"
if len(toShow) == 1 {
suffix = "y"
}
fmt.Printf("Found %d vulnerabilit%s:\n\n", len(toShow), suffix)

for i := range toShow {
if i > 0 {
fmt.Println()
}
printAdvisory(&toShow[i], false, verbose)
}
},
}

var packageCmd = &cobra.Command{
Use: "package",
Short: "Package search commands",
Expand Down Expand Up @@ -1671,6 +1762,12 @@ func init() {
fetchCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
pullCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
updateCmd.Flags().Bool("force", false, "Force update even if current version is newer than latest release")
vulnCmd.Flags().StringP("server", "s", "juliahub.com", "JuliaHub server")
vulnCmd.Flags().StringP("version", "V", "", "Package version to check (defaults to latest stable)")
vulnCmd.Flags().StringP("advisory", "a", "", "Look up a specific advisory by ID")
vulnCmd.Flags().StringP("registry", "r", "General", "Registry name for version lookup")
vulnCmd.Flags().Bool("all", false, "Show all advisories regardless of affected status")
vulnCmd.Flags().BoolP("verbose", "v", false, "Show full advisory details (aliases, dates, details, references)")

authCmd.AddCommand(authLoginCmd, authRefreshCmd, authStatusCmd, authEnvCmd)
jobCmd.AddCommand(jobListCmd, jobStartCmd)
Expand All @@ -1693,7 +1790,7 @@ func init() {
runCmd.AddCommand(runSetupCmd)
gitCredentialCmd.AddCommand(gitCredentialHelperCmd, gitCredentialGetCmd, gitCredentialStoreCmd, gitCredentialEraseCmd, gitCredentialSetupCmd)

rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, packageCmd, registryCmd, userCmd, adminCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd)
rootCmd.AddCommand(authCmd, jobCmd, datasetCmd, projectCmd, packageCmd, registryCmd, userCmd, adminCmd, juliaCmd, cloneCmd, pushCmd, fetchCmd, pullCmd, runCmd, gitCredentialCmd, updateCmd, vulnCmd)
}

func main() {
Expand Down
Loading
Loading